Come scorrere più elegantemente attraverso raccolte parallele?

Diciamo che ho 2 collezioni parallele, ad esempio: un elenco di nomi di persone in un List e un elenco della loro età in un List nello stesso ordine (in modo che ogni indice specificato in ogni raccolta si riferisca alla stessa persona ).

Voglio scorrere entrambe le raccolte contemporaneamente e recuperare il nome e l’età di ogni persona e fare qualcosa con esso. Con gli array questo è fatto facilmente con:

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

Quale sarebbe il modo più elegante (in termini di leggibilità e velocità) di farlo con le collezioni?

Creerei un nuovo object che incapsula i due. Buttalo nell’array e fallo scorrere sopra.

 List 

Dove

 public class Person { public string name; public int age; } 
 it1 = coll1.iterator(); it2 = coll2.iterator(); while(it1.hasNext() && it2.hasNext()) { value1 = it1.next(); value2 = it2.next(); do something with it1 and it2; } 

Questa versione termina quando viene esaurita la raccolta più breve; in alternativa, è ansible continuare finché non si esaurisce quello più lungo, impostando il valore 1 risp. valore2 su null.

Potresti creare un’interfaccia per questo:

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

E poi hai:

 Collection c1 = ... Collection c2 = ... zip(c1, c2, new ZipIterator() { public boolean each(String s, Long l) { ... } }); 
 for (int i = 0; i < names.length; ++i) { name = names.get(i); age = ages.get(i); // do your stuff } 

Non importa. Il tuo codice non otterrà punti per l'eleganza. Fallo e basta così che funzioni. E per favore non gonfiare.

Ho preso @cletus comment e lo ho migliorato abit, ed è quello che uso:

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

Uso:

 zip(list1, list2, (v1, v2) -> { // Do stuff }); 

Mentre le soluzioni inviate sono corrette, preferisco la seguente perché segue le guide dall’effettivo elemento java 57: minimizza l’ambito delle variabili locali:

  for (Iterator i = lst1.iterator(), ii = lst2.iterator(); i.hasNext() && ii.hasNext(); ) { String e1 = i.next(); String e2 = ii.next(); .... } 

Come suggerito da jeef3, modellare il vero dominio piuttosto che tenere elenchi separati, implicitamente accoppiati è la strada giusta da percorrere … quando questa è un’opzione.

Ci sono vari motivi per cui potresti non essere in grado di adottare questo approccio. Se è così…

A. È ansible utilizzare un approccio di callback, come suggerito da cletus.

B. È comunque ansible scegliere di esporre un iteratore che espone l’elemento object dominio per ogni istanza composita. Questo approccio non ti obbliga a mantenere una struttura di lista parallela attorno.

 private List _names = ...; private List _ages = ...; Iterator allPeople() { final Iterator ni = _names.iterator(); final Iterator 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. Puoi utilizzare una variante di questo e utilizzare un’API di cursore di stile RowSet. Diciamo che IPerson è un’interfaccia che descrive la Persona. Quindi possiamo fare:

 public interface IPerson { String getName(); void setName(String name); ... } public interface ICursor { boolean next(); T current(); } private static class PersonCursor implements IPerson, ICursor { private final List _names; ... private int _index = -1; PersonCursor(List names, List 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 _names = ...; private List _ages = ...; Cursor allPeople() { return new PersonCursor(_names, _ages); } 

Si noti che l’approccio B deve anche essere fatto per supportare gli aggiornamenti da elencare introducendo un’interfaccia Dominio e facendo in modo che Iterator restituisca oggetti “live”.

Ho appena postato questa funzione in questa domanda simile (che @Nils von Barth afferma non è un duplicato;)), ma è ugualmente applicabile qui:

 public static  List zipLists( BiFunction factory, Iterable left, Iterable right) { Iterator lIter = left.iterator(); Iterator rIter = right.iterator(); ImmutableList.Builder 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(); } 

È quindi ansible fornire un’operazione di fabbrica per BiFunction , ad esempio un costruttore di tipo valore:

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

Se si desidera semplicemente eseguire un’iterazione su di essi e eseguire alcune operazioni, anziché creare una nuova raccolta, è ansible scambiare BiFunction per un BiConsumer e BiConsumer la funzione.