64

Say I have 2 parallel collections, eg: a list of people's names in a List<String> and a list of their age in a List<Int> in the same order (so that any given index in each collection refers to the same person).

I want to iterate through both collections at the same time and fetch the name and age of each person and do something with it. With arrays this is easily done with:

for (int i = 0; i < names.length; i++) {
   do something with names[i] ....
   do something with ages[i].....
}

What would be the most elegant way (in terms of readability and speed) of doing this with collections?

tekumara
  • 8,357
  • 10
  • 57
  • 69
  • possible duplicate of [Best way to iterate over two lists simultaneously?](http://stackoverflow.com/questions/3137944/best-way-to-iterate-over-two-lists-simultaneously) – Greg Mattes Dec 12 '11 at 21:05
  • That question is subtly different: it's about how to iterate _outside_ a class over two collections _inside_ a class, which changes the interface a bit. – Nils von Barth Jun 05 '15 at 05:03

8 Answers8

89
it1 = coll1.iterator();
it2 = coll2.iterator();
while(it1.hasNext() && it2.hasNext()) {
   value1 = it1.next();
   value2 = it2.next();
   do something with it1 and it2;
}

This version terminates when the shorter collection is exhausted; alternatively, you could continue until the longer one is exhausted, setting value1 resp. value2 to null.

Martin v. Löwis
  • 124,830
  • 17
  • 198
  • 235
  • 1
    thanks for the answer but i didn't understand you in case when we want to continue until the longer one is exhausted – Hayi Jan 21 '15 at 14:57
  • 5
    This is in fact the official answer: "Use Iterator instead of the for-each construct when you need to: ... Iterate over multiple collections in parallel." [The Java™ Tutorials: The Collection Interface](http://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html) – Nils von Barth Jun 05 '15 at 04:54
  • 3
    To handle one list running out first, you can use `value1 = it1.hasNext() ? it1.next() : null;` (ditto for `it2`) to check before getting the next element. Alternatively, you could use try-catch with `NoSuchElementException`, which is a bit longer. – Nils von Barth Jun 05 '15 at 05:07
  • I would suggest adding either a precondition check that the collections are the same size, or a postcondition check that both iterators are drained. It's rare that you really want to process two lists in parallel but don't care if one is larger than the other (and if you're really doing that, and you're sure it's not a code smell, just remove the checks). `checkArgument(coll1.size() == coll2.size())` or `checkArgument(!it1.hasNext() && !it2.hasNext())` with Guava's `Preconditions`. – dimo414 Feb 24 '17 at 16:53
36

I would create a new object that encapsulates the two. Throw that in the array and iterate over that.

List<Person>

Where

public class Person {
    public string name;
    public int age;
}
jeef3
  • 2,007
  • 2
  • 17
  • 22
  • 2
    +1 - very reasonable suggestion, as it is the most extendable – James Black Sep 02 '09 at 04:34
  • 2
    This is the obvious solution - define objects & domain correctly: Age and Name are attributes of a person. Map them in and work with them. – Precipitous Sep 02 '09 at 04:36
  • I agree this is the most elegant solution. – André Chalella Sep 02 '09 at 13:25
  • By doing this I was able to refactor away a lot of parallel list manipulation into an object that became an entity in its own right and helped me understand the domain better. This made for cleaner, clearer code. – tekumara Oct 05 '09 at 00:55
  • 27
    It doesn't quite answer the question of how to create such a List when given separate input lists of name and age however. So an elegant way of iterating over both is still needed in such a case. – Dave L. Apr 12 '11 at 23:30
  • can anyone please give some program of this? – Rahul Kulhari Oct 08 '13 at 12:01
  • 2
    This is the idiomatic Java answer: define a type for it. See [What is the equivalent of the C++ Pair in Java?](http://stackoverflow.com/questions/156275/what-is-the-equivalent-of-the-c-pairl-r-in-java) for why anonymous pairs are discouraged in Java. – Nils von Barth Jun 16 '15 at 02:35
  • 5
    It's good advice when you're able to refactor your code to use one list, but this answer fails to describe *how* to construct such a list if you're really starting with two separate lists (e.g. if they're coming from different data sources). – dimo414 Feb 24 '17 at 16:50
  • 1
    -1. I can also become a farmer so I don't need to solve programming problems, or I could send a patch to add parallel iteration as a feature. – petabyte Mar 16 '17 at 16:08
13
for (int i = 0; i < names.length; ++i) {
  name = names.get(i);
  age = ages.get(i);
  // do your stuff
}

It doesn't really matter. Your code won't get points for elegance. Just do it so that it works. And please don't bloat.

André Chalella
  • 13,788
  • 10
  • 54
  • 62
  • 13
    Be forewarned here that not all Lists have efficient get(int) implementations. The double-iterator is less likely to be inefficient. – TREE Sep 02 '09 at 13:12
10

You could create an interface for it:

public interface ZipIterator<T,U> {
  boolean each(T t, U u);
}

public class ZipUtils {
  public static <T,U> boolean zip(Collection<T> ct, Collection<U> cu, ZipIterator<T,U> each) {
    Iterator<T> it = ct.iterator();
    Iterator<U> iu = cu.iterator();
    while (it.hasNext() && iu.hasNext()) {
      if (!each.each(it.next(), iu.next()) {
        return false;
      }
    }
    return !it.hasNext() && !iu.hasNext();
  }
}

And then you have:

Collection<String> c1 = ...
Collection<Long> c2 = ...
zip(c1, c2, new ZipIterator<String, Long>() {
  public boolean each(String s, Long l) {
    ...
  }
});
cletus
  • 616,129
  • 168
  • 910
  • 942
  • 3
    And with Java 8's lambdas you can even use it this way: `zip(c1, c2, (s, l) -> ... )`. (Also, your ZipIterator is basically the same as a BiConsumer.) – Paŭlo Ebermann May 13 '16 at 13:52
  • seems to be the most elegantly way with java 8 lambdas. Note you get change the type from `Collection` to `Iterable` to make it more general – Sisyphus Nov 05 '17 at 14:02
4

I took @cletus comment and Improved it abit, And that's what I use:

public static <T,U> void zip(Collection<T> ct, Collection<U> cu, BiConsumer<T, U> consumer) {
    Iterator<T> it = ct.iterator();
    Iterator<U> iu = cu.iterator();
    while (it.hasNext() && iu.hasNext()) {
        consumer.accept(it.next(), iu.next());
    }
}

Usage:

zip(list1, list2, (v1, v2) -> {
    // Do stuff
});
StationaryTraveller
  • 1,449
  • 2
  • 19
  • 31
  • It'd be wonderful if you could make a `zip` that consumed an arbitrary number of input streams (perhaps all of the same type). – Ahmed Fasih Nov 09 '18 at 15:43
2

While the submitted solutions are correct I prefer the following one because it follows the guides from effective java item 57: minimize the scope of local variables:

    for (Iterator<String> i = lst1.iterator(), ii = lst2.iterator(); i.hasNext() && ii.hasNext(); ) {
        String e1 = i.next();
        String e2 = ii.next();
        ....
    }
Liviu Stirb
  • 5,876
  • 3
  • 35
  • 40
0

As suggested by jeef3, modeling the true domain rather than keeping separate, implicitly coupled Lists is the right way to go... when this is an option.

There are various reasons why you might not be able to adopt this approach. If so...

A. You can use a callback approach, as suggested by cletus.

B. You can still choose to expose an Iterator that exposes domain object element for each composite instance. This approach doesn't force you to keep a parallel List structure around.

private List<String> _names = ...;
private List<Integer> _ages = ...;

Iterator<Person> allPeople() {
  final Iterator<String> ni = _names.iterator();
  final Iterator<Integer> ai = _ages.iterator();
  return new Iterator() {
    public boolean hasNext() {
      return ni.hasNext();
    }
    public Person next() {
      return new Person(ni.next(), ai.next());
    }
    public void remove() {
      ni.remove();
      ai.remove();
    }
  };
}

C. You can use a variation of this and use a RowSet style cursor API. Let's say IPerson is an interface that describes Person. Then we can do:

public interface IPerson {
  String getName();
  void setName(String name);
  ...
}

public interface ICursor<T> {
  boolean next();
  T current();
}

private static class PersonCursor implements IPerson, ICursor<IPerson> {
  private final List<String> _names;
  ...
  private int _index = -1;

  PersonCursor(List<String> names, List<Integer> ages) {
    _names = names;
    ...
  }

  public boolean next() {
    return ++_index < _names.size();
  }

  public Person current() {
    return this;
  }

  public String getName() {
    return _names.get(_index);
  }

  public void setName(String name) {
    _names.set(0, name);
  }

  ...
}

private List<String> _names = ...;
private List<Integer> _ages = ...;

Cursor<Person> allPeople() {
  return new PersonCursor(_names, _ages);
}

Note that the B approach also be made to support updates to list by introducing a Domain interface, and having the Iterator return 'live' objects.

Dilum Ranatunga
  • 13,254
  • 3
  • 41
  • 52
0

I just posted this function in this similar question (which @Nils von Barth asserts is not a duplicate ;) ), but it's equally applicable here:

public static <L,R,M> List<M> zipLists(
    BiFunction<L,R,M> factory, Iterable<L> left, Iterable<R> right) {
  Iterator<L> lIter = left.iterator();
  Iterator<R> rIter = right.iterator();
  ImmutableList.Builder<M> builder = ImmutableList.builder();

  while (lIter.hasNext() && rIter.hasNext()) {
    builder.add(factory.apply(lIter.next(), rIter.next()));
  }

  // Most of the existing solutions fail to enforce that the lists are the same
  // size. That is a *classic* source of bugs. Always enforce your invariants!
  checkArgument(!lIter.hasNext(),
      "Unexpected extra left elements: %s", ImmutableList.copyOf(lIter));
  checkArgument(!rIter.hasNext(),
      "Unexpected extra right elements: %s", ImmutableList.copyOf(rIter));
  return builder.build();
}

You can then provide a factory operation for the BiFunction, such as a value-type's constructor:

List<Person> people = zipLists(Person::new, names, ages);

If you really just want to iterate over them and do some operation, rather than construct a new collection, you could swap the BiFunction for a BiConsumer and have the function return void.

Community
  • 1
  • 1
dimo414
  • 47,227
  • 18
  • 148
  • 244