- 2.5.2.1. TraversalDescription
- 2.5.2.2. Evaluator
- 2.5.2.3. Traverser
- 2.5.2.4. Uniqueness
- 2.5.2.5. Order
- 2.5.2.6. BranchSelector
- 2.5.2.7. Path
- 2.5.2.8. PathExpander/RelationshipExpander
- 2.5.2.9. Expander
- 2.5.2.10. 如何使用Traversal框架
traversal框架由除了Node
和 Relationship
以外的一系列主要接口组合而成:
TraversalDescription
, Evaluator
, Traverser
和 Uniqueness
是最主要的. Path
在遍历中也有一定的用途, 因为在评价一个位置他用来表示一个位置。此外,PathExpander
(或者 RelationshipExpander
) 和 Expander
接口也属于traversals,但用户使用他们时需要自己实现。这里有大量的接口的高级用法,当在遍历时要求明确控制顺序时:BranchSelector
,BranchOrderingPolicy
和 TraversalBranch
。
TraversalDescription
是用于定义和初始化traversals的最主要接口。并不意味着要由用户实现,而是由traversal框架作为一个描述traversals的方法提供。
TraversalDescription
的实例是不可改变的,他的方法返回一个新的TraversalDescription
,相对对象来说,这个方法可以通过带参数修改调用。
Evaluator
用在决定,在每一个位置(用Path
表示):是应该继续遍历,是/否在结果中包含节点。给一个路径
,要求指定四个分支中的一个:
Evaluation.INCLUDE_AND_CONTINUE
: 在结果中包括这个节点并继续遍历Evaluation.INCLUDE_AND_PRUNE
: 在结果中包括这个节点并不再遍历Evaluation.EXCLUDE_AND_CONTINUE
: 在结果中不包括这个节点并继续遍历Evaluation.EXCLUDE_AND_PRUNE
: 在结果中不包括这个节点并不再遍历
可以配置不只一个evaluator。注意evaluator会被遍历中遇到的每一个位置所调用,包括起点。
Traverser
对象是调用traverse()
的结果。他表明了遍历在图中的位置,以及返回结果格式的规范。实际的遍历都是赖允许的,只有当调用迭代器中的 next()
方法才会真正调用Traverser
。
当一个遍历启动后,在Uniqueness
中设置那些位置可以被重新访问。默认情况下,设置成:NODE_GLOBAL
。
一个由TraversalDescription提供的Uniqueness可以表明可以被重新访问的相同位置的情况。不同的uniqueness等级如下:
NONE
- 在图中任何位置都可以被多次访问。NODE_GLOBAL
uniqueness – 在整个图中,没有节点可以被访问超过一次。这会消耗大量内存,因为他要求保存所有访问过的节点到内存中。RELATIONSHIP_GLOBAL
uniqueness – 在整个图中,没有关系可以被访问超过一次。这会消耗大量内存,因为他要求保存所有访问过的关系到内存中。NODE_PATH
uniqueness – 节点不会出现在之前路径出现过的地方。RELATIONSHIP_PATH
uniqueness – 关系不会出现在之前路径出现过的地方。NODE_RECENT
uniqueness – 是相对于NODE_GLOBAL的简化,表明有一个曾经访问过的节点的全局集合。然而这个等级设置有一个内存消耗的上限,他的集合只包括最近访问过的节点。这个集合的大小可以通过方法TraversalDescription.uniqueness()的第二个参数来指定。RELATIONSHIP_RECENT
uniqueness – 跟NODE_RECENT工作原理类似,只是用关系代替了节点。
宽度优先 / 深度优先
有一些方法专门用于设置 BranchSelector|ordering
的宽度优先/深度优先策略。调用 Traversal factory
里面的order
方法可以达到同样的效果,或者开发你自己的 BranchSelector/BranchOrderingPolicy
。
深度优先/宽度优先方法的一般版本允许通过一个随意的BranchOrderingPolicy
被注入description到中。
一个BranchSelector是用来设置traversal下一步将遍历哪一个分支。这个一般用来实现遍历顺序。traversal框架提供了一个基本的顺序实现:
Traversal.preorderDepthFirst()
- 遍历深度优先, 在访问他的子节点之前访问其他每一个节点。Traversal.postorderDepthFirst()
- 遍历深度优先, 在访问他的子节点之后访问其他每一个节点。Traversal.preorderBreadthFirst()
- 遍历宽度优先, 在访问他的子节点之前访问其他每一个节点。Traversal.postorderBreadthFirst()
- 遍历宽度优先, 在访问他的子节点之后访问其他每一个节点。
Note | |
---|---|
请注意宽度优先比深度优先消耗更多的内存。 |
BranchSelectors没有维持状态,因此需要每个遍历唯一实例。因此通过一个BranchOrderingPolicy接口来提供给TraversalDescription,BranchSelector实例的一个工厂模式。
Traversal框架的一个用户很少需要自己实现BranchSelector或者BranchOrderingPolicy,图算法已经提供了。Neo4j图算法包包含了一些范例,说明BranchSelector/BranchOrderingPolicy中用的一些算法,比如A* 和 Dijkstra。
BranchOrderingPolicy
创建BranchSelectors的工厂决定了返回的分支方向(分支的位置用Path
表示)。一般的策略是宽度优先
和深度优先
。举个例子,调用TraversalDescription#depthFirst()
:
description.order( Traversal.preorderDepthFirst() ); |
TraversalBranch
被BranchSelector使用的一个对象从某一个分支获取更多的分支。本质上,有一个由一个路径和一个RelationshipExpander(用来从当前分支中获取更多新的TraversalBranch
)复合组成。
一个路径也是Neo4j API接口的一部分。在traversal API中,路径的用法是双重的。Traversers可以返回他们自己的结果,这些结果由在图中被访问过的并且标记成需要被返回的路径组成。路径对象在图中也被用于路径的评估,用于判断traversal在某个点是否继续或者某个点是否应该包括在结果中。
traversal使用PathExpanders (取代 RelationshipExpander) 发现关系,通过这些关系,可以从一个指定的路径遍历更多的分支出来。
注入RelationshipExpander
到关系中的一般情况是定义遍历的所有关系。默认情况下,一个默认的expander会被使用,任何其他关系的顺序都无法保证。为了保证在关系类型的顺序中的关系被遍历,还有另外一种实现方式,在其中关系的类型是被新增的。
Expander
接口继承了RelationshipExpander
,确保他能自定义Expander
。TraversalDescription
的实现使用这个提供定义关系类型的方法,
在TraversalDescription
内部构建一个RelationshipExpander
,是用户使用API的一种常用方法。
被Neo4j框架提供的所有RelationshipExpanders也实现了Expander接口。对于traversal API的一个用户来说,实现PathExpander/RelationshipExpander接口变得更加容易,因为他之需要包括一个方法,这个方法用于从路径/节点中获取关系,Expander接口增加的方法之用于构建新的Expanders。
定义RelationshipTypes
:
private
enum
Rels
implements
RelationshipType
{
LIKES, KNOWS
}
|
下面的放里图数据库将被遍历,从节点”Joe”开始:
for
( Path position : Traversal.description()
.depthFirst()
.relationships( Rels.KNOWS )
.relationships( Rels.LIKES, Direction.INCOMING )
.evaluator( Evaluators.toDepth(
5
) )
.traverse( node ) )
{
output += position +
"\n"
;
}
|
遍历后输出结果:
(7)
(7)<--[LIKES,1]--(4)
(7)<--[LIKES,1]--(4)--[KNOWS,6]-->(1)
(7)<--[LIKES,1]--(4)--[KNOWS,6]-->(1)--[KNOWS,4]-->(6)
(7)<--[LIKES,1]--(4)--[KNOWS,6]-->(1)--[KNOWS,4]-->(6)--[KNOWS,3]-->(5)
(7)<--[LIKES,1]--(4)--[KNOWS,6]-->(1)--[KNOWS,4]-->(6)--[KNOWS,3]-->(5)--[KNOWS,2]-->(2)
(7)<--[LIKES,1]--(4)--[KNOWS,6]-->(1)<--[KNOWS,5]--(3)
|
因为TraversalDescription
是不可改变的,所以创建descriptions模版来在不同的遍历中共享使用是个不错的方法。举个例子,让我们开始遍历:
final
TraversalDescription FRIENDS_TRAVERSAL = Traversal.description()
.depthFirst()
.relationships( Rels.KNOWS )
.uniqueness( Uniqueness.RELATIONSHIP_GLOBAL );
|
traverser输出结果 (我们从节点“Joe“开始):
(7)
(7)--[KNOWS,0]-->(2)
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)<--[KNOWS,3]--(6)
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)<--[KNOWS,3]--(6)<--[KNOWS,4]--(1)
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)<--[KNOWS,3]--(6)<--[KNOWS,4]--(1)<--[KNOWS,5]--(3)
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)<--[KNOWS,3]--(6)<--[KNOWS,4]--(1)<--[KNOWS,6]--(4)
|
现在,让我们创建一个遍历,并限制深度为三:
for
( Path path : FRIENDS_TRAVERSAL
.evaluator( Evaluators.toDepth(
3
) )
.traverse( node ) )
{
output += path +
"\n"
;
}
|
返回结果:
(7)
(7)--[KNOWS,0]-->(2)
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)<--[KNOWS,3]--(6)
|
如果深度是2或者4呢:
for
( Path path : FRIENDS_TRAVERSAL
.evaluator( Evaluators.fromDepth(
2
) )
.evaluator( Evaluators.toDepth(
4
) )
.traverse( node ) )
{
output += path +
"\n"
;
}
|
遍历返回结果:
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)<--[KNOWS,3]--(6)
(7)--[KNOWS,0]-->(2)<--[KNOWS,2]--(5)<--[KNOWS,3]--(6)<--[KNOWS,4]--(1)
|
获取各种不同的Evaluator的用法,查看Evaluators Java API 或者自己去实现 Evaluator接口。
如果你对路径没有兴趣,你可以像下面这样将遍历后的节点转换成节点迭代器:
for
( Node currentNode : FRIENDS_TRAVERSAL
.traverse( node )
.nodes() )
{
output += currentNode.getProperty(
"name"
) +
"\n"
;
}
|
在这种情况下,我们用他接受名称:
Joe
Sara
Peter
Dirk
Lars
Ed
Lisa
|
关系也是非常容易的,下面是获取方法:
for
( Relationship relationship : FRIENDS_TRAVERSAL
.traverse( node )
.relationships() )
{
output += relationship.getType() +
"\n"
;
}
|
关系类型写入后,我们会得到:
KNOWS
KNOWS
KNOWS
KNOWS
KNOWS
KNOWS
|
相关源代码下载:TraversalExample.java