Using Traversal Objects

The JavaScript module @arangodb/graph/traversal (traversal module for short)is deprecated from version 3.4.0 on. The preferred way to traverse graphs is with AQL.

To use a traversal object, we first need to require the traversal module:

  1. var traversal = require("@arangodb/graph/traversal");
  2. var examples = require("@arangodb/graph-examples/example-graph.js");
  3. examples.loadGraph("worldCountry");

We then need to setup a configuration for the traversal and determine at which vertex to start the traversal:

  1. var config = {
  2. datasource: traversal.generalGraphDatasourceFactory("worldCountry"),
  3. strategy: "depthfirst",
  4. order: "preorder",
  5. filter: traversal.visitAllFilter,
  6. expander: traversal.inboundExpander,
  7. maxDepth: 1
  8. };
  9. var startVertex = db._document("v/world");

Note: 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:

  1. var result = {
  2. visited: {
  3. vertices: [ ],
  4. paths: [ ]
  5. }
  6. };
  7. var traverser = new traversal.Traverser(config);
  8. 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:

  1. require("@arangodb").print(result.visited.vertices.map(function(vertex) {
  2. return vertex.name + " (" + vertex.type + ")";
  3. }));

The full script, which includes all steps carried out so far is thus:

  1. var traversal = require("@arangodb/graph/traversal");
  2. var config = {
  3. datasource: traversal.generalGraphDatasourceFactory("worldCountry"),
  4. strategy: "depthfirst",
  5. order: "preorder",
  6. filter: traversal.visitAllFilter,
  7. expander: traversal.inboundExpander,
  8. maxDepth: 1
  9. };
  10. var startVertex = db._document("v/world");
  11. var result = {
  12. visited: {
  13. vertices: [ ],
  14. paths: [ ]
  15. }
  16. };
  17. var traverser = new traversal.Traverser(config);
  18. traverser.traverse(result, startVertex);
  19. require("@arangodb").print(result.visited.vertices.map(function(vertex) {
  20. return vertex.name + " (" + vertex.type + ")";
  21. }));

The result is an array of vertices that were visited during the traversal, starting at thestart vertex (i.e. v/world in our example):

  1. [
  2. "World (root)",
  3. "Africa (continent)",
  4. "Asia (continent)",
  5. "Australia (continent)",
  6. "Europe (continent)",
  7. "North America (continent)",
  8. "South America (continent)"
  9. ]

Note: The result is limited to vertices directly connected to the start vertex. Weachieved this by setting the maxDepth attribute to 1. Not setting it would return thefull array of vertices.

Traversal Direction

For the examples contained in this manual, we’ll be starting the traversals at vertexv/world. Vertices in our graph are connected like this:

  1. 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, wehave specified this via the expander attribute:

  1. var config = {
  2. ...
  3. expander: traversal.inboundExpander
  4. };

For other graphs, we might want to traverse via the outgoing edges. For this, we canuse the outboundExpander. There is also an anyExpander, which will follow both outgoingand incoming edges. This should be used with care and the traversal should always belimited to a maximum number of iterations (e.g. using the maxIterations attribute) inorder to terminate at some point.

To invoke the default outbound expander for a graph, simply use the predefined function:

  1. var config = {
  2. ...
  3. expander: traversal.outboundExpander
  4. };

Please note the outbound expander will not produce any output for the examples if we stillstart the traversal at the v/world vertex.

Still, we can use the outbound expander if we start somewhere else in the graph, e.g.

  1. var traversal = require("@arangodb/graph/traversal");
  2. var config = {
  3. datasource: traversal.generalGraphDatasourceFactory("world_graph"),
  4. strategy: "depthfirst",
  5. order: "preorder",
  6. filter: traversal.visitAllFilter,
  7. expander: traversal.outboundExpander
  8. };
  9. var startVertex = db._document("v/capital-algiers");
  10. var result = {
  11. visited: {
  12. vertices: [ ],
  13. paths: [ ]
  14. }
  15. };
  16. var traverser = new traversal.Traverser(config);
  17. traverser.traverse(result, startVertex);
  18. require("@arangodb").print(result.visited.vertices.map(function(vertex) {
  19. return vertex.name + " (" + vertex.type + ")";
  20. }));

The result is:

  1. [
  2. "Algiers (capital)",
  3. "Algeria (country)",
  4. "Africa (continent)",
  5. "World (root)"
  6. ]

which confirms that now we’re going outbound.

Traversal Strategy

Depth-first traversals

The visitation order of vertices is determined by the strategy and order attributes setin the configuration. We chose depthfirst and preorder, meaning the traverser will visit 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 (directlyand indirectly connected to the start vertex):

  1. var config = {
  2. datasource: traversal.generalGraphDatasourceFactory("world_graph"),
  3. strategy: "depthfirst",
  4. order: "preorder",
  5. filter: traversal.visitAllFilter,
  6. expander: traversal.inboundExpander
  7. };
  8. var result = {
  9. visited: {
  10. vertices: [ ],
  11. paths: [ ]
  12. }
  13. };
  14. var traverser = new traversal.Traverser(config);
  15. traverser.traverse(result, startVertex);
  16. require("@arangodb").print(result.visited.vertices.map(function(vertex) {
  17. return vertex.name + " (" + vertex.type + ")";
  18. }));

The result will be a longer array, assembled in depth-first, pre-order order. For each continent found, the traverser will descend into linked countries, and then intothe linked capital:

  1. [
  2. "World (root)",
  3. "Africa (continent)",
  4. "Algeria (country)",
  5. "Algiers (capital)",
  6. "Angola (country)",
  7. "Luanda (capital)",
  8. "Botswana (country)",
  9. "Gaborone (capital)",
  10. "Burkina Faso (country)",
  11. "Ouagadougou (capital)",
  12. ...
  13. ]

Let’s switch the order attribute from preorder to postorder. This will make thetraverser visit vertices after all connected vertices were visited (i.e. most distantvertices will be emitted first):

  1. [
  2. "Algiers (capital)",
  3. "Algeria (country)",
  4. "Luanda (capital)",
  5. "Angola (country)",
  6. "Gaborone (capital)",
  7. "Botswana (country)",
  8. "Ouagadougou (capital)",
  9. "Burkina Faso (country)",
  10. "Bujumbura (capital)",
  11. "Burundi (country)",
  12. "Yaounde (capital)",
  13. "Cameroon (country)",
  14. "N'Djamena (capital)",
  15. "Chad (country)",
  16. "Yamoussoukro (capital)",
  17. "Cote d'Ivoire (country)",
  18. "Cairo (capital)",
  19. "Egypt (country)",
  20. "Asmara (capital)",
  21. "Eritrea (country)",
  22. "Africa (continent)",
  23. ...
  24. ]

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:

  1. [
  2. "World (root)",
  3. "Africa (continent)",
  4. "Asia (continent)",
  5. "Australia (continent)",
  6. "Europe (continent)",
  7. "North America (continent)",
  8. "South America (continent)",
  9. "Burkina Faso (country)",
  10. "Burundi (country)",
  11. "Cameroon (country)",
  12. "Chad (country)",
  13. "Algeria (country)",
  14. "Angola (country)",
  15. ...
  16. ]

Note: The order of items returned for the same level is undefined.This is because there is no natural order of edges for a vertex withmultiple connected edges. To explicitly set the order for edges on thesame level, you can specify an edge comparator function with the _sort_attribute:

  1. var config = {
  2. ...
  3. sort: function (l, r) { return l._key < r._key ? 1 : -1; }
  4. ...
  5. };

The arguments l and r are edge documents.This will traverse edges of the same vertex in backward _key order:

  1. [
  2. "World (root)",
  3. "South America (continent)",
  4. "North America (continent)",
  5. "Europe (continent)",
  6. "Australia (continent)",
  7. "Asia (continent)",
  8. "Africa (continent)",
  9. "Ecuador (country)",
  10. "Colombia (country)",
  11. "Chile (country)",
  12. "Brazil (country)",
  13. "Bolivia (country)",
  14. "Argentina (country)",
  15. ...
  16. ]

Note: This attribute only works for the usual expanderstraversal.inboundExpander, traversal.outboundExpander,traversal.anyExpander and their corresponding “WithLabels” variants.If you are using custom expandersyou have to organize the sorting within the specified expander.

Writing Custom Visitors

So far we have used much of the traverser’s default functions. The traverser is veryconfigurable and many of the default functions can be overridden with custom functionality.

For example, we have been using the default visitor function (which is always used ifthe configuration does not contain the visitor attribute). The default visitor functionis 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, andneeded to be initialized before the traversal was started.

Note that the default visitor (named trackingVisitor) will add every visited vertexinto the result, including the full paths from the start vertex. This is useful for learning anddebugging purposes, but should be avoided in production because it might produce (andcopy) huge amounts of data. Instead, only those data should be copied into the result that are actually necessary.

The traverser comes with the following predefined visitors:

  • trackingVisitor: this is the default visitor. It will copy all data of each visitedvertex plus the full path information into the result. This can be slow if the resultset is huge or vertices contain a lot of data.
  • countingVisitor: this is a very lightweight visitor: all it does is increase acounter in the result for each vertex visited. Vertex data and paths will not be copiedinto the result.
  • doNothingVisitor: if no action shall be carried out when a vertex is visited, thisvisitor can be employed. It will not do anything and will thus be fast. It can be usedfor performance comparisons with other visitors.We can also write our own visitor function if we want to. The general function signature forvisitor functions is as follows:
  1. var config = {
  2. ...
  3. visitor: function (config, result, vertex, path, connected) { ... }
  4. };

Note: the connected parameter value will only be set if the traversal order isset to preorder-expander. Otherwise, this parameter won’t be set by the traverser.

Visitor functions are not expected to return any values. Instead, they can modify theresult 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 aboutthe current vertex as we traverse:

  1. var config = {
  2. datasource: traversal.generalGraphDatasourceFactory("world_graph"),
  3. strategy: "depthfirst",
  4. order: "preorder",
  5. filter: traversal.visitAllFilter,
  6. expander: traversal.inboundExpander,
  7. visitor: function (config, result, vertex, path) {
  8. require("@arangodb").print("visiting vertex", vertex.name);
  9. }
  10. };
  11. var traverser = new traversal.Traverser(config);
  12. traverser.traverse(undefined, startVertex);

To write a visitor that increments a counter each time a vertex is visited,we could write the following custom visitor:

  1. config.visitor = function (config, result, vertex, path, connected) {
  2. if (! result) {
  3. result = { };
  4. }
  5. if (! result.hasOwnProperty('count')) {
  6. result.count = 0;
  7. }
  8. ++result.count;
  9. }

Note that such visitor is already predefined (it’s the countingVisitor describedabove). It can be used as follows:

  1. config.visitor = traversal.countingVisitor;

Another example of a visitor is one that collects the _id values of all vertices visited:

  1. config.visitor = function (config, result, vertex, path, connected) {
  2. if (! result) {
  3. result = { };
  4. }
  5. if (! result.hasOwnProperty("visited")) {
  6. result.visited = { vertices: [ ] };
  7. }
  8. result.visited.vertices.push(vertex._id);
  9. }

When the traversal order is set to preorder-expander, the traverser will passa fifth parameter value into the visitor function. This parameter contains theconnected edges of the visited vertex as an array. This can be handy because in thiscase the visitor will get all information about the vertex and the connected edgestogether.

For example, the following visitor can be used to print only leaf nodes (thatdo not have any further connected edges):

  1. config.visitor = function (config, result, vertex, path, connected) {
  2. if (connected && connected.length === 0) {
  3. require("@arangodb").print("found a leaf-node: ", vertex);
  4. }
  5. }

Note that for this visitor to work, the traversal order attribute needs to beset to the value preorder-expander.

Filtering Vertices and Edges

Filtering Vertices

So far we have printed or 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.:

  1. var config = {
  2. filter: function (config, vertex, path) {
  3. if (vertex.type !== 'capital') {
  4. return 'exclude';
  5. }
  6. }
  7. }

The above filter function will exclude all vertices that do not have a type value ofcapital. 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 an array of edges, and an array of vertices. We could also filter everything but capitals by checking thelength of the path from the start vertex to the current vertex. Capitals will have adistance of 3 from the v/world start vertex(capital → is-in → country → is-in → continent → is-in → world):

  1. var config = {
  2. ...
  3. filter: function (config, vertex, path) {
  4. if (path.edges.length < 3) {
  5. return 'exclude';
  6. }
  7. }
  8. }

Note: If a filter function returns nothing (or undefined), the current vertexwill be included, and all connected edges will be followed. If a filter functionreturns exclude the current vertex will be excluded from the result, and all stillall connected edges will be followed. If a filter function returns prune, thecurrent vertex will be included, but no connected edges will be followed.

For example, the following filter function will not descend into connected edges ofcontinents, limiting the depth of the traversal. Still, continent vertices will beincluded in the result:

  1. var config = {
  2. ...
  3. filter: function (config, vertex, path) {
  4. if (vertex.type === 'continent') {
  5. return 'prune';
  6. }
  7. }
  8. }

It is also possible to combine exclude and prune by returning an array with bothvalues:

  1. return [ 'exclude', 'prune' ];

Filtering Edges

It is possible to exclude certain edges from the traversal. To filter on edges, afilter 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 edgeconnects to (in the direction of the traversal). It also receives the current pathfrom the start vertex up to the current vertex (excluding the current edge and thevertex the edge points to).

If the function returns true, the edge will be followed. If the function returnsfalse, the edge will not be followed.Here is a very simple custom edge filter function implementation, which simplyincludes edges if the (edges) path length is less than 1, and will exclude anyother edges. This will effectively terminate the traversal after the first level of edges:

  1. var config = {
  2. ...
  3. expandFilter: function (config, vertex, edge, path) {
  4. return (path.edges.length < 1);
  5. }
  6. };

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 theedge collection specified in the traversal configuration.

There is also a default outbound expander, which will enumerate all connected outgoingedges. Finally, there is an any expander, which will follow both ingoing and outgoingedges.

If connected edges must be determined in some different fashion for whatever reason, acustom expander can be written and registered by setting the expander attribute of the configuration. The expander function signature is as follows:

  1. var config = {
  2. ...
  3. expander: function (config, vertex, path) { ... }
  4. }

It is the expander’s responsibility to return all edges and vertices directlyconnected 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 viathe path variable.An expander is expected to return an array of objects, which need to have an edge_and a _vertex attribute each.

Note: If you want to rely on a particular order in which the edgesare traversed, you have to sort the edges returned by your expanderwithin the code of the expander. The functions to get outbound, inboundor any edges from a vertex do not guarantee any particular order!

A custom implementation of an inbound expander could look like this (this is anon-deterministic expander, which randomly decides whether or not to includeconnected edges):

  1. var config = {
  2. ...
  3. expander: function (config, vertex, path) {
  4. var connected = [ ];
  5. var datasource = config.datasource;
  6. datasource.getInEdges(vertex._id).forEach(function (edge) {
  7. if (Math.random() >= 0.5) {
  8. connected.push({ edge: edge, vertex: (edge._from) });
  9. }
  10. });
  11. return connected;
  12. }
  13. };

A custom expander can also be used as an edge filter because it has full controlover which edges will be returned.

Following are two examples of custom expanders that pick edges based on attributesof the edges and the connected vertices.

Finding the connected edges / vertices based on an attribute when in theconnected vertices. The goal is to follow the edge that leads to the vertexwith the highest value in the when attribute:

  1. var config = {
  2. ...
  3. expander: function (config, vertex, path) {
  4. var datasource = config.datasource;
  5. // determine all outgoing edges
  6. var outEdges = datasource.getOutEdges(vertex);
  7. if (outEdges.length === 0) {
  8. return [ ];
  9. }
  10. var data = [ ];
  11. outEdges.forEach(function (edge) {
  12. data.push({ edge: edge, vertex: datasource.getInVertex(edge) });
  13. });
  14. // sort outgoing vertices according to "when" attribute value
  15. data.sort(function (l, r) {
  16. if (l.vertex.when === r.vertex.when) {
  17. return 0;
  18. }
  19. return (l.vertex.when < r.vertex.when ? 1 : -1);
  20. });
  21. // pick first vertex found (with highest "when" attribute value)
  22. return [ data[0] ];
  23. }
  24. ...
  25. };

Finding the connected edges / vertices based on an attribute when in theedge itself. The goal is to pick the one edge (out of potentially many) thathas the highest when attribute value:

  1. var config = {
  2. ...
  3. expander: function (config, vertex, path) {
  4. var datasource = config.datasource;
  5. // determine all outgoing edges
  6. var outEdges = datasource.getOutEdges(vertex);
  7. if (outEdges.length === 0) {
  8. return [ ]; // return an empty array
  9. }
  10. // sort all outgoing edges according to "when" attribute
  11. outEdges.sort(function (l, r) {
  12. if (l.when === r.when) {
  13. return 0;
  14. }
  15. return (l.when < r.when ? -1 : 1);
  16. });
  17. // return first edge (the one with highest "when" value)
  18. var edge = outEdges[0];
  19. try {
  20. var v = datasource.getInVertex(edge);
  21. return [ { edge: edge, vertex: v } ];
  22. }
  23. catch (e) { }
  24. return [ ];
  25. }
  26. ...
  27. };

Handling Uniqueness

Graphs may contain cycles. To be on top of what happens when a traversal encounters a vertexor an edge it has already visited, there are configuration options.

The default configuration is to visit every vertex, regardless of whether it was already visitedin the same traversal. However, edges will by default only be followed if they are not already present in the current path.

Imagine the following graph which contains a cycle:

  1. A -> B -> C -> A

When the traversal finds the edge from C to A, it will by default follow it. This is becausewe have not seen this edge yet. It will also visit vertex A again. This is because by defaultall vertices will be visited, regardless of whether already visited or not.

However, the traversal will not again following the outgoing edge from A to B. This is becausewe already have the edge from A to B in our current path.

These default settings will prevent infinite traversals.

To adjust the uniqueness for visiting vertices, there are the following options for uniqueness.vertices:

  • “none”: always visit a vertices, regardless of whether it was already visited or not
  • “global”: visit a vertex only if it was not visited in the traversal
  • “path”: visit a vertex if it is not included in the current pathTo adjust the uniqueness for following edges, there are the following options for uniqueness.edges:

  • “none”: always follow an edge, regardless of whether it was followed before

  • “global”: follow an edge only if it wasn’t followed in the traversal
  • “path”: follow an edge if it is not included in the current pathNote that uniqueness checking will have some effect on both runtime and memory usage. For example, whenuniqueness checks are set to “global”, arrays of visited vertices and edges must be kept in memory while thetraversal is executed. Global uniqueness should thus only be used when a traversal is expected to visitfew nodes.

In terms of runtime, turning off uniqueness checks (by setting both options to “none”) is the best choice,but it is only safe for graphs that do not contain cycles. When uniqueness checks are deactivated in a graphwith cycles, the traversal might not abort in a sensible amount of time.

Optimizations

There are a few options for making a traversal run faster.

The best option is to make the amount of visited vertices and followed edges as small as possible. This canbe achieved by writing custom filter and expander functions. Such functions should only include vertices ofinterest, and only follow edges that might be interesting.

Traversal depth can also be bounded with the minDepth and maxDepth options.

Another way to speed up traversals is to write a custom visitor function. The default visitor function (trackingVisitor) will copy every visited vertex into the result. If vertices contain lots of data, this might be expensive. It is therefore recommended to only copy such data into the result that is actually needed. The default visitor function will also copy the full path to the visited document into the result. This is even more expensive and should be avoided if possible.

If the goal of a traversal is to only count the number of visited vertices, the prefab _countingVisitor_will be much more efficient than the default visitor.

For graphs that are known to not contain any cycles, uniqueness checks should be turned off. This can achievedvia the uniqueness configuration options. Note that uniqueness checks should not be turned off for graphsthat are known contain cycles or if there is no information about the graph’s structure.

By default, a traversal will only process a limited number of vertices. This is protect the user fromunintentionally run a never-ending traversal on a graph with cyclic data. How many vertices will be processedat most is determined by the maxIterations configuration option. If a traversal hits the cap specified by maxIterations, it will abort and throw a too many iterations exception. If this error is encountered,the maxIterations value should be increased if it is made sure that the other traversal configurationparameters are sane and the traversal will abort naturally at some point.

Finally, the buildVertices configuration option can be set to false to avoid looking up and fully constructingvertex data. If all that’s needed from vertices are the id_ or key attributes, the _buildvertices option can be set to false. If visitor, filter or expandFilter functions need to access other vertexattributes, the option should not be changed.

Configuration Overview

This section summarizes the configuration attributes for the traversal object. Theconfiguration can consist of the following attributes:

  • visitor: visitor function for vertices. It will be called for all non-excluded vertices. Thegeneral visitor function signature is function (config, result, vertex, path). If the traversalorder is preorder-expander, the connecting edges of the visited vertex will be passed as thefifth parameter, extending the function signature to: function (config, result, vertex, path, edges).

Visitor functions are not expected to return values, but they may modify the result variable as needed (e.g. by pushing vertex data into the result).

  • expander: expander function that is responsible for returning edges and verticesdirectly connected to a vertex. The function signature is function (config, vertex, path).The expander function is required to return an array of connection objects, consisting of anedge and vertex attribute each. If there are no connecting edges, the expander is expected toreturn an empty array.
  • filter: vertex filter function. The function signature is function (config, vertex, path). Itmay 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
    • 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 returntrue 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 processed. Thefunction signature is function (l, r). The function is required to return one of the followingvalues:
    • -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, postorder, andpreorder-expander. preorder-expander is the same as preorder, except that the signature ofthe visitor function will change as described above.
  • 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 issensible 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. Eachsub-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 besensible 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.
  • buildVertices: this attribute controls whether vertices encountered during the traversal will belooked up in the database and will be made available to visitor, filter, and expandFilter functions.By default, vertices will be looked up and made available. However, there are some special usecases when fully constructing vertex objects is not necessary and can be avoided. For example, ifa traversal is meant to only count the number of visited vertices but do not read any data from vertices, this option might be set to true.