基於矩陣法邏輯匹配實現方案

1. 需求

在知識圖譜關係網絡中,節點之間每每會存在多種不一樣類型的邊,例如在人際關係網絡中,人與人之間的關係可能存在同窗關係、轉帳關係、微信好友、相互關注、同一城市等等。在作節點特徵進行統計分析的時候,咱們可能會跟進給定的關係的邏輯條件去篩選符合條件的關係進行統計,例如知足「即便同窗關係同時又是同一城市」,或者是「存在轉帳規則或存在相互關注」,即在知足設定的關係邏輯組合下進行鄰居節點特徵統計。json

把問題抽象化爲以下:數組

已知待匹配邏輯關係集合,判斷某一個組合是否知足邏輯表達式。
例如,已知待匹配邏輯運算:
(X1 or X3) and (X4 or X5)
X1 or ((X2 or X4) and X5)

對應給定的組合X1X3X4X6, 判斷該組合是否知足上述其中一種模式

例如:X1X3是知足,由於匹配邏輯中存在X1便可, 故X1X3能夠匹配上;X5X6不知足,由於X5必須是要與X一、X三、X二、X4中至少一個同時出現

2.解決方案

2.1 邏輯矩陣表示法

  • 將邏輯元素進行編號, X0:0, X1:1, X2:2, X3:3, X4:4, X5:微信

  • 將匹配邏輯運算進行拆分,去掉括號分解爲若干個and單元,上述拆完後結果爲網絡

    X0
    X1
    X3
    X2X5
    X4X5
    X1X2X3X4
    X1X2X3X5
  • 將上述拆分結果轉化爲數組, 數組元素爲上述每一行中對應的編號組成的集合ui

    Seq(Set(0), Set(1), Set(3), Set(2, 5), Set(4, 5), Set(1, 2, 3, 4), Set(1, 2, 3, 5))
  • 將數組轉化爲0-1矩陣,矩陣的行數爲數組的長度,矩陣的列數爲邏輯元素編號的個數,轉化後的矩陣是7行6列以下:scala

    M = [1 0 0 0 0 0
         0 1 0 0 0 0
         0 0 1 0 0 0
         0 0 1 0 0 1
         0 0 0 0 1 1
         0 1 1 1 1 0
         0 1 1 1 0 1]

    上述矩陣的第5行中[0 0 0 0 1 1], 就表明了邏輯運算X4X5。簡單瞭解其實就是矩陣的行表明邏輯‘或’運算,而矩陣的列表明邏輯‘且’運算。例如:code

    0 0 1 0 0 0
    0 0 1 0 0 1

    第一列對應X0,第2列對應X2,以此類推,第5列對應X5,上述矩陣就表明的邏輯運算爲:X2 or (X2 and X5)遞歸

2.2 邏輯匹配

當邏輯矩陣表示完成後,接下來的匹配就很是簡單了,之間經過矩陣的運算便可判斷給出的組合是否匹配, 具體步驟以下:索引

    1. 輸入組合:X2X3X4
    1. 將組合轉化爲對應的編號集合:Set(2, 3, 4)
    1. 以邏輯矩陣列數做爲向量長度建立一個0向量,將該向量中下標爲2中集合元素值的份量設置爲1
    例如當前集合爲Set(2, 3, 4),那麼向量結果爲:
    v = [0 0 1 1 1 0]
  • 4 計算矩陣與向量的運算結果:s = M * (v - one),這裏one爲同型的全1向量
  • 5 判斷:若是向量s中存在某一份量的值等於0,則該組合能匹配上邏輯運算,不然匹配不上。

2.3 源碼

下面給出Scala版本的實現代碼,代碼中沒有包括轉碼與邏輯解析部分,後續補充完整ip

package com.haizhi.alg.tag.util

import breeze.linalg.{Matrix, Vector}

/**
  * Created by zhoujiamu on 2020/2/24.
  */
object RelateLogicUtils extends Serializable {

  /**
    * 建立關係邏輯矩陣,矩陣元素是0或者1,矩陣的列表明且,行表明或
    * 例如想要表達邏輯關係爲:(rel1&rel2 ) || (rel3&rel4) || rel5
    * 先作關係與下標映射: rel1:0, rel2:1, rel3:2, rel4:3, rel5:4
    * 再將邏輯表達式轉爲下標集合: Seq(Set(0, 1), Set(2, 3), Set(4))
    * 最後轉爲邏輯關係矩陣:
    * 1  1  0  0  0
    * 0  0  1  1  0
    * 0  0  0  0  1
    * @param seq 邏輯操做關係下標集合3####################
    * @return 返回邏輯矩陣
    */
  def createLogicMatrix(seq: Seq[Set[Int]]): Matrix[Int] ={
    require(seq.exists(_.nonEmpty), s"輸入參數不能爲空, 但當前獲取的參數值爲${seq.toString}")

    val numRow = seq.length
    val numCol = seq.map(x => x.max).max + 1
    val matrix = Matrix.zeros[Int](numRow, numCol)

    seq.indices.foreach(rowIndex => {
      seq(rowIndex).foreach(colIndex => {
        require(colIndex >= 0, s"邏輯關係集轉換爲邏輯矩陣異常, 行索引必須爲非零整數, 但當前值爲$colIndex")
        matrix(rowIndex, colIndex) = 1
      })
    })

    matrix
  }

  /**
    * 邏輯匹配
    * @param set 兩節點之間的全部關係對應的編號集合
    * @param matrix 邏輯矩陣
    * @return
    */
  def logicMatch(set: Set[Int], matrix: Matrix[Int]): Boolean ={
    require(set.max < matrix.cols, s"set集合中元素必須小於matrix的列數, 當前set值爲$set, matrix列數爲${matrix.cols}")

    val vec = Vector.zeros[Int](matrix.cols)
    set.foreach(i => vec(i) = 1)

    val one = Vector.fill(matrix.cols)(1)
    val diff = matrix * (vec - one)

    diff.exists(_ == 0)
  }

  def main(args: Array[String]): Unit = {


    val seq = Seq(Set(1, 2), Set(2, 3, 4), Set(4))

    val m = createLogicMatrix(seq)

    println(m)

    val set = Set(2, 3)

    println(logicMatch(set, m))
    println(logicMatch(Set(2, 4), m))

  }

}

3 邏輯操做解析

3.1 json格式的邏輯樹解析

json格式的邏輯形式以下:

{
    "logicOperator": "AND",
    "rules": [{
      "logicOperator": "AND",
      "rules": [{
        "logicOperator": "OR",
        "rules": [{
          "name": "te_transfer_dm_v1"
        }]
      }]
    }]
}

上述的json是一個嵌套結構,根據不一樣的嵌套深度,能夠表示任意嵌套的邏輯操做,在同一層下,logicOperator的值是AND或者OR,表示該層級的邏輯關係,即rules對於數據內的關係是AND仍是OR,若是logicOperator的值爲AND,則rules對於數組內全部邏輯關係都是用AND進行操做

經過遞歸地去解析,其源碼以下:

def parseLogicTree(jSONObject: JSONObject): immutable.Seq[immutable.Set[Int]] ={
    if (jSONObject.containsKey("rules")){
      val logicOperator = jSONObject.getString("logicOperator")
      require(logicOperator == "AND" || logicOperator == "OR",
        s"邏輯操做符必須是'AND'或者'OR', 但當前獲取的值爲$logicOperator")
      val rules = jSONObject.getJSONArray("rules")
      val result = if (!rules.isEmpty){
        logicOperator match {
          case "AND" =>
            val list = rules.toArray.map(x => parseLogicTree(x.asInstanceOf[JSONObject]))
            val mergeResult = list.reduceLeft((list1, list2) => {
              list1.flatMap(s1 => list2.map(s2 => s1++s2))
            })
            mergeResult
          case "OR" =>
            rules.toArray.flatMap(x => parseLogicTree(x.asInstanceOf[JSONObject])).toList
        }
      } else {
        immutable.Seq.empty[immutable.Set[Int]]
      }
      result
    } else {
      val tableName = jSONObject.getString("name")
      updateTableIndexMap(tableName)
      immutable.Seq(immutable.Set(tableIndexMap.getOrElse(tableName, 0)))
    }
  }

完整代碼:

package com.haizhi.alg.tag.util

import breeze.linalg.{Matrix, Vector}
import com.alibaba.fastjson.{JSON, JSONObject}

import scala.collection.mutable

/**
  * Created by zhoujiamu on 2020/2/24.
  */
object RelateLogicUtils extends Serializable {

  private val nameIndexMap: mutable.HashMap[String, Int] = new mutable.HashMap[String, Int]()
  private var index: Int = 0

  /**
    * 爲每一個關係分配id
    * @param relateName 關係名稱
    */
  private def updateTableIndexMap(relateName: String): Unit ={
    if (!nameIndexMap.contains(relateName)) {
      index += 1
      nameIndexMap.put(relateName, index)
    }
  }

  def parseLogicTree(jSONObject: JSONObject): Seq[Set[Int]] ={
    if (jSONObject.containsKey("rules")){
      val logicOperator = jSONObject.getString("logicOperator")
      require(logicOperator == "AND" || logicOperator == "OR",
        s"邏輯操做符必須是'AND'或者'OR', 但當前獲取的值爲$logicOperator")
      val rules = jSONObject.getJSONArray("rules")
      val result = if (!rules.isEmpty){
        logicOperator match {
          case "AND" =>
            val list = rules.toArray.map(x => parseLogicTree(x.asInstanceOf[JSONObject]))
            val mergeResult = list.reduceLeft((list1, list2) => {
              list1.flatMap(s1 => list2.map(s2 => s1++s2))
            })
            mergeResult
          case "OR" =>
            rules.toArray.flatMap(x => parseLogicTree(x.asInstanceOf[JSONObject])).toList
        }
      } else {
        Seq.empty[Set[Int]]
      }
      result
    } else {
      val name = jSONObject.getString("name")
      updateTableIndexMap(name)
      Seq(Set(nameIndexMap.getOrElse(name, -1)))
    }
  }

  /**
    * 建立關係邏輯矩陣,矩陣元素是0或者1,矩陣的列表明且,行表明或
    * 例如想要表達邏輯關係爲:(rel1&rel2 ) || (rel3&rel4) || rel5
    * 先作關係與下標映射: rel1:0, rel2:1, rel3:2, rel4:3, rel5:4
    * 再將邏輯表達式轉爲下標集合: Seq(Set(0, 1), Set(2, 3), Set(4))
    * 最後轉爲邏輯關係矩陣:
    * 1  1  0  0  0
    * 0  0  1  1  0
    * 0  0  0  0  1
    * @param seq 邏輯操做關係下標集合3####################
    * @return 返回邏輯矩陣
    */
  def createLogicMatrix(seq: Seq[Set[Int]]): Matrix[Int] ={
    require(seq.exists(_.nonEmpty), s"輸入參數不能爲空, 但當前獲取的參數值爲${seq.toString}")

    val numRow = seq.length
    val numCol = seq.map(x => x.max).max + 1
    val matrix = Matrix.zeros[Int](numRow, numCol)

    seq.indices.foreach(rowIndex => {
      seq(rowIndex).foreach(colIndex => {
        require(colIndex >= 0, s"邏輯關係集轉換爲邏輯矩陣異常, 行索引必須爲非零整數, 但當前值爲$colIndex")
        matrix(rowIndex, colIndex) = 1
      })
    })

    matrix
  }

  /**
    * 邏輯匹配
    * @param set 兩節點之間的全部關係對應的編號集合
    * @param matrix 邏輯矩陣
    * @return
    */
  def logicMatch(set: Set[Int], matrix: Matrix[Int]): Boolean ={
    require(set.max < matrix.cols, s"set集合中元素必須小於matrix的列數, 當前set值爲$set, matrix列數爲${matrix.cols}")

    val vec = Vector.zeros[Int](matrix.cols)
    set.foreach(i => vec(i) = 1)

    val one = Vector.fill(matrix.cols)(1)
    val diff = matrix * (vec - one)

    diff.exists(_ == 0)
  }

  def main(args: Array[String]): Unit = {

    val seq = Seq(Set(1, 2), Set(2, 3, 4), Set(4))

    val m = createLogicMatrix(seq)

    println("-------------Case-1--------------")

    println("邏輯矩陣: \n" + m)

    var set = Set(2, 3)

    if (logicMatch(set, m))
      println(set + " 匹配上")
    else
      println(set + " 匹配不上")

    set = Set(2, 4)
    if (logicMatch(set, m))
      println(set + " 匹配上")
    else
      println(set + " 匹配不上")

    println("\n-------------Case-2--------------")

    val str =
      """
        |{
        |    "logicOperator": "AND",
        |    "rules": [{
        |         "logicOperator": "AND",
        |         "rules": [{
        |               "logicOperator": "OR",
        |               "rules": [{
        |                     "name": "v1"
        |               },{
        |                     "name": "v0"
        |               }]
        |         }, {
        |               "logicOperator": "AND",
        |               "rules": [{
        |                     "name": "v2"
        |               }, {
        |                     "name": "v3"
        |               }]
        |         }]
        |    },{
        |         "logicOperator": "OR",
        |         "rules": [{
        |               "logicOperator": "AND",
        |               "rules": [{
        |                     "name": "v4"
        |               },{
        |                     "name": "v5"
        |               }]
        |         }, {
        |               "logicOperator": "OR",
        |               "rules": [{
        |                     "name": "v6"
        |               }, {
        |                     "name": "v7"
        |               }]
        |         }]
        |    }],
        |    "superVertexLimit": "10000"
        |}
      """.stripMargin


    val jSONObject = JSON.parseObject(str)
    val parseResult = parseLogicTree(jSONObject)

    val matrix = createLogicMatrix(parseResult)

    println("邏輯矩陣: ")
    println(matrix)

    val relateTypes = Set("v4", "v2", "v8")

    println("名稱對應的索引: ")
    nameIndexMap.foreach(println)

    val index = relateTypes.map(x => nameIndexMap.getOrElse(x, 0))

    if (logicMatch(index, matrix))
      println("能夠匹配上: " + relateTypes)
    else
      println("未匹配上: " + relateTypes)

    val relateTypes1 = Set("v1", "v2", "v3", "v6", "v8")

    val index1 = relateTypes1.map(x => nameIndexMap.getOrElse(x, 0))

    if (logicMatch(index1, matrix))
      println("能夠匹配上: " + relateTypes1)
    else
      println("未匹配上: " + relateTypes1)
  }
}

運行結果:

-------------Case-1--------------
邏輯矩陣: 
0  1  1  0  0  
0  0  1  1  1  
0  0  0  0  1  
Set(2, 3) 匹配不上
Set(2, 4) 匹配上

-------------Case-2--------------
邏輯矩陣: 
0  1  0  1  1  1  1  0  0  
0  1  0  1  1  0  0  1  0  
0  1  0  1  1  0  0  0  1  
0  0  1  1  1  1  1  0  0  
0  0  1  1  1  0  0  1  0  
0  0  1  1  1  0  0  0  1  
名稱對應的索引: 
(v2,3)
(v4,5)
(v7,8)
(v1,1)
(v0,2)
(v3,4)
(v6,7)
(v5,6)
未匹配上: Set(v4, v2, v8)
能夠匹配上: Set(v2, v6, v8, v1, v3)

在case1中,對於Set(2, 3),邏輯矩陣不存在二、3列(列號從0開始)元素之和大於0,且其他元素之和0的行,因此匹配不上。對於Set(2, 4), 矩陣的最後一行中,二、4列元素之和爲1,大於0,且其他元素之和是0,因此能夠匹配上

在case2中,與case1同樣,只不過這裏須要經過索引映射把名稱轉換爲索引值,再去取矩陣的元素進行分析。

相關文章
相關標籤/搜索