bboyjing's blog

Neo4j学习笔记八【深度遍历(二)】

管理唯一性

遍历的唯一性问题决定在遍历期间能访问一个节点多少次。在Neo4j的遍历Api中有几个不同的唯一性设置,它们能作用于遍历中,这个问题之前已经稍微探讨过,这一节来详细了解下。

1
2
3
4
5
6
7
8
9
10
11
12
13
//首先构造数据
create(jane:HUMAN{name : '1.Jane'}),
(john:HUMAN{name : '2.John'}),
(kate:HUMAN{name : '3.Kate'}),
(jack:HUMAN{name : '4.Jack'}),
(leeo:HUMAN{name : '5.Leeo'}),
(emma:HUMAN{name : '6.Emma'}),
(jane)-[:KNOWS]->(john),
(jane)-[:KNOWS]->(kate),
(john)-[:KNOWS]->(kate),
(john)-[:KNOWS]->(jack),
(john)-[:KNOWS]->(leeo),
(kate)-[:KNOWS]->(emma);

数据展示图如下:
图12

NODE_GLOBAL唯一性

NODE_GLOBAL意味着每个节点只能访问一次并且在遍历期间只能访问一次
下面我们解决一个问题:找出将用户1(Jane)介绍给用户5(Leeo)的直接联系人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static void main(String[] args) {
File file = new File("/opt/neo4j-community-3.0.3/data/databases/graph.db");
GraphDatabaseService graphDB = new GraphDatabaseFactory().newEmbeddedDatabase(file);
try(Transaction tx = graphDB.beginTx()){
//获取目标起始节点和目标节点
Node jane = graphDB.getNodeById(89l);
Node leeo = graphDB.getNodeById(93l);
//创建KNOWS关系遍历
TraversalDescription traversalDescription = graphDB.traversalDescription()
.relationships(MyRelationshipTypes.KNOWS)
.evaluator(path -> {
Node currentNode = path.endNode();
//当到达目标节点Leeo时,停止遍历
if(currentNode.getId() == leeo.getId()){
return Evaluation.EXCLUDE_AND_PRUNE;
}
//在当前节点和目标节点(Leeo)之间查找直接路径
Path singlePath = GraphAlgoFactory
.shortestPath(PathExpanders.forType(MyRelationshipTypes.KNOWS), 1)
.findSinglePath(currentNode, leeo);
if(singlePath != null){
//当前节点能直接能到达目标节点,将该节点包含在结果中并继续遍历
return Evaluation.INCLUDE_AND_CONTINUE;
}else{
//当前节点不能直接达到目标节点,丢弃该节点并继续遍历
return Evaluation.EXCLUDE_AND_CONTINUE;
}
})
.uniqueness(Uniqueness.NODE_GLOBAL);
Iterable<Node> nodes = traversalDescription.traverse(jane).nodes();
for(Node n : nodes){
System.out.println(n.getProperty("name"));
}
tx.success();
}
graphDB.shutdown();
}
//输出结果
2.John

可以通过查看上图确认John时Jane网络中唯一能将塔介绍给Ben的人。

NODE_PATH唯一性

NODE_PATH意味着同一个节点在不同路径中可以多次被访问,但在同一路径中只能被访问一次
上面的程序进需要修改一行代码

1
2
3
4
5
6
...
.uniqueness(Uniqueness.NODE_PATH);
...
//输出结果
2.John
2.John

从结果看出同一个节点John被遍历了两次,表示有两条路径可以到达目的地,下面修改下遍历查询,将之打印节点name属性修改成打印整个遍历路径,所用的代码之前已经贴出来过,这里直接使用就浩。

1
2
3
4
5
6
7
8
9
10
11
12
...
PathPrinter pathPrinter = new PathPrinter( "name" );
String output = "";
for ( Path path : traversalDescription.traverse(jane) ) {
output += Paths.pathToString( path, pathPrinter );
output += "\n";
}
System.out.println(output);
...
//输出结果
(1.Jane)--[KNOWS]-->(3.Kate)<--[KNOWS]--(2.John)
(1.Jane)--[KNOWS]-->(2.John)

可以清楚的看出为什么会有两个John节点,它们来自不同的路径。
但是发现一个问题,再次修改成.uniqueness(Uniqueness.NODE_GLOBAL),并且打印出全路径,会发现输出结果为:
(1.Jane)–[KNOWS]–>(3.Kate)<–[KNOWS]–(2.John)
也就是说Neo4j并没有优先遍历最短路劲,也许是我想多了。

其他唯一性

  • RELATIONSHIP_GLOBAL 图中每一个关系只能别访问一次
  • RELATIONSHIP_PATH 与NODE_PATH类似
  • NODE_RECENT 记忆访问过的节点有一个上线
  • NODE_LEVEL 确保处于同一级的节点在遍历期间只能被访问一次
  • RELATIONSHIP_LEVEL 确保处于统一级的关系在遍历期间只能被访问一次

其他唯一性使用场景可能不多,暂不深究了。

双向遍历

双向遍历是指分别从起始节点和结束节点同事开始遍历图,当这两个遍历相遇时(称作碰撞点),就完成了遍历,并且可以确定整个路径。
图13
下面给出一个双向遍历的例子,以本章构造的数据为例,从起始节点1.John,遍历到结束节点5.Leeo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static void main(String[] args) {
File file = new File("/opt/neo4j-community-3.0.3/data/databases/graph.db");
GraphDatabaseService graphDB = new GraphDatabaseFactory().newEmbeddedDatabase(file);
try(Transaction tx = graphDB.beginTx()){
//获取目标起始节点和目标节点
Node jane = graphDB.getNodeById(89l);
Node leeo = graphDB.getNodeById(93l);
//初始化双向遍历描述BidirectionalTraversalDescription
BidirectionalTraversalDescription description = graphDB.bidirectionalTraversalDescription()
//设置遍历描述的起始侧遍历出
.startSide(
graphDB.traversalDescription()
.relationships(MyRelationshipTypes.KNOWS)
.uniqueness(Uniqueness.NODE_PATH)
)
//设置遍历描述的结束侧节点遍历进的方向
.endSide(
graphDB.traversalDescription()
.relationships(MyRelationshipTypes.KNOWS)
.uniqueness(Uniqueness.NODE_PATH)
)
//设置碰撞评估函数为包含找到的所有碰撞点
.collisionEvaluator(path -> Evaluation.INCLUDE_AND_CONTINUE)
//设置侧选择器为在两个遍历方向交替变换
.sideSelector(SideSelectorPolicies.ALTERNATING, 100);
PathPrinter pathPrinter = new PathPrinter( "name" );
String output = "";
for ( Path path : description.traverse(jane, leeo) ) {
output += Paths.pathToString( path, pathPrinter );
output += "\n";
}
System.out.println(output);
tx.success();
}
graphDB.shutdown();
}
//输出结果
(1.Jane)--[KNOWS]-->(2.John)--[KNOWS]-->(5.Leeo)
(1.Jane)--[KNOWS]-->(3.Kate)<--[KNOWS]--(2.John)--[KNOWS]-->(5.Leeo)

到此,深度遍历一章结束,在具体项目中该如何去遍历,还得自己琢磨了。