Repository: tinkerpop Updated Branches: refs/heads/TINKERPOP-1602 d7b7e66d7 -> 1633bd945 (forced update)
Added more cycle detection and traversal induced value recipes. Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/6dba5ec6 Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/6dba5ec6 Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/6dba5ec6 Branch: refs/heads/TINKERPOP-1602 Commit: 6dba5ec636aed6fd3eeb1ac7db7b4043657689b1 Parents: 9c44f0d Author: Stephen Mallette <sp...@genoprime.com> Authored: Fri Jan 6 08:57:31 2017 -0500 Committer: Stephen Mallette <sp...@genoprime.com> Committed: Fri Jan 6 08:57:31 2017 -0500 ---------------------------------------------------------------------- docs/src/recipes/cycle-detection.asciidoc | 55 ++++++++++++++++++ .../recipes/traversal-induced-values.asciidoc | 60 ++++++++++++++++++++ 2 files changed, 115 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/6dba5ec6/docs/src/recipes/cycle-detection.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/recipes/cycle-detection.asciidoc b/docs/src/recipes/cycle-detection.asciidoc index 1a8650b..7cebb00 100644 --- a/docs/src/recipes/cycle-detection.asciidoc +++ b/docs/src/recipes/cycle-detection.asciidoc @@ -58,4 +58,59 @@ arbitrary length over both incoming and outgoing edges in the modern graph? g.V().as('a').repeat(both().simplePath()).emit(loops().is(gt(1))). both().where(eq('a')).path(). dedup().by(unfold().order().by(id).dedup().fold()) +---- + +An interesting type of cycle is known as the Eulerian circuit which is a path taken in a graph where each edge is +visited once and the path starts and ends with the same vertex. Consider the following graph, representative of an +imaginary but geographically similar link:https://en.wikipedia.org/wiki/Seven_Bridges_of_K%C3%B6nigsberg[Königsberg] +that happens to have an eighth bridge (the diagram depicts edge direction but direction won't be considered in the traversal): + +image:eulerian-circuit.png[width=500] + +Gremlin can detect if such a cycle exists with: + +[gremlin-groovy] +---- +g.addV().property(id, 'blue').as('b'). + addV().property(id, 'orange').as('o'). + addV().property(id, 'red').as('r'). + addV().property(id, 'green').as('g'). + addE('bridge').from('g').to('b'). + addE('bridge').from('g').to('o'). + addE('bridge').from('g').to('r'). + addE('bridge').from('g').to('r'). + addE('bridge').from('o').to('b'). + addE('bridge').from('o').to('b'). + addE('bridge').from('o').to('r'). + addE('bridge').from('o').to('r').iterate() +g.V().sideEffect(outE("bridge").aggregate("bridges")).barrier(). <1> + repeat(bothE(). <2> + or(__.not(select('e')), + __.not(filter(__.as('x').select(all, 'e').unfold(). <3> + where(eq('x'))))).as('e'). + otherV()). + until(select(all, 'e').count(local).as("c"). <4> + select("bridges").count(local).where(eq("c"))).hasNext() +---- + +<1> Gather all the edges in a "bridges" side effect. +<2> As mentioned earlier with the diagram, directionality is ignored as the traversal uses `bothE` and, later, `otherV`. +<3> In continually traversing over both incoming and outgoing edges, this path is only worth continuing if the edges +traversed thus far are only traversed once. That set of edges is maintained in "e". +<4> The traversal should repeat until the number of edges traversed in "e" is equal to the total number gathered in +the first step above, which would mean that the complete circuit has been made. + +Unlike Königsberg, with just seven bridges, a Eulerian circuit exists in the case with an eighth bridge. The first +detected circuit can be displayed with: + +[gremlin-groovy,existing] +---- +g.V().sideEffect(outE("bridge").aggregate("bridges")).barrier(). + repeat(bothE().or(__.not(select('e')), + __.not(filter(__.as('x').select(all, 'e').unfold(). + where(eq('x'))))).as('e').otherV()). + until(select(all, 'e').count(local).as("c"). + select("bridges").count(local).where(eq("c"))).limit(1). + path().by(id).by(constant(" -> ")). + map {String.join("", it.get().objects())} ---- \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/6dba5ec6/docs/src/recipes/traversal-induced-values.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/recipes/traversal-induced-values.asciidoc b/docs/src/recipes/traversal-induced-values.asciidoc index 5f48591..e292e16 100644 --- a/docs/src/recipes/traversal-induced-values.asciidoc +++ b/docs/src/recipes/traversal-induced-values.asciidoc @@ -64,4 +64,64 @@ of one `Vertex` to another: g.V().has('name', 'marko').as('marko'). out('created').property('creator', select('marko').by('name')) g.V().has('name', 'marko').out('created').valueMap() +---- + +In a more complex example of how this might work, consider a situation where the goal is to propagate a value stored on +a particular vertex through one of more additional connected vertices using some value on the connecting edges to +determine the value to assign. For example, the following graph depicts three "tank" vertices where the edges represent +the direction a particular "tank" should drain and the "factor" by which it should do it: + +image:traversal-induced-values-1.png[width=700] + +If the traversal started at tank "a", then the value of "amount" on that tank would be used to calculate what the value +of tank "b" was by multiplying it by the value of the "factor" property on the edge between vertices "a" and "b". In +this case the amount of tank "b" would then be 50. Following this pattern, when going from tank "b" to tank "c", the +value of the "amount" of tank "c" would be 5. + +image:traversal-induced-values-2.png[width=700] + +Using Gremlin `sack()`, this kind of operation could be specified as a single traversal: + +[gremlin-groovy] +---- +g.addV(label, 'tank', 'name', 'a', 'amount', 100.0).as('a'). + addV(label, 'tank', 'name', 'b', 'amount', 0.0).as('b'). + addV(label, 'tank', 'name', 'c', 'amount', 0.0).as('c'). + addE('drain').property('factor', 0.5).from('a').to('b'). + addE('drain').property('factor', 0.1).from('b').to('c').iterate() +a = g.V().has('name','a').next() +g.withSack(a.value('amount')). + V(a).repeat(outE('drain').sack(mult).by('factor'). + inV().property('amount', sack())). + until(__.outE('drain').count().is(0)).iterate() +g.V().valueMap() +---- + +The "sack value" gets initialized to the value of tank "a". The traversal iteratively traverses out on the "drain" +edges and uses `mult` to multiply the sack value by the value of "factor". The sack value at that point is then +written to the "amount" of the current vertex. + +As shown in the previous example, `sack()` is a useful way to "carry" and manipulate a value that can be later used +elsewhere in the traversal. Here is another example of its usage where it is utilized to increment all the "age" values +in the modern toy graph by 10: + +[gremlin-groovy,modern] +---- +g.withSack(0).V().has("age"). + sack(assign).by("age").sack(sum).by(constant(10)). + property("age", sack()).valueMap() +---- + +In the above case, the sack is initialized to zero and as each vertex is iterated, the "age" is assigned to the sack +with `sack(assign).by('age')`. That value in the sack is then incremented by the value `constant(10)` and assigned to +the "age" property of the same vertex. + +This value the sack is incremented by need not be a constant. It could also be derived from the traversal itself. +Using the same example, the "weight" property on the incident edges will be used as the value to add to the sack: + +[gremlin-groovy,modern] +---- +g.withSack(0).V().has("age"). + sack(assign).by("age").sack(sum).by(bothE().values("weight").sum()). + property("age", sack()).valueMap() ---- \ No newline at end of file