Enforcing a common type of elements in a stream filtered with limit(local)

219 views
Skip to first unread message

Daniel C. Weber

unread,
Jun 22, 2020, 6:05:41 AM6/22/20
to Gremlin-users
Hello,

The type of elements in a traversal that was filtered with limit(local, count) depends on count. For example, in a graph with e.g. 5 vertices, the query

g.V().fold().limit(local, 2)
yields
[v[0],v[1]]

whereas the query
g.V().fold().limit(local, 1)
will yield
v[0]

This is because the RangeStep that is used will yield a single element if it can determine the length of the range to be == 1, and an array otherwise.

For the object-graph-mapper I am developing, I was now trying to find a query rewrite that will enforce the same type of elements in the stream if count == 1. The best I could come up with was

g.V().fold().local(unfold().limit(1).fold())

which will of course fail if the locally limited stream didn't contain any iterable elements to begin with, i.e. for 

g.V().local(unfold().limit(1).fold())

it'll a stream of single element arrays, which is unhelpful in this case. Unfortunately, it doesn't seem possible to determine in Gremlin whether something is iterable or not.

Is there a smarter way of rewriting queries such that a common type of elements can be enforced for limit(local, ...), tail(local,...) and range(local, ...) ?


Best regards,
Daniel

Stephen Mallette

unread,
Jun 23, 2020, 3:51:18 PM6/23/20
to gremli...@googlegroups.com
This was a tough one....I think I'm doing what you want with this:

gremlin> lim = 1
==>1
gremlin> g.V().
......1>   choose(__.as('a').unfold().limit(1).where(eq('a')),
......2>          fold().limit(local,lim).unfold(),
......3>          limit(local,lim).choose(limit(local,2).count(local).is(1),fold(),identity()))
==>v[1]
gremlin> g.V().fold().
......1>   choose(__.as('a').unfold().limit(1).where(eq('a')),
......2>          fold().limit(local,lim).unfold(),
......3>          limit(local,lim).choose(limit(local,2).count(local).is(1),fold(),identity()))
==>[v[1]]
gremlin> lim = 2
==>2
gremlin> g.V().
......1>   choose(__.as('a').unfold().limit(1).where(eq('a')),
......2>          fold().limit(local,lim).unfold(),
......3>          limit(local,lim).choose(limit(local,2).count(local).is(1),fold(),identity()))
==>v[1]
==>v[2]
gremlin> g.V().fold().
......1>   choose(__.as('a').unfold().limit(1).where(eq('a')),
......2>          fold().limit(local,lim).unfold(),
......3>          limit(local,lim).choose(limit(local,2).count(local).is(1),fold(),identity()))
==>[v[1],v[2]]


You would basically have to replace a simple call to limit() with all that insanity. I sorta had my mind set on one way of solving this so maybe there's an easier way that I'm overlooking? I'm a little worried that the solution is not generalized enough for you to use it as you wish to and given all the Gremlin gymnastics there I'm not even sure you'd want to use it :)


--
You received this message because you are subscribed to the Google Groups "Gremlin-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to gremlin-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/gremlin-users/183e10c4-4333-4895-8eba-53f41134e030o%40googlegroups.com.
Message has been deleted
Message has been deleted

Stephen Mallette

unread,
Jun 23, 2020, 6:31:17 PM6/23/20
to gremli...@googlegroups.com
I think I had that variation too, but at some point I had this sense that Daniel couldn't use that for what he was doing.......then things got more complicated haha

I guess we'll see what he thinks of our options.... :)



On Tue, Jun 23, 2020 at 6:05 PM Kelvin Lawrence <kelvin.r...@gmail.com> wrote:
I guess that does not handle the case where there is no result though.

Kelvin

On Tuesday, June 23, 2020 at 5:03:18 PM UTC-5, Kelvin Lawrence wrote:
What about this as an alternative - maybe I am missing a subtlety ?

gremlin> g.V().fold().limit(local,2).choose(count(local).is(gt(1)),identity(),fold())
==>[v[0],v[1]]
gremlin> g.V().fold().limit(local,1).choose(count(local).is(gt(1)),identity(),fold())
==>[v[0]]    

Cheers
Kelvin
To unsubscribe from this group and stop receiving emails from it, send an email to gremli...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Gremlin-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to gremlin-user...@googlegroups.com.

Kelvin Lawrence

unread,
Jun 23, 2020, 7:05:36 PM6/23/20
to Gremlin-users
I realized after I posted my attempt had a flaw! - limit(local,3) even if there is only one result will wrap it in a list. So your gymnastics is probably the answer :-)

Kelvin

Daniel C. Weber

unread,
Jun 24, 2020, 9:14:46 AM6/24/20
to Gremlin-users
Hi Stephen,

thanks for the great effort. I think it might do the trick. The key here I could not come up with is your proposed way of checking whether something is not iterable i.e. when is its unfold() == the item itself.

I think I could even tweak it more: The presented logic would only have to be put in place when the result range has a previously known count of 1. So whenever the argument to limit is != 1, the simple syntax may be used. I don't actually need a closed formula for all cases, I can branch in code.

A few questions though, always assuming from now on that lim = 1

fold().limit(local,lim).unfold() will be chosen when the element is scalar. In the case of lim = 1, this should be equivalent to limit(global, 1), right?

Also in case of lim = 1, could the second path become 

limit(local,lim).fold() ?


So the resulting method could become (pseudo)

LimitLocal(count) => count == 1
 ? __.choose(__.as('a').unfold().limit(1).where(eq('a')), limit(1), limit(local, 1).fold())
  :  __.limit(local, count)

Does this miss something ?
Again, thanks a lot!

Daniel

Stephen Mallette

unread,
Jun 25, 2020, 7:44:57 AM6/25/20
to gremli...@googlegroups.com
A few questions though, always assuming from now on that lim = 1

I wish I'd have known that lim=1 because I seem to remember having something "nice" that worked but then i was like, "this is dumb, it only works for lim=1". I have no idea what that approach was now.......

LimitLocal(count) => count == 1
 ? __.choose(__.as('a').unfold().limit(1).where(eq('a')), limit(1), limit(local, 1).fold())
  :  __.limit(local, count)

Does this miss something ?

It seems fine as long as the else-condition for lim>1 wants local scoping for scalar:

gremlin> g.V().limit(local, 2)

==>v[1]
==>v[2]
==>v[3]
==>v[4]
==>v[5]
==>v[6]
gremlin>
gremlin> g.V().fold().limit(local,2)

==>[v[1],v[2]]


I think that's what you expect, so I think you have your solution.
 

To unsubscribe from this group and stop receiving emails from it, send an email to gremlin-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/gremlin-users/bb81b690-6dae-4d07-8a46-b771447afa51o%40googlegroups.com.

Daniel C. Weber

unread,
Jun 25, 2020, 9:03:05 AM6/25/20
to Gremlin-users
Yes, the local scoping for scalars is ok, it's a valid query although it shouldn't do anything. But I found neither of our approaches to be fully working, but I think I got it now:

LimitLocal(1) = choose(__.as(_a).unfold().where(eq(_a)), __.limit(local, _b), __.limit(local, _b).fold())

The behaviour I am aimimg for is to preserve the static type of the input traversal, i.e. for g.V() it should be a stream of vertices, for g.V().fold() it should be a stream of arrays of vertices. Outlined below:


g.V().count() | g.V().limit(local, 1) | g.V().fold().limit(local, 1)
    0         |         -             |           []
    1         |         v1            |          [v1]
    2         |       v1, v2          |          [v1]
  etc.


Daniel C. Weber

unread,
Mar 10, 2025, 12:09:55 PMMar 10
to Gremlin-users
Will there be a consolidation of behaviour in TinkerPop 4 ?
Reply all
Reply to author
Forward
0 new messages