Calculating the shortest path with Gremlin-Scala
Updated 12/12/2015 for Gremlin-Scala 3.1.0-incubating.
Graph databases are fun - they are fundamentally simple as they only have vertices and edges - yet they are much more powerful than e.g. relational databases for many scenarios. Gremlin can be seen as the JDBC for graph databases - it allows you to define traversals and has drivers for a large number of graph dbs. I am the maintainer of Gremlin-Scala, a Scala wrapper to make Gremlin easier to use from Scala.
Gremlin-Scala-Examples is a collection of sample projects and recipies to get you started as quickly as possible. This article explains one of the recipies - ShortestPath - in more detail.
I stole the scenario from my former colleague Stefan Bleibinhaus who did a great job explaining this for an earlier version of Gremlin-Scala (2.5): let’s try and find the shortest path between Auckland and Cape Reinga in New Zealand. I live in Auckland and Cape Reinga is quite a popular tourist destination - it’s the northernmost point and a very unique place.
Here’s how to setup an example graph in Gremlin-Scala - in this case we are using neo4j, but it would be almost the same with any other graph db. First we model some cities in New Zealand as vertices and the routes between them as edges. The routes carry the distance as a property.
val graph = Neo4jGraph.open("neo4j").asScala
val auckland = graph + (Location, Name → "Auckland")
val whangarei = graph + (Location, Name → "Whangarei")
val dargaville = graph + (Location, Name → "Dargaville")
val kaikohe = graph + (Location, Name → "Kaikohe")
val kerikeri = graph + (Location, Name → "Kerikeri")
val kaitaia = graph + (Location, Name → "Kaitaia")
val capeReinga = graph + (Location, Name → "Cape Reinga")
auckland <-- (Road, Distance → 158) --> whangarei
whangarei <-- (Road, Distance → 85) --> kaikohe
kaikohe <-- (Road, Distance → 82) --> kaitaia
kaitaia <-- (Road, Distance → 111) --> capeReinga
whangarei <-- (Road, Distance → 85) --> kerikeri
kerikeri <-- (Road, Distance → 88) --> kaitaia
auckland <-- (Road, Distance → 175) --> dargaville
dargaville <-- (Road, Distance → 77) --> kaikohe
kaikohe <-- (Road, Distance → 36) --> kerikeri
To find the shortest path from Auckland to Cape Reinga we simply start in Auckland and follow all outgoing edges (roads) with the outE
step and immediately traverse to the next incoming Vertex (city) using the inV
step. So in the first iteration Gremlin will traverse to Whangarei and Dargaville.
We instruct Gremlin to repeat
these two steps (outE.inV
) until
we reach Cape Reinga.
Then we use the path
step to get the complete path of the traversal, and toList
to finally execute it (Gremlin is lazy). This returns a List[Path].
val shortestPath = auckland.asScala
.repeat(_.outE.inV.simplePath)
.until(_.is(capeReinga.vertex))
.path
.toList
We are basically done, all we need is to beauty it up a bit and make sure we find the actual road lengths. Each path holds the list of things Gremlin encountered while traversing the graph. In our case that’s just Vertices (cities) and Edges (roads). For each path we collect the names of the city to get the complete route, for each road we get the distance travelled. That gives us a List of DescriptionAndDistance
- one entry per path travelled through New Zealand:
case class DescriptionAndDistance(description: String, distance: Int)
val descriptionAndDistances: List[DescriptionAndDistance] = paths map { p: Path ⇒
val pathDescription = p.objects collect {
case v: Vertex ⇒ v.value[String]("name")
} mkString(" -> ")
val pathTotalKm = p.objects collect {
case e: Edge => e.value[Int]("distance")
} sum
DescriptionAndDistance(pathDescription, pathTotalKm)
}
To get the shortest path we just need to sort our list by the distance:
val shortestPath = descriptionAndDistances.sortBy(_.distance).head
And here is the output:
time elapsed: 37ms
Paths from Auckland to Cape Reinga:
DescriptionAndDistance(Auckland -> Whangarei -> Kaikohe -> Kaitaia -> Cape Reinga,436)
DescriptionAndDistance(Auckland -> Whangarei -> Kerikeri -> Kaitaia -> Cape Reinga,442)
DescriptionAndDistance(Auckland -> Dargaville -> Kaikohe -> Kaitaia -> Cape Reinga,445)
DescriptionAndDistance(Auckland -> Whangarei -> Kaikohe -> Kerikeri -> Kaitaia -> Cape Reinga,478)
DescriptionAndDistance(Auckland -> Whangarei -> Kerikeri -> Kaikohe -> Kaitaia -> Cape Reinga,472)
DescriptionAndDistance(Auckland -> Dargaville -> Kaikohe -> Kerikeri -> Kaitaia -> Cape Reinga,487)
DescriptionAndDistance(Auckland -> Dargaville -> Kaikohe -> Whangarei -> Kerikeri -> Kaitaia -> Cape Reinga,621)
You can find the source code and everything to run it yourself here.