Neo4j Cypher:基于节点属性计算

Neo4j Cypher:基于节点属性计算,neo4j,cypher,Neo4j,Cypher,我开发了一个路由系统,通过使用几个移动服务来寻找最佳的路由,例如。G公共交通或拼车。 基本上我有节点,代表公共汽车站、火车站等。这些站点包括每天的时间表。此外,我还有步行站。这些站点由关系连接。如果我从行人换乘公交车,或在公交车线路之间换乘公交车,这种关系称为切换到。如果我在不更改服务线路的情况下驾驶多个站点,则这些站点通过一个名为connected_TO的关系连接。如果我想从A点到Z点,我必须在公交车站C换乘到公交车站D的另一条服务线,路径如下: (A:Stop:Pedestrian)-[SW

我开发了一个路由系统,通过使用几个移动服务来寻找最佳的路由,例如。G公共交通或拼车。 基本上我有节点,代表公共汽车站、火车站等。这些站点包括每天的时间表。此外,我还有步行站。这些站点由关系连接。如果我从行人换乘公交车,或在公交车线路之间换乘公交车,这种关系称为切换到。如果我在不更改服务线路的情况下驾驶多个站点,则这些站点通过一个名为connected_TO的关系连接。如果我想从A点到Z点,我必须在公交车站C换乘到公交车站D的另一条服务线,路径如下:

(A:Stop:Pedestrian)-[SWITCH_TO{switchTime:2}]->
(A:Stop:Bus{serviceLine:1})-[CONNECTED_TO{travelTime:5}]->
(B:Stop:Bus{serviceLine:1})-[CONNECTED_TO{travelTime:6}]->
(C:Stop:Bus{serviceLine:1})-[SWITCH_TO{switchTime:2}]->
(C:Stop:Pedestrian)-[CONNECTED_TO{travelTime:7}]->
(D:Stop:Pedestrian)-[SWITCH_TO{switchTime:2}]->
(D:Stop:Bus{serviceLine:2})-[CONNECTED_TO{travelTime:8}]->(Z:Stop:Bus{serviceLine:2})-[SWITCH_TO{switchTime:2}]->
(Z:Stop:Pedestrian)
我想根据用户的期望出发时间(或者期望到达时间)计算完整旅行时间,并获得5个最佳连接(更少的时间)。 在上面的示例中,您可以看到SWITCH_TO关系的switchTime为2。这意味着我需要2分钟从当前位置切换到公交车站(例如,我必须寻找它)。与行程时间相关的时间段是公交车从一站到另一站所需的时间段。 假设我想7点出发。第一次切换需要2分钟。因此,如果7:02后有航班,我必须看一下(A:车站:公共汽车)的时刻表。假设下一班飞机7点10分起飞。那我得等8分钟。此等待时间不是固定值,而是每个特定请求的可变时间段。我7点10分出发。我需要5+6=11分钟才能停车(C:停车:公交车),2分钟才能下车(切换到)。然后我得步行7分钟。因此,如果必须检查服务线2的时间表。如果在7:00+2+8+5+6+2+7+2=7:32之后有一个发车,就拿这个。如果下一次出发时间是7:35,我将在7:00+2+8+5+6+2+7+2+3+8+2=7:45到达目的地。我知道,我有点复杂。:)

我在这里准备了一个例子:

CREATE (newStop:Stop:Pedestrian {
    stopName : 'A-Pedestrian',
    mode : 'Pedestrian'
})
RETURN newStop;

CREATE (newStop:Stop:Bus {
    stopName : 'A-Bus',
    mode : 'Bus',
    serviceLine : '1',
    monday:[510,610,710,810,835,910],
    tuesday:[510,610,710,810,835,910],
    wednesday:[510,610,710,810,835,910],
    thursday:[510,610,710,810,835,910],
    friday:[510,610,710,810,835,910],
    saturday:[510,610,710,810,835,910],
    sunday:[510,610,710,810,835,910]
})
RETURN newStop;

CREATE (newStop:Stop:Bus {
    stopName : 'B-Bus',
    mode : 'Bus',
    serviceLine : '1',
    monday:[515,615,715,815,840,915],
    tuesday:[515,615,715,815,840,915],
    wednesday:[515,615,715,815,840,915],
    thursday:[515,615,715,815,840,915],
    friday:[515,615,715,815,840,915],
    saturday:[515,615,715,815,840,915],
    sunday:[515,615,715,815,840,915]
})
RETURN newStop;

CREATE (newStop:Stop:Bus {
    stopName : 'C-Bus',
    mode : 'Bus',
    serviceLine : '1',
    monday:[521,621,711,821,846,921],
    tuesday:[521,621,711,821,846,921],
    wednesday:[521,621,711,821,846,921],
    thursday:[521,621,711,821,846,921],
    friday:[521,621,711,821,846,921],
    saturday:[521,621,711,821,846,921],
    sunday:[521,621,711,821,846,921]
})
RETURN newStop;

CREATE (newStop:Stop:Pedestrian {
    stopName : 'C-Pedestrian',
    mode : 'Pedestrian'
})
RETURN newStop;

CREATE (newStop:Stop:Pedestrian {
    stopName : 'D-Pedestrian',
    mode : 'Pedestrian'
})
RETURN newStop;

CREATE (newStop:Stop:Bus {
    stopName : 'D-Bus',
    mode : 'Bus',
    serviceLine : '2',
    monday:[535,635,735,835,935],
    tuesday:[535,635,735,835,935],
    wednesday:[535,635,735,835,935],
    thursday:[535,635,735,835,935],
    friday:[535,635,735,835,935],
    saturday:[535,635,735,835,935],
    sunday:[]
})
RETURN newStop;

CREATE (newStop:Stop:Bus {
    stopName : 'Z-Bus',
    mode : 'Bus',
    serviceLine : '2',
    monday:[543,643,743,843,943],
    tuesday:[543,643,743,843,943],
    wednesday:[543,643,743,843,943],
    thursday:[543,643,743,843,943],
    friday:[543,643,743,843,943],
    saturday:[543,643,743,843,943],
    sunday:[]
})
RETURN newStop;

CREATE (newStop:Stop:Pedestrian {
    stopName : 'Z-Pedestrian',
    mode : 'Pedestrian'
})
RETURN newStop;




MATCH (s1:Stop), (s2:Stop)
WHERE s1.stopName = 'A-Pedestrian' AND s2.stopName = 'A-Bus'
CREATE
    (s1)-[r:SWITCH_TO{ switchTime : 2 } ]->(s2)
RETURN s1, s2, r;

MATCH (s1:Stop), (s2:Stop)
WHERE s1.stopName = 'A-Bus' AND s2.stopName = 'B-Bus'
CREATE
    (s1)-[r:CONNECTED_TO{ travelTime : 5 } ]->(s2)
RETURN s1, s2, r;

MATCH (s1:Stop), (s2:Stop)
WHERE s1.stopName = 'B-Bus' AND s2.stopName = 'C-Bus'
CREATE
    (s1)-[r:CONNECTED_TO{ travelTime : 6 } ]->(s2)
RETURN s1, s2, r;

MATCH (s1:Stop), (s2:Stop)
WHERE s1.stopName = 'C-Bus' AND s2.stopName = 'C-Pedestrian'
CREATE
    (s1)-[r:SWITCH_TO{ switchTime : 2 } ]->(s2)
RETURN s1, s2, r;

MATCH (s1:Stop), (s2:Stop)
WHERE s1.stopName = 'C-Pedestrian' AND s2.stopName = 'D-Pedestrian'
CREATE
    (s1)-[r:CONNECTED_TO{ travelTime : 7 } ]->(s2)
RETURN s1, s2, r;

MATCH (s1:Stop), (s2:Stop)
WHERE s1.stopName = 'D-Pedestrian' AND s2.stopName = 'D-Bus'
CREATE
    (s1)-[r:SWITCH_TO{ switchTime : 2 } ]->(s2)
RETURN s1, s2, r;

MATCH (s1:Stop), (s2:Stop)
WHERE s1.stopName = 'D-Bus' AND s2.stopName = 'Z-Bus'
CREATE
    (s1)-[r:CONNECTED_TO{ travelTime : 8 } ]->(s2)
RETURN s1, s2, r;

MATCH (s1:Stop), (s2:Stop)
WHERE s1.stopName = 'Z-Bus' AND s2.stopName = 'Z-Pedestrian'
CREATE
    (s1)-[r:SWITCH_TO{ switchTime : 2 } ]->(s2)
RETURN s1, s2, r;
如您所见,在某些站点,出发时间的int数组也可能为空(如果这些天没有提供连接)。当然,步行站不包括时间表。 我的问题是:我如何通过cypher进行查询?我必须总结这些时间来选择正确的下次出发时间。我必须知道我什么时候到达目的地。我想向用户展示最好的5个连接。有办法做到这一点吗?如果没有,有什么建议如何解决这个问题

多谢各位! 斯特凡


Edit1:有没有一种用Java开发的方法?在最简单的情况下,它可能只是一条最短路径,但具有智能成本函数?而不是使用固定值使用函数来计算一个特定边的成本?任何帮助都将不胜感激

[提前道歉,这本书在我不看的时候变成了一本俄罗斯小说。它仍然只会让你找到最快的一条路线,而不是最快的5条,但希望有比我更聪明的人能改进这一点。]

您正试图基于难以计算的成本进行一些极其复杂的路径规划。您肯定需要重构一些,以便更严格地解决成本问题,然后应用
apoc.algo.dijkstra
获得一条具有权重的可行路径。要做到这一点,您需要从非常通用的模型更改为某种事件“链”,按物理位置组织。交通方式与每周时间表相结合将为您提供一些结构;在不同地点之间行走的能力只会使其稍微复杂化。在此过程中,我们最终将剥离一些不太相关和冗余的节点。让我们埋头干吧

首先,我们需要能够将您的时间转换为机器可解析的时间。您可以使用apoc将其转换为
isoformat
或类似格式,但对于需要频繁订购且仅以分钟为单位存在的周期性时间,我建议从周日上午的午夜开始计算0分钟,然后从那里开始计算。所以,从周日午夜开始的几分钟基本上是你关注的时间,然后通过一些技巧,你可以处理周期性的部分

MATCH (stop:Stop:Bus)
WITH stop, ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'] AS days
UNWIND RANGE(0, 6) AS day_index
WITH stop, day_index, days[day_index] AS day_key
UNWIND stop[day_key] AS int_time
WITH stop, day_index * 24 * 60 AS day_minutes, int_time % 100 AS minutes, ((int_time - (int_time % 100))/100)*60 AS hour_minutes 
WHERE day_minutes IS NOT NULL
WITH stop, day_minutes + hour_minutes + minutes AS time_since_sunday
MERGE (dep:Departure:Bus {time: time_since_sunday})
MERGE (dep) - [:AT] -> (stop)
WITH stop, time_since_sunday
ORDER BY time_since_sunday
WITH stop, collect(time_since_sunday) AS times
SET stop.departure_times = times
好的,这给了我们每个公共汽车站周围的发车事件,表示您可以在其他地方开始固定长度旅行的时间,如果您在此之前在车站。现在,如果我们只考虑基于公交的移动,我们可以将一个
:Stop
上的每个
:出发
节点连接到公交时间过后下一站可用的任何
:出发
节点(并考虑等待时间)。不过,增加步行(多式联运)会稍微改变这一点,因为无论何时交通工具到达某个地方,你都可以立即用自己的双脚“出发”。因此,我们应该对
:到达
节点进行建模,以对应基于
:出发
的行程的另一端,这样我们就可以区分基于
:出发
的等待下一次公交和时间上的步行

MATCH (stop:Stop:Bus) <- [:AT] - (dep:Departure:Bus)
WITH stop, dep, dep.time AS dep_time
MATCH (stop) - [r:CONNECTED_TO] -> (other:Stop:Bus)
WITH dep, dep_time, dep_time + r.travelTime AS arrival_time, other
MERGE (a:Arrival:Bus {time: arrival_time})
MERGE (a) - [:AT] -> (other)
MERGE (dep) - [:TRAVEL {time: arrival_time - dep_time, mode: 'Bus'}] -> (a)
WITH a, arrival_time, other, other.departure_times AS dep_times
WITH a, other, arrival_time, REDUCE(s = HEAD(dep_times), x IN TAIL(dep_times) | CASE WHEN x < arrival_time THEN s WHEN s < x THEN s ELSE x END) AS next_dep_time
WITH a, other, next_dep_time, next_dep_time - arrival_time AS wait_time
MATCH (other) <- [:AT] - (next_dep:Departure:Bus {time: next_dep_time})
MERGE (a) - [:TRAVEL {time: wait_time, mode: 'Wait'}] -> (next_dep)
好的,这就解决了基本的转移问题。顺便说一句,这个模型假设,如果你要在公共汽车或火车“线路”之间切换,你会将它们建模为单独的站点(如果它们真的在同一个地方,步行时间为0是可以的,但是如果你共享
:Stop
s,跟踪就复杂得多)。现在来处理更复杂的换乘,以前的模式是切换到
:行人
,旅行,然后切换回:

MATCH (stop:Stop:Bus) - [r1:SWITCH_TO] -> (:Stop:Pedestrian) - [r2:CONNECTED_TO] -> (:Stop:Pedestrian) - [r3:SWITCH_TO] -> (other:Stop)
WITH stop, other, other.departure_times AS dep_times, REDUCE(s = 0 , x IN [r1, r2, r3] | s + COALESCE(x.travelTime, x.switchTime) ) AS walking_time
MATCH (stop) <- [:AT] - (arr:Arrival)
WITH arr, other, dep_times, walking_time, arr.time + walking_time AS new_arr_time
MERGE (new_arr:Arrival:Pedestrian {time:new_arr_time})
MERGE (new_arr) - [:AT] -> (other)
MERGE (arr) - [:TRAVEL {time:walking_time, mode: 'Pedestrian'}] -> (new_arr)
WITH new_arr, new_arr_time, other, REDUCE(s = HEAD(dep_times), x IN TAIL(dep_times) | CASE WHEN x < new_arr_time THEN s WHEN s < x THEN s ELSE x END) AS next_dep_time
WITH new_arr, other, next_dep_time, next_dep_time - new_arr_time AS wait_time
MATCH (other) <- [:AT] - (next_dep:Departure {time:next_dep_time})
MERGE (new_arr) - [:TRAVEL {time: wait_time, mode: 'Wait'}] -> (next_dep)

您的出发时间表的值将是有问题的。您试图捕获并使用时间值(例如,515表示5:15,或者至少在我看来是这样),但您使用的是整数。现在的情况是,你的时间计算不会自动滚动到下一个小时,因为你每超过60分钟计算一次。时间开始变得模棱两可。如果我们从846(8:46)开始,一次旅行需要50分钟,我们得到896(9:36)。日分
MATCH (stop:Stop:Bus) - [r1:SWITCH_TO] -> (:Stop:Pedestrian) - [r2:CONNECTED_TO] -> (:Stop:Pedestrian) - [r3:SWITCH_TO] -> (other:Stop)
WITH stop, other, other.departure_times AS dep_times, REDUCE(s = 0 , x IN [r1, r2, r3] | s + COALESCE(x.travelTime, x.switchTime) ) AS walking_time
MATCH (stop) <- [:AT] - (arr:Arrival)
WITH arr, other, dep_times, walking_time, arr.time + walking_time AS new_arr_time
MERGE (new_arr:Arrival:Pedestrian {time:new_arr_time})
MERGE (new_arr) - [:AT] -> (other)
MERGE (arr) - [:TRAVEL {time:walking_time, mode: 'Pedestrian'}] -> (new_arr)
WITH new_arr, new_arr_time, other, REDUCE(s = HEAD(dep_times), x IN TAIL(dep_times) | CASE WHEN x < new_arr_time THEN s WHEN s < x THEN s ELSE x END) AS next_dep_time
WITH new_arr, other, next_dep_time, next_dep_time - new_arr_time AS wait_time
MATCH (other) <- [:AT] - (next_dep:Departure {time:next_dep_time})
MERGE (new_arr) - [:TRAVEL {time: wait_time, mode: 'Wait'}] -> (next_dep)
MATCH (:Stop {stopName: {begin_id} }) <- [:AT] - (d:Departure {time: {depart_time} })
WITH d
MATCH (end:Stop {stopName: {end_id} })
CALL apoc.algo.dijkstraWithDefaultWeight(d, end, 'TRAVELS>|AT>', 'time', 0) YIELD path, weight
RETURN path