双向遍历框架

双向遍历由两个从不同节点开始的遍历组成,并返回两条遍历相遇的路径。双向遍历框架允许使用 BidirectionalTraversalDescription 来描述此类遍历。

以下是从起始节点 1 到结束节点 5 的双向遍历的可视化表示,所有加粗的关系在满足以下限制时会出现在结果路径中

  • 起始侧遍历器仅遍历类型为 A 的关系。

  • 结束侧遍历器仅遍历类型为 B 的关系。

bidirectional example

请注意,仅有一个遍历器到达对侧起始/结束节点的路径也会被包括,例如 (1)-[:A]→(5)(1)-[:B]→(5)。在接受的路径 (1)-[:A]→(2)-[:A]→(3)-[:B]→(4)-[:B]→(5) 中,遍历器在节点 3 处相遇。

Uniqueness 在两个遍历之间共享,这意味着如果将 Uniqueness 设置为 Uniqueness.NODE_GLOBAL(默认值),则不会有结果,因为两个遍历都需要到达同一个节点才能产生碰撞。因此,在使用 BidirectionalTraversalDescription 时,请始终手动设置 Uniqueness。有关可用选项的更多信息,请参阅 Uniqueness 选项

定义双向遍历器

创建 BidirectionalTraversalDescription 时,可以为每一侧选择各自的 TraversalDescription。这可以通过为每一侧(startSide/endSide)单独设置,或为两侧统一设置(mirroredSides)来实现。

起始侧和结束侧遍历

下面是一个使用 BidirectionalTraversalDescription、为 startSide()endSide() 分别提供单独遍历器的示例

BidirectionalTraversalDescription td = transaction
        .bidirectionalTraversalDescription()
        .startSide(transaction
                .traversalDescription()
                .expand(PathExpanders.forTypeAndDirection(RelationshipType.withName("A"), Direction.OUTGOING))
                .uniqueness(Uniqueness.RELATIONSHIP_GLOBAL))
        .endSide(transaction
                .traversalDescription()
                .expand(PathExpanders.forTypeAndDirection(RelationshipType.withName("B"), Direction.INCOMING))
                .uniqueness(Uniqueness.RELATIONSHIP_GLOBAL));
td.traverse(startNode, endNode);

一侧从起始节点向前遍历,并展开所有类型为 A 的外向关系。另一侧则从结束节点向后遍历,展开所有类型为 B 的内向关系。

最终路径从起始节点到结束节点,且所有关系均为外向方向。可能的路径有:

  • 所有关系均为类型 A

  • 所有关系均为类型 B

  • 起始节点的关系为类型 A,在相遇之后全部变为类型 B

镜像遍历

mirroredSides(TraversalDescription) 方法同时为双向遍历的起始侧和结束侧设置 TraversalDescription。然而,结束侧是起始侧的反向。对于 RelationshipsDirection,这意味着 OUTGOING 变为 INCOMING,反之亦然。PathExpander 接口还提供了 reverse() 方法,如果在 mirroredSides 实现中使用,需要对其进行覆盖。

BidirectionalTraversalDescription td = transaction
        .bidirectionalTraversalDescription()
        .mirroredSides(transaction
                               .traversalDescription()
                               .uniqueness(Uniqueness.RELATIONSHIP_GLOBAL));
td.traverse(startNode, endNode);

侧选择器

在双向遍历中,遍历器会在每一步选择从哪一侧(起始或结束)前进。可以通过实现 SideSelectorPolicy 接口来修改此行为,因为该接口包含用于决定下一步从哪一侧遍历的函数。

  • Direction.OUTGOING — 来自起始节点的遍历器。

  • Direction.INCOMING — 来自结束节点的遍历器。

内置的策略包括:

  • SideSelectorPolicies.LEVEL — 如果组合深度大于给定的最大深度,则停止遍历。

  • SideSelectorPolicies.LEVEL_STOP_DESCENT_ON_RESULT — 一旦找到结果就立即停止。

  • SideSelectorPolicies.ALTERNATING — 交替决定哪一侧继续遍历。

SideSelectorPolicy 可选地接受 maxDepth 参数,表示两侧必须共同遵守的最大组合深度。

以下示例展示了如何使用内置的 SideSelectorPolicy,并将最大组合深度设置为 5

BidirectionalTraversalDescription td = transaction
        .bidirectionalTraversalDescription()
        .mirroredSides(transaction
                               .traversalDescription()
                               .uniqueness(Uniqueness.RELATIONSHIP_GLOBAL))
                               .sideSelector(SideSelectorPolicies.LEVEL, 5);
td.traverse(startNode, endNode);

碰撞策略

BranchCollisionPolicy 定义了在双向遍历中何时检测并接受碰撞。给定评估器和路径谓词,BranchCollisionPolicy 会创建 BranchCollisionDetector,该检测器在两个遍历器之间检测碰撞,并在满足碰撞评估器和唯一性约束的条件下将结果路径加入结果集。

以下是内置的 BranchCollisionPolicies

  • STANDARD — 返回所有含有碰撞的路径。

  • SHORTEST_PATH — 返回所有具有最小遍历深度的路径。

以下示例展示了如何使用内置的 BranchCollisionPolicy

BidirectionalTraversalDescription td = transaction
        .bidirectionalTraversalDescription()
        .mirroredSides(transaction
                               .traversalDescription()
                               .uniqueness(Uniqueness.RELATIONSHIP_GLOBAL))
        .collisionPolicy(BranchCollisionPolicies.SHORTEST_PATH);
td.traverse(startNode, endNode);

碰撞评估器

便利方法 collisionEvaluator() 会添加 PathEvaluator,用于在有效碰撞中验证路径。多次使用该方法会将评估器组合起来。

BidirectionalTraversalDescription td = transaction
    .bidirectionalTraversalDescription()
    .mirroredSides(transaction
       .traversalDescription()
       .uniqueness(Uniqueness.RELATIONSHIP_GLOBAL))
    .collisionEvaluator(Evaluators.atDepth(3));
td.traverse(startNode, endNode);