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.


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

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

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.


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

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...


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

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

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
}

5

Crea una costumbre reverseIterable.


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) {
  ...
}



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

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

-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) {
  ...
}

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.