如何在 Gremlin 重复开始时执行一次关闭副作用?

问题描述

我需要运行一个 Gremlin 查询,该查询从图的叶子(没有出边的顶点)和无边顶点沿着图向下移动,收集起始顶点和传入顶点(一次 1 级)直到一定的限度。不得超过此限制,因此如果下一级的入站顶点会导致计数超过限制,那么我们不会收集这些顶点并返回我们拥有的内容。这是我目前所拥有的:

g.V().or(__.not(outE()),__.not(bothE())).limit(700)
.store('a')
.repeat(__.sideEffect(select('b').store('a')).in().as('b'))
.until(union(cap('a').unfold().count(),select('b').count()).sum().is(gt(700)))
.cap('a').unfold()

问题在于 sideEffect 步骤中的 repeat 步骤对流中的每个顶点执行一次。无论流中有多少个顶点,我都希望它只执行一次。我该如何实现?

解决方法

我不确定这种方法是否适用于 CosmosDB,但它很可能是您可以实际使用 Gremlin 的唯一方法,它不涉及多个 Gremlin 请求和额外处理。我使用此示例图进行演示:

g = TinkerGraph.open().traversal()
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').
           addE('next').from('a').to('b').
           addE('next').from('b').to('c').
           addE('next').from('b').to('d').
           addE('next').from('c').to('e').
           addE('next').from('d').to('e').
           addE('next').from('e').to('f').iterate()

该方法涉及使用 group(),每次循环遍历 repeat() 时,您基本上都会形成一个新的遍历对象分组:

gremlin> g.V('A').
......1>   group('m').by(constant(-1)).
......2>   repeat(out().group('m').by(loops())).
......3>   cap('m')
==>[-1:[v[A]],0:[v[B]],1:[v[C],v[D]],2:[v[E],v[E]],3:[v[F],v[F]]]

这为您提供了要处理的数据的结构,现在您只需要确保尽早终止 repeat()

gremlin> g.V('A').
......1>   group('m').by(constant(-1)).
......2>   until(cap('m').select(values).unfold().count(local).sum().is(gte(3))).
......3>     repeat(out().group('m').by(loops())).
......4>   cap('m')
==>[-1:[v[A]],v[D]]]

在上面的例子中,我们查看 until() 中的“m”,并对到目前为止收集的所有顶点进行计数。当它超过我们的最大值时,在这种情况下为“3”,我们退出。当我们退出时,我们可以看到我们收集的可能会或可能不会超出我们的需要。在这个例子中我们做到了,所以我们需要把它扔掉。从技术上讲,您需要除最后一组之外的所有分组来满足您的限制,但不幸的是,Gremlin 的“除最后一组”并不容易。我最终采用了这种方法,它基本上抓住了要扔掉的最后一项,然后将其用作对结果的过滤器。请注意,我们得到两个结果,因为遍历到下一个级别将超过我们总共“3”个结果的限制:

gremlin> g.V('A').
......1>   group('m').by(constant(-1)).
......2>   until(cap('m').select(values).unfold().count(local).sum().is(gt(3))).
......3>     repeat(out().group('m').by(loops())).
......4>   cap('m').
......5>   select(values).as('v').
......6>   tail(local).as('e').
......7>   select('v').unfold().
......8>   where(P.neq('e')).
......9>   unfold()
==>v[A]
==>v[B]

请注意,当我们从“3”的限制增加到“4”时,结果会发生变化,因为遍历到下一个级别会将总数增加 2,但总数不会超过 4。

gremlin> g.V('A').
......1>   group('m').by(constant(-1)).
......2>   until(cap('m').select(values).unfold().count(local).sum().is(gt(4))).
......3>     repeat(out().group('m').by(loops())).
......4>   cap('m').
......5>   select(values).as('v').
......6>   tail(local).as('e').
......7>   select('v').unfold().
......8>   where(P.neq('e')).
......9>   unfold()
==>v[A]
==>v[B]
==>v[C]
==>v[D]

下一个示例指出,我们不考虑重复项,因为从您的用例中不清楚预期的内容(或者这是否可行),但希望这为您提供了足够的结构,至少可以形成遍历您正在寻找:

gremlin> g.V('A').
......1>   group('m').by(constant(-1)).
......2>   until(cap('m').select(values).unfold().count(local).sum().is(gt(5))).
......3>     repeat(out().group('m').by(loops())).
......4>   cap('m').
......5>   select(values).as('v').
......6>   tail(local).as('e').
......7>   select('v').unfold().
......8>   where(P.neq('e')).
......9>   unfold()
==>v[A]
==>v[B]
==>v[C]
==>v[D]

感谢 Kelvin Lawrence 建议我在这个答案中采用的一般方法。