Directed Graph In Prolog - path

can someone please elaborate upon the functionality/conditions of travel(A,B,Visited,Path) and travel(A,B,P,[B|P]) .. this code finds the path between path A and B in a graph
edge(a,b).
edge(b,c).
edge(b,d).
edge(c,d).
edge(d,b).
edge(e,f).
edge(e,g).
edge(e,h).
edge(h,i).
edge(i,g).
edge(b,e).
connectedEdges(X,Y) :- edge(X,Y).
connectedEdges(X,Y) :- edge(Y,X).
path(A,B,Path) :-
travel(A,B,[A],Q),
reverse(Q,Path).
travel(A,B,P,[B|P]) :-
connectedEdges(A,B).
travel(A,B,Visited,Path) :-
connectedEdges(A,C),
C \== B,
\+member(C,Visited),
travel(C,B,[C|Visited],Path).

Lets start with the first travel/4 rule:
travel(A,B,P,[B|P]) :-
connectedEdges(A,B).
"If points A and B are directly connected to each other, then we have found a direct sub-path, and hence can succeed by adding point B to the path which is unified with all the points we have visited thus far."
In other words, since we have solved a sub-problem (by finding a direct connection to 2 nodes), we can essentially state that P (all that we have visited so far), is the tail of the path list [B|P] (the total path is the last node we have visited .... the current destination node, prepended to all the previous nodes we've visited).
Now for the next travel/4 rule:
travel(A,B,Visited,Path) :-
connectedEdges(A,C),
C \== B,
\+member(C,Visited),
travel(C,B,[C|Visited],Path).
It is important to note that this second rule will always be tried as an alternative, whether or not the first rule succeeded. Due to that fact of this implementation, the implication here is that this code can possibly find multiple paths (if more than one exists).
Anyway, in this second rule we find any nodes that are connected to A, other than B. Why?, this is because the first rule above already tried that; in this rule we're searching for alternatives. If that node C hasn't already been visited, we simply try to travel from that point (C) to our destination. Then we recursively query/call travel/4 yet again, until we've found a complete path(s).
Note again, that this implementation may find 0, 1, or more than 1 solution to a given query.
To recapitulate, the first rule is called to find direct connections between points. The second rule is called to find indirect connections between points.

Related

neo4j - shortest path with conditions include plugin functions

I have a problem with the implementation in cypher. My problem is this: I have a database model, which is photographed here as an overview: https://www.instpic.de/QTIhBbPgVHBHg5pKwVdk.PNG
Short for the explanation. The red nodes simulate star systems, the yellow one jump points. Each jump point has a certain size, which determines which body can pass the point. The size is stored as a property at the relation between the yellow nodes. Among the red nodes are other nodes that represent the orbital celestial bodies of a star system. (Planets, moons, stations, etc.) Now, from any point within a solar system (planet, station, moon), I would like to find the shortest path to another lying point in the same solar system or another. In addition, I can calculate the distance of two celestial bodies within a system using the plugin that I have programmed. This value should now be included in finding the path, so I have the shortest path on the database and also the smallest distance between the celestial bodies within a solar system. I already have a query, unfortunately it fails partly because of its performance. I also think that the paths here are very variable, so a change to the database model is well considered.
Here is a part of my acutal query iam using:
MATCH (origin:Marketplace)
WHERE origin.eid = 'c816c4fa501244a48292f5d881103d7f'
OPTIONAL MATCH (marketplace:Marketplace)-[:Sell]->(currentPrice:Price)-[:Content]->(product:Product)
OPTIONAL MATCH p = shortestPath((origin)-[:HasMoon|:HasStation|:HasLandingZone|:HasPlanet|:HasJumpPoint|:CanTravel*]-(marketplace))
WHERE SIZE([rel in relationships(p) WHERE EXISTS(rel.size)]) <= 3 AND ALL(rel IN [rel in relationships(p) WHERE EXISTS(rel.size)] WHERE rel.size IN ['small', 'medium', 'large'])
WITH origin, marketplace, p, currentPrice, product
CALL srt.getRoutes(origin, marketplace, p) YIELD node, jump_sizes, jump_gates, jump_distance, hops, distance
OPTIONAL MATCH (currentPrice)-[:CompletedVotes]->(:Wrapper)-[:CompletedVote]->(voteHistory:CompletedVote)
OPTIONAL MATCH (currentPrice)-[:CurrentVote]->(vote:Vote)-[:VotedPrices]->(currentVotings)
WITH node, currentPrice, product, jump_sizes, jump_gates, jump_distance, hops, distance, voteHistory, currentVotings, vote, origin
WITH {eid: product.eid, displayName: product.displayName, name: product.name, currentPrice: {eid: currentPrice.eid, price: currentPrice.price}, currentVoting: {approved: vote.approved, count: Count(currentVotings), declined: vote.declined, users: Collect(currentVotings.userId), votes: Collect(currentVotings.price), voteAvg: round(100 * avg(currentVotings.price)) / 100}, voteHistory: Collect({votings: voteHistory.votings, users: voteHistory.users, completed: voteHistory.completed,
vote: voteHistory.votes}), marketplace: {eid: node.eid, name: node.name, type: node.type, designation: node.designation}, travel: {jumpSizes: jump_sizes, jumpGate: jump_gates, jumpDistance: jump_distance, jumps: hops, totalDistance: distance}} as sellOptions, currentPrice ORDER BY currentPrice.price
WITH Collect(sellOptions) as sellOptions
For the moment, this query works pretty well, but now I want to filter (after ".... dium ',' large '])" -> line 5) the minimum total distance you need to travel to reach your destination , I would like to realize this with my written plugin, which calculates the total distance in the path (getTotalDistance (path AS PATH))
For additional: when I cut of 'big' from the possible jump sizes, I get no result, but there is still a path in my graph that leads me to the goal.
For additional 2: iam working on neo4j 3.3.1 and i have set these config:
cypher.forbid_shortestpath_common_nodes=false
which not works in 3.3.3
EIDT 1: (More detailed explanation)
I have a place where I am. Then I search for marketplaces that sell some product. For this I can specify further filters. I can e.g. say that I can travel only through jump points of the size "large". Also, I only want marketplaces that are 4 system away.
Now, looking in the database for the above restrictions, I search for the shortest path to the market places I found.
It may well be that I have several paths that meet the conditions. If this is the case, I would like to filter out of all the shortest paths, the one in which one has to overcome the smallest distance within each solar system.
Is that accurate enough? Otherwise, please just report.
The latest APOC releases may be able to help here, though the APOC path expanders work best with labels and relationship types, so a slight change to your model may be needed.
In particular, the size of your jump points. Right now this is a property on the relationships between them, but for APOC to work optimally, these might be better modeled with the size as a label on the :JumpPoint nodes themselves, so you might have :JumpPoint:Small, :JumpPoint:Medium, and :JumpPoint:Large (you can add this in addition to the rel properties if you like).
Keep in mind this approach will be more complex than shortestPath(), as the idea is we're trying to find systems within a certain number of jumps, then find :Marketplaces reachable at those star systems, then filter based on whether they sell the product we want, and we'll stitch the path together as we find the pieces.
MATCH localSystemPath = (origin:Marketplace)-[*]-(s:Solarsystem)
WHERE origin.eid = $originId
WITH origin, localSystemPath, s
LIMIT 1
WITH origin, localSystemPath, s,
CASE WHEN coalesce($maxJumps, -1) = -1
THEN -1,
ELSE 3*$maxJumps
END as maxJumps,
CASE $shipSize
WHEN 'small' THEN ''
WHEN 'medium' THEN '|-Small'
ELSE '|-Small|-Medium'
END as sizeBlacklist
CALL apoc.path.spanningTree(s,
{relationshipFilter:'HasJumpPoint|CanTravel>', maxLevel:maxJumps,
labelFilter:'>Solarsystem' + sizeBlacklist, filterStartNode:true}) YIELD path as jumpSystemPath
WITH origin, localSystemPath, jumpSystemPath, length(jumpSystemPath) / 3 as jumps, last(nodes(jumpSystemPath)) as destSystem
MATCH destSystemPath = (destSystem)-[*]-(marketplace:Market)
WHERE none(rel in relationships(destSystemPath) WHERE type(rel) = 'HasJumpPoint')
AND <insert predicate for filtering which :Markets you want>
WITH origin, apoc.path.combine(apoc.path.combine(localSystemPath, jumpSystemPath), destSystemPath) as fullPath, jumps, destSystem, marketplace
CALL srt.getRoutes(origin, marketplace, fullPath) YIELD node, jump_sizes, jump_gates, jump_distance, hops, distance
...
This assumes parameter inputs of $shipSize for the minimum size of all jump gates to pass through, $originId as the id of the origin :Marketplace (plus you DEFINITELY need an index or unique constraint on :Marketplace(eid) for fast lookups here), and $maxJumps, for the maximum number of jumps to reach a destination system.
Keep in mind the expansion procedure used, spanningTree(), will only find the single shortest path to another system. If you need all possible paths, including multiple paths to the same system, then change the procedure to expandConfig() instead.

In a doubly linked list, How many pointers are affected on an insertion operation?

I had an interview yesterday. As it started, the first thing that the interviewer asked was
" In a doubly linked list, How many pointers will be affected on an insertion operation ? "
Since, he didn't specifically asked where to insert I replied that it depends on how many nodes are there in DLL.
As total pointers that will be affected will depend on whether the list is empty or not and where insertion takes place.
But, he didn't say anything whether I had convinced him or not.
Was I correct or maybe I missed something ?
I think the answer depends on whether we are inserting the new node in the middle of the list (surrounded by two nodes), or at the head or tail of the list.
For insertions in the middle of the list, to splice in a new node as follows:
A --- B
^^ splice M in here
A.next = M
M.prev = A
B.prev = M
M.next = B
Hence four pointer assignments take place. However, if the insertion be at the head or tail, then only two pointer assignments would be needed:
TAIL (insert M afterward)
TAIL.next = M
M.prev = TAIL

How to concatenate three columns into one and obtain count of unique entries among them using Cypher neo4j?

I can query using Cypher in Neo4j from the Panama database the countries of three types of identity holders (I define that term) namely Entities (companies), officers (shareholders) and Intermediaries (middle companies) as three attributes/columns. Each column has single or double entries separated by colon (eg: British Virgin Islands;Russia). We want to concatenate the countries in these columns into a unique set of countries and hence obtain the count of the number of countries as new attribute.
For this, I tried the following code from my understanding of Cypher:
MATCH (BEZ2:Officer)-[:SHAREHOLDER_OF]->(BEZ1:Entity),(BEZ3:Intermediary)-[:INTERMEDIARY_OF]->(BEZ1:Entity)
WHERE BEZ1.address CONTAINS "Belize" AND
NOT ((BEZ1.countries="Belize" AND BEZ2.countries="Belize" AND BEZ3.countries="Belize") OR
(BEZ1.status IN ["Inactivated", "Dissolved shelf company", "Dissolved", "Discontinued", "Struck / Defunct / Deregistered", "Dead"]))
SET BEZ4.countries= (BEZ1.countries+","+BEZ2.countries+","+BEZ3.countries)
RETURN BEZ3.countries AS IntermediaryCountries, BEZ3.name AS
Intermediaryname, BEZ2.countries AS OfficerCountries , BEZ2.name AS
Officername, BEZ1.countries as EntityCountries, BEZ1.name AS Companyname,
BEZ1.address AS CompanyAddress,DISTINCT count(BEZ4.countries) AS NoofConnections
The relevant part is the SET statement in the 7th line and the DISTINCT count in the last line. The code shows error which makes no sense to me: Invalid input 'u': expected 'n/N'. I guess it means to use COLLECT probably but we tried that as well and it shows the error vice-versa'd between 'u' and 'n'. Please help us obtain the output that we want, it makes our job hell lot easy. Thanks in advance!
EDIT: Considering I didn't define variable as suggested by #Cybersam, I tried the command CREATE as following but it shows the error "Invalid input 'R':" for the command RETURN. This is unfathomable for me. Help really needed, thank you.
CODE 2:
MATCH (BEZ2:Officer)-[:SHAREHOLDER_OF]->(BEZ1:Entity),(BEZ3:Intermediary)-
[:INTERMEDIARY_OF]->(BEZ1:Entity)
WHERE BEZ1.address CONTAINS "Belize" AND
NOT ((BEZ1.countries="Belize" AND BEZ2.countries="Belize" AND
BEZ3.countries="Belize") OR
(BEZ1.status IN ["Inactivated", "Dissolved shelf company", "Dissolved",
"Discontinued", "Struck / Defunct / Deregistered", "Dead"]))
CREATE (p:Connections{countries:
split((BEZ1.countries+";"+BEZ2.countries+";"+BEZ3.countries),";")
RETURN BEZ3.countries AS IntermediaryCountries, BEZ3.name AS
Intermediaryname, BEZ2.countries AS OfficerCountries , BEZ2.name AS
Officername, BEZ1.countries as EntityCountries, BEZ1.name AS Companyname,
BEZ1.address AS CompanyAddress, AS TOTAL, collect (DISTINCT
COUNT(p.countries)) AS NumberofConnections
Lines 8 and 9 are the ones new and to be in examination.
First Query
You never defined the identifier BEZ4, so you cannot set a property on it.
Second Query (which should have been posted in a separate question):
You have several typos and a syntax error.
This query should not get an error (but you will have to determine if it does what you want):
MATCH (BEZ2:Officer)-[:SHAREHOLDER_OF]->(BEZ1:Entity),(BEZ3:Intermediary)- [:INTERMEDIARY_OF]->(BEZ1:Entity)
WHERE BEZ1.address CONTAINS "Belize" AND NOT ((BEZ1.countries="Belize" AND BEZ2.countries="Belize" AND BEZ3.countries="Belize") OR (BEZ1.status IN ["Inactivated", "Dissolved shelf company", "Dissolved", "Discontinued", "Struck / Defunct / Deregistered", "Dead"]))
CREATE (p:Connections {countries: split((BEZ1.countries+";"+BEZ2.countries+";"+BEZ3.countries), ";")})
RETURN BEZ3.countries AS IntermediaryCountries,
BEZ3.name AS Intermediaryname,
BEZ2.countries AS OfficerCountries ,
BEZ2.name AS Officername,
BEZ1.countries as EntityCountries,
BEZ1.name AS Companyname,
BEZ1.address AS CompanyAddress,
SIZE(p.countries) AS NumberofConnections;
Problems with the original:
The CREATE clause was missing a closing } and also a closing ).
The RETURN clause had a dangling AS TOTAL term.
collect (DISTINCT COUNT(p.countries)) was attempting to perform nested aggregation, which is not supported. In any case, even if it had worked, it probably would not have returned what you wanted. I suspect that you actually wanted the size of the p.countries collection, so that is what I used in my query.

Why is Neo4J telling me there is no spoon?

I am using Neo4J to represent texts; in the simplest case a text is a sequence of words joined by the relationship LEMMA_TEXT.
I am trying to find the Nth word after a known word, with a query that looks something like this.
MATCH (anchor)-[:LEMMA_TEXT*32]->(word)
WHERE id(anchor) = 3275
RETURN word
In one particular case, if I increase the path length to 33, I get this error:
Neo.DatabaseError.Statement.ExecutionFailure: There is no spoon.
And yet the following query returns the correct result.
MATCH (anchor)-[:LEMMA_TEXT*32]->(word)-[:LEMMA_TEXT]->(next)
WHERE id(anchor) = 3275
RETURN next
which demonstrates that the node I want exists and is reachable.
Where is the section of the manual that tells me how to bend the spoon with my mind? More importantly, what does this actually mean?!
If anything breaks at number like 33, it means that there was a restriction upto 32, why 32? 2^5.
It's not trivial that most of the restrictions are in a factor of 2, MongoDB document size cannot be more than 16 MB, on a collection there could be maximum index, no more than 64. etc.
why it works as 32 and then next, because till 32 it can achieve in one operation and for last one it can see the next one as another operation. But it cannot go for 33 in one operation.
Most of these restrictions are basically sanity check though and not really technical boundary.
As for why it is almost always a factor of 2, I want someone else to answer or in other words I don't know.
Have you tried splitting the landing and the search statements in 2?
Plus you should add the label for the text word (forormance)
Example:
MATCH (anchor)
WHERE id(anchor) = 3275
WITH anchor
MATCH (anchor)-[:LEMMA_TEXT*32]->(word)
RETURN word
You get the same error?

Neo4j doesn't look for multiple starting points if one of them is null

In the following scenario, node "x" does not exist.
start x=node:node_auto_index(key="x"), y=node(*)
return count(x), count(y)
It seems that if any of the starting points can't be found, nothing is returned.
Any suggestions how to work around this issue?
This is like saying the below (in SQL)--what do you expect will happen if table X is empty?
select count(x), count(y)
from x, y
I'm not sure exactly what you're trying to query here, but you might need to get your counts one at a time, if there's a chance that x will come back with no results:
start x=node:node_auto_index(key="x")
with count(x) as cntx
start y=node(*)
return cntx, count(y) as cnty
Thanks to Wes, I figured out how to do a "conditional add" with the old Cypher syntax (pre 2.0):
START x=node:node_auto_index(key="x")
with count(x) as exists
start y=node:node_auto_index(key="y")
where exists = 0
create (n {key:"y"})<-[:rel]-y
return n, y
The crux here is that you can't fire another "start" after a "where" clause. You need to query for the second node before checking for conditions (which is kind of bad for performance). This is remedied in 2.0 with if-then-else statements anyway...

Resources