如何快速生成Neo4j中的k-ary树?

时间:2016-10-09 13:23:50

标签: neo4j

我在neo4j中生成完美的k-ary树,但我这样做的查询看起来效率不高我想知道我是否可以改进它们,我的下面的代码显示了所有三个查询我正在运行以生成树,k是每个节点的子节点数,h是树高:

part of a perfect 3-ary tree

func createPerfectKaryTreeInNeo(k, h int, execNeo func(string) error) error {
    lastNode := ((iPow(k, (h + 1)) - 1) / (k - 1)) - 1
    err := execNeo(fmt.Sprintf("FOREACH(i IN RANGE(0, %d, 1) | CREATE (:NODE {id:i, value:i}))", lastNode))
    if err != nil {
        return err
    }
    err = execNeo(fmt.Sprintf("MATCH (a:NODE), (b:NODE) WHERE b.id = a.id * %d + 1 CREATE (a)-[:FIRST_CHILD]->(b)", k))
    if err != nil {
        return err
    }
    err = execNeo(fmt.Sprintf("MATCH (a:NODE), (b:NODE) WHERE b.id = a.id + 1 AND a.id %% %d <> 0 CREATE (a)-[:NEXT_SIBLING]->(b)", k))
    if err != nil {
        return err
    }
    return nil
}

我认为这对于h&gt;来说很慢9因为最后2个查询,2个未连接节点上的MATCH,当我在neo4j web客户端运行它时它警告:

  

此查询在断开连接的模式之间构建笛卡尔积。   如果查询的一部分包含多个断开连接的模式,则此操作   将在所有这些部分之间建立一个笛卡尔积。这可能   产生大量数据并减慢查询处理速度。而   偶尔打算,通常可以重新制定   查询,避免使用这个交叉产品,也许通过添加一个   不同部分之间的关​​系或使用OPTIONAL MATCH   (标识符为:(b))

有没有办法可以重新制定这些查询以提高效率?

编辑:

如果你想运行它,代码就在这里:https://github.com/robsix/data_model_perf_test

2 个答案:

答案 0 :(得分:1)

图表旨在快速识别单个点,然后从那里进行遍历。您的查询结构(写入所有节点,然后对它们进行排序并添加关系)几乎完全相反,这就是您获取所有这些警告的原因。不幸的是,要为每个节点提供变量子节点,您需要能够快速查询id属性,因此请确保在:Node(id)上有一个索引,然后尝试单个大查询,如下所示:

WITH 3 AS k, 2 AS h
WITH k, REDUCE(s = toFloat(0), x IN RANGE(1, h-1)|s + k^x) AS max_parent_id
UNWIND RANGE(0, toInt(max_parent_id)) AS parent_id
WITH k, parent_id, k*parent_id+1 AS first_child_id
MERGE (parent:NODE {id:parent_id, value:parent_id})
MERGE (child:NODE {id: first_child_id, value:first_child_id})
MERGE (parent) - [:FIRST_CHILD] -> (child)
WITH k, first_child_id
UNWIND RANGE(first_child_id + 1, first_child_id + k - 1) AS next_child_id
MERGE (last_child:NODE {id:next_child_id -1, value:next_child_id -1})
MERGE (next_child:NODE {id:next_child_id, value:next_child_id})
MERGE (last_child) - [:NEXT_SIBLING] -> (next_child)

这将贯穿所有可能的父ID,并且对于每个父ID,将MERGE(匹配或创建)具有正确ID的节点。然后,它将MERGE第一个子节点,您已经可以计算其ID,以及 FIRST_CHILD关系。这样可以避免笛卡尔问题。然后,查询将通过每个可能的兄弟的ID到第一个,MATCH现有的兄弟,MERGE下一个兄弟以及关系。

更新:我很抱歉,在测试时我完全忽略了节点可视化。我已经更新了查询,以解决索引错误和帐户重新排序,我不知道Cypher做了什么。你每天都学到东西!但是,现在那里有什么产生了正确的图表。

答案 1 :(得分:0)

我能想到的最好的仍然是使用三个查询,但是以一种有趣的方式使用它来创建K-ary树而不使neo4j进行太多搜索:

func createPerfectKaryTreeInNeo(k, h int, execNeo func(string) error) error {
    lastNode := ((iPow(k, (h+1)) - 1) / (k - 1)) - 1
    if lastNode % 2 != 0 {
        err := execNeo(fmt.Sprintf("UNWIND RANGE(0, %d, 2) AS id CREATE (a:NODE {id:id, value: id})-[:NEXT_SIBLING]->(b:NODE {id: id+1, value: id+1}) WITH a, b MATCH (c:NODE {id: b.id+1}) CREATE (b)-[:NEXT_SIBLING]->(c)", lastNode - 1))
        if err != nil {
            return err
        }
    } else {
        err := execNeo(fmt.Sprintf("UNWIND RANGE(1, %d, 2) AS id CREATE (a:NODE {id:id, value: id})-[:NEXT_SIBLING]->(b:NODE {id: id+1, value: id+1}) WITH a, b MATCH (c:NODE {id: b.id+1}) CREATE (b)-[:NEXT_SIBLING]->(c)", lastNode))
        if err != nil {
            return err
        }
        err = execNeo("MATCH (a:NODE {id:1}) CREATE (:NODE {id:0, value:0})-[:NEXT_SIBLING]->(a)")
    }
    lastParentNode := (lastNode - 1) / k
    err := execNeo(fmt.Sprintf("UNWIND RANGE(0, %d, 1) AS id MATCH shortestPath((a:NODE {id:id})-[:NEXT_SIBLING *]->(b:NODE {id:id*%d+1})) CREATE (a)-[:FIRST_CHILD]->(b)", lastParentNode, k))
    if err != nil {
        return err
    }
    err = execNeo(fmt.Sprintf("MATCH (a:NODE)-[r:NEXT_SIBLING]->(b:NODE) WHERE a.id %% %d = 0 DELETE r", k))
    if err != nil {
        return err
    }
    return nil
}

我应该注意到这个算法专门用于完整的k-ary树,其中节点id以广度优先顺序分配,它的工作方式是:

1)成对生成所有节点并按顺序将它们全部分配为彼此的NEXT_SIBLINGS,即0-> 1-> 2-> 3-> 4,因此最终得到直图。

2)循环遍历所有小到足以生成子项的id并使用shortestPath函数进行匹配,希望neo4j足够聪明,只要找到图形的当前形状就可以解决问题匹配,这是最短路径,所以尽早返回而不继续搜索。

3)最后一个查询然后抓取不应被视为NEXT_SIBLINGS的相邻节点并删除关系,留下一个深度为h的完美k-ary树。

这些变化加速了数据创建至少一个数量级。

更新:

上面接受的答案是正确的,这只是匹配它的go代码:

func createPerfectKaryTreeInNeo(k, h int, execNeo func(string) error) error {
    return execNeo(fmt.Sprintf(`
    WITH %d AS k, %d AS h
    WITH k AS k, REDUCE(s = toFloat(0), x IN RANGE(1, h-1)|s + k^x) AS max_parent_id
    UNWIND RANGE(0, toInt(max_parent_id)) AS parent_id
    WITH k AS k, parent_id, k*parent_id+1 AS first_child_id
    MERGE (parent:NODE {id:parent_id, value:parent_id})
    MERGE (child:NODE {id: first_child_id, value:first_child_id})
    MERGE (parent) - [:FIRST_CHILD] -> (child)
    WITH k AS k, first_child_id
    UNWIND RANGE(first_child_id + 1, first_child_id + k - 1) AS next_child_id
    MERGE (last_child:NODE {id:next_child_id -1, value:next_child_id -1})
    MERGE (next_child:NODE {id:next_child_id, value:next_child_id})
    MERGE (last_child) - [:NEXT_SIBLING] -> (next_child)
    `, k, h))
}

这比我在本回答中最初描述的改进快了几个数量级