Search code examples
gremlintinkerpop

Tinkerpop Gremlin - How to simplify {a - b} maths on a 2 count array?


I'm trying to do something that should be quite simple. I have a stream of arrays that contain 2 edges. For each array, I just want to subtract a value on the second edge from that of the first. In some languages, this would simply be array[0].value - array[1].value. However, I've only managed to achieve this doing the following:

local(
    project('a', 'b').
      by(unfold().limit(1)).
      by(unfold().tail(1)).
    math('a - b').
      by('bias'))

This works, however it seems this should be much simpler without project(), limit() and tail(). It also makes me question how I would get the correct item from an array if there were 3 or more items in there. Eg. how would I get the second item if it's no longer the tail()? I guess limit(2).tail(1)? It just seems extremely messy. If I simple use a fold() step, I can get extremely close to simplifying this operation:

local(
    unfold().
    values('bias').
    fold(0, minus))

Using fold() in this way is so clean, however results in 0 - a - b. If I could replace that seed with a dynamic value, then I could achieve more. But even then, I would have to do something like (2 * a) - a - b, to achieve the correct result. Attempts to replace the 0 seed with a dynamic variable such as limit(1).values('bias') fail as it's the wrong type. But I wouldn't be satisfied with such arbitrary code anyway. Then My final thought was to index the items then select by index:

local(
    index().
      with(WithOptions.indexer, WithOptions.map))

While the edges are indexed fine, I can't find a way to make this useful. I thought I could select('0') to get the first item, but nothing is returned.

I guess my question is as follows: Is there a simple way to achieve a - b as simply as a + b is achieved with sum() or fold(0, sum)? If not, Is there a cleaner way of choosing items in an array for maths? Or should everything generally be put in project() before maths? It just seems so cumbersome...


Solution

  • You can skip project() easy enough:

    gremlin> arr = [[g.E(7).next(),g.E(8).next()],[g.E(8).next(),g.E(10).next()]]
    ==>[e[7][1-knows->2],e[8][1-knows->4]]
    ==>[e[8][1-knows->4],e[10][4-created->5]]
    gremlin> g.inject(arr).unfold().as('a','b').
    ......1>   math('a - b').
    ......2>     by(limit(local,1).values('weight')).
    ......3>     by(tail(local,1).values('weight'))
    ==>-0.5
    ==>0.0
    

    You still have to unfold().limit()/tail() but I'd say this is a better approach as it avoids creating a Map just to throw it away.

    It also makes me question how I would get the correct item from an array if there were 3 or more items in there. Eg. how would I get the second item if it's no longer the tail()? I guess limit(2).tail(1)?

    You could use range() to avoid a multi-step approach:

    gremlin> arr = [[g.E(7).next(),g.E(8).next(),g.E(9).next()],[g.E(8).next(),g.E(10).next(),g.E(10).next()]]
    ==>[e[7][1-knows->2],e[8][1-knows->4],e[9][1-created->3]]
    ==>[e[8][1-knows->4],e[10][4-created->5],e[10][4-created->5]]
    gremlin> g.inject(arr).unfold().as('a','b').
    ......1>   math('a - b').
    ......2>     by(limit(local,1).values('weight')).
    ......3>     by(range(local,1,2).values('weight'))
    ==>-0.5
    ==>0.0
    

    You could use sack() to avoid math() but I'm not sure it simplifies the picking apart of your inner list (i.e. your still stuck with limit()/tail()):

    gremlin> arr = [[g.E(7).next(),g.E(8).next()],[g.E(8).next(),g.E(10).next()]]
    ==>[e[7][1-knows->2],e[8][1-knows->4]]
    ==>[e[8][1-knows->4],e[10][4-created->5]]
    gremlin> g.withSack(0).inject(arr).unfold().
    ......1>   sack(assign).by(limit(local,1).values('weight')).
    ......2>   sack(minus).by(tail(local,1).values('weight')).
    ......3>   sack()
    ==>-0.5
    ==>0.0
    

    Good question by the way.