基于矩阵法逻辑匹配实现方案

南笙酒味 提交于 2020-03-12 19:31:08

1. 需求

在知识图谱关系网络中,节点之间往往会存在多种不同类型的边,例如在人际关系网络中,人与人之间的关系可能存在同学关系、转账关系、微信好友、相互关注、同一城市等等。在做节点特征进行统计分析的时候,我们可能会跟进给定的关系的逻辑条件去筛选符合条件的关系进行统计,例如满足“即使同学关系同时又是同一城市”,或者是“存在转账规则或存在相互关注”,即在满足设定的关系逻辑组合下进行邻居节点特征统计。

把问题抽象化为如下:

已知待匹配逻辑关系集合,判断某一个组合是否满足逻辑表达式。
例如,已知待匹配逻辑运算:
(X1 or X3) and (X4 or X5)
X1 or ((X2 or X4) and X5)

对应给定的组合X1X3X4X6, 判断该组合是否满足上述其中一种模式

例如:X1X3是满足,因为匹配逻辑中存在X1即可, 故X1X3可以匹配上;X5X6不满足,因为X5必须是要与X1、X3、X2、X4中至少一个同时出现

2.解决方案

2.1 逻辑矩阵表示法

  • 将逻辑元素进行编号, X0:0, X1:1, X2:2, X3:3, X4:4, X5:

  • 将匹配逻辑运算进行拆分,去掉括号分解为若干个and单元,上述拆完后结果为

    X0
    X1
    X3
    X2X5
    X4X5
    X1X2X3X4
    X1X2X3X5
    
  • 将上述拆分结果转化为数组, 数组元素为上述每一行中对应的编号组成的集合

    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列如下:

    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。简单了解其实就是矩阵的行代表逻辑‘或’运算,而矩阵的列代表逻辑‘且’运算。例如:

    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版本的实现代码,代码中没有包括转码与逻辑解析部分,后续补充完整

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),逻辑矩阵不存在2、3列(列号从0开始)元素之和大于0,且其余元素之和0的行,所以匹配不上。对于Set(2, 4), 矩阵的最后一行中,2、4列元素之和为1,大于0,且其余元素之和是0,所以可以匹配上

在case2中,与case1一样,只不过这里需要通过索引映射把名称转换为索引值,再去取矩阵的元素进行分析。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!