Znajdź najkrótszą ścieżkę i do domu

Znajdź najkrótszą ścieżkę i do domu

Magiczna komenda jaką daje nam silnik i pozamiatane. Nie no co wy, zwróci nam najkrótszą drogę ale po ilości relacji.

MATCH (s:Point),(e:Point),
p = shortestpath((s)-[*]-(e))
WHERE s.name = 'JASŁO' AND e.name = 'RZESZÓW'
return p

No ale nam chodzi o odległość, a posiadamy strukturę punktów osadzonych na kawałkach drogi, a poruszamy się między punktami, droga to tylko medium. Więc w głowie pojawia się pomysł bezpośredniego połączenia punktów.

MATCH (s:Point)-[r:Stay]-(t:Segment)
where  r.lineNo = t.lineNo
with distinct s,r.km as km, t.lineNo as  lineNo
order by km 
with collect({s:s,km:km,lineNo:lineNo}) as one , collect({s:s,km:km,lineNo:lineNo}) as two
UNWIND one as ss 
with  ss  , filter(next IN two WHERE ss.lineNo = next.lineNo and next.km >= ss.km and NOT next.s.name = ss.s.name)[0] as nss
with  ss.s as fi,  nss.km-ss.km as distance , ss.lineNo as lineNo, nss.s as se
where se is not null
merge (fi)-[:Connect{distance: distance ,lineNo: lineNo}]-(se)

Przy tym skrypcie się już trochę rozpoznałem Cypher'a, ale jeśli ktoś ma inny pomysł na połączenie tego to proszę bardzo :).

Mając na połączone punkty no to tylko sumować odległości dzieki reduce i dawaj najkrótszą. 

START  startNode=node:node_auto_index(name="Start"),
             endNode=node:node_auto_index(name="Finish")
MATCH  p=(startNode)-[:NAVIGATE_TO*]->(endNode)
RETURN p AS shortestPath,
                reduce(distance=0, r in relationships(p) :  distance+r.distance) AS totalDistance
                ORDER BY totalDistance ASC
                LIMIT 1;

I lepiej tego nie puszczać jeśli się ma więcej niż 100 nodów. XD Gdy rozpoczeła się walka z brakiem RAM i mając już 5 min zastanawiania się dlaczego to tak długo, zrozumiałem że gość tak naprawdę robi iloczyn kartezjański i wszystko próbuje obliczyć... Porażka, Jak takie zajefajne grafy i na 25k Pointów i 60k odcinów się wysypuje. 

No ale 90% problemu to brak zrozumienia jak to działa. I oczywiście każdy miał na studiach algorytmy i było o takim na D, co tak szybko szuka. 

Daleko szukać i głowić się nie trzeba, jest już zaimplementowany jako plugin do neo, neo4j-apoc-procedures

MATCH (s:Point{name:'JASŁO'}),(e:Point{name:'RZESZÓW'})
CALL apoc.algo.dijkstra(s, e, 'Connect', 'distance') YIELD path, weight
RETURN path, weight

No i to już jest faktyczna prawdziwa najkrótsza ścieżka.

Teraz temat co jeśli nie możemy jechać przez np Czudec.  Na szybko mam tylko taki pomysł żeby skopiować relacje które są dostępne czyli bez Czudca, a następnie wyszukać połączenie, i oczywiście pamiętać żeby to chwilowe powiązanie usunąć.

MATCH (s:Point)-[c:Connect]-(t:Point)
where s.name <> 'Czudec' and t.name <> 'Czudec' 
merge (s)-[:Connect_tempWOCzud{distance: c.distance ,lineNo: c.lineNo}]-(t)

Jak sprawdziłem skopiowanie 2k relacji bez 2 bo akurat tyle miał Czudec to 450ms, więc jeszcze nie tak źle. Gorsza jest trasa bez przejazdu przez Czudec gdzie okazuje się że muszę nadgonić 100km aby go ominąć. Taką mamy mapę :)

MATCH (s:Point{name:'JASŁO'}),(e:Point{name:'RZESZÓW'})
CALL apoc.algo.dijkstra(s, e, 'Connect_tempWOCzud', 'distance') YIELD path, weight
RETURN path, weight

 

Próbując wytyczyć alternatywne ścieżki okazało się że dane są nie dokładne a najlepszy błąd to stworzenie punktu Granica Państwa gdzie z odległością 0 można się teleportować z Zgorzelca do Cieszyna :) 

Tyle eksperymentów z czystym Neo, następny krok to kawałek, który będzie miał za zadanie obsługę obsługę całej transakcji bez wyłączonych odcinków itd.

I DO DOMU :D 

 

Pierwsze rysowanki - E to Graf jest

Hehe powrót do przedszkola normalnie. Ale ten feature jest zajebisty :) ktoś za to powinien dostać 1mln$. Gdyby nie taka wizualizacja to pewnie długo bym siedział przy SQL i jazda jak exel.

Na grafach znam się tyle co teoria w 2 linijkach na sprawdzianie. Natomiast ostatnio podczas rg-dev w Rzeszowie była prelekcja Szymona Warda @maklipsa dotycząca grafów. Fajnie wszystko opowiedział, i widać z pasją i zaangażowaniem, ale z przyczyn filipińskich i późnej pozy to miałem content w głowie ale microservice od analizy informacji już leżał. Jedyne co zwrócił mi. :)

Cola + Ryba => "Cooo...????"

I tak po 2 dniach serwis wstał i zwraca: że te grafy to są błeee, bo gdzie tu not Nulle, gdzie tu spójność, biznes w to z trudem bo do exela będzie trudno. Nie mija 5 min i w tej czasce zaświeciła się lampka: "Przecież to jest jak mapa połączeń". Ha odkrywcze nie ? Normalnie geniusz. W definicji tak pisze. "Ale nieee, nieee, nie ze same połączenia, to jest baza danych gość mówił. To normalnie się JE, to jakiś ma język zapytań na 5 piętrze. To to będzie dobre bo to da się zrobić inaczej joiny.".

No i właśnie do roboty. Szymon mówił o jakimś silniku jak rybka. Neo.... Neo4j. https://neo4j.com/ 
Powiem tyle. Jest ZajeEEEEEE ty, przez tą wizualizację. To nie to co Młotek do sql'a. aż się chce wpisywać Match (s) return s.  Normalnie byle by RAM w chromie starczył bo jednak przy wielu elementach można sobie zaszkodzić.

Import danych   

https://neo4j.com/developer/guide-import-csv/  
https://neo4j.com/developer/guide-importing-data-and-etl/

Przecież z marszu nie będziemy robić mam jakieś dane odnośnie komunikacji to sobie z SQL wyciągnę. Ale tu jest haczyk. Jak 5 lat romantycznie spędzałeś z SQL, tak teraz ciężko obrobić dwie kochanki i najlepiej całe "żarcie"(dane) dla nowej zrobił byś w kuchni starej dziewczyny. Ehh Ale dobre porównanie, dosłowne. Pierwsze co to zamiast importować to zaczynają się triki, a to może zrobię 2,3..8 joinów 7 subSelect i jazda. GŁUPOTA!

Nowa kochanka to też silnik bazodanowy, mając dane 1:1 w strukturze sql-csv, wystarczy nauczyć rozmawiać w nowym języku.

Pierwszy najważniejszy skrypt: 

MATCH (n) DETACH
DELETE n;

Trudno się nie domyślić, jest najlepszy bo za każdym razem puszczamy go ostatni raz. :)

Ale dobra importujemy jeden label(to te kropki-ala tabela po staremu), drugi. - Odcinki drogi (Segment), Przystanki/Punkty docelowe (Pointy)

USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///Segment.csv" AS row
CREATE (:Segment {lineNo: toInt(row.lineNo) ,speedLimit: toInt(row.speedLimit),relationSort: toInt(row.relationSort) ,kmFrom :  toFloat(row.kmFrom),kmTo : toFloat(row.kmTo)});

USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///point.csv" AS row
CREATE (:Point {name: row.nazwa   });

O ile Segmenty są maja swoją kolejność i można bardzo łatwo połączyć w jedną linie. A tak naprawdę dla każdej linii można było by zrobić label.

USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///tracks2.csv" AS row
MATCH (sta:Segment {lineNo: toInt(row.lineNo),relationSort: toInt(row.relationSort)})
MATCH (stc:Segment {lineNo: sta.lineNo,relationSort: (sta.relationSort+1)})
MERGE (sta)-[:Continue]-(stc);

Ale to to pikuś, problemem w SQL było napisanie takiego skryptu aby segmenty połączyć z Poitami. Udało mi się z innych źródeł złapać informacje jaki Pint(nazwa) na jakim jest km linii(lineNo), i od tego momentu to byłą bajka w połączeniu(ten pierwszy skrypt uruchomiłem 30 razy i taki poszło):

USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///pointLocation.csv" AS row
MATCH (p:Point {name: row.nazwa })
MATCH (stc:Segment {lineNo: toInt(row.nr_lini)})
Where toInt(row.km_os) >= toInt(stc.kmFrom) and (toInt(stc.kmTo) >  toInt(row.km_os) or toInt(row.km_os) = toInt(stc.kmTo))
MERGE (p)-[:Stay { km: toFloat(row.km_os),  lineNo: toInt(row.nr_lini) }]-(stc);

import.done.pl

Znajdź najkrótszą ścieżkę i do domu

Magiczna komenda jaką daje nam silnik i pozamiatane. Nie no co wy, zwróci nam najkrótszą drogę ale po ilości relacji.

MATCH (s:Point),(e:Point),
p = shortestpath((s)-[*]-(e))
WHERE s.name = 'JASŁO' AND e.name = 'RZESZÓW'
return p

Reszta suuuun. bo dziś Dzień kobiet więc "Panowie Zdrowie Pań". BACZNOŚĆ DO OBOWIĄZKÓW.
Panie: Wszystkiego najlepszego.