Arango logo

ArangoDB v4.x is under development and not released yet.

This documentation is not final and potentially incomplete.

k Shortest Paths in AQL

Find a number of shortest paths in the order of increasing path length or weight

General query idea

This type of query finds the first k paths in order of length (or weight) between two given documents (startNode and endNode) in your graph.

Every such path is returned as a JSON object with three components:

  • an array containing the vertices on the path
  • an array containing the edges on the path
  • the weight of the path, that is the sum of all edge weights

If no weightAttribute is specified, the weight of the path is just its length.

Example

Here is an example graph to explain how the k Shortest Paths algorithm works:

Train Connection Map

Each ellipse stands for a train station with the name of the city written inside of it. They are the nodes of the graph. Arrows represent train connections between cities and are the edges of the graph. The numbers near the arrows describe how long it takes to get from one station to another. They are used as edge weights.

Let us assume that you want to go from Aberdeen to London by train.

You expect to see the following nodes on the shortest path, in this order:

  1. Aberdeen
  2. Leuchars
  3. Edinburgh
  4. York
  5. London

By the way, the weight of the path is: 1.5 + 1.5 + 3.5 + 1.8 = 8.3.

Let us look at alternative paths next, for example because you know that the direct connection between York and London does not operate currently. An alternative path, which is slightly longer, goes like this:

  1. Aberdeen
  2. Leuchars
  3. Edinburgh
  4. York
  5. Carlisle
  6. Birmingham
  7. London

Its weight is: 1.5 + 1.5 + 3.5 + 2.0 + 1.5 = 10.0.

Another route goes via Glasgow. There are seven stations on the path as well, however, it is quicker if you compare the edge weights:

  1. Aberdeen
  2. Leuchars
  3. Edinburgh
  4. Glasgow
  5. Carlisle
  6. Birmingham
  7. London

The path weight is lower: 1.5 + 1.5 + 1.0 + 1.0 + 2.0 + 1.5 = 8.5.

Syntax

The syntax for k Shortest Paths queries is similar to the one for Shortest Path and there are also two options to either use a named graph or a set of edge collections. It only emits a path variable however, whereas SHORTEST_PATH emits a node and an edge variable.

It is highly recommended that you use a LIMIT statement, as k Shortest Paths is a potentially expensive operation. On large connected graphs it can return a large number of paths, or perform an expensive (but unsuccessful) search for more short paths.

Working with named graphs

FOR path
  IN OUTBOUND|INBOUND|ANY K_SHORTEST_PATHS
  startNode TO endNode
  GRAPH graphName
  [OPTIONS options]
  [LIMIT offset, count]
  • FOR: Emits the variable path which contains one path as an object containing vertices (nodes), edges, and the weight of the path.
  • IN OUTBOUND|INBOUND|ANY: Defines in which direction edges are followed (outgoing, incoming, or both).
  • K_SHORTEST_PATHS: The keyword to compute k Shortest Paths
  • startNode TO endNode (both string|object): The two nodes between which the paths are computed. This can be specified in the form of a ID string or in the form of a document with the attribute _id. All other values lead to a warning and an empty result. If one of the specified documents does not exist, the result is empty as well and there is no warning.
  • GRAPH graphName (string): The name identifying the named graph. Its node and edge collections are looked up by the path search.
  • OPTIONS options (object, optional): See the path search options.
  • LIMIT (see LIMIT operation, optional): the maximal number of paths to return. It is highly recommended to use a LIMIT for K_SHORTEST_PATHS.
k Shortest Paths traversals do not support negative weights. If a document attribute (as specified by weightAttribute) with a negative value is encountered during traversal, or if defaultWeight is set to a negative number, then the query is aborted with an error.

Working with collection sets

FOR path
  IN OUTBOUND|INBOUND|ANY K_SHORTEST_PATHS
  startNode TO endNode
  edgeCollection1, ..., edgeCollectionN
  [OPTIONS options]
  [LIMIT offset, count]

Instead of GRAPH graphName you can specify a list of edge collections. The involved node collections are determined by the edges of the given edge collections.

Path search options

You can optionally specify the following options to modify the execution of a graph path search. If you specify unknown options, query warnings are raised.

weightAttribute

The edge attribute to use as the weight (string|array):

  • A string refers to a top-level attribute of exactly this name. A . is interpreted as a literal dot and not as a separator for an attribute path. For example, "attr.sub" reads the weight from an edge document like { "attr.sub": 5 }.

  • An array of strings describes an attribute path, letting you use a sub-attribute as the edge weight. Each element is one level of nesting, for example ["attr", "sub"] to read the weight from an edge document like { "attr": { "sub": 3 } }.

    An array with a single element is equivalent to passing that element as a string. ["attr.sub"] therefore refers to the top-level attribute attr.sub just like "attr.sub" does.

If the value is neither a string nor an array of strings, a query warning is raised and the option is ignored, which means the defaultWeight is used as the weight of every edge. An empty string or an empty array has the same effect but raises no warning.

For example, consider edge documents with both a nested sub attribute and a top-level attribute whose name contains a dot:

{
  "attr": { "sub": 3 },
  "attr.sub": 5
}
weightAttributeResulting edge weight
["attr", "sub"]3
["attr.sub"]5
"attr.sub"5
FOR p IN OUTBOUND K_SHORTEST_PATHS startNode TO endNode GRAPH "graphName"
  OPTIONS { weightAttribute: ["attr", "sub"] }
  LIMIT 3
  RETURN p.weight

If the attribute path cannot be resolved in the edge document or the value it refers to is not numeric, the defaultWeight is used instead.

The attribute value must not be negative.

defaultWeight

This value is used as fallback if there is no weightAttribute in the edge document, or if it’s not a number (number).

The value must not be negative. The default is 1.

useCache

Introduced in: v3.12.2

Whether to use the in-memory cache for edges. The default is true.

You can set this option to false to not make a large graph operation pollute the edge cache.

Traversing in mixed directions

For k shortest paths with a list of edge collections you can optionally specify the direction for some of the edge collections. Say for example you have three edge collections edges1, edges2 and edges3, where in edges2 the direction has no relevance, but in edges1 and edges3 the direction should be taken into account. In this case you can use OUTBOUND as general search direction and ANY specifically for edges2 as follows:

FOR node IN OUTBOUND K_SHORTEST_PATHS
  startNode TO endNode
  edges1, ANY edges2, edges3

All collections in the list that do not specify their own direction use the direction defined after IN (here: OUTBOUND). This allows to use a different direction for each collection in your path search.

Graph path searches in a cluster

Due to the nature of graphs, edges may reference nodes from arbitrary collections. Following the paths can thus involve documents from various collections and it is not possible to predict which are visited in a path search - unless you use named graphs that define all node and edge collections that belong to them and the graph data is consistent.

If you use anonymous graphs / collection sets for graph queries, which node collections need to be loaded by the graph engine can be deduced automatically if there is a named graph with a matching edge collection in its edge definitions (introduced in v3.12.6). Edge collections are always declared explicitly in queries, directly or via referencing a named graph.

Without a named graph, the involved node collections can only be determined at run time. Use the WITH operation to declare the node collections upfront. This is required for path searches using collection sets in cluster deployments (if there is no named graph to deduce the node collections from). Declare the collection of the start node as well if it’s not declared already (like by a FOR loop).

For example, suppose you have two node collections, person and movie, and an acts_in edge collection that connects them. If you want to run a path search query that starts (and ends) at a person that you specify with its document ID, you need to declare both node collections at the beginning of the query:

WITH person, movie
FOR p IN ANY K_SHORTEST_PATHS "person/1544" TO "person/52560" acts_in
  LIMIT 2
  RETURN p.vertices[*].label

However, if there is a named graph that includes an edge definition for the acts_in edge collection, with person as the from collection and movie as the to collection, you can omit WITH person, movie. That is, if you specify acts_in as an edge collection in an anonymous graph query, all named graphs are checked for this edge collection, and if there is a matching edge definition, its node collections are automatically added as data sources to the query.

FOR p IN ANY K_SHORTEST_PATHS "person/1544" TO "person/52560" acts_in
  LIMIT 2
  RETURN p.vertices[*].label

// Chris Rock --> Dogma <-- Ben Affleck --> Surviving Christmas <-- Jennifer Morrison
// Chris Rock --> The Longest Yard <-- Rob Schneider --> Big Stan <-- Jennifer Morrison

You can still declare collections manually, in which case they are added as data sources in addition to automatically deduced collections.

Examples

You can load the kShortestPathsGraph example graph to get a named graph that reflects some possible train connections in Europe and North America.

Train Connection Map

var examples = require("@arangodb/graph-examples/example-graph");
var graph = examples.loadGraph("kShortestPathsGraph");
db.places.toArray();
db.connections.toArray();
Show output
[ 
  { 
    "_key" : "Inverness", 
    "_id" : "places/Inverness", 
    "_rev" : "_hg5kISW---", 
    "label" : "Inverness" 
  }, 
  { 
    "_key" : "Aberdeen", 
    "_id" : "places/Aberdeen", 
    "_rev" : "_hg5kISW--_", 
    "label" : "Aberdeen" 
  }, 
  { 
    "_key" : "Leuchars", 
    "_id" : "places/Leuchars", 
    "_rev" : "_hg5kISa---", 
    "label" : "Leuchars" 
  }, 
  { 
    "_key" : "StAndrews", 
    "_id" : "places/StAndrews", 
    "_rev" : "_hg5kISe---", 
    "label" : "StAndrews" 
  }, 
  { 
    "_key" : "Edinburgh", 
    "_id" : "places/Edinburgh", 
    "_rev" : "_hg5kISe--_", 
    "label" : "Edinburgh" 
  }, 
  { 
    "_key" : "Glasgow", 
    "_id" : "places/Glasgow", 
    "_rev" : "_hg5kISi---", 
    "label" : "Glasgow" 
  }, 
  { 
    "_key" : "York", 
    "_id" : "places/York", 
    "_rev" : "_hg5kISi--_", 
    "label" : "York" 
  }, 
  { 
    "_key" : "Carlisle", 
    "_id" : "places/Carlisle", 
    "_rev" : "_hg5kISi--A", 
    "label" : "Carlisle" 
  }, 
  { 
    "_key" : "Birmingham", 
    "_id" : "places/Birmingham", 
    "_rev" : "_hg5kISi--B", 
    "label" : "Birmingham" 
  }, 
  { 
    "_key" : "London", 
    "_id" : "places/London", 
    "_rev" : "_hg5kISm---", 
    "label" : "London" 
  }, 
  { 
    "_key" : "Brussels", 
    "_id" : "places/Brussels", 
    "_rev" : "_hg5kISm--_", 
    "label" : "Brussels" 
  }, 
  { 
    "_key" : "Cologne", 
    "_id" : "places/Cologne", 
    "_rev" : "_hg5kISm--A", 
    "label" : "Cologne" 
  }, 
  { 
    "_key" : "Toronto", 
    "_id" : "places/Toronto", 
    "_rev" : "_hg5kISm--B", 
    "label" : "Toronto" 
  }, 
  { 
    "_key" : "Winnipeg", 
    "_id" : "places/Winnipeg", 
    "_rev" : "_hg5kISm--C", 
    "label" : "Winnipeg" 
  }, 
  { 
    "_key" : "Saskatoon", 
    "_id" : "places/Saskatoon", 
    "_rev" : "_hg5kISm--D", 
    "label" : "Saskatoon" 
  }, 
  { 
    "_key" : "Edmonton", 
    "_id" : "places/Edmonton", 
    "_rev" : "_hg5kISq---", 
    "label" : "Edmonton" 
  }, 
  { 
    "_key" : "Jasper", 
    "_id" : "places/Jasper", 
    "_rev" : "_hg5kISq--_", 
    "label" : "Jasper" 
  }, 
  { 
    "_key" : "Vancouver", 
    "_id" : "places/Vancouver", 
    "_rev" : "_hg5kISq--A", 
    "label" : "Vancouver" 
  } 
]

[ 
  { 
    "_key" : "62300", 
    "_id" : "connections/62300", 
    "_from" : "places/Inverness", 
    "_to" : "places/Aberdeen", 
    "_rev" : "_hg5kISq--B", 
    "travelTime" : 3 
  }, 
  { 
    "_key" : "62302", 
    "_id" : "connections/62302", 
    "_from" : "places/Aberdeen", 
    "_to" : "places/Inverness", 
    "_rev" : "_hg5kISq--C", 
    "travelTime" : 2.5 
  }, 
  { 
    "_key" : "62304", 
    "_id" : "connections/62304", 
    "_from" : "places/Aberdeen", 
    "_to" : "places/Leuchars", 
    "_rev" : "_hg5kISq--D", 
    "travelTime" : 1.5 
  }, 
  { 
    "_key" : "62306", 
    "_id" : "connections/62306", 
    "_from" : "places/Leuchars", 
    "_to" : "places/Aberdeen", 
    "_rev" : "_hg5kISu---", 
    "travelTime" : 1 
  }, 
  { 
    "_key" : "62308", 
    "_id" : "connections/62308", 
    "_from" : "places/Leuchars", 
    "_to" : "places/Edinburgh", 
    "_rev" : "_hg5kISu--_", 
    "travelTime" : 1.5 
  }, 
  { 
    "_key" : "62310", 
    "_id" : "connections/62310", 
    "_from" : "places/Edinburgh", 
    "_to" : "places/Leuchars", 
    "_rev" : "_hg5kISu--A", 
    "travelTime" : 3 
  }, 
  { 
    "_key" : "62312", 
    "_id" : "connections/62312", 
    "_from" : "places/Edinburgh", 
    "_to" : "places/Glasgow", 
    "_rev" : "_hg5kISu--B", 
    "travelTime" : 1 
  }, 
  { 
    "_key" : "62314", 
    "_id" : "connections/62314", 
    "_from" : "places/Glasgow", 
    "_to" : "places/Edinburgh", 
    "_rev" : "_hg5kISu--C", 
    "travelTime" : 1 
  }, 
  { 
    "_key" : "62316", 
    "_id" : "connections/62316", 
    "_from" : "places/Edinburgh", 
    "_to" : "places/York", 
    "_rev" : "_hg5kISu--D", 
    "travelTime" : 3.5 
  }, 
  { 
    "_key" : "62318", 
    "_id" : "connections/62318", 
    "_from" : "places/York", 
    "_to" : "places/Edinburgh", 
    "_rev" : "_hg5kISu--E", 
    "travelTime" : 4 
  }, 
  { 
    "_key" : "62320", 
    "_id" : "connections/62320", 
    "_from" : "places/Glasgow", 
    "_to" : "places/Carlisle", 
    "_rev" : "_hg5kISy---", 
    "travelTime" : 1 
  }, 
  { 
    "_key" : "62322", 
    "_id" : "connections/62322", 
    "_from" : "places/Carlisle", 
    "_to" : "places/Glasgow", 
    "_rev" : "_hg5kISy--_", 
    "travelTime" : 1 
  }, 
  { 
    "_key" : "62324", 
    "_id" : "connections/62324", 
    "_from" : "places/Carlisle", 
    "_to" : "places/York", 
    "_rev" : "_hg5kISy--A", 
    "travelTime" : 2.5 
  }, 
  { 
    "_key" : "62326", 
    "_id" : "connections/62326", 
    "_from" : "places/York", 
    "_to" : "places/Carlisle", 
    "_rev" : "_hg5kISy--B", 
    "travelTime" : 3.5 
  }, 
  { 
    "_key" : "62328", 
    "_id" : "connections/62328", 
    "_from" : "places/Carlisle", 
    "_to" : "places/Birmingham", 
    "_rev" : "_hg5kISy--C", 
    "travelTime" : 2 
  }, 
  { 
    "_key" : "62330", 
    "_id" : "connections/62330", 
    "_from" : "places/Birmingham", 
    "_to" : "places/Carlisle", 
    "_rev" : "_hg5kISy--D", 
    "travelTime" : 1 
  }, 
  { 
    "_key" : "62332", 
    "_id" : "connections/62332", 
    "_from" : "places/Birmingham", 
    "_to" : "places/London", 
    "_rev" : "_hg5kISy--E", 
    "travelTime" : 1.5 
  }, 
  { 
    "_key" : "62334", 
    "_id" : "connections/62334", 
    "_from" : "places/London", 
    "_to" : "places/Birmingham", 
    "_rev" : "_hg5kISy--F", 
    "travelTime" : 2.5 
  }, 
  { 
    "_key" : "62336", 
    "_id" : "connections/62336", 
    "_from" : "places/Leuchars", 
    "_to" : "places/StAndrews", 
    "_rev" : "_hg5kIS2---", 
    "travelTime" : 0.2 
  }, 
  { 
    "_key" : "62338", 
    "_id" : "connections/62338", 
    "_from" : "places/StAndrews", 
    "_to" : "places/Leuchars", 
    "_rev" : "_hg5kIS2--_", 
    "travelTime" : 0.2 
  }, 
  { 
    "_key" : "62340", 
    "_id" : "connections/62340", 
    "_from" : "places/York", 
    "_to" : "places/London", 
    "_rev" : "_hg5kIS2--A", 
    "travelTime" : 1.8 
  }, 
  { 
    "_key" : "62342", 
    "_id" : "connections/62342", 
    "_from" : "places/London", 
    "_to" : "places/York", 
    "_rev" : "_hg5kIS2--B", 
    "travelTime" : 2 
  }, 
  { 
    "_key" : "62344", 
    "_id" : "connections/62344", 
    "_from" : "places/London", 
    "_to" : "places/Brussels", 
    "_rev" : "_hg5kIS2--C", 
    "travelTime" : 2.5 
  }, 
  { 
    "_key" : "62346", 
    "_id" : "connections/62346", 
    "_from" : "places/Brussels", 
    "_to" : "places/London", 
    "_rev" : "_hg5kIS6---", 
    "travelTime" : 3.5 
  }, 
  { 
    "_key" : "62348", 
    "_id" : "connections/62348", 
    "_from" : "places/Brussels", 
    "_to" : "places/Cologne", 
    "_rev" : "_hg5kIS6--_", 
    "travelTime" : 2 
  }, 
  { 
    "_key" : "62350", 
    "_id" : "connections/62350", 
    "_from" : "places/Cologne", 
    "_to" : "places/Brussels", 
    "_rev" : "_hg5kIS6--A", 
    "travelTime" : 1.5 
  }, 
  { 
    "_key" : "62352", 
    "_id" : "connections/62352", 
    "_from" : "places/Toronto", 
    "_to" : "places/Winnipeg", 
    "_rev" : "_hg5kIS6--B", 
    "travelTime" : 36 
  }, 
  { 
    "_key" : "62354", 
    "_id" : "connections/62354", 
    "_from" : "places/Winnipeg", 
    "_to" : "places/Toronto", 
    "_rev" : "_hg5kIS6--C", 
    "travelTime" : 35 
  }, 
  { 
    "_key" : "62356", 
    "_id" : "connections/62356", 
    "_from" : "places/Winnipeg", 
    "_to" : "places/Saskatoon", 
    "_rev" : "_hg5kIS6--D", 
    "travelTime" : 12 
  }, 
  { 
    "_key" : "62358", 
    "_id" : "connections/62358", 
    "_from" : "places/Saskatoon", 
    "_to" : "places/Winnipeg", 
    "_rev" : "_hg5kIS6--E", 
    "travelTime" : 5 
  }, 
  { 
    "_key" : "62360", 
    "_id" : "connections/62360", 
    "_from" : "places/Saskatoon", 
    "_to" : "places/Edmonton", 
    "_rev" : "_hg5kIT----", 
    "travelTime" : 12 
  }, 
  { 
    "_key" : "62362", 
    "_id" : "connections/62362", 
    "_from" : "places/Edmonton", 
    "_to" : "places/Saskatoon", 
    "_rev" : "_hg5kIT---_", 
    "travelTime" : 17 
  }, 
  { 
    "_key" : "62364", 
    "_id" : "connections/62364", 
    "_from" : "places/Edmonton", 
    "_to" : "places/Jasper", 
    "_rev" : "_hg5kIT---A", 
    "travelTime" : 6 
  }, 
  { 
    "_key" : "62366", 
    "_id" : "connections/62366", 
    "_from" : "places/Jasper", 
    "_to" : "places/Edmonton", 
    "_rev" : "_hg5kIT---B", 
    "travelTime" : 5 
  }, 
  { 
    "_key" : "62368", 
    "_id" : "connections/62368", 
    "_from" : "places/Jasper", 
    "_to" : "places/Vancouver", 
    "_rev" : "_hg5kIT---C", 
    "travelTime" : 12 
  }, 
  { 
    "_key" : "62370", 
    "_id" : "connections/62370", 
    "_from" : "places/Vancouver", 
    "_to" : "places/Jasper", 
    "_rev" : "_hg5kIT---D", 
    "travelTime" : 13 
  } 
]

Suppose you want to query a route from Aberdeen to London, and compare the outputs of SHORTEST_PATH and K_SHORTEST_PATHS with LIMIT 1. Note that while SHORTEST_PATH and K_SHORTEST_PATH with LIMIT 1 should return a path of the same length (or weight), they do not need to return the same path.

Using SHORTEST_PATH:

FOR v, e IN OUTBOUND SHORTEST_PATH 'places/Aberdeen' TO 'places/London'
GRAPH 'kShortestPathsGraph'
    RETURN { place: v.label, travelTime: e.travelTime }
Show output
[ 
  { 
    "place" : "Aberdeen", 
    "travelTime" : null 
  }, 
  { 
    "place" : "Leuchars", 
    "travelTime" : 1.5 
  }, 
  { 
    "place" : "Edinburgh", 
    "travelTime" : 1.5 
  }, 
  { 
    "place" : "York", 
    "travelTime" : 3.5 
  }, 
  { 
    "place" : "London", 
    "travelTime" : 1.8 
  } 
]

Using K_SHORTEST_PATHS:

FOR p IN OUTBOUND K_SHORTEST_PATHS 'places/Aberdeen' TO 'places/London'
GRAPH 'kShortestPathsGraph'
    LIMIT 1
    RETURN { places: p.vertices[*].label, travelTimes: p.edges[*].travelTime }
Show output
[ 
  { 
    "places" : [ 
      "Aberdeen", 
      "Leuchars", 
      "Edinburgh", 
      "York", 
      "London" 
    ], 
    "travelTimes" : [ 
      1.5, 
      1.5, 
      3.5, 
      1.8 
    ] 
  } 
]

With K_SHORTEST_PATHS, you can ask for more than one option for a route:

FOR p IN OUTBOUND K_SHORTEST_PATHS 'places/Aberdeen' TO 'places/London'
GRAPH 'kShortestPathsGraph'
    LIMIT 3
    RETURN {
        places: p.vertices[*].label,
        travelTimes: p.edges[*].travelTime,
        travelTimeTotal: SUM(p.edges[*].travelTime)
    }
Show output
[ 
  { 
    "places" : [ 
      "Aberdeen", 
      "Leuchars", 
      "Edinburgh", 
      "York", 
      "London" 
    ], 
    "travelTimes" : [ 
      1.5, 
      1.5, 
      3.5, 
      1.8 
    ], 
    "travelTimeTotal" : 8.3 
  }, 
  { 
    "places" : [ 
      "Aberdeen", 
      "Leuchars", 
      "Edinburgh", 
      "Glasgow", 
      "Carlisle", 
      "Birmingham", 
      "London" 
    ], 
    "travelTimes" : [ 
      1.5, 
      1.5, 
      1, 
      1, 
      2, 
      1.5 
    ], 
    "travelTimeTotal" : 8.5 
  }, 
  { 
    "places" : [ 
      "Aberdeen", 
      "Leuchars", 
      "Edinburgh", 
      "Glasgow", 
      "Carlisle", 
      "York", 
      "London" 
    ], 
    "travelTimes" : [ 
      1.5, 
      1.5, 
      1, 
      1, 
      2.5, 
      1.8 
    ], 
    "travelTimeTotal" : 9.3 
  } 
]

If you ask for routes that don’t exist, you get an empty result (from Aberdeen to Toronto):

FOR p IN OUTBOUND K_SHORTEST_PATHS 'places/Aberdeen' TO 'places/Toronto'
GRAPH 'kShortestPathsGraph'
    LIMIT 3
    RETURN {
        places: p.vertices[*].label,
        travelTimes: p.edges[*].travelTime,
        travelTimeTotal: SUM(p.edges[*].travelTime)
    }
Show output
[ ]

You can use the travelTime attribute that connections have as edge weights to take into account which connections are quicker. A high default weight is set, to be used if an edge has no travelTime attribute (not the case with the example graph). This returns the top three routes with the fewest changes and favoring the least travel time for the connection Saint Andrews to Cologne:

FOR p IN OUTBOUND K_SHORTEST_PATHS 'places/StAndrews' TO 'places/Cologne'
GRAPH 'kShortestPathsGraph'
OPTIONS {
    weightAttribute: 'travelTime',
    defaultWeight: 15
}
    LIMIT 3
    RETURN {
        places: p.vertices[*].label,
        travelTimes: p.edges[*].travelTime,
        travelTimeTotal: SUM(p.edges[*].travelTime)
    }
Show output
[ 
  { 
    "places" : [ 
      "StAndrews", 
      "Leuchars", 
      "Edinburgh", 
      "York", 
      "London", 
      "Brussels", 
      "Cologne" 
    ], 
    "travelTimes" : [ 
      0.2, 
      1.5, 
      3.5, 
      1.8, 
      2.5, 
      2 
    ], 
    "travelTimeTotal" : 11.5 
  }, 
  { 
    "places" : [ 
      "StAndrews", 
      "Leuchars", 
      "Edinburgh", 
      "Glasgow", 
      "Carlisle", 
      "Birmingham", 
      "London", 
      "Brussels", 
      "Cologne" 
    ], 
    "travelTimes" : [ 
      0.2, 
      1.5, 
      1, 
      1, 
      2, 
      1.5, 
      2.5, 
      2 
    ], 
    "travelTimeTotal" : 11.7 
  }, 
  { 
    "places" : [ 
      "StAndrews", 
      "Leuchars", 
      "Edinburgh", 
      "Glasgow", 
      "Carlisle", 
      "York", 
      "London", 
      "Brussels", 
      "Cologne" 
    ], 
    "travelTimes" : [ 
      0.2, 
      1.5, 
      1, 
      1, 
      2.5, 
      1.8, 
      2.5, 
      2 
    ], 
    "travelTimeTotal" : 12.5 
  } 
]

And finally clean up by removing the named graph:

var examples = require("@arangodb/graph-examples/example-graph");
examples.dropGraph("kShortestPathsGraph");
Show output
Empty Output