Rob McFeely
Rob McFeely

Reputation: 3043

cypher path with condition on relationship direction

I want to find all paths given a start node

MATCH path=(n)-[rels*1..10]-(m) 

with the following 2 conditions on path inlcusion:

  1. true if relationship between subsequent nodes in path has property PROP='true'
  2. if type(relationship)=SENDS then true if direction of the relationship is outgoing (from one path node to the next node in the path)

Another way of phrasing this is that direction doesn't matter unless the relationship name is SENDS

I can do condition 1 with WHERE ALL (r IN rels WHERE r.PROP='true') however ive no idea how to do condition 2.

Upvotes: 1

Views: 2706

Answers (2)

Rob McFeely
Rob McFeely

Reputation: 3043

For completeness I've answered the question using jjaderberg correct solution plus a condition to fix the start node and ensure that no zero length paths are included

MATCH p = (n)-[*1..10]-(m) 
WHERE ALL(n in nodes(p) WHERE 1=length(filter(m in nodes(p) WHERE m=n)))  
    AND (id(n)=1) 
WITH p, nodes(p) as ns, relationships(p) as rs 
WHERE REDUCE(acc = [0,1], r IN rs | [acc[0]+1, 
    CASE WHEN r.PROP='true' AND (type(r) <> "SEND" OR startNode(r) = ns[acc[0]]) 
    THEN acc[1]*1 
    ELSE acc[1]*0
    END])[1] = 1 
RETURN nodes(p);

Or my alternative answer based on jjaderbags answer but does not use accumulator but which is slightly slower

   MATCH p=(n)-[rels*1..10]-(m) 
WHERE ALL(n in nodes(p) WHERE 1=length(filter(m in nodes(p) WHERE m=n)))  
    AND(  ALL (r IN rels WHERE r.PROP='true')  
    AND id(n)=1)        
WITH p, range(0,length(p)-1) AS idx, nodes(p) as ns, relationships(p) as rs      
WHERE ALL (i in idx WHERE 
    CASE type(rs[i])='SEND' 
    WHEN TRUE THEN startnode(rs[i])=ns[i] 
    ELSE TRUE 
    END)  
RETURN nodes(p);

Upvotes: 0

jjaderberg
jjaderberg

Reputation: 9952

The only way I can think of to filter on relationship direction without declaring direction in the match pattern is by comparing the start node of each relationship in the path with the node at the corresponding index of the nodes() collection from the path. For this you need the relationship and node collections from the path, an index counter and some boolean evaluation equivalent to ALL(). One way to do it is to use REDUCE with a collection for the accumulator, so you can accumulate index and maintain a true/false value for the path at the same time. Here's an example, the accumulator starts at [0,1] where the 0 is the index for testing that startNode(r) equals the node at the corresponding index in the node collection (i.e. it's an outgoing relationship) and the 1 represents true, which signifies that the path has not yet failed your conditions. For each relationship the index value is incremented and the CASE/WHEN clause multiplies the 'boolean' with 1 if your conditions are satisfied, with 0 if not. The evaluation of the path is then the evaluation of the second value in the collection returned by REDUCE -- if 1 then yay, if 0 then boo.

MATCH path = (n)-[*1..10]-(m)
WITH path, nodes(path) as ns, relationships(path) as rs
WHERE REDUCE(acc = [0,1], r IN rs |
    [acc[0]+1, CASE WHEN 
        r.PROP='true' AND
        (type(r) <> "SENDS" OR startNode(r) = ns[acc[0]]) THEN acc[1]*1 ELSE acc[1]*0 END]
)[1] = 1
RETURN path

or maybe this is more readable

WHERE REDUCE(acc = [0,1], r IN rs |
    CASE WHEN 
        r.PROP=true AND
        (type(r) <> "SENDS" OR startNode(r) = ns[acc[0]]) 
        THEN [acc[0]+1, acc[1]*1]
        ELSE [acc[0]+1, acc[1]*0]
    END
)[1] = 1

Here's a console: http://console.neo4j.org/?id=v3kgz9

Upvotes: 2

Related Questions