views:

466

answers:

6

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?

+5  A: 
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
+3  A: 
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é Neves
Be forewarned here that not all Lists have efficient get(int) implementations. The double-iterator is less likely to be inefficient.
TREE
+11  A: 

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
+1 - very reasonable suggestion, as it is the most extendable
James Black
Precipitous
I agree this is the most elegant solution.
André Neves
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.
tukushan
+3  A: 

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
A: 

Agree with

jeef3

As the way java tend to be.

A: 

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