1
0
Fork 0
arangodb/Documentation/UserManual/Traversals.md

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 is function (config, result, vertex, path). This function is not expected to return a value, but may modify the variable 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 is function (config, vertex, path). The expander function is required to return a list of connection objects, consisting of an edge and vertex attribute each.
  • filter: vertex filter function. The function signature is function (config, vertex, path). It may return one of the following values:
    • undefined: vertex will be included in the result and connected edges will be traversed
    • exclude: vertex will not be included in the result and connected edges will be traversed
    • prune: 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 is function (config, vertex, edge, path). The function should return true if the edge/vertex combination should be processed, and false if it should be ignored.
  • sort: a filter function to determine the order in which connected edges are procssed. The function signature is function (l, r). The function is required to return one of the following values:
    • -1 if l should have a sort value less than r
    • 1 if l should have a higher sort value than r
    • 0 if l and r have the same sort value
  • strategy: determines the visitation strategy. Possible values are depthfirst and breadthfirst.
  • order: determines the visitation order. Possible values are preorder and postorder.
  • itemOrder: determines the order in which connections returned by the expander will be processed. Possible values are forward and backward.
  • maxDepth: if set to a value greater than 0, this will limit the traversal to this maximum depth.
  • minDepth: if set to a value greater than 0, all vertices found on a level below the minDepth 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. The uniqueness object can have a sub-attribute vertices, and a sub-attribute edges. Each sub-attribute can have one of the following values:
    • none: no uniqueness constraints
    • path: 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