mejorado - recorrer lista en java
Manera ingeniosa de iterar sobre matrices paralelas en Java usando foreach (8)
Desde la página oficial de Oracle en el bucle for mejorado:
Finalmente, no es utilizable para bucles que deben iterar sobre múltiples colecciones en paralelo. Estos defectos fueron conocidos por los diseñadores, quienes tomaron la decisión consciente de utilizar una construcción simple y limpia que cubriría la gran mayoría de los casos.
Básicamente, es mejor que uses el ciclo for normal.
Si está utilizando estos pares de matrices para simular un Mapa, siempre puede escribir una clase que implemente la interfaz de Mapa con las dos matrices; esto podría permitirle abstraer mucho del bucle.
Sin mirar su código, no puedo decirle si esta opción es la mejor manera de avanzar, pero es algo que podría considerar.
Heredé un montón de código que hace un uso extenso de matrices paralelas para almacenar pares clave / valor. Realmente tiene sentido hacerlo de esta manera, pero es un poco incómodo escribir bucles que iteren sobre estos valores. Realmente me gusta la nueva construcción de Java foreach, pero no parece que haya una forma de iterar sobre listas paralelas usando esto.
Con un ciclo for
normal, puedo hacerlo fácilmente:
for (int i = 0; i < list1.length; ++i) {
doStuff(list1[i]);
doStuff(list2[i]);
}
Pero en mi opinión, esto no es semánticamente puro, ya que no estamos verificando los límites de list2
durante la iteración. ¿Hay alguna sintaxis ingeniosa similar a la para-cada que puedo usar con listas paralelas?
Yo usaría un Map
yo mismo. Pero tomándote la palabra que un par de matrices tiene sentido en tu caso, ¿qué tal un método de utilidad que toma tus dos matrices y devuelve un contenedor Iterable
?
Conceptualmente:
for (Pair<K,V> p : wrap(list1, list2)) {
doStuff(p.getKey());
doStuff(p.getValue());
}
El contenedor Iterable<Pair<K,V>>
ocultaría la comprobación de límites.
//Do you think I''m sexy?
if(list1.length == list2.length){
for (int i = 0; i < list1.length; ++i) {
doStuff(list1[i]);
doStuff(list2[i]);
}
}
Este fue un ejercicio divertido. Creé un objeto llamado ParallelList que toma un número variable de listas tipadas, y puede iterar sobre los valores en cada índice (devuelto como una lista de valores):
public class ParallelList<T> implements Iterable<List<T>> {
private final List<List<T>> lists;
public ParallelList(List<T>... lists) {
this.lists = new ArrayList<List<T>>(lists.length);
this.lists.addAll(Arrays.asList(lists));
}
public Iterator<List<T>> iterator() {
return new Iterator<List<T>>() {
private int loc = 0;
public boolean hasNext() {
boolean hasNext = false;
for (List<T> list : lists) {
hasNext |= (loc < list.size());
}
return hasNext;
}
public List<T> next() {
List<T> vals = new ArrayList<T>(lists.size());
for (int i=0; i<lists.size(); i++) {
vals.add(loc < lists.get(i).size() ? lists.get(i).get(loc) : null);
}
loc++;
return vals;
}
public void remove() {
for (List<T> list : lists) {
if (loc < list.size()) {
list.remove(loc);
}
}
}
};
}
}
Ejemplo de uso:
List<Integer> list1 = Arrays.asList(new Integer[] {1, 2, 3, 4, 5});
List<Integer> list2 = Arrays.asList(new Integer[] {6, 7, 8});
ParallelList<Integer> list = new ParallelList<Integer>(list1, list2);
for (List<Integer> ints : list) {
System.out.println(String.format("%s, %s", ints.get(0), ints.get(1)));
}
Que imprimiría:
1, 6
2, 7
3, 8
4, null
5, null
Este objeto admite listas de longitudes variables, pero claramente podría modificarse para ser más estricto.
Lamentablemente, no pude deshacerme de una advertencia del compilador en el constructor ParallelList: A generic array of List<Integer> is created for varargs parameters
, así que si alguien sabe cómo deshacerse de eso, hágamelo saber :)
Con Java 8, los utilizo para enlazar de manera sexy:
//parallel loop
public static <A, B> void loop(Collection<A> a, Collection<B> b, IntPredicate intPredicate, BiConsumer<A, B> biConsumer) {
Iterator<A> ait = a.iterator();
Iterator<B> bit = b.iterator();
if (ait.hasNext() && bit.hasNext()) {
for (int i = 0; intPredicate.test(i); i++) {
if (!ait.hasNext()) {
ait = a.iterator();
}
if (!bit.hasNext()) {
bit = b.iterator();
}
biConsumer.accept(ait.next(), bit.next());
}
}
}
//nest loop
public static <A, B> void loopNest(Collection<A> a, Collection<B> b, BiConsumer<A, B> biConsumer) {
for (A ai : a) {
for (B bi : b) {
biConsumer.accept(ai, bi);
}
}
}
Un ejemplo, con estas 2 listas:
List<Integer> a = Arrays.asList(1, 2, 3);
List<String> b = Arrays.asList("a", "b", "c", "d");
Bucle dentro del tamaño mínimo de ayb :
loop(a, b, i -> i < Math.min(a.size(), b.size()), (x, y) -> {
System.out.println(x + " -> " + y);
});
Salida:
1 -> a
2 -> b
3 -> c
Bucle dentro del tamaño máximo de ayb (los elementos de la lista más corta se reciclarán):
loop(a, b, i -> i < Math.max(a.size(), b.size()), (x, y) -> {
System.out.println(x + " -> " + y);
});
Salida:
1 -> a
2 -> b
3 -> c
1 -> d
Loop n veces ((los elementos se ciclarán si n es más grande que el tamaño de las listas):
loop(a, b, i -> i < 5, (x, y) -> {
System.out.println(x + " -> " + y);
});
Salida:
1 -> a
2 -> b
3 -> c
1 -> d
2 -> a
Bucle para siempre:
loop(a, b, i -> true, (x, y) -> {
System.out.println(x + " -> " + y);
});
Aplicar a su situación:
loop(list1, list2, i -> i < Math.min(a.size(), b.size()), (e1, e2) -> {
doStuff(e1);
doStuff(e2);
});
Puede usar una segunda restricción en su bucle for:
for (int i = 0; i < list1.length && i < list2.length; ++i)
{
doStuff(list1[i]);
doStuff(list2[i]);
}//for
Uno de mis métodos preferidos para atravesar colecciones es el ciclo for-each, pero como menciona el tutorial de Oracle, cuando se trata de colecciones paralelas para usar el iterador en lugar del for-each .
La siguiente fue una respuesta de Martin v. Löwis en una publicación similar:
it1 = list1.iterator();
it2 = list2.iterator();
while(it1.hasNext() && it2.hasNext())
{
value1 = it1.next();
value2 = it2.next();
doStuff(value1);
doStuff(value2);
}//while
La ventaja del iterador es que es genérico, de modo que si no sabe qué colecciones se están usando, utilice el iterador; de lo contrario, si sabe cuáles son sus colecciones, entonces conoce las funciones de longitud / tamaño y, por lo tanto, el ciclo for-loop regular con la restricción adicional se puede utilizar aquí. (Tenga en cuenta que estoy siendo muy plural en esta publicación, ya que una posibilidad interesante sería que las colecciones utilizadas sean diferentes, por ejemplo, una podría ser una lista y la otra una matriz, por ejemplo)
Espero que esto haya ayudado.
Respuesta simple: No.
¿Quieres iteración sexy y código de bytes Java? Echa un vistazo a Scala: Scala para el bucle en dos listas al mismo tiempo
Descargo de responsabilidad: Esta es una respuesta de "use otro idioma". Créeme, desearía que Java tuviera una iteración sexy en paralelo, pero nadie comenzó a desarrollar en Java porque quieren un código sexy.
ArrayIterator le permite evitar la indexación, pero no puede usar un bucle for-each
sin escribir una clase separada o al menos funcionar. Como comenta @Alexei Blue, la recomendación oficial (en The Collection Interface ) es: "Use Iterator
lugar del constructo for-each
cuando necesite: ... Iterar sobre múltiples colecciones en paralelo.":
import static com.google.common.base.Preconditions.checkArgument;
import org.apache.commons.collections.iterators.ArrayIterator;
// …
checkArgument(array1.length == array2.length);
Iterator it1 = ArrayIterator(array1);
Iterator it2 = ArrayIterator(array2);
while (it1.hasNext()) {
doStuff(it1.next());
doOtherStuff(it2.next());
}
Sin embargo:
- La indexación es natural para las matrices: una matriz es, por definición, algo que indicas, y un bucle numérico para, como en tu código original, es perfectamente natural y más directo.
- Los pares clave-valor forman naturalmente un
Map
, como comenta @Isaac Truett, por lo que lo más útil sería crear mapas para todas sus matrices paralelas (por lo que este bucle solo estaría en la función de fábrica que crea los mapas), aunque esto sería ineficiente si solo quieres iterar sobre ellos. (Utilice Multimap si necesita admitir duplicados). - Si tiene muchos de estos, podría (parcialmente) implementar
ParallelArrayMap<>
(es decir, un mapa respaldado por matrices paralelas), o quizásParallelArrayHashMap<>
(para agregar unHashMap
si desea búsqueda eficiente por clave), y usar eso , lo que permite la iteración en el orden original. Sin embargo, esto es probablemente exagerado, pero permite una respuesta sexy.
Es decir:
Map<T, U> map = new ParallelArrayMap<>(array1, array2);
for (Map.Entry<T, U> entry : map.entrySet()) {
doStuff(entry.getKey());
doOtherStuff(entry.getValue());
}
Filosóficamente, el estilo de Java es tener tipos explícitos y nombrados , implementados por clases. Entonces cuando dice "[Tengo] matrices paralelas [que] almacenan pares de clave / valor", Java responde "Escribir una clase ParallelArrayMap
que implementa Map
(pares clave / valor) y que tiene un constructor que toma matrices paralelas, y luego puede usar entrySet
para devolver un Set
que puede iterar, ya que Set
implementa Collection
. "- haga que la estructura sea explícita en un tipo implementado por una clase.
Para iterar sobre dos colecciones o matrices paralelas, desea iterar sobre un Iterable<Pair<T, U>>
, que los idiomas menos explícitos le permiten crear con zip
(que las llamadas a @Isaac Truett wrap
). Esto no es idiomático Java, sin embargo, ¿cuáles son los elementos del par? Ver Java: ¿Cómo escribir una función zip
? ¿Cuál debería ser el tipo de devolución? para una extensa discusión sobre cómo escribir esto en Java y por qué se desaconseja.
Este es exactamente el tradeoff estilístico que hace Java: usted sabe exactamente de qué tipo es todo y tiene que especificarlo e implementarlo.