1
votes

I want to get all the simple cycles/circuits on a graph that go through a given node. I am able to so with this cypher query:

MATCH p=(n)-[*]->(n)
WHERE n.postId = 71 //postId is a node property
RETURN nodes(p)

However, the above retrieves duplicate nodes within the 'circuit' (except from the start and the end nodes), which is not a circuit at all according to the graph theory.

With the following query I can remove those duplicates in the circuit but I have to limit the length of the circuit or path in the MATCH pattern which is kind of hardcoding it.

// In this example the length of the path is hardcoded to 4
MATCH p=
    (n)-[:RELATES_TO]->
    (p2)-[:RELATES_TO]->
    (p3)-[:RELATES_TO]->
    (p4)-[:RELATES_TO]->(n)
WHERE n.postId = 71
    AND p2.postId <> 71
    AND p3.postId <> 71
    AND p4.postId <> 71
RETURN nodes(p)

Is there a way to filter the nodes between the relationships in the first query?

MATCH p=(n)-[*]->(n)
WHERE n.postId = 71 //postId is a node property
RETURN nodes(p)

Important notes:

3

3 Answers

1
votes

Have you tried using filter() or none()? I think I correctly understood your problem but here's how I am using the above functions. (If this is off, just downwote.)

The gist: http://console.neo4j.org/?id=99xkcu

CREATE
  (g:Person {name: 'Gorduin'}), (a:Person {name: 'Alvaro'}),
  (pn:PhoneNumber {number: '555-512-2017'}),
  (e11:Extension {extension: 11}),
  (e27:Extension {extension: 27}),
  (e19:Extension {extension: 19}),

  (e11)-[:extension_of]->(pn)<-[:extension_of]-(e27),
  (e19)-[:extension_of]->(pn),

  (g)<-[:phone_number_of]-(e11),
  (g)<-[:phone_number_of]-(e27),
  (a)<-[:phone_number_of]-(e19),
  (a)<-[:phone_number_of]-(pn);

enter image description here

Variable length query needs to be used because the :phone_number_of pointer can originate from the extension (linking to the phone number) or the phone number itself. Arrow directions don't matter, you can reverse any of them and try the queries below.

(Limiting the length of the query would be the obvious solution in my case such as

MATCH path = (p:Person)-[*1..3]-(n:PhoneNumber)
RETURN nodes(path);

but that wasn't the OP's question.)


(1) Getting every possible path from a person to a phone number (edited for readability):

MATCH path = (p:Person)-[*]-(n:PhoneNumber)
RETURN nodes(path) as every_possible_path_from_a_Person_to_a_PhoneNumber;

╒══════════════════════════════════════════════════════════════════════╕
│"every_possible_path_from_a_Person_to_a_PhoneNumber"                  │
╞══════════════════════════════════════════════════════════════════════╡
│[a,pn]                                                                │
├──────────────────────────────────────────────────────────────────────┤
│[g,e11,pn,e19,a,pn]                                                   │
├──────────────────────────────────────────────────────────────────────┤
│[g,e27,pn,e19,a,pn]                                                   │
├──────────────────────────────────────────────────────────────────────┤
│[g,e11,pn]                                                            │
├──────────────────────────────────────────────────────────────────────┤
│[a,pn,e27,g,e11,pn]                                                   │
├──────────────────────────────────────────────────────────────────────┤
│[a,e19,pn,e27,g,e11,pn]                                               │
├──────────────────────────────────────────────────────────────────────┤
│[a,e19,pn]                                                            │
├──────────────────────────────────────────────────────────────────────┤
│[g,e11,pn,a,e19,pn]                                                   │
├──────────────────────────────────────────────────────────────────────┤
│[g,e27,pn,a,e19,pn]                                                   │
├──────────────────────────────────────────────────────────────────────┤
│[g,e27,pn]                                                            │
├──────────────────────────────────────────────────────────────────────┤
│[a,pn,e11,g,e27,pn]                                                   │
├──────────────────────────────────────────────────────────────────────┤
│[a,e19,pn,e11,g,e27,pn]                                               │
└──────────────────────────────────────────────────────────────────────┘

(2) Using none() to remove redundancy by filtering out paths that contain a specific node (a node with certain properties or labels):

MATCH path = (p:Person)-[*]-(n:PhoneNumber)
WITH nodes(path) as ns
WHERE NONE(node IN ns WHERE (exists(node.name) and node.name ='Gorduin'))
RETURN ns as path_nodes_NOT_containing_a_specific_person;

╒══════════════════════════════════════════════════════════════╕
│"path_nodes_NOT_containing_a_specific_person"                 │
╞══════════════════════════════════════════════════════════════╡
│[a,pn]                                                        │
├──────────────────────────────────────────────────────────────┤
│[a,e19,pn]                                                    │
└──────────────────────────────────────────────────────────────┘

(3) Using filter() to remove a specific node from the returned paths:

MATCH path = (p:Person)-[*]-(n:PhoneNumber)
WITH nodes(path) as ns
WHERE NONE(node IN ns WHERE (exists(node.name) and node.name ='Gorduin'))
RETURN filter(node in ns  WHERE NOT node:Person) as personless_nodelist;

╒══════════════════════════════════════════════════════════════╕
│"personless_nodelist"                                         │
╞══════════════════════════════════════════════════════════════╡
│[pn]                                                          │
├──────────────────────────────────────────────────────────────┤
│[e19,pn]                                                      │
└──────────────────────────────────────────────────────────────┘
0
votes

Although this may not be as fast as using using the allSimplePaths APOC procedure, as suggested by @InverseFalcon (I have not tried it), here is a away to get simple paths in pure Cypher:

MATCH p=(n)-[*]->(n)
WHERE n.postId = 71
WITH NODES(p) AS nds
UNWIND nds AS nd
WITH nds, COUNT(DISTINCT nd) AS dnd
WHERE dnd = LENGTH(nds)-1
RETURN nds;

Basically, this query requires that the number of distinct nodes in a path is equal to the number nodes minus one (since the last node must be the same as the first).

0
votes

You may want to try out the APOC Procedures Library, specifically the allSimplePaths function in the Graph Algorithms section. Simple paths should have no repeating nodes.

EDIT

Note that currently the algorithm can't currently be used as-is to find simple cycles where the start and end node are the same.

However, if you define the end nodes as the second-to-last step in the cycle, all adjacent nodes to the start node that have a directed relationship to the start node, then you should be able to get your result (though the path obviously won't include the final traversal to the start node to complete the cycle).