图的遍历
在最一般的层次上,有Traversal<S,E> ,它实现了Iterator<E> ,其中S代表开始,E代表结束。遍历由四个主要组件组成:
-
Step<S,E> :应用于S产生E的单个函数。
-
TraversalStrategy:拦截器方法来改变遍历的执行(例如,查询、重写)。
-
TraversalSideEffects:可用于存储关于遍历的全局信息的键/值对。
-
Traverser<T> :在遍历中传播的对象,当前表示类型为T的对象。
GraphTraversal<S,E> 提供了图遍历的经典概念,它扩展自Traversal<S,E> 。GraphTraversal 提供了graph 数据的顶点、边等方面的翻译,从而实现了图形遍历DSL。
IMPORTANT | TinkerPop提供的底层Step实现包含DSL作者需要的大部分功能。DSL作者利用提供的Step是很重要的,因为这样通用优化和装饰策略就可以根据底层遍历序列推理。如果引入了新的Step,则公用的遍历策略可能无法正常运行。 |
---|
图遍历的步骤
GraphTraversal<S,E>是从GraphTraversalSource派生的。它也可以通过__.
匿名产生(即empty)。GraphTraversal 由一系列有序的step组成。GraphTraversal 提供的所有步骤都继承了上面所示的普通形式。TinkerPop3
GraphTraversal JavaDoc中提供了所有step
(及其描述)的列表。下面的小节将演示在Gremlin控制台使用GraphTraversal step的流程。
IMPORTANT | Graph Process章节及章节描述了使用遍历的基础知识。 |
---|---|
NOTE | 为了避免表达式的冗长写法,最好是使用 import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph..*。这样,在遍历中可以用inE()代替.inE()的写法。在使用匿名遍历时,注意语言的保留关键字。例如,in和as在Groovy中是保留关键字,因此必须使用详细语法 .in()和.as ()避免冲突。 |
General (常用)Step
有五个常规Step,均支持遍历和lambda,后面描述的所有Step都是通过这些步骤进行扩展使用。
Step | 描述 |
---|---|
map(Traversal<S, E>) map(Function<Traverser<S>, E>) | 遍历映射S为对象E,供下一个Step处理。 |
flatMap(Traversal<S, E>) flatMap(Function<Traverser<S>, Iterator<E>>) | 遍历映射S为对象E并流化处理。 |
filter(Traversal<?, ?>) filter(Predicate<Traverser<S>>) | 返回ture或false,false的记录不会传递到下一个Step。 |
sideEffect(Traversal<S, S>) sideEffect(Consumer<Traverser<S>>) | 执行一些操作,并将其传递到下一Step。 |
branch(Traversal<S, M>) branch(Function<Traverser<S>,M>) | 以M标记的索引分割遍历。 |
WARNING | Lambda步骤是为了教学目的而提供的,因为它们表示Gremlin语言的基本构成。在实践中,应该避免lambda步骤,以支持它们的遍历显示,除非显式地“关闭”,遍历验证策略中不允许使用。有关lambdas的更多信息,请阅读。 |
Traverser<S>有以下访问形式:
-
获取当前Traverser对象S—Traverser.get()。
-
当前Traverser对象的路径—traverser .path()。
- 获取指定历史路径记录的对象—Traverser.path(String) ==
Traverser.path().get(String)。
- 获取指定历史路径记录的对象—Traverser.path(String) ==
-
当前对象的遍历次数—traverser .loops()。
-
当前遍历器所代表的对象数目—traverser .bulk()。
-
当前遍历器关联的本地数据结构—traverser .sack()。
-
当前遍历器的附加作用—traver . sideeffects()。
- 获取指定附加作用—Traverser.sideEffect(String) ==
Traverser.sideEffects().get(String)。
- 获取指定附加作用—Traverser.sideEffect(String) ==
> gremlin\> g.V(1).out().values('name')
> [./media/image3.png](./media/image3.png)
> map lambda
> ==\>lop
> ==\>vadas
> ==\>josh
> gremlin\> g.V(1).out().map {it.get().value('name')}
> ==\>lop
> ==\>vadas
> ==\>josh
> gremlin\> g.V(1).out().map(values('name'))
> ==\>lop
> ==\>vadas
> ==\>josh
-
从顶点1向外遍历相邻顶点的name属性值。
-
含义同上,换成使用lambda访问name属性值的形式。
-
含义同上,换成使用map()的遍历表示。
> gremlin\> g.V().filter {it.get().label() == 'person'} 1
> ==\>v[1]
> ==\>v[2]
> ==\>v[4]
> ==\>v[6]
> gremlin\> g.V().filter(label().is('person'))
> ==\>v[1]
> ==\>v[2]
> ==\>v[4]
> ==\>v[6]
> gremlin\> g.V().hasLabel('person')
> ==\>v[1]
> ==\>v[2]
> ==\>v[4]
> ==\>v[6]
-
在全部顶点中查找label=person的顶点
-
含义同上,使用filter()方式。
-
含义同上,使用更具体的has()-step作为filter()的一种实现,使用方式不同。
> gremlin\> g.V().hasLabel('person').sideEffect(System.out.&println) 1
> v[1]
> ==\>v[1]
> v[2]
> ==\>v[2]
> v[4]
> ==\>v[4]
> v[6]
> ==\>v[6]
> gremlin\> g.V().sideEffect(outE().count().store("o")).
> sideEffect(inE().count().store("i")).cap("o","i")
> ==\>[i:[0,0,1,1,1,3],o:[3,0,0,0,2,1]]
-
进入sideEffect()的内容将被传递到下一Step,并被打印。
-
计算每个顶点的进出度,以o和i分别代表结果。两个sideEffect
()都针对同一个的顶点。
> gremlin\> g.V().branch {it.get().value('name')}.
> option('marko', values('age')).
> option(none, values('name'))
> ==\>29
> ==\>vadas
> ==\>lop
> ==\>josh
> ==\>ripple
> ==\>peter
> gremlin\> g.V().branch(values('name')).
> option('marko', values('age')).
> option(none, values('name')) 2
> ==\>29
> ==\>vadas
> ==\>lop
> ==\>josh
> ==\>ripple
> ==\>peter
> gremlin\> g.V().choose(has('name','marko'),
> values('age'),
> values('name'))
> ==\>29
> ==\>vadas
> ==\>lop
> ==\>josh
> ==\>ripple
> ==\>peter
-
使用branch,用it遍历顶点,如果顶点是“marko”,获取age,否则得到顶点的name。
-
含义同上,使用branch()的遍历形式。
-
含义同上,使用choose()代替branch()功能,choose返回true false 表达式为
choose(condition-step,true-result-step,false-result-step)。
Terminal(终端) Step
通常,当一个Step串联到一个traversal 时,将返回一个traversal 。这样,一个遍历就以流畅的、一元的方式建立起来。但是,有些Step不返回traversal ,而是执行traversal 并返回结果。这些Step称为终端步骤(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]
> gremlin\> g.addV('person').iterate() *//*
-
hasNext() 确认是否有可用结果 返回true /false。
-
next() 返回下一个结果。
-
next(n) 将返回next列表中的第n个结果。
-
tryNext() 返回一个Optional 对象,可看做hasNext()/next()的组合。
-
toList() 以List形式返回结果。
-
toSet() 以Set形式返回结果(起到去重的作用)。
-
toBulkSet() 返回一个加权List(通过加权保留可重复项)。
-
fill(集合) 把结果放到一个已定义的集合中,返回该集合。
-
iterate()并不完全符合终端步骤的定义,因为它不返回结果,但仍然返回遍历——但它的行为确实像终端步骤,因为它迭代遍历并产生了附加作用,附加作用的结果就是不返回实际结果。
最后,explain()-step也是一个结束步骤,在随后的章节中进行描述。
AddEdge(增加边) Step
推断是指使数据中隐含的内容显式的过程。在graph 中显式的是graph 的对象。例如对graph 进行遍历时指的就是顶点和边。换句话说,traversal显示的内容含义由traversal的定义决定。例如,以“co-developers”的概念为例。如果两个人一起做过同一个项目,那么他们就是co-developers。这个概念适用于traversal,因此。此外,隐式操作可以通过addE()-step
(map/sideEffect)来显式调用。
> gremlin\> g.V(1).as('a').out('created').in('created').where(neq('a')).
> addE('co-developer').from('a').property('year',2009)
> ==\>e[13][1-co-developer-\>4]
> ==\>e[14][1-co-developer-\>6]
> gremlin\> g.V(3,4,5).aggregate('x').has('name','josh').as('a').
> select('x').unfold().hasLabel('software').addE('createdBy').to('a')
> ==\>e[15][3-createdBy-\>4]
> ==\>e[16][5-createdBy-\>4]
> gremlin\>
> g.V().as('a').out('created').addE('createdBy').to('a').property('acl','public')
> \\
> ==\>e[17][3-createdBy-\>1]
> ==\>e[18][5-createdBy-\>4]
> ==\>e[19][3-createdBy-\>4]
> ==\>e[20][3-createdBy-\>6]
> gremlin\> g.V(1).as('a').out('knows').
> addE('livesNear').from('a').property('year',2009).
> inV().inE('livesNear').values('year') \\
> ==\>2009
> ==\>2009
> gremlin\> g.V().match(
> \_\_.as('a').out('knows').as('b'),
> \_\_.as('a').out('created').as('c'),
> \_\_.as('b').out('created').as('c')).
> addE('friendlyCollaborator').from('a').to('b').
> property(id,23).property('project',select('c').values('name'))
> ==\>e[23][1-friendlyCollaborator-\>4]
> gremlin\> g.E(23).valueMap()
> ==\>[project:lop]
> gremlin\> marko = g.V().has('name','marko').next()
> ==\>v[1]
> gremlin\> peter = g.V().has('name','peter').next()
> ==\>v[6]
> gremlin\> g.V(marko).addE('knows').to(peter)
> ==\>e[24][1-knows-\>6]
> gremlin\> g.addE('knows').from(marko).to(peter)
> ==\>e[25][1-knows-\>6]
-
在marko(也就是示例图中V(1)的顶点)和与他合作者(out)的合作商(in)之间创建一条“ co-developer”的Edge,并设置属性year=2009(虚线部分)。
-
以3、4、5的顶点作为集合x,从x中找到name=josh的顶点结果作为a,从x中找到有label=software的顶点,添加“createdBy”
边到a中,最终的结论就是添加从josh顶点到lop顶点传入边“createdBy”。 -
为具有出边“created”的顶点创建“createdBy”并且属性
acl=public的入边,最终效果是为带“created”出边的顶点都添加一个“createdBy”入边。 -
对新创建的edge 也可进行遍历
-
traversal 中的两个任意绑定可以用from()→`to()来连接, `id可以为用户提供id。
-
在marko和peter之间添加一条有向(单向)边(label= knows)。
-
意义同上,写法不一样。
其他用法
AddVertex(增加顶点) Step
addV()-step用于向graph
(map/sideEffect).中添加顶点。对于每个传入的对象,都会创建一个顶点。此外,GraphTraversalSource维护了一个addV()方法。
> gremlin\> g.addV('person').property('name','stephen')
> ==\>v[13]
> gremlin\> g.V().values('name')
> ==\>marko
> ==\>vadas
> ==\>lop
> ==\>josh
> ==\>ripple
> ==\>peter
> ==\>stephen
> gremlin\> g.V().outE('knows').addV().property('name','nothing')
> ==\>v[15]
> ==\>v[17]
> gremlin\> g.V().has('name','nothing')
> ==\>v[17]
> ==\>v[15]
> gremlin\> g.V().has('name','nothing').bothE()
其他用法
addV(), addV(String)), addV(Traversal)
AddProperty(增加属性) Step
property()-step用于向graph
(sideEffect)的元素添加属性与addV()和addE()不同,property()是一个完全的sideEffect 步骤,因为它不返回它创建的属性,而是返回流进其中的元素。此外,如果property()遵循addV()或addE(),那么它将被“折叠”到前面的步骤中,在一次创建操作中启用顶点和边的所有属性。
> gremlin\> g.V(1).property('country','usa')
> ==\>v[1]
> gremlin\> g.V(1).property('city','santa fe').property('state','new
> mexico').valueMap()
> ==\>[country:[usa],city:[santa fe],name:[marko],state:[**new**
> mexico],age:[29]]
> gremlin\> g.V(1).property(list,'age',35)
> ==\>v[1]
> gremlin\> g.V(1).valueMap()
> ==\>[country:[usa],city:[santa fe],name:[marko],state:[**new**
> mexico],age:[29,35]]
> gremlin\>
> g.V(1).property('friendWeight',outE('knows').values('weight').sum(),'acl','private')
> ==\>v[1]
> gremlin\> g.V(1).properties('friendWeight').valueMap() \\
> ==\>[acl:**private**]
-
对于顶点,可以为顶点属性提供基数。
-
可以通过遍历选择属性值(以及键)。
-
对于顶点,property()-step可以添加元属性。
其他用法
property(Object, Object,
Object…), property(Cardinality,
Object, Object,
Object…), Cardinality
Aggregate(聚合) Step
aggregate()-step
(sideEffect)用于将特定的遍历点上的所有对象聚合到一个集合中。该步骤使用了急求,因为在之前的所有对象都被完全聚合之前,任何对象都不会继续(与延迟填充集合的store()相反)。在需要在特定点上的所有东西用于未来计算的情况下,急于求值的性质非常重要。下面提供了一个示例。
> gremlin\> g.V(1).out('created') \\
> ==\>v[3]
> gremlin\> g.V(1).out('created').aggregate('x') \\
> ==\>v[3]
> gremlin\> g.V(1).out('created').aggregate('x').in('created')
> ==\>v[1]
> ==\>v[4]
> ==\>v[6]
> gremlin\> g.V(1).out('created').aggregate('x').in('created').out('created')
> ==\>v[3]
> ==\>v[5]
> ==\>v[3]
> ==\>v[3]
> gremlin\> g.V(1).out('created').aggregate('x').in('created').out('created').
> where(without('x')).values('name') 5
> ==\>ripple
-
marko创造了什么?
-
集合了marko 的所有创作。
-
谁是marko的合作者?
-
marko的合作者创造了什么?
-
马可的合作者创造了什么他没有创造的?
在推荐系统中,采用了上述模式:
“user A喜欢什么?”还有谁喜欢这些东西?他们喜欢的哪些东西是user A不喜欢的?”
最后,可以通过by()-对aggregate()-step进行调节。
> gremlin\> g.V().out('knows').aggregate('x').cap('x')
> ==\>[v[2],v[4]]
> gremlin\> g.V().out('knows').aggregate('x').by('name').cap('x')
> ==\>[vadas,josh]
其他用法
And(与) Step
and()步骤让traversal产生一个过滤结果。参阅 or()来了解or语义。
> gremlin\> g.V().and(
> outE('knows'),
> values('age').is(lt(30))).
> values('name')
> ==\>marko
and()步骤可以执行多次。所有traversal都必须为原始遍历器生成至少一个输出,以便传递给下一Step。
也可以使用中缀表示法。但是,使用中缀表示法只能支持两个遍历。
> gremlin\> g.V().where(outE('created').and().outE('knows')).values('name')
> ==\>marko
其他用法
As(别名) Step
as()步骤不是一个真正的步骤,而是一个类似by()和option()的“step调解器”。有了as(),就有可能为step提供一个label,稍后使用这些标签的step和数据结构可以访问这个标签。例如select()、match()和path。
> 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().as('a').out('created').as('b').select('a','b').by('name')
> ==\>[a:marko,b:lop]
> ==\>[a:josh,b:ripple]
> ==\>[a:josh,b:lop]
> ==\>[a:peter,b:lop]
-
从path中选择标记为“a”和“b”的对象。
-
从path中选择标记为“a”和“b”的对象,并对每个对象获取其name值。
一个步骤可以有任意数量的标签与之关联。这对于在以后的步骤中多次引用相同的步骤非常有用。
> 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]]
其他用法
-
从path中选择标记为“a”和“b”的对象。
-
从path中选择标记为“a”和“b”的对象,并对每个对象获取其name值。
一个步骤可以有任意数量的标签与之关联。这对于在以后的步骤中多次引用相同的步骤非常有用。
> 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]]
其他用法