TinkerPop 图中的深度优先树遍历
Depth-first tree traversal in TinkerPop graph
给定一个树状结构的 TinkerPop 图,其顶点通过标记的父子关系 ([parent-PARENT_CHILD->child]
) 连接,遍历和查找所有这些节点的惯用方法是什么?
我是图遍历的新手,所以用递归函数遍历它们似乎或多或少简单:
Stream<Vertex> depthFirst(Vertex v) {
Stream<Vertex> selfStream = Stream.of(v);
Iterator<Vertex> childIterator = v.vertices(Direction.OUT, PARENT_CHILD);
if (childIterator.hasNext()) {
return selfStream.appendAll(
Stream.ofAll(() -> childIterator)
.flatMap(this::depthFirst)
);
}
return selfStream;
}
(N.b。此示例使用 Vavr 流,但 Java 流版本类似,只是稍微更冗长。)
我认为图形原生实现的性能会更高,尤其是在内存中 TinkerGraph 以外的数据库上。
但是,当我查看 TinkerPop tree recipes 时,我并不清楚 repeat()
/ until()
等的哪种组合才是我想要的正确组合。
如果我只想找到那些具有特定标签的顶点(叶子或分支),我可以看到如何使用上面的函数来实现:
Stream<Vertex> nodesWithMyLabel = depthFirst(root)
.filter(v -> "myLabel".equals(v.label()));
但这是否有效还远非显而易见,我认为必须有更好的图形原生方法。
如果您使用的是 TinkerPop,最好只使用 Gremlin 编写您的遍历。让我们使用食谱中描述的树:
g.addV().property(id, 'A').as('a').
addV().property(id, 'B').as('b').
addV().property(id, 'C').as('c').
addV().property(id, 'D').as('d').
addV().property(id, 'E').as('e').
addV().property(id, 'F').as('f').
addV().property(id, 'G').as('g').
addE('hasParent').from('a').to('b').
addE('hasParent').from('b').to('c').
addE('hasParent').from('d').to('c').
addE('hasParent').from('c').to('e').
addE('hasParent').from('e').to('f').
addE('hasParent').from('g').to('f').iterate()
要找到 "A" 的所有 children,您只需执行以下操作:
gremlin> g.V('A').repeat(out()).emit()
==>v[B]
==>v[C]
==>v[E]
==>v[F]
上面的遍历基本上是说,"Start at 'A" 个顶点并遍历外边,直到没有更多,哦,顺便说一句,边走边发射每个 child 个顶点。”如果你也想获得 "A" 的根,那么你只需要稍微改变一下:
gremlin> g.V('A').emit().repeat(out())
==>v[A]
==>v[B]
==>v[C]
==>v[E]
==>v[F]
更进一步,如果您只想根据某些过滤器(在您的问题中指定的标签)发出某些顶点,您只需向 emit()
提供一个过滤参数。在这种情况下,我只发出那些有多个入边的顶点:
gremlin> g.V('A').emit(inE().count().is(gt(1))).repeat(out())
==>v[C]
==>v[F]
经过一定数量的反复试验,我最终得到的结果是:
GraphTraversal<Vertex, Vertex> traversal =
graph.traversal().V(parent)
.repeat(out(PARENT_CHILD)) // follow only edges labeled PARENT_CHILD
.emit()
.hasLabel("myLabel"); // filter for vertices labeled "myLabel"
请注意,这与原始问题中的递归版本略有不同,因为我意识到我实际上并不想在结果中包含父项。 (我 认为 ,来自 Repeat Step docs,我可以通过将 emit()
放在 repeat()
之前来包含父项,但我还没有尝试过。 )
给定一个树状结构的 TinkerPop 图,其顶点通过标记的父子关系 ([parent-PARENT_CHILD->child]
) 连接,遍历和查找所有这些节点的惯用方法是什么?
我是图遍历的新手,所以用递归函数遍历它们似乎或多或少简单:
Stream<Vertex> depthFirst(Vertex v) {
Stream<Vertex> selfStream = Stream.of(v);
Iterator<Vertex> childIterator = v.vertices(Direction.OUT, PARENT_CHILD);
if (childIterator.hasNext()) {
return selfStream.appendAll(
Stream.ofAll(() -> childIterator)
.flatMap(this::depthFirst)
);
}
return selfStream;
}
(N.b。此示例使用 Vavr 流,但 Java 流版本类似,只是稍微更冗长。)
我认为图形原生实现的性能会更高,尤其是在内存中 TinkerGraph 以外的数据库上。
但是,当我查看 TinkerPop tree recipes 时,我并不清楚 repeat()
/ until()
等的哪种组合才是我想要的正确组合。
如果我只想找到那些具有特定标签的顶点(叶子或分支),我可以看到如何使用上面的函数来实现:
Stream<Vertex> nodesWithMyLabel = depthFirst(root)
.filter(v -> "myLabel".equals(v.label()));
但这是否有效还远非显而易见,我认为必须有更好的图形原生方法。
如果您使用的是 TinkerPop,最好只使用 Gremlin 编写您的遍历。让我们使用食谱中描述的树:
g.addV().property(id, 'A').as('a').
addV().property(id, 'B').as('b').
addV().property(id, 'C').as('c').
addV().property(id, 'D').as('d').
addV().property(id, 'E').as('e').
addV().property(id, 'F').as('f').
addV().property(id, 'G').as('g').
addE('hasParent').from('a').to('b').
addE('hasParent').from('b').to('c').
addE('hasParent').from('d').to('c').
addE('hasParent').from('c').to('e').
addE('hasParent').from('e').to('f').
addE('hasParent').from('g').to('f').iterate()
要找到 "A" 的所有 children,您只需执行以下操作:
gremlin> g.V('A').repeat(out()).emit()
==>v[B]
==>v[C]
==>v[E]
==>v[F]
上面的遍历基本上是说,"Start at 'A" 个顶点并遍历外边,直到没有更多,哦,顺便说一句,边走边发射每个 child 个顶点。”如果你也想获得 "A" 的根,那么你只需要稍微改变一下:
gremlin> g.V('A').emit().repeat(out())
==>v[A]
==>v[B]
==>v[C]
==>v[E]
==>v[F]
更进一步,如果您只想根据某些过滤器(在您的问题中指定的标签)发出某些顶点,您只需向 emit()
提供一个过滤参数。在这种情况下,我只发出那些有多个入边的顶点:
gremlin> g.V('A').emit(inE().count().is(gt(1))).repeat(out())
==>v[C]
==>v[F]
经过一定数量的反复试验,我最终得到的结果是:
GraphTraversal<Vertex, Vertex> traversal =
graph.traversal().V(parent)
.repeat(out(PARENT_CHILD)) // follow only edges labeled PARENT_CHILD
.emit()
.hasLabel("myLabel"); // filter for vertices labeled "myLabel"
请注意,这与原始问题中的递归版本略有不同,因为我意识到我实际上并不想在结果中包含父项。 (我 认为 ,来自 Repeat Step docs,我可以通过将 emit()
放在 repeat()
之前来包含父项,但我还没有尝试过。 )