views:

267

answers:

8

Is it possible to merge iterators in Java? I have two iterators and I want to combine/merge them so that I could iterate though their elements in one go (in same loop) rather than two steps. Is that possible?

Note that the number of elements in the two lists can be different therefore one loop over both lists is not the solution.

Iterator<User> pUsers = userService.getPrimaryUsersInGroup(group.getId());
Iterator<User> sUsers = userService.getSecondaryUsersInGroup(group.getId());

while(pUsers.hasNext()) {
  User user = pUsers.next();
  .....
}

while(sUsers.hasNext()) {
  User user = sUsers.next();
  .....
}
+10  A: 

You could create your own implementation of the Iterator interface which iterates over the iterators:

public class IteratorOfIterators implements Iterator {
    private final List<Iterator> iterators;

    public IteratorOfIterators(List<Iterator> iterators) {
        this.iterators = iterators;
    }

    public IteratorOfIterators(Iterator... iterators) {
        this.iterators = Arrays.asList(iterators);
    }


    public boolean hasNext() { /* implementation */ }

    public Object next() { /* implementation */ }

    public void remove() { /* implementation */ }
}

(I've not added generics to the Iterator for brevity.) The implementation is not too hard, but isn't the most trivial, you need to keep track of which Iterator you are currently iterating over, and calling next() you'll need to iterate as far as you can through the iterators until you find a hasNext() that returns true, or you may hit the end of the last iterator.

I'm not aware of any implementation that already exists for this.

Update:
I've up-voted Andrew Duffy's answer - no need to re-invent the wheel. I really need to look into Guava in more depth.

I've added another constructor for a variable number of arguments - almost getting off topic, as how the class is constructed here isn't really of interest, just the concept of how it works.

Noel M
Add a varargs constructor as well and I'd vote you up :-)
Christoffer
+16  A: 

Guava (formerly Google Collections) has Iterators.concat.

Andrew Duffy
There appears to be no way to get that second link working properly :(
Andrew Duffy
second link working for me.
youssef azari
Yeah it worked fine for me the way you originally had it.
ColinD
@youssef @Colin: The intent of the link is to go to the `concat()` method immediately without scrolling (using the `#` hash fragment). That part was however not correctly URL-encoded. I fixed it (long live Firefox with automagic URL-encoding when copypasting links from its address bar).
BalusC
Hmm interesting. Though cannot use it as we aren't using Guava in our software.
Jahanzeb Farooq
@BalusC Yes, and he originally had the URL basically the same as you have it now.
ColinD
@Jahanzeb Farooq Well you should be! =) That said, obviously that's often not a decision you can just make yourself.
ColinD
+3  A: 

move your loop to a method and pass the iterator to method.

void methodX(Iteartor x) {
    while (x.hasNext()) {
        ....
    }
}
mohammad shamsi
Thanks. But I still have to call the method twice.
Jahanzeb Farooq
This seems to me to be the simplest solution (without Guava) for your specific case. Yes you have to call methodX twice but you'd have to make two method calls anyway, one to merge the iterators, and one to do what methodX does.Your own solution with the flag seems more complicated and probably more code.
Alb
A: 

every Iterator object holds own memory location (adress), so you can't simply "merge" them. except if you extend iterator class and write your own implementation there.

If you are dealing with the same number of objects in both iterators an alternative solution would be to process two iterators in one loop like this :

   while (iterator1.hasNext() && iterator2.hasNext()) {
      // code
    }
youssef azari
+2  A: 

I haven't written Java code in a while, and this got me curious to whether I've still "got it".

First try:

import java.util.Iterator;
import java.util.Arrays; /* For sample code */

public class IteratorIterator<T> implements Iterator<T> {
    private final Iterator<T> is[];
    private int current;

    public IteratorIterator(Iterator<T>... iterators)
    {
            is = iterators;
            current = 0;
    }

    public boolean hasNext() {
            while ( current < is.length && !is[current].hasNext() )
                    current++;

            return current < is.length;
    }

    public T next() {
            while ( current < is.length && !is[current].hasNext() )
                    current++;

            return is[current].next();
    }

    public void remove() { /* not implemented */ }

    /* Sample use */
    public static void main(String... args)
    {
            Iterator<Integer> a = Arrays.asList(1,2,3,4).iterator();
            Iterator<Integer> b = Arrays.asList(10,11,12).iterator();
            Iterator<Integer> c = Arrays.asList(99, 98, 97).iterator();

            Iterator<Integer> ii = new IteratorIterator<Integer>(a,b,c);

            while ( ii.hasNext() )
                    System.out.println(ii.next());
    }
}

You could of course use more Collection classes rather than a pure array + index counter, but this actually feels a bit cleaner than the alternative. Or am I just biased from writing mostly C these days?

Anyway, there you go. The answer to you question is "yes, probably".

Christoffer
+1  A: 

an iterator comes FROM a collection or a set.
why not use the method already available
Collection.addAll(Collection c);
and then create your iterator from the last object.
this way, your iterator will iterate all the contents of both collection.

p01ntbl4nk
+2  A: 

Also the Apache Commons Collection have several classes for manipulating Iterators, like the IteratorChain, that wraps a number of Iterators.

Ither
A: 

I would refactor the original design from:

Iterator<User> pUsers = userService.getPrimaryUsersInGroup(group.getId());
Iterator<User> sUsers = userService.getSecondaryUsersInGroup(group.getId());

To something like:

Iterator<User> users = userService.getUsersInGroup(group.getId(), User.PRIMARY, User.SECONDARY, ...);
Billworth Vandory