Iterando a través de una lista en orden inverso en Java

251

Estoy migrando un fragmento de código para hacer uso de genéricos. Un argumento para hacerlo es que el ciclo for es mucho más limpio que hacer un seguimiento de los índices o usar un iterador explícito.

En aproximadamente la mitad de los casos, la lista (una ArrayList) se itera en orden inverso mediante el uso de un índice hoy.

¿Alguien puede sugerir una forma más limpia de hacerlo (ya que no me gusta indexed for loopcuando trabajo con colecciones), aunque funciona?

 for (int i = nodes.size() - 1; i >= 0; i--) {
    final Node each = (Node) nodes.get(i);
    ...
 }

Nota: No puedo agregar ninguna dependencia nueva fuera del JDK.

Allain Lalonde
fuente
10
¿Qué tiene de malo usar un índice explícito para iterar sobre una estructura de datos indexados? Al menos te muestra lo que está sucediendo exactamente. Para iterar hacia atrás siempre uso el siguiente modismo un poco más corto:for (int i = nodes.size(); --i >= 0;)
x4u
44
Nada en particular, prefiero programar en una interfaz y no saber qué tipo de lista estoy usando. Aunque me gusta mucho tu mano corta. (+1 comentario)
Allain Lalonde
1
@ x4u: no hay mucho, aunque Iterator es rápido y también permite que los elementos se eliminen fácilmente durante la iteración.
Adamski
2
Esta clase está rota, porque el usuario puede querer iterar por segunda vez sobre el mismo Iterable, o la lista puede cambiar entre cuándo se construye el iterable y cuándo se itera. Estoy seguro de que para sus propósitos solo se asegurará de no hacer eso, pero no sería demasiado difícil arreglar el código; o simplemente robar el código de Guava (licencia Apache 2.0): code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
Kevin Bourrillion
1
Es justo, pero incluso el Guayaba es susceptible a lo mismo si lo estoy leyendo bien. Si un usuario guardara una copia del resultado de forma inversa, tendría el mismo problema.
Allain Lalonde

Respuestas:

447

Prueba esto:

// Substitute appropriate type.
ArrayList<...> a = new ArrayList<...>();

// Add elements to list.

// Generate an iterator. Start just after the last element.
ListIterator li = a.listIterator(a.size());

// Iterate in reverse.
while(li.hasPrevious()) {
  System.out.println(li.previous());
}
John Feminella
fuente
44
No está mal. No utiliza el índice, pero pierde la elegancia de cada sintaxis. +1 de todos modos.
Allain Lalonde
26
Llamar a listIterator () sin un argumento de índice dará un Iterator al comienzo de la lista y, por lo tanto, hasPrevious () devolverá false en la primera llamada.
Adamski
2
Quiere un índice en la listIteratorllamada, creo.
Tom Hawtin - tackline
1
Puede escribir un Iteratorque use un ListIteratorreverso, pero puede que no valga la pena para un ciclo.
Tom Hawtin - tackline
1
No es un bucle, así que tomé esto y lo envolví. pastebin.ca/1759041 entonces, ahora puedo hacerlofor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde
35

Ofertas de guayabaLists#reverse(List) y ImmutableList#reverse(). Como en la mayoría de los casos para Guava, el primero delega a este último si el argumento es un ImmutableList, por lo que puede usar el primero en todos los casos. Estos no crean nuevas copias de la lista, sino simplemente "vistas invertidas" de la misma.

Ejemplo

List reversed = ImmutableList.copyOf(myList).reverse();
Geoffrey Zheng
fuente
23

No creo que sea posible usar la sintaxis del bucle for. Lo único que puedo sugerir es hacer algo como:

Collections.reverse(list);
for (Object o : list) {
  ...
}

... pero no diría que esto es "más limpio" dado que será menos eficiente.

Adamski
fuente
26
y también cambia en su lugar la lista que atraviesas, lo cual es un gran efecto secundario. (Digamos que envuelve esto en un método, cada vez que se le llama, recorre la lista al revés ^^)
jolivier
Esta es la solución más limpia.
jfajunior
14

Opción 1: ¿Ha pensado en invertir la Lista con Colecciones # reverse () y luego usar foreach?

Por supuesto, es posible que también desee refactorizar su código de modo que la lista esté ordenada correctamente para que no tenga que revertirla, lo que usa espacio / tiempo adicional.


EDITAR:

Opción 2: Alternativamente, ¿podría usar una Deque en lugar de una ArrayList? Te permitirá iterar hacia adelante y hacia atrás


EDITAR:

Opción 3: como otros han sugerido, puede escribir un iterador que recorra la lista al revés, aquí hay un ejemplo:

import java.util.Iterator;
import java.util.List;

public class ReverseIterator<T> implements Iterator<T>, Iterable<T> {

    private final List<T> list;
    private int position;

    public ReverseIterator(List<T> list) {
        this.list = list;
        this.position = list.size() - 1;
    }

    @Override
    public Iterator<T> iterator() {
        return this;
    }

    @Override
    public boolean hasNext() {
        return position >= 0;
    }

    @Override
    public T next() {
        return list.get(position--);
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

}


List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");

for (String s : new ReverseIterator<String>(list)) {
    System.out.println(s);
}
Kevin
fuente
1
Lo he pensado, pero el costo de revertirlo es prohibitivo. Además, solo requiere este tipo de iteración aproximadamente la mitad del tiempo. Voltearlo solo movería el problema a la otra mitad.
Allain Lalonde
3
+ para Deque; implementaciones típicas tienen descendingIterator().
trashgod
Esto sería terrible para las listas vinculadas, la variante del OP es mejor.
masterxilo
1
Usar una for eachexpresión es la solución más idiomática en mi opinión. Es bueno darse cuenta de que esto es posible si su Lista implementa Iterable de una manera que itera hacia atrás. Usaré este enfoque y usaré la clase ReverseListIterator de Apache Commons Collections.
David Groomes
11

Podría usar la clase concreta en LinkedListlugar de la interfaz general List. Entonces tienes un descendingIteratorpara iterar con la dirección inversa.

LinkedList<String > linkedList;
for( Iterator<String > it = linkedList.descendingIterator(); it.hasNext(); ) {
    String text = it.next();
}

No sé por qué no hay descendingIteratorcon ArrayList...

tangenos
fuente
9

Esta es una vieja pregunta, pero carece de una respuesta amigable para java8. Aquí hay algunas formas de iterar en reversa la lista, con la ayuda de la API de Streaming:

List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 3, 3, 7, 5));
list.stream().forEach(System.out::println); // 1 3 3 7 5

int size = list.size();

ListIterator<Integer> it = list.listIterator(size);
Stream.generate(it::previous).limit(size)
    .forEach(System.out::println); // 5 7 3 3 1

ListIterator<Integer> it2 = list.listIterator(size);
Stream.iterate(it2.previous(), i -> it2.previous()).limit(size)
    .forEach(System.out::println); // 5 7 3 3 1

// If list is RandomAccess (i.e. an ArrayList)
IntStream.range(0, size).map(i -> size - i - 1).map(list::get)
    .forEach(System.out::println); // 5 7 3 3 1

// If list is RandomAccess (i.e. an ArrayList), less efficient due to sorting
IntStream.range(0, size).boxed().sorted(Comparator.reverseOrder())
    .map(list::get).forEach(System.out::println); // 5 7 3 3 1
Federico Peralta Schaffner
fuente
2
muy agradable, solo un cambio cosmético: int size = list.size (); ListIterator <Integer> it = list.listIterator (tamaño); Stream.generate (it :: anterior) .limit (tamaño) .forEach (System.out :: println);
benez
5

Aquí hay una implementación (no probada) de a ReverseIterable. Cuando iterator()se llama, crea y devuelve una ReverseIteratorimplementación privada , que simplemente asigna llamadas hasNext()a hasPrevious()y las llamadas a next()se asignan previous(). Significa que podría iterar sobre una ArrayListinversa de la siguiente manera:

ArrayList<String> l = ...
for (String s : new ReverseIterable(l)) {
  System.err.println(s);
}

Definición de clase

public class ReverseIterable<T> implements Iterable<T> {
  private static class ReverseIterator<T> implements Iterator {
    private final ListIterator<T> it;

    public boolean hasNext() {
      return it.hasPrevious();
    }

    public T next() {
      return it.previous();
    }

    public void remove() {
      it.remove();
    }
  }

  private final ArrayList<T> l;

  public ReverseIterable(ArrayList<T> l) {
    this.l = l;
  }

  public Iterator<T> iterator() {
    return new ReverseIterator(l.listIterator(l.size()));
  }
}
Adamski
fuente
Me parece correcto, aunque exponerlo como un método estático en lugar de un constructor público evitaría (en la mayoría de los casos) la necesidad de que el cliente especifique el parámetro de tipo. Así es como lo hace Guava ..
Kevin Bourrillion
2
Esta es la mejor implementación, sin embargo, ReverseIteratorfalta el constructor necesario y el código debe usarse en Listlugar de ArrayList.
Andy
5

Si las listas son bastante pequeñas, de modo que el rendimiento no es un problema real, se puede usar el reversemétodo de la Listsclase en Google Guava. Produce un for-eachcódigo bonito , y la lista original permanece igual. Además, la lista reversa está respaldada por la lista original, por lo que cualquier cambio en la lista original se reflejará en la reversa.

import com.google.common.collect.Lists;

[...]

final List<String> myList = Lists.newArrayList("one", "two", "three");
final List<String> myReverseList = Lists.reverse(myList);

System.out.println(myList);
System.out.println(myReverseList);

myList.add("four");

System.out.println(myList);
System.out.println(myReverseList);

Produce el siguiente resultado:

[one, two, three]
[three, two, one]
[one, two, three, four]
[four, three, two, one]

Lo que significa que la iteración inversa de myList se puede escribir como:

for (final String someString : Lists.reverse(myList)) {
    //do something
}
Tobb
fuente
5

Crea una costumbre reverseIterable.

nanda
fuente
No sé por qué esto se está votando, podría hacer un contenedor para la lista que hace esto.
Allain Lalonde
Esto no es menos limpio que llamar a Collections.reverse () en mi humilde opinión.
Adamski
@Allain: acordó re. los votos negativos, aunque no veo por qué ves una llamada a listIterator (list.size ()) como "no limpio". Incluso si lo envuelve, todavía tiene que hacer el mismo método para llamar a alguna parte.
Adamski
Suponga que no, simplemente no está dispuesto a tomar el golpe de rendimiento, por limpieza.
Allain Lalonde
18
Votado como no creo que esta sea una respuesta completa.
Maarten Bodewes
3

Ejemplo muy simple:

List<String> list = new ArrayList<String>();

list.add("ravi");

list.add("kant");

list.add("soni");

// Iterate to disply : result will be as ---     ravi kant soni

for (String name : list) {
  ...
}

//Now call this method

Collections.reverse(list);

// iterate and print index wise : result will be as ---     soni kant ravi

for (String name : list) {
  ...
}
Ravi Kant Soni
fuente
2

También encontré el método inverso de las colecciones de google .

Allain Lalonde
fuente
¿Qué versión de la colección de google? Su enlace ya no existe.
AaA
1

Para tener un código que se vea así:

List<Item> items;
...
for (Item item : In.reverse(items))
{
    ...
}

Pon este código en un archivo llamado "In.java":

import java.util.*;

public enum In {;
    public static final <T> Iterable<T> reverse(final List<T> list) {
        return new ListReverseIterable<T>(list);
    }

    class ListReverseIterable<T> implements Iterable<T> {
        private final List<T> mList;

        public ListReverseIterable(final List<T> list) {
            mList = list;
        }

        public Iterator<T> iterator() {
            return new Iterator<T>() {
                final ListIterator<T> it = mList.listIterator(mList.size());

                public boolean hasNext() {
                    return it.hasPrevious();
                }
                public T next() {
                    return it.previous();
                }
                public void remove() {
                    it.remove();
                }
            };
        }
    }
}
intrepidis
fuente
1
Esto se ha mencionado en otra parte del hilo, pero el listIteratorcampo debe estar dentro de la Iteratorimplementación, no la Iterableimplementación.
Andy
1
¿Por qué usar un tipo de enumeración, no una clase?
Aubin
1
Hace que la clase 'In' no sea instanciable, sin tener que escribir un constructor privado predeterminado. Bueno para clases que solo tienen métodos estáticos.
intrepidis
Diría que abusar de las enumeraciones solo para evitar escribir un constructor privado predeterminado es confuso en el mejor de los casos. ¿Qué tal usar enumeraciones para enumeraciones y clases para clases? Al convertir una clase en una enumeración, también obtiene implícitamente un name()método, un ordinal()método y un static valueOf()método, por ejemplo.
JHH
1
Sí, puedes continuar con tu opinión y eso también funcionará bien, pero pienso lo contrario. Las clases son para crear instancias de objetos y abusar de ellos al incluir un constructor privado predeterminado para inhibir su creación de instancias es, en el mejor de los casos, confuso. En Java, las enumeraciones en realidad son clases, pero que el diseño nunca puede crear instancias.
intrepidis
0

Como se ha sugerido al menos dos veces, puede usar descendingIteratorcon a Deque, en particular con a LinkedList. Si desea utilizar el ciclo for-each (es decir, tener un Iterable), puede construir y utilizar un wraper como este:

import java.util.*;

public class Main {

    public static class ReverseIterating<T> implements Iterable<T> {
        private final LinkedList<T> list;

        public ReverseIterating(LinkedList<T> list) {
            this.list = list;
        }

        @Override
        public Iterator<T> iterator() {
            return list.descendingIterator();
        }
    }

    public static void main(String... args) {
        LinkedList<String> list = new LinkedList<String>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        list.add("E");

        for (String s : new ReverseIterating<String>(list)) {
            System.out.println(s);
        }
    }
}
masterxilo
fuente
-10

Motivo: "No sé por qué no hay ningún iterador descendente con ArrayList ..."

Dado que la lista de matriz no mantiene la lista en el mismo orden en que se agregaron los datos a la lista. Por lo tanto, nunca use Arraylist.

La lista vinculada mantendrá los datos en el mismo orden de AGREGAR a la lista.

Entonces, arriba en mi ejemplo, usé ArrayList () para hacer que el usuario tuerza su mente y haga que entrenen algo desde su lado.

En lugar de esto

List<String> list = new ArrayList<String>();

UTILIZAR:

List<String> list = new LinkedList<String>();

list.add("ravi");

list.add("kant");

list.add("soni");

// Iterate to disply : result will be as ---     ravi kant soni

for (String name : list) {
  ...
}

//Now call this method

Collections.reverse(list);

// iterate and print index wise : result will be as ---     soni kant ravi

for (String name : list) {
  ...
}
Ravi Kant Soni
fuente
44
"Dado que la lista de matriz no mantiene la lista en el mismo orden en que se agregaron los datos a la lista" umm, sí, al menos lo hace de la misma manera que una lista vinculada. ¿Puedes dar un ejemplo de cómo no lo hacen?
corsiKa
Sí, ArrayList y LinkedList (el contrato de la Lista en general) mantienen los elementos en inserción en orden. El contrato establecido no está ordenado u ordenado por ordenación (TreeSet). La idea inversa no es mala, pero recuerde que en realidad reordena la lista que podría ser más lenta.
Bill K
2
Desestimé esta respuesta porque afirma erróneamente que ArrayLists no mantiene los elementos en orden de inserción. Como @ bill-k señala, todas las listas son colecciones ordenadas por definición. La sugerencia de usar un LinkedList en su lugar tiene mérito, ya que presenta el método descendingIterator (), pero solo si el rendimiento es una preocupación mayor que la memoria y la abstracción.
Michael Scheper