2009-09-02 13 views
35

Digamos que tengo 2 colecciones paralelas, por ejemplo: una lista de nombres de personas en List<String> y una lista de su edad en List<Int> en el mismo orden (para que cualquier índice dado en cada colección se refiera a la misma persona).¿Cómo iterar más elegantemente a través de colecciones paralelas?

Quiero iterar a través de ambas colecciones al mismo tiempo y buscar el nombre y la edad de cada persona y hacer algo con ella. Con los arreglos de esto se hace fácilmente con:

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

¿Cuál sería la forma más elegante (en términos de facilidad de lectura y velocidad) de hacer esto con las colecciones?

+0

posible duplicado de [Bes ¿Cómo puedo iterar en dos listas al mismo tiempo?] (http://stackoverflow.com/questions/3137944/best-way-to-iterate-over-thwo-lists-simultaneously) –

+0

Esa pregunta es sutilmente diferente: se trata de cómo iterar _outside_ una clase sobre dos colecciones _inside_ una clase, lo que cambia un poco la interfaz. –

Respuesta

30

Crearía un nuevo objeto que encapsula los dos. Tíralo en la matriz e itera sobre eso.

List<Person> 

Dónde

public class Person { 
    public string name; 
    public int age; 
} 
+2

+1 - sugerencia muy razonable, ya que es la más extensible –

+1

Esta es la solución obvia: defina correctamente los objetos y el dominio: la edad y el nombre son atributos de una persona. Localícelos y trabaje con ellos. – Precipitous

+0

Acepto que esta es la solución más elegante. –

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

Esta versión finaliza cuando se agota la colección más corta; alternativamente, puede continuar hasta que el más largo se agote, estableciendo value1 resp. value2 a null.

+0

gracias por la respuesta pero no te entendí en caso de que cuando queremos continuar hasta que se agota el más largo – Youssef

+4

Esta es de hecho la respuesta oficial: "Use Iterator en lugar de la construcción de cada uno cuando necesite: ... Iterar sobre múltiples colecciones en paralelo. " [Los tutoriales de Java ™: la interfaz de colección] (http://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html) –

+1

Para manejar una lista que se está acabando primero, puede usar 'value1 = it1 .hasNext()? it1.next(): null; '(ditto para' it2') para verificar antes de obtener el siguiente elemento. Alternativamente, podría usar try-catch con 'NoSuchElementException', que es un poco más largo. –

7
for (int i = 0; i < names.length; ++i) { 
    name = names.get(i); 
    age = ages.get(i); 
    // do your stuff 
} 

Realmente no importa. Su código no obtendrá puntos por elegancia. Solo hazlo para que funcione. Y por favor no hinches.

+8

Tenga en cuenta que no todas las listas tienen implementaciones get (int) eficientes. El doble iterador es menos probable que sea ineficiente. – TREE

9

Se puede crear una interfaz para que:

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(); 
    } 
} 

y entonces usted tiene:

Collection<String> c1 = ... 
Collection<Long> c2 = ... 
zip(c1, c2, new ZipIterator<String, Long>() { 
    public boolean each(String s, Long l) { 
    ... 
    } 
}); 
+2

Y con las lambdas de Java 8 puedes incluso usarlo de esta manera: 'zip (c1, c2, (s, l) -> ...)'. (Además, su ZipIterator es básicamente el mismo que un BiConsumer.) –

+0

parece ser la forma más elegante con java 8 lambdas. Tenga en cuenta que obtiene cambiar el tipo de 'Colección' a' Iterable' para hacerlo más general – Sisyphus

0

Como sugiere jeef3, el modelado de la verdad dominio en lugar de mantener sepa tasa, Listas implícitamente acopladas es el camino correcto a seguir ... cuando esta es una opción.

Existen varias razones por las que es posible que no pueda adoptar este enfoque. Si es así ...

A. Puede utilizar un enfoque de devolución de llamada, como lo sugiere el cletus.

B. Aún puede optar por exponer un iterador que expone el elemento de objeto de dominio para cada instancia compuesta. Este enfoque no te obliga a mantener una estructura de listas paralelas.

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. Puede usar una variación de esto y usar una API de cursor del estilo RowSet. Digamos que IPerson es una interfaz que describe Persona. Entonces podemos hacer:

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); 
} 

Tenga en cuenta que el enfoque B también ser hecho para apoyar cambios a la lista mediante la introducción de una interfaz de dominio, y tener el retorno iterador 'en vivo' los objetos.

0

Me acaba de publicar esta función en este similar question (que @Nils von Barth afirma que no es un duplicado;)), pero es igualmente aplicable aquí:

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(); 
} 

A continuación, puede proporcionar una operación de fábrica para la BiFunction , tales como el constructor de un tipo de valor:

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

Si realmente que desea es iterar sobre ellos y hacer alguna operación, en lugar de construir una nueva colección, usted podría cambie el BiFunction por un BiConsumer y tenga la función return void.

1

Tomé comentario @cletus y lo mejoró un poco, y eso es lo que yo uso:

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()); 
    } 
} 

Uso:

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

Mientras que las soluciones presentadas son correctas, prefiero el siguiente porque sigue las guías del elemento efectivo de Java 57: minimizan el alcance de las variables locales:

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