Quantcast

Traversing collections with indexes

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Traversing collections with indexes

Andrew Taylor
It's currently possible to iterate though a collection in groovy with an
index:

     collection.eachWithIndex { it, index ->
         ...
     }

This is pretty useful, but there's no equivalent for the other methods
that traverse collections such as collect, find, findAll, etc.

For these, the alternative is usually something like this:

     def index = 0
     collection.collect {
         ...
         index++
         ...
     }

Which is quite ugly.  Also, it involves changing state on a local
variable which is not nice from a functional programming perspective.

An alternative exists:

     [0..<collection.size(), collection].transpose().collect { index, it ->
         ...
     }

But this is getting pretty obfuscated, and only works on collections
where the size is known.  On the other hand, eachWithIndex works with
any Iterable or Iterator without knowing the size in advance.

A solution for this I've been playing around with is a new method
enumerate().  This mirrors enumerate in python, where it is used for the
same purpose.  This illustrates how it works:

     assert ['one', 'two', 'three'].enumerate().toList() ==
         [[0, 'one'], [1, 'two'], [2, 'three']]
     assert ['a', 'b', 'c'].enumerate().collect{ index, it ->
         index == 1 ? 'B' : it
     } == ['a', 'B', 'c']

Here's a quick and dirty implementation:

     Iterable.metaClass.enumerate = {
         Iterator iter = delegate.iterator()
         int index = 0
         [
             hasNext: { -> iter.hasNext() },
             next: { -> [index++, iter.next()]}
         ] as Iterator
     }

Is there a better solution in groovy I've missed?  Does anyone else feel
like this would be valuable addition to DefaultGroovyMethods?

--
Andrew Taylor

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Traversing collections with indexes

asafdav2
where seem to be a jira for this - http://jira.codehaus.org/browse/GROOVY-3797
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Traversing collections with indexes

tim yates-2
In reply to this post by Andrew Taylor
I like it :-)

As a side-note, this is one of the use cases I've been looking to when writing my groovy-stream code:

https://github.com/timyates/groovy-stream

It's basically a lazy generator/list comprehension class with which you can do:

import groovy.stream.Stream

def collection = [1,2,3]
def s = Stream.from collection transform { [ idx++, it ] } using idx:0
s.each { idx, it ->
    println "$idx) $it"
}

Or limit the returned iterator with:

import groovy.stream.Stream

def collection = [1,2,3]
def s = Stream.from collection where { it % 2 } transform { [ idx++, it ] } using idx:0
s.each { idx, it ->
    println "$idx) $it"
}

(to get only the odd entries for example)

It also does list comprehension of sorts (as can be seen on the project page), such as:
Stream s = Stream.from x:1..2, y:1..4 where { x + y == 4 }

assert s.collect() == [ [ x:1, y:3 ], [ x:2, y:2 ] ]
I just pushed v0.1, but it could do with some eyes looking over it and seeing where it is broken/wrong/limited/confused

Cheers,

Tim

PS: Writing a better README and a blog about it hopefully done soon ;-)
On 27 April 2012 18:26, Andrew Taylor <[hidden email]> wrote:
It's currently possible to iterate though a collection in groovy with an index:

   collection.eachWithIndex { it, index ->
       ...
   }

This is pretty useful, but there's no equivalent for the other methods that traverse collections such as collect, find, findAll, etc.

For these, the alternative is usually something like this:

   def index = 0
   collection.collect {
       ...
       index++
       ...
   }

Which is quite ugly.  Also, it involves changing state on a local variable which is not nice from a functional programming perspective.

An alternative exists:

   [0..<collection.size(), collection].transpose().collect { index, it ->
       ...
   }

But this is getting pretty obfuscated, and only works on collections where the size is known.  On the other hand, eachWithIndex works with any Iterable or Iterator without knowing the size in advance.

A solution for this I've been playing around with is a new method enumerate().  This mirrors enumerate in python, where it is used for the same purpose.  This illustrates how it works:

   assert ['one', 'two', 'three'].enumerate().toList() ==
       [[0, 'one'], [1, 'two'], [2, 'three']]
   assert ['a', 'b', 'c'].enumerate().collect{ index, it ->
       index == 1 ? 'B' : it
   } == ['a', 'B', 'c']

Here's a quick and dirty implementation:

   Iterable.metaClass.enumerate = {
       Iterator iter = delegate.iterator()
       int index = 0
       [
           hasNext: { -> iter.hasNext() },
           next: { -> [index++, iter.next()]}
       ] as Iterator
   }

Is there a better solution in groovy I've missed?  Does anyone else feel like this would be valuable addition to DefaultGroovyMethods?

--
Andrew Taylor

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

  http://xircles.codehaus.org/manage_email



Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Traversing collections with indexes

Jochen Theodorou
In reply to this post by Andrew Taylor
Am 27.04.2012 19:26, schrieb Andrew Taylor:
[...]

> A solution for this I've been playing around with is a new method
> enumerate(). This mirrors enumerate in python, where it is used for the
> same purpose. This illustrates how it works:
>
> assert ['one', 'two', 'three'].enumerate().toList() ==
> [[0, 'one'], [1, 'two'], [2, 'three']]
> assert ['a', 'b', 'c'].enumerate().collect{ index, it ->
> index == 1 ? 'B' : it
> } == ['a', 'B', 'c']
>
> Here's a quick and dirty implementation:
>
> Iterable.metaClass.enumerate = {
> Iterator iter = delegate.iterator()
> int index = 0
> [
> hasNext: { -> iter.hasNext() },
> next: { -> [index++, iter.next()]}
> ] as Iterator
> }

One problem of this solution is that the type of the list is lost.
Instead of trying to keep the type this will then always create an
ArrayList.

Tim's lazy collection idea could provide a solution for this, in that we
can recognize the lazy wrappers and so go back to the original type and
produce something from that instead.

> Is there a better solution in groovy I've missed? Does anyone else feel
> like this would be valuable addition to DefaultGroovyMethods?

well... long time ago I suggested something like this:

['a','b','c'].collect (def index=0;) {it->
   index==1?'B':it
}

based on the idea to generalize the classic for-loop construct for
custom iterations and other things. Another idea was this:

['a','b','c'].collect {it->
   @Field index=0
   index==1?'B':it
}

which makes index a field of the closure and moves the initialization
into the constructor, thus allowing here to use it for the iteration.
This is more or less from Groovy++ and I think it is available in Groovy
as well. I personally don't like this version and would instead prefer a
less implementation oriented variant of this. There are programming
languages that have this kind of one time initialized local variables,
but I feel not too good about introducing that concept into Groovy somehow.

bye blackdrag

--
Jochen "blackdrag" Theodorou - Groovy Project Tech Lead
blog: http://blackdragsview.blogspot.com/
german groovy discussion newsgroup: de.comp.lang.misc
For Groovy programming sources visit http://groovy-lang.org


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Traversing collections with indexes

Andrew Taylor
On 4/30/2012 4:25 AM, Jochen Theodorou wrote:
>
> One problem of this solution is that the type of the list is lost.
> Instead of trying to keep the type this will then always create an
> ArrayList.

I see the main use-case immediately iterating over the returned
iterator.  An advantage here is that enumeration is lazy.  Turning the
enumeration back into a collection -- with toList() in my example, or to
the original type as you suggest -- forces the enumeration to be
evaluated immediately.

> Tim's lazy collection idea could provide a solution for this, in that we
> can recognize the lazy wrappers and so go back to the original type and
> produce something from that instead.

Yeah, sounds like an ideal solution.  I haven't quite figured out how
that would look.

>> Is there a better solution in groovy I've missed? Does anyone else feel
>> like this would be valuable addition to DefaultGroovyMethods?
>
> well... long time ago I suggested something like this:
>
> ['a','b','c'].collect (def index=0;) {it->
> index==1?'B':it
> }
>
> based on the idea to generalize the classic for-loop construct for
> custom iterations and other things. Another idea was this:
>
> ['a','b','c'].collect {it->
> @Field index=0
> index==1?'B':it
> }
>
> which makes index a field of the closure and moves the initialization
> into the constructor, thus allowing here to use it for the iteration.
> This is more or less from Groovy++ and I think it is available in Groovy
> as well. I personally don't like this version and would instead prefer a
> less implementation oriented variant of this. There are programming
> languages that have this kind of one time initialized local variables,
> but I feel not too good about introducing that concept into Groovy somehow.

I kind of like this direction.  What about setting the closure delegate
to a one value map [index: 0] where index gets incremented each
iteration?  E.g.

     ['a', 'b', 'c'].collect { it -> "$index: $it" }

The "index" variable would come implicitly from the delegate.  If the
owner already has "index" bound, that would still get used with the
default resolution strategy, so it would be mostly backwards compatible.

--
Andrew Taylor

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Traversing collections with indexes

Jochen Theodorou
Am 30.04.2012 21:44, schrieb Andrew Taylor:
[...]
> I kind of like this direction. What about setting the closure delegate
> to a one value map [index: 0] where index gets incremented each
> iteration? E.g.
>
> ['a', 'b', 'c'].collect { it -> "$index: $it" }
>
> The "index" variable would come implicitly from the delegate. If the
> owner already has "index" bound, that would still get used with the
> default resolution strategy, so it would be mostly backwards compatible.

If you as programmer in your program do that it is ok to do so. But as
default not. There is no clear separation of concerns here. Imagine
someone setting a delegate by himself and then forwarding to collect.

bye blackdrag

--
Jochen "blackdrag" Theodorou - Groovy Project Tech Lead
blog: http://blackdragsview.blogspot.com/
german groovy discussion newsgroup: de.comp.lang.misc
For Groovy programming sources visit http://groovy-lang.org


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Loading...