图遍历步骤(Graph Traversal Steps)
在最一般的层次上,Traversal<S,E>实现了Iterator
- Step<S,E>: 一个用来从S产生E的方法。Step在遍历中是链式的。
- TraversalStrategy: 拦截器方法来改变遍历的执行(例如查询重写)。
- TraversalSideEffects: 键/值对,可用于存储有关遍历的全局信息。
- Traverser
: the object propagating through the Traversal currently representing an object of type T.
示例数据
示例数据大多出自于Modern数据,如下图:
可以通过下图加载Modern图数据,并获取图遍历引用g:
gremlin> graph = TinkerFactory.createModern()
gremlin> g = graph.traversal()
1. General Steps
Step | Description |
---|---|
map(Traversal<S, E>) map(Function<Traverser<S>, E>) |
将运行程序映射到类型E的某个对象,以便下一步处理。 |
flatMap(Traversal<S, E>) flatMap(Function<Traverser<S>, Iterator<E>>) |
将遍历器映射到流向下一步的E对象的迭代器。 |
filter(Traversal<?, ?>) filter(Predicate<Traverser<S>>) |
将运行程序映射为true或false,其中false将不会将运行程序传递给下一步。 |
sideEffect(Traversal<S, S>) sideEffect(Consumer<Traverser<S>>) |
在移动器上执行一些操作,并将其传递到下一步。 |
branch(Traversal<S, M>) branch(Function<Traverser<S>,M>) |
将移动器拆分为由M令牌索引的所有遍历。 |
- filter步骤的示例
gremlin> g.V().filter(label().is('person'))
- map步骤的示例
gremlin> g.V(1).out().map(values('name'))
- sideEffect步骤的示例
gremlin> g.V().hasLabel('person').sideEffect(System.out.&println)
- branch步骤的示例
gremlin> g.V().branch(values('name')).
option('marko', values('age')).
option(none, values('name'))
2. Terminal Steps
一些步骤不返回遍历,而是执行遍历并返回结果。这些步骤就是Terminal步骤(终端步骤),并且通过下面的示例来解释它们。
gremlin> g.V().out('created').hasNext()
==>true
gremlin> g.V().out('created').next()
==>v[3]
gremlin> g.V().out('created').next(2)
==>v[3]
==>v[5]
gremlin> g.V().out('nothing').tryNext()
==>Optional.empty
gremlin> g.V().out('created').toList()
==>v[3]
==>v[5]
==>v[3]
==>v[3]
gremlin> g.V().out('created').toSet()
==>v[3]
==>v[5]
gremlin> g.V().out('created').toBulkSet()
==>v[3]
==>v[3]
==>v[3]
==>v[5]
gremlin> results = ['blah',3]
==>blah
==>3
gremlin> g.V().out('created').fill(results)
==>blah
==>3
==>v[3]
==>v[5]
==>v[3]
==>v[3]
注意几点:
- 上述
tryNext()
将返回一个Optional的,是一个hasNext()/next()的组合。 toSet()
与toBulkSet()
都返回一个去重的集合,区别在于后者通过加权处理重复对象。
3. AddEdge Step
推理是明确显示数据中隐含的内容的过程。图中显式的是图形的对象,即顶点和边。图中隐含的是遍历。即,通过遍历揭示了意义,而所谓的意义是有遍历的定义来决定的。
比如定义一个遍历,找出节点的合作开发者(co-developer),并在这种关系(边)上添加属性“year”:
gremlin> g.V(1).as('a').out('created').in('created').where(neq('a')).
addE('co-developer').from('a').property('year',2009)
如此,曾经隐含的含义可以可以通过addE()-step
(map/sideEffect)来显式。
Question
这里addE()-step
(map/sideEffect)意味着addE()步骤产生了map和sideEffect普通步骤的效果吗?
4. AddVertex Step
addV()
步骤用于向图中添加顶点(map/sideEffect)。
增加顶点,示例如下:
gremlin> g.addV('person').property('name','stephen')
gremlin> g.V().outE('knows').addV().property('name','nothing')
Question
gremlin> g.V().outE('knows').addV().property('name','nothing')
这种方式并没有增加节点之间的关联(边),那么它的意义在哪里?
5. AddProperty Step
property()
步骤用于向图形元素添加属性(sideEffect)。与addV()
和addE()
不同,property()
是一个完全的sideEffect步骤,它不会返回其创建的属性,而是返回添加属性的元素。那么,可以推测出在addV()
或addE()
步骤后使用property()
步骤可以在创建顶点或边的时候一次性创建多个属性。
- 为一个节点添加一个属性
gremlin> g.V(1).property('country','usa')
- 为一个节点添加多个属性
gremlin> g.V(1).property('city','santa fe').property('state','new mexico').valueMap()
- (对顶点)添加多值属性(Cardinality.list)
gremlin> g.V(1).property(list,'age',35)
- 添加元属性
gremlin> g.V(1).properties('name').property('author','inspur')
6. Aggregate Step
aggregate()
步骤(sideEffect)用于将遍历特定点处的所有对象(贪婪评估方式eager evaluation)聚合到集合中。
aggregate step
gremlin> g.V(1).out('created').aggregate('x').in('created').out('created').
where(without('x')).values('name')
==>ripple
7. And Step
and()
步骤确保所有提供的遍历产生结果(filter)。请参阅or()
。
gremlin> g.V().and(
outE('knows'),
values('age').is(lt(30))).
values('name')
==>marko
使用or()
步骤会产生以下结果:
gremlin> g.V().or(
outE('knows'),
values('age').is(lt(30))).
values('name')
==>marko
==>vadas
8. As Step
as()
步骤不是一个真正的步骤,而是一个类似于by()
和option()
的“调节器”。使用as()
,可以为步骤提供标签,后续的步骤或数据结果可以通过使用这些标签访问这些步骤。
gremlin> g.V().as('a').out('created').as('b').select('a','b')
==>[a:v[1],b:v[3]]
==>[a:v[4],b:v[5]]
==>[a:v[4],b:v[3]]
==>[a:v[6],b:v[3]]
一步可以有任何数量的与之相关联的标签。这对于在将来的步骤中多次引用相同的步骤很有用:
gremlin> g.V().hasLabel('software').as('a','b','c').
select('a','b','c').
by('name').
by('lang').
by(__.in('created').values('name').fold())
==>[a:lop,b:java,c:[marko,josh,peter]]
==>[a:ripple,b:java,c:[josh]]
Note
以上__.
(两个_)通过匿名方式产生了GraphTraversal。并使用了后续介绍的fold()
步骤。
9. Barrier Step
barrier()
步骤将延迟遍历管道转换为批量同步管道。可类比线程同步中的栅栏。在以下情况下,此步骤很有用:
- 使用在需要栅栏的场景
- 进行“膨胀优化”(bulking optimization)
膨胀优化对于某些程序执行效率的提高是非常明显的,如下示例:
gremlin> g = graph.traversal().withoutStrategies(LazyBarrierStrategy) //屏蔽默认的遍历策略
gremlin> clockWithResult(1){g.V().both().both().both().count().next()} //未使用barrier
gremlin> clockWithResult(1){g.V().both().barrier().both().barrier().both().barrier().count().next()} //使用barrier
上述优化过程需要去掉LazyBarrierStrategy
遍历策略,即默认情况下遍历器已经使用LazyBarrierStrategy
进行了优化(barrier()
步骤会在适当的情况下自动添加)。
- 支持参数
如果barrier()
被提供一个整数参数n,那么在将聚合的遍历器入下一个步骤之前,栅栏只会在其屏障中保留n个唯一的遍历器。
10. By Step
如果一个步骤能够接受遍历、函数或比较器等,那么by()
是添加它们的手段。
gremlin> g.V().group().by(bothE().count()) //将元素按其边数进行分组,属于接受“遍历”的情况
==>[1:[v[2],v[5],v[6]],3:[v[1],v[3],v[4]]]
gremlin> g.V().group().by(bothE().count()).by('name') //将通过其名称(元素属性投影)处理分组的元素
==>[1:[vadas,ripple,peter],3:[marko,lop,josh]]
gremlin> g.V().group().by(bothE().count()).by(count()) //将计算每个组中的元素数量
==>[1:3,3:3]
- 哪些步骤支持
by()
以下步骤支持by(),注意有些步骤支持一个by(),有些步骤支持多个by(),使用时需要参考文档。
dedup()
cyclicPath()
simplePath()
sample()
where()
groupCount()
group()
order()
path()
project()
select()
tree()
aggregate()
store()
11. Cap Step
cap()
用来将一些副作用步骤产生的结果发射(emits)出来。
如下使用label进行分组统计,并使用cap()
将分组结果展现出来:
gremlin> g.V().groupCount('a').by(label) //不使用cap()
==>v[1]
==>v[2]
==>v[3]
==>v[4]
==>v[5]
==>v[6]
gremlin> g.V().groupCount('a').by(label).cap('a') //使用cap()
==>[software:2,person:4]
另外,cap()支持多个key值,不同的key会组成 Map<String,Object>效果展现出来。
12. Choose Step
choose()
将当前遍历器路由到特定的遍历分支选项。可用来实现if-then-else语法结构。
判断条件可以放在choose步骤中,也可以放在option步骤中:
- choose中进行判断
gremlin> g.V().hasLabel('person').
choose(values('age').is(lte(30)),
__.in(),
__.out()).values('name')
==>marko
==>ripple
==>lop
==>lop
- option中进行判断
gremlin> g.V().hasLabel('person').
choose(values('age')).
option(27, __.in()).
option(32, __.out()).values('name')
==>marko
==>ripple
==>lop
13. Coalesce Step
coalesce()
- 步骤按顺序评估提供的遍历,并返回发出至少一个元素的第一个遍历。
即coalesce()
可以传入多个遍历,并对这些遍历按照顺序进行评估。若某个元素匹配上某个遍历,那么返回该元素在该遍历上产生的结果,然后跳到下一个元素进行相同的操作。
比如,当person含有nickname则返回nickname,当含有name则返回name;不会即返回nickname又返回name;当既没有nickname也没有name,则返回空。
gremlin> g.V().hasLabel('person').coalesce(values('nickname'), values('name'))
==>okram
==>vadas
==>josh
==>peter
14. Coin Step
要随机过滤出一个遍历器,请使用coin()
步骤(filter)。
gremlin> g.V().coin(0.0) //不返回节点
gremlin> g.V().coin(0.5) //随机返回节点,注意它并不是随机返回一半
gremlin> g.V().coin(1.0) //返回全部
15. Constant Step
要为运行程序指定常量值,请使用常量constant()
步骤(map)。对于诸如choose()
或`coalesce() 的条件步骤通常很有用。
gremlin> g.V().hasLabel('person').coalesce(values('nickname11'), values('name11'),constant("haha"))
==>haha
==>haha
==>haha
==>haha
16. Count Step
属于map步骤范畴。举例如下:
gremlin> g.V().count()
==>6
gremlin> g.V().hasLabel('person').count()
==>4
该步骤是减少栅栏的步骤(reducing barrier step),意味着所有先前的遍历器被折叠成新的遍历器。
gremlin> g.V().hasLabel('person').outE('created').path()
==>[v[1],e[9][1-created->3]]
==>[v[4],e[10][4-created->5]]
==>[v[4],e[11][4-created->3]]
==>[v[6],e[12][6-created->3]]
gremlin> g.V().hasLabel('person').outE('created').count()
==>4
gremlin> g.V().hasLabel('person').outE('created').count().path()
==>[4]
17. CyclicPath Step
每个遍历器在遍历图形的时候会维护其历史,即其路径。如果重要的是遍历器重复它的过程,那么应该使用cyclic()
- 路径(filter)。
该步骤分析到目前为止的运行程序的路径,并且如果有任何重复,则遍历器被过滤掉以免重复遍历。
如果需要非循环行为,请参阅simplePath()
。
gremlin> g.V(1).both().both().cyclicPath().path() //查看循环的遍历路径
==>[v[1],v[3],v[1]]
==>[v[1],v[2],v[1]]
==>[v[1],v[4],v[1]]
gremlin> g.V(1).both().both().simplePath().path() //查看非循环的遍历路径
==>[v[1],v[3],v[4]]
==>[v[1],v[3],v[6]]
==>[v[1],v[4],v[5]]
==>[v[1],v[4],v[3]]
18. Dedup Step
使用dedup()
步骤(filter),重复的对象将从遍历流中删除。
gremlin> g.V().values('lang')
==>java
==>java
gremlin> g.V().values('lang').dedup()
==>java
19. Drop Step
drop()
步骤(filter/sideEffect)用于从图形中删除元素和属性(即删除)。它是一个过滤器步骤,因为遍历不产生传出对象。
gremlin> g.V().outE().drop() //删除边
gremlin> g.E()
gremlin> g.V().properties('name').drop() 删除属性
gremlin> g.V().valueMap()
==>[age:[29]]
==>[age:[27]]
==>[lang:[java]]
==>[age:[32]]
==>[lang:[java]]
==>[age:[35]]
gremlin> g.V().drop() //删除图
gremlin> g.V()
20. Explain Step
explain()
步骤(terminal)将返回一个TraversalExplanation。遍历说明详细说明了如何根据注册的遍历策略编译遍历(在explain()
之前)。
第一列是应用的遍历策略。第二列是遍历策略类别:[D]ecoration,[O]ptimization,[P]rovider optimization,[F]inalization和[V]erification。最后,第三列是遍历后策略应用的状态。最终遍历是最终的执行计划。
21. Fold Step
有些情况下,遍历流需要“栅栏”来聚合所有对象并产生作为聚合函数的计算结果。fold()
步骤(map)就是这样的例子。
gremlin> g.V(1).out('knows').values('name')
==>vadas
==>josh
gremlin> g.V(1).out('knows').values('name').fold() //简单合并
==>[vadas,josh]
gremlin> g.V(1).out('knows').values('name').fold(0) {a,b -> a + b.length()} //统计name值的字符长度
==>9
gremlin> g.V(1).out('knows').values('name').fold(0) {a,b -> a + b} //合并所有名字为一个长串
==>vadasjosh
22. Graph Step
V()
步骤通常用于启动GraphTraversal,但也可以在遍历中间使用。
gremlin> g.V().has('name', within('marko', 'vadas', 'josh')).as('person').
V().has('name', within('lop', 'ripple')).addE('uses').from('person')
==>e[13][1-uses->3]
==>e[14][1-uses->5]
==>e[15][2-uses->3]
==>e[16][2-uses->5]
==>e[17][4-uses->3]
==>e[18][4-uses->5]
23. From Step
from()
不是一个真正的步骤,而是类似by()
和as()
等的步骤调节器(step-modulator)。如果一个步骤可以接受一个遍历器或者String,那么可以用from()
。
- 接受
from()
的步骤
simplePath()
cyclicPath()
path()
addE()