39 KiB
Traversals
@NAVIGATE_Traversals @EMBEDTOC{TraversalsTOC}
Introduction
ArangoDB provides several ways to query graph data. Very simple operations can
be composed with the low-level edge methods edges
, inEdges
, and outEdges
for
edge collections (see @ref HandlingEdgesShell). For more complex operations,
ArangoDB provides predefined traversal objects.
For any of the following examples, we'll be using the example collections v
and e
,
populated with continents, countries and capitals data listed below (see @ref TraversalsExampleData).
Starting from Scratch
ArangoDB provides the edges
, inEdges
, and outEdges
methods for edge collections.
These methods can be used to quickly determine if a vertex is connected to other vertices,
and which.
This functionality can be exploited to write very simple graph queries in JavaScript.
For example, to determine which edges are linked to the world
vertex, we can use inEdges
:
db.e.inEdges('v/world').forEach(function(edge) {
require("internal").print(edge._from, "->", edge.type, "->", edge._to);
});
inEdges
will give us all ingoing edges for the specified vertex v/world
. The result
is a JavaScript list, that we can iterate over and print the results:
v/continent-africa -> is-in -> v/world
v/continent-south-america -> is-in -> v/world
v/continent-asia -> is-in -> v/world
v/continent-australia -> is-in -> v/world
v/continent-europe -> is-in -> v/world
v/continent-north-america -> is-in -> v/world
Note that edges
, inEdges
, and outEdges
return a list of edges. If we want to retrieve
the linked vertices, we can use each edges' _from
and _to
attributes as follows:
db.e.inEdges('v/world').forEach(function(edge) {
require("internal").print(db._document(edge._from).name, "->", edge.type, "->", db._document(edge._to).name);
});
We are using the document
method from the db
object to retrieve the connected vertices now.
While this may be sufficient for one-level graph operations, writing a traversal by hand may become too complex for multi-level traversals.
Using Traversal Objects
ArangoDB provides predefined traversal objects that can be used to run a multi-level traversal on a graph. To use the traversal objects, it is required that all edges of the graph are stored in a single edge collection.
Getting started
To use a traversal object, we first need to require the traversal
module:
var traversal = require("org/arangodb/graph/traversal");
We then need to setup a configuration for the traversal and determine at which vertex to start the traversal:
var config = {
datasource: traversal.collectionDatasourceFactory(db.e),
strategy: "depthfirst",
order: "preorder",
filter: traversal.visitAllFilter,
expander: traversal.inboundExpander,
maxDepth: 1
};
var startVertex = db._document("v/world");
Note that the startVertex needs to be a document, not only a document id.
We can then create a traverser and start the traversal by calling its traverse
method.
Note that traverse
needs a result
object, which it can modify in place:
var result = {
visited: {
vertices: [ ],
paths: [ ]
}
};
var traverser = new traversal.Traverser(config);
traverser.traverse(result, startVertex);
Finally, we can print the contents of the results
object, limited to the visited vertices.
We will only print the name and type of each visited vertex for brevity:
require("internal").print(result.visited.vertices.map(function(vertex) {
return vertex.name + " (" + vertex.type + ")";
}));
The full script, which includes all steps carried out so far is thus:
var traversal = require("org/arangodb/graph/traversal");
var config = {
datasource: traversal.collectionDatasourceFactory(db.e),
strategy: "depthfirst",
order: "preorder",
filter: traversal.visitAllFilter,
expander: traversal.inboundExpander,
maxDepth: 1
};
var startVertex = db._document("v/world");
var result = {
visited: {
vertices: [ ],
paths: [ ]
}
};
var traverser = new traversal.Traverser(config);
traverser.traverse(result, startVertex);
require("internal").print(result.visited.vertices.map(function(vertex) {
return vertex.name + " (" + vertex.type + ")";
}));
The result is a list of vertices that were visited during the traversal, starting at the
start vertex (i.e. v/world
in our example):
[
"World (root)",
"Africa (continent)",
"Asia (continent)",
"Australia (continent)",
"Europe (continent)",
"North America (continent)",
"South America (continent)"
]
Note that the result is limited to vertices directly connected to the start vertex. We
achieved this by setting the maxDepth
attribute to 1
. Not setting it would return the
full list of vertices.
Traversal Direction
For the examples contained in this manual, we'll be starting the traversals at vertex
v/world
. Vertices in our graph are connected like this:
v/world <- is-in <- continent (Africa) <- is-in <- country (Algeria) <- is-in <- capital (Algiers)
To get any meaningful results, we must traverse the graph in inbound order. This means,
we'll be following all incoming edges of to a vertex. In the traversal configuration, we
have specified this via the expander
attribute:
var config = {
...
expander: traversal.inboundExpander
};
For other graphs, we might want to traverse via the outgoing edges. For this, we can
use the outboundExpander
. There is also an anyExpander
, which will follow both outgoing
and incoming edges. This should be used with care and the traversal should always be
limited to a maximum number of iterations (e.g. using the maxIterations
attribute) in
order to terminate at some point.
To invoke the default outbound expander for a graph, simply use the predefined function:
var config = {
...
expander: traversal.outboundExpander
};
Please note the outbound expander will not produce any output for the examples if we still
start the traversal at the v/world
vertex.
Still, we can use the outbound expander if we start somewhere else in the graph, e.g.
var traversal = require("org/arangodb/graph/traversal");
var config = {
datasource: traversal.collectionDatasourceFactory(db.e),
strategy: "depthfirst",
order: "preorder",
filter: traversal.visitAllFilter,
expander: traversal.outboundExpander
};
var startVertex = db._document("v/capital-algiers");
var result = {
visited: {
vertices: [ ],
paths: [ ]
}
};
var traverser = new traversal.Traverser(config);
traverser.traverse(result, startVertex);
require("internal").print(result.visited.vertices.map(function(vertex) {
return vertex.name + " (" + vertex.type + ")";
}));
The result is:
[
"Algiers (capital)",
"Algeria (country)",
"Africa (continent)",
"World (root)"
]
which confirms that now we're going outbound.
Traversal Strategy
###Depth-first traversals
The visitation order of vertices is determined by the strategy
, order
attributes set
in the configuration. We chose depthfirst
and preorder
, meaning the traverser will
emit each vertex before handling connected edges (pre-order), and descend into any
connected edges before processing other vertices on the same level (depth-first).
Let's remove the maxDepth
attribute now. We'll now be getting all vertices (directly
and indirectly connected to the start vertex):
var config = {
datasource: traversal.collectionDatasourceFactory(db.e),
strategy: "depthfirst",
order: "preorder",
filter: traversal.visitAllFilter,
expander: traversal.inboundExpander
};
var result = {
visited: {
vertices: [ ],
paths: [ ]
}
};
var traverser = new traversal.Traverser(config);
traverser.traverse(result, startVertex);
require("internal").print(result.visited.vertices.map(function(vertex) {
return vertex.name + " (" + vertex.type + ")";
}));
The result will be a longer list, assembled in depth-first, pre-order order. For each continent found, the traverser will descend into linked countries, and then into the linked capital:
[
"World (root)",
"Africa (continent)",
"Algeria (country)",
"Algiers (capital)",
"Angola (country)",
"Luanda (capital)",
"Botswana (country)",
"Gaborone (capital)",
"Burkina Faso (country)",
"Ouagadougou (capital)",
...
]
Let's switch the order
attribute from preorder
to postorder
. This will make the
traverser emit vertices after all connected vertices were visited (i.e. most distant
vertices will be emitted first):
[
"Algiers (capital)",
"Algeria (country)",
"Luanda (capital)",
"Angola (country)",
"Gaborone (capital)",
"Botswana (country)",
"Ouagadougou (capital)",
"Burkina Faso (country)",
"Bujumbura (capital)",
"Burundi (country)",
"Yaounde (capital)",
"Cameroon (country)",
"N'Djamena (capital)",
"Chad (country)",
"Yamoussoukro (capital)",
"Cote d'Ivoire (country)",
"Cairo (capital)",
"Egypt (country)",
"Asmara (capital)",
"Eritrea (country)",
"Africa (continent)",
...
]
###Breadth-first traversals
If we go back to preorder
, but change the strategy to breadth-first
and re-run the
traversal, we'll see that the return order changes, and items on the same level will be
returned adjacently:
[
"World (root)",
"Africa (continent)",
"Asia (continent)",
"Australia (continent)",
"Europe (continent)",
"North America (continent)",
"South America (continent)",
"Burkina Faso (country)",
"Burundi (country)",
"Cameroon (country)",
"Chad (country)",
"Algeria (country)",
"Angola (country)",
...
]
Note that the order of items returned for the same level is undefined.
This is because there is no natural order of edges for a vertex with
multiple connected edges. To explicitly set the order for edges on the
same level, you can specify an edge comparator function with the sort
attribute:
var config = {
...
sort: function (l, r) { return l._key < r._key ? 1 : -1; }
...
};
The arguments l and r are edge documents.
This will traverse edges of the same vertex in backward _key
order:
[
"World (root)",
"South America (continent)",
"North America (continent)",
"Europe (continent)",
"Australia (continent)",
"Asia (continent)",
"Africa (continent)",
"Ecuador (country)",
"Colombia (country)",
"Chile (country)",
"Brazil (country)",
"Bolivia (country)",
"Argentina (country)",
...
]
Note that this attribute only works for the usual expanders
traversal.inboundExpander
, traversal.outboundExpander
,
traversal.anyExpander
and their corresponding "WithLabels" variants.
If you are using custom expanders (see @ref TraversalsCustomExpanders)
you have to organise the sorting within the specified expander.
Writing Custom Visitors
So far we have used much of the traverser's default functions. The traverser is very configurable and many of the default functions can be overriden with custom functionality.
For example, we have been using the default visitor function (which is always used if
the configuration does not contain the visitor
attribute). The default visitor function
is called for each vertex in a traversal, and will push it into the result.
This is the reason why the result
variable looked different after the traversal, and
needed to be initialised before the traversal was started.
We can write our own visitor function if we want to. The general function signature for visitor function is as follows:
var config = {
...
visitor: function (config, result, vertex, path) { ... }
};
Visitor functions are not expected to return any values. Instead, they can modify the
result
variable (e.g. by pushing the current vertex into it), or do anything else.
For example, we can create a simple visitor function that only prints information about
the current vertex as we traverse:
var config = {
datasource: traversal.collectionDatasourceFactory(db.e),
strategy: "depthfirst",
order: "preorder",
filter: traversal.visitAllFilter,
expander: traversal.inboundExpander,
visitor: function (config, result, vertex, path) {
require("internal").print("visiting vertex", vertex.name);
}
};
var traverser = new traversal.Traverser(config);
traverser.traverse(undefined, startVertex);
Filtering Vertices and Edges
Filtering Vertices
So far we have returned all vertices that were visited during the traversal. This is not
always required. If the result shall be restrict to just specific vertices, we can use a
filter function for vertices. It can be defined by setting the filter
attribute of a
traversal configuration, e.g.:
var config = {
filter: function (config, vertex, path) {
if (vertex.type !== 'capital') {
return 'exclude';
}
}
}
The above filter function will exclude all vertices that do not have a type
value of
capital
. The filter function will be called for each vertex found during the traversal.
It will receive the traversal configuration, the current vertex, and the full path from
the traversal start vertex to the current vertex. The path consists of a list of edges,
and a list of vertices. We could also filter everything but capitals by checking the
length of the path from the start vertex to the current vertex. Capitals will have a
distance of 3 from the v/world
start vertex
(capital -> is-in -> country -> is-in -> continent -> is-in -> world):
var config = {
...
filter: function (config, vertex, path) {
if (path.edges.length < 3) {
return 'exclude';
}
}
}
Note that if a filter function returns nothing (or undefined
), the current vertex
will be included, and all connected edges will be followed. If a filter function
returns exclude
the current vertex will be excluded from the result, and all still
all connected edges will be followed. If a filter function returns prune
, the
current vertex will be included, but no connected edges will be followed.
For example, the following filter function will not descend into connected edges of continents, limiting the depth of the traversal. Still, continent vertices will be included in the result:
var config = {
...
filter: function (config, vertex, path) {
if (vertex.type === 'continent') {
return 'prune';
}
}
}
It is also possible to combine exclude
and prune
by returning a list with both
values:
return [ 'exclude', 'prune' ];
Filtering Edges
It is possible to exclude certain edges from the traversal. To filter on edges, a
filter function can be defined via the expandFilter
attribute. The expandFilter
is a function which is called for each edge during a traversal.
It will receive the current edge (edge
variable) and the vertex which the edge
connects to (in the direction of the traversal). It also receives the current path
from the start vertex up to the current vertex (excluding the current edge and the
vertex the edge points to).
If the function returns true
, the edge will be followed. If the function returns
false
, the edge will not be followed.
Here is a very simple custom edge filter function implementation, which simply
includes edges if the (edges) path length is less than 1, and will exclude any
other edges. This will effectively terminate the traversal after the first level
of edges:
var config = {
...
expandFilter: function (config, vertex, edge, path) {
return (path.edges.length < 1);
}
};
Writing Custom Expanders
The edges connected to a vertex are determined by the expander. So far we have used a default expander (the default inbound expander to be precise). The default inbound expander simply enumerates all connected ingoing edges for a vertex, based on the edge collection specified in the traversal configuration.
There is also a default outbound expander, which will enumerate all connected outgoing edges. Finally, there is an anyexpander, which will follow both ingoing and outgoing edges.
If connected edges must be determined in some different fashion for whatever reason, a
custom expander can be written and registered by setting the expander
attribute of the
configuration. The expander function signature is as follows:
var config = {
...
expander: function (config, vertex, path) { ... }
}
It is the expander's responsibility to return all edges and vertices directly
connected to the current vertex (which is passed via the vertex
variable).
The full path from the start vertex up to the current vertex is also supplied via
the path
variable.
An expander is expected to return a list of objects, which need to have an edge
and a vertex
attribute each.
Note that if you want to rely on a particular order in which the edges are traversed, you have to sort the edges returned by your expander within the code of the expander. The functions to get outbound, inbound or any edges from a vertex do not guarantee any particular order!
A custom implementation of an inbound expander could look like this (this is a non-deterministic expander, which randomly decides whether or not to include connected edges):
var config = {
...
expander: function (config, vertex, path) {
var connections = [ ];
db.e.inEdges(vertex._id).forEach(function (edge) {
if (Math.random() >= 0.5) {
connections.push({ edge: edge, vertex: db._document(edge._from) });
}
});
return connections;
}
};
A custom expander can also be used as an edge filter because it has full control over which edges will be returned.
Following are two examples of custom expanders that pick edges based on attributes of the edges and the connected vertices.
Finding the connected edges / vertices based on an attribute when
in the
connected vertices. The goal is to follow the edge that leads to the vertex
with the highest value in the when
attribute:
var config = {
...
expander: function (config, vertex, path) {
var datasource = config.datasource;
// determine all outgoing edges
var outEdges = datasource.getOutEdges(vertex);
if (outEdges.length === 0) {
return [ ];
}
var data = [ ];
outEdges.forEach(function (edge) {
data.push({ edge: edge, vertex: datasource.getInVertex(edge) });
});
// sort outgoing vertices according to "when" attribute value
data.sort(function (l, r) {
if (l.vertex.when === r.vertex.when) {
return 0;
}
return (l.vertex.when < r.vertex.when ? 1 : -1);
});
// pick first vertex found (with highest "when" attribute value)
return [ data[0] ];
}
...
};
Finding the connected edges / vertices based on an attribute when
in the
edge itself. The goal is to pick the one edge (out of potentially many) that
has the highest when
attribute value:
var config = {
...
expander: function (config, vertex, path) {
var datasource = config.datasource;
// determine all outgoing edges
var outEdges = datasource.getOutEdges(vertex);
if (outEdges.length === 0) {
return [ ]; // return an empty list
}
// sort all outgoing edges according to "when" attribute
outEdges.sort(function (l, r) {
if (l.when === r.when) {
return 0;
}
return (l.when < r.when ? -1 : 1);
});
// return first edge (the one with highest "when" value)
var edge = outEdges[0];
try {
var v = datasource.getInVertex(edge);
return [ { edge: edge, vertex: v } ];
}
catch (e) { }
return [ ];
}
...
};
Configuration Overview
This section summarizes the configuration attributes for the traversal object. The configuration can consist of the following attributes:
visitor
: visitor function for vertices. The function signature isfunction (config, result, vertex, path)
. This function is not expected to return a value, but may modify thevariable
as needed (e.g. by pushing vertex data into the result).expander
: expander function that is responsible for returning edges and vertices directly connected to a vertex . The function signature isfunction (config, vertex, path)
. The expander function is required to return a list of connection objects, consisting of anedge
andvertex
attribute each.filter
: vertex filter function. The function signature isfunction (config, vertex, path)
. It may return one of the following values:undefined
: vertex will be included in the result and connected edges will be traversedexclude
: vertex will not be included in the result and connected edges will be traversedprune
: vertex will be included in the result but connected edges will not be traversed- [
prune
,exclude
]: vertex will not be included in the result and connected edges will not be returned
expandFilter
: filter function applied on each edge/vertex combination determined by the expander. The function signature isfunction (config, vertex, edge, path)
. The function should returntrue
if the edge/vertex combination should be processed, andfalse
if it should be ignored.sort
: a filter function to determine the order in which connected edges are procssed. The function signature isfunction (l, r)
. The function is required to return one of the following values:-1
ifl
should have a sort value less thanr
1
ifl
should have a higher sort value thanr
0
ifl
andr
have the same sort value
strategy
: determines the visitation strategy. Possible values aredepthfirst
andbreadthfirst
.order
: determines the visitation order. Possible values arepreorder
andpostorder
.itemOrder
: determines the order in which connections returned by the expander will be processed. Possible values areforward
andbackward
.maxDepth
: if set to a value greater than0
, this will limit the traversal to this maximum depth.minDepth
: if set to a value greater than0
, all vertices found on a level below theminDepth
level will not be included in the result.maxIterations
: the maximum number of iterations that the traversal is allowed to perform. It is sensible to set this number so unbounded traversals will terminate at some point.uniqueness
: an object that defines how repeated visitations of vertices should be handled. Theuniqueness
object can have a sub-attributevertices
, and a sub-attributeedges
. Each sub-attribute can have one of the following values:none
: no uniqueness constraintspath
: element is excluded if it is already contained in the current path. This setting may be sensible for graphs that contain cycles (e.g. A -> B -> C -> A).global
: element is excluded if it was already found/visited at any point during the traversal.
Example Data
The above examples all use a vertex collection e
and an edge collection e
. The vertex
collection v
contains continents, countries, and captials. The edge collection e
contains connections between continents and countries, and between countries and captials.
To set up the collections and populate them with initial data, the following script was used:
db._create("v");
db._createEdgeCollection("e");
// vertices: root node
db.v.save({ _key: "world", name: "World", type: "root" });
// vertices: continents
db.v.save({ _key: "continent-africa", name: "Africa", type: "continent" });
db.v.save({ _key: "continent-asia", name: "Asia", type: "continent" });
db.v.save({ _key: "continent-australia", name: "Australia", type: "continent" });
db.v.save({ _key: "continent-europe", name: "Europe", type: "continent" });
db.v.save({ _key: "continent-north-america", name: "North America", type: "continent" });
db.v.save({ _key: "continent-south-america", name: "South America", type: "continent" });
// vertices: countries
db.v.save({ _key: "country-afghanistan", name: "Afghanistan", type: "country", code: "AFG" });
db.v.save({ _key: "country-albania", name: "Albania", type: "country", code: "ALB" });
db.v.save({ _key: "country-algeria", name: "Algeria", type: "country", code: "DZA" });
db.v.save({ _key: "country-andorra", name: "Andorra", type: "country", code: "AND" });
db.v.save({ _key: "country-angola", name: "Angola", type: "country", code: "AGO" });
db.v.save({ _key: "country-antigua-and-barbuda", name: "Antigua and Barbuda", type: "country", code: "ATG" });
db.v.save({ _key: "country-argentina", name: "Argentina", type: "country", code: "ARG" });
db.v.save({ _key: "country-australia", name: "Australia", type: "country", code: "AUS" });
db.v.save({ _key: "country-austria", name: "Austria", type: "country", code: "AUT" });
db.v.save({ _key: "country-bahamas", name: "Bahamas", type: "country", code: "BHS" });
db.v.save({ _key: "country-bahrain", name: "Bahrain", type: "country", code: "BHR" });
db.v.save({ _key: "country-bangladesh", name: "Bangladesh", type: "country", code: "BGD" });
db.v.save({ _key: "country-barbados", name: "Barbados", type: "country", code: "BRB" });
db.v.save({ _key: "country-belgium", name: "Belgium", type: "country", code: "BEL" });
db.v.save({ _key: "country-bhutan", name: "Bhutan", type: "country", code: "BTN" });
db.v.save({ _key: "country-bolivia", name: "Bolivia", type: "country", code: "BOL" });
db.v.save({ _key: "country-bosnia-and-herzegovina", name: "Bosnia and Herzegovina", type: "country", code: "BIH" });
db.v.save({ _key: "country-botswana", name: "Botswana", type: "country", code: "BWA" });
db.v.save({ _key: "country-brazil", name: "Brazil", type: "country", code: "BRA" });
db.v.save({ _key: "country-brunei", name: "Brunei", type: "country", code: "BRN" });
db.v.save({ _key: "country-bulgaria", name: "Bulgaria", type: "country", code: "BGR" });
db.v.save({ _key: "country-burkina-faso", name: "Burkina Faso", type: "country", code: "BFA" });
db.v.save({ _key: "country-burundi", name: "Burundi", type: "country", code: "BDI" });
db.v.save({ _key: "country-cambodia", name: "Cambodia", type: "country", code: "KHM" });
db.v.save({ _key: "country-cameroon", name: "Cameroon", type: "country", code: "CMR" });
db.v.save({ _key: "country-canada", name: "Canada", type: "country", code: "CAN" });
db.v.save({ _key: "country-chad", name: "Chad", type: "country", code: "TCD" });
db.v.save({ _key: "country-chile", name: "Chile", type: "country", code: "CHL" });
db.v.save({ _key: "country-colombia", name: "Colombia", type: "country", code: "COL" });
db.v.save({ _key: "country-cote-d-ivoire", name: "Cote d'Ivoire", type: "country", code: "CIV" });
db.v.save({ _key: "country-croatia", name: "Croatia", type: "country", code: "HRV" });
db.v.save({ _key: "country-czech-republic", name: "Czech Republic", type: "country", code: "CZE" });
db.v.save({ _key: "country-denmark", name: "Denmark", type: "country", code: "DNK" });
db.v.save({ _key: "country-ecuador", name: "Ecuador", type: "country", code: "ECU" });
db.v.save({ _key: "country-egypt", name: "Egypt", type: "country", code: "EGY" });
db.v.save({ _key: "country-eritrea", name: "Eritrea", type: "country", code: "ERI" });
db.v.save({ _key: "country-finland", name: "Finland", type: "country", code: "FIN" });
db.v.save({ _key: "country-france", name: "France", type: "country", code: "FRA" });
db.v.save({ _key: "country-germany", name: "Germany", type: "country", code: "DEU" });
db.v.save({ _key: "country-people-s-republic-of-china", name: "People's Republic of China", type: "country", code: "CHN" });
// vertices: capitals
db.v.save({ _key: "capital-algiers", name: "Algiers", type: "capital" });
db.v.save({ _key: "capital-andorra-la-vella", name: "Andorra la Vella", type: "capital" });
db.v.save({ _key: "capital-asmara", name: "Asmara", type: "capital" });
db.v.save({ _key: "capital-bandar-seri-begawan", name: "Bandar Seri Begawan", type: "capital" });
db.v.save({ _key: "capital-beijing", name: "Beijing", type: "capital" });
db.v.save({ _key: "capital-berlin", name: "Berlin", type: "capital" });
db.v.save({ _key: "capital-bogota", name: "Bogota", type: "capital" });
db.v.save({ _key: "capital-brasilia", name: "Brasilia", type: "capital" });
db.v.save({ _key: "capital-bridgetown", name: "Bridgetown", type: "capital" });
db.v.save({ _key: "capital-brussels", name: "Brussels", type: "capital" });
db.v.save({ _key: "capital-buenos-aires", name: "Buenos Aires", type: "capital" });
db.v.save({ _key: "capital-bujumbura", name: "Bujumbura", type: "capital" });
db.v.save({ _key: "capital-cairo", name: "Cairo", type: "capital" });
db.v.save({ _key: "capital-canberra", name: "Canberra", type: "capital" });
db.v.save({ _key: "capital-copenhagen", name: "Copenhagen", type: "capital" });
db.v.save({ _key: "capital-dhaka", name: "Dhaka", type: "capital" });
db.v.save({ _key: "capital-gaborone", name: "Gaborone", type: "capital" });
db.v.save({ _key: "capital-helsinki", name: "Helsinki", type: "capital" });
db.v.save({ _key: "capital-kabul", name: "Kabul", type: "capital" });
db.v.save({ _key: "capital-la-paz", name: "La Paz", type: "capital" });
db.v.save({ _key: "capital-luanda", name: "Luanda", type: "capital" });
db.v.save({ _key: "capital-manama", name: "Manama", type: "capital" });
db.v.save({ _key: "capital-nassau", name: "Nassau", type: "capital" });
db.v.save({ _key: "capital-n-djamena", name: "N'Djamena", type: "capital" });
db.v.save({ _key: "capital-ottawa", name: "Ottawa", type: "capital" });
db.v.save({ _key: "capital-ouagadougou", name: "Ouagadougou", type: "capital" });
db.v.save({ _key: "capital-paris", name: "Paris", type: "capital" });
db.v.save({ _key: "capital-phnom-penh", name: "Phnom Penh", type: "capital" });
db.v.save({ _key: "capital-prague", name: "Prague", type: "capital" });
db.v.save({ _key: "capital-quito", name: "Quito", type: "capital" });
db.v.save({ _key: "capital-saint-john-s", name: "Saint John's", type: "capital" });
db.v.save({ _key: "capital-santiago", name: "Santiago", type: "capital" });
db.v.save({ _key: "capital-sarajevo", name: "Sarajevo", type: "capital" });
db.v.save({ _key: "capital-sofia", name: "Sofia", type: "capital" });
db.v.save({ _key: "capital-thimphu", name: "Thimphu", type: "capital" });
db.v.save({ _key: "capital-tirana", name: "Tirana", type: "capital" });
db.v.save({ _key: "capital-vienna", name: "Vienna", type: "capital" });
db.v.save({ _key: "capital-yamoussoukro", name: "Yamoussoukro", type: "capital" });
db.v.save({ _key: "capital-yaounde", name: "Yaounde", type: "capital" });
db.v.save({ _key: "capital-zagreb", name: "Zagreb", type: "capital" });
// edges: continent -> world
db.e.save("v/continent-africa", "v/world", { type: "is-in" });
db.e.save("v/continent-asia", "v/world", { type: "is-in" });
db.e.save("v/continent-australia", "v/world", { type: "is-in" });
db.e.save("v/continent-europe", "v/world", { type: "is-in" });
db.e.save("v/continent-north-america", "v/world", { type: "is-in" });
db.e.save("v/continent-south-america", "v/world", { type: "is-in" });
// edges: country -> continent
db.e.save("v/country-afghanistan", "v/continent-asia", { type: "is-in" });
db.e.save("v/country-albania", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-algeria", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-andorra", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-angola", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-antigua-and-barbuda", "v/continent-north-america", { type: "is-in" });
db.e.save("v/country-argentina", "v/continent-south-america", { type: "is-in" });
db.e.save("v/country-australia", "v/continent-australia", { type: "is-in" });
db.e.save("v/country-austria", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-bahamas", "v/continent-north-america", { type: "is-in" });
db.e.save("v/country-bahrain", "v/continent-asia", { type: "is-in" });
db.e.save("v/country-bangladesh", "v/continent-asia", { type: "is-in" });
db.e.save("v/country-barbados", "v/continent-north-america", { type: "is-in" });
db.e.save("v/country-belgium", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-bhutan", "v/continent-asia", { type: "is-in" });
db.e.save("v/country-bolivia", "v/continent-south-america", { type: "is-in" });
db.e.save("v/country-bosnia-and-herzegovina", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-botswana", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-brazil", "v/continent-south-america", { type: "is-in" });
db.e.save("v/country-brunei", "v/continent-asia", { type: "is-in" });
db.e.save("v/country-bulgaria", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-burkina-faso", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-burundi", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-cambodia", "v/continent-asia", { type: "is-in" });
db.e.save("v/country-cameroon", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-canada", "v/continent-north-america", { type: "is-in" });
db.e.save("v/country-chad", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-chile", "v/continent-south-america", { type: "is-in" });
db.e.save("v/country-colombia", "v/continent-south-america", { type: "is-in" });
db.e.save("v/country-cote-d-ivoire", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-croatia", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-czech-republic", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-denmark", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-ecuador", "v/continent-south-america", { type: "is-in" });
db.e.save("v/country-egypt", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-eritrea", "v/continent-africa", { type: "is-in" });
db.e.save("v/country-finland", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-france", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-germany", "v/continent-europe", { type: "is-in" });
db.e.save("v/country-people-s-republic-of-china", "v/continent-asia", { type: "is-in" });
// edges: capital -> country
db.e.save("v/capital-algiers", "v/country-algeria", { type: "is-in" });
db.e.save("v/capital-andorra-la-vella", "v/country-andorra", { type: "is-in" });
db.e.save("v/capital-asmara", "v/country-eritrea", { type: "is-in" });
db.e.save("v/capital-bandar-seri-begawan", "v/country-brunei", { type: "is-in" });
db.e.save("v/capital-beijing", "v/country-people-s-republic-of-china", { type: "is-in" });
db.e.save("v/capital-berlin", "v/country-germany", { type: "is-in" });
db.e.save("v/capital-bogota", "v/country-colombia", { type: "is-in" });
db.e.save("v/capital-brasilia", "v/country-brazil", { type: "is-in" });
db.e.save("v/capital-bridgetown", "v/country-barbados", { type: "is-in" });
db.e.save("v/capital-brussels", "v/country-belgium", { type: "is-in" });
db.e.save("v/capital-buenos-aires", "v/country-argentina", { type: "is-in" });
db.e.save("v/capital-bujumbura", "v/country-burundi", { type: "is-in" });
db.e.save("v/capital-cairo", "v/country-egypt", { type: "is-in" });
db.e.save("v/capital-canberra", "v/country-australia", { type: "is-in" });
db.e.save("v/capital-copenhagen", "v/country-denmark", { type: "is-in" });
db.e.save("v/capital-dhaka", "v/country-bangladesh", { type: "is-in" });
db.e.save("v/capital-gaborone", "v/country-botswana", { type: "is-in" });
db.e.save("v/capital-helsinki", "v/country-finland", { type: "is-in" });
db.e.save("v/capital-kabul", "v/country-afghanistan", { type: "is-in" });
db.e.save("v/capital-la-paz", "v/country-bolivia", { type: "is-in" });
db.e.save("v/capital-luanda", "v/country-angola", { type: "is-in" });
db.e.save("v/capital-manama", "v/country-bahrain", { type: "is-in" });
db.e.save("v/capital-nassau", "v/country-bahamas", { type: "is-in" });
db.e.save("v/capital-n-djamena", "v/country-chad", { type: "is-in" });
db.e.save("v/capital-ottawa", "v/country-canada", { type: "is-in" });
db.e.save("v/capital-ouagadougou", "v/country-burkina-faso", { type: "is-in" });
db.e.save("v/capital-paris", "v/country-france", { type: "is-in" });
db.e.save("v/capital-phnom-penh", "v/country-cambodia", { type: "is-in" });
db.e.save("v/capital-prague", "v/country-czech-republic", { type: "is-in" });
db.e.save("v/capital-quito", "v/country-ecuador", { type: "is-in" });
db.e.save("v/capital-saint-john-s", "v/country-antigua-and-barbuda", { type: "is-in" });
db.e.save("v/capital-santiago", "v/country-chile", { type: "is-in" });
db.e.save("v/capital-sarajevo", "v/country-bosnia-and-herzegovina", { type: "is-in" });
db.e.save("v/capital-sofia", "v/country-bulgaria", { type: "is-in" });
db.e.save("v/capital-thimphu", "v/country-bhutan", { type: "is-in" });
db.e.save("v/capital-tirana", "v/country-albania", { type: "is-in" });
db.e.save("v/capital-vienna", "v/country-austria", { type: "is-in" });
db.e.save("v/capital-yamoussoukro", "v/country-cote-d-ivoire", { type: "is-in" });
db.e.save("v/capital-yaounde", "v/country-cameroon", { type: "is-in" });
db.e.save("v/capital-zagreb", "v/country-croatia", { type: "is-in" });
@BNAVIGATE_Traversals