Reputation: 488
I am facing a seemingly simple issue. I want to traverse the nodes of a Neo4j
graph, using the traversal Java API. I want to include only paths where all relationships have the same direction, though. If I do something like
db.traversalDescription().relationships(<type>, Direction.BOTH)
paths like
[a]-[:TYPE]->[b]<-[:TYPE]-[c]
are included as well which I don't want. I only want paths like
[a]-[:TYPE]->[b]-[:TYPE]->[c]
[a]<-[:TYPE]-[b]<-[:TYPE]-[c]
Can anybody help? It seems like the solution should be quite simple.
Upvotes: 3
Views: 755
Reputation: 39905
Detailing Wes' idea a little bit. In your custom PathEvaluator
you need to set branch state to remember the direction of the first relationship. All subsequent visits check if the last relationship matches the direction. In pseudo code:
class SameDirectionPathEvaluator implements PathEvaluator<Direction> {
public Evaluation evaluate(Path path, BranchState<Direction> state) {
if (path.length()==0) {
return Evaluation.EXCLUDE_AND_CONTINUE;
} else if (path.length()==1) {
state.setState(getDirectionOfLastRelationship(path));
return Evaluation.INCLUDE_AND_CONTINUE;
} else {
if (state.getState().equals(getDirectionOfLastRelationship(path)) {
return Evaluation.INCLUDE_AND_CONTINUE;
} else {
return Evaluation.EXCLUDE_AND_PRUNE;
}
}
}
private Direction getDirectionOfLastRelationship(Path path) {
assert path.length() > 0;
Direction direction = Direction.INCOMING
if (path.endNode().equals(path.lastRelationship().getEndNode()) {
direction = Direction.OUTGOING;
}
return direction;
}
}
Please note I did not compile or test the code above - it's just for sketching the idea.
It seems there is a more efficient way to do this. Since traversals use expanders before evaluators are invoked it makes more sense to implement this behaviour in an expander:
class ConstantDirectionExpander implements PathExpander<STATE>() {
@Override
public Iterable<Relationship> expand(Path path, BranchState<STATE> state) {
if (path.length()==0) {
return path.endNode().getRelationships(types);
} else {
Direction direction = getDirectionOfLastRelationship(path);
return path.endNode().getRelationships(direction, types);
}
}
@Override
public PathExpander<STATE> reverse() {
return this;
}
private Direction getDirectionOfLastRelationship(Path path) {
assert path.length() > 0;
Direction direction = Direction.INCOMING;
if (path.endNode().equals(path.lastRelationship().getEndNode())) {
direction = Direction.OUTGOING;
}
return direction;
}
}
In your traversal you need use a InitialBranchSate
:
TraversersDescription td = graphDatabaseService.traversalDescriptioin().
.expand(new ConstantDirectionExpander(reltype))
.traverse(startNode)
Upvotes: 4
Reputation: 33145
The way I can think of is you'd implement a PathEvaluator, which scans the path and checks that the nodes are pointing all the same direction. Then you can add it to your TraversalDescription
with .evaluator()
.
There may be a fancier way, though.. you're right, this does seem like it should be built-in.
Upvotes: 3