Scorrendo un elenco in ordine inverso in Java


251

Sto migrando un pezzo di codice per usare i generici. Un argomento per farlo è che il ciclo for è molto più pulito rispetto al tenere traccia degli indici o all'utilizzo di un iteratore esplicito.

In circa la metà dei casi, l'elenco (una ArrayList) viene ripetuto in ordine inverso utilizzando oggi un indice.

Qualcuno può suggerire un modo più pulito di farlo (dal momento che non mi piace indexed for loopquando lavoro con le raccolte), sebbene funzioni?

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

Nota: non riesco ad aggiungere nuove dipendenze al di fuori di JDK.


10
Cosa c'è di male nell'usare un indice esplicito per scorrere su una struttura di dati indicizzata? Almeno ti mostra cosa sta succedendo esattamente. Per iterare all'indietro ho sempre il seguente linguaggio leggermente più breve:for (int i = nodes.size(); --i >= 0;)
x4u

4
Niente di particolare, preferirei solo programmare su un'interfaccia e non sapere quale tipo di elenco sto usando. Anche se mi piace molto la tua mano corta. (+1 commento)
Allain Lalonde,

1
@ x4u: non c'è molto in esso sebbene Iterator sia fail-fast e consenta anche di rimuovere facilmente gli elementi durante l'iterazione.
Adamski,

2
Questa classe è interrotta, perché l'utente potrebbe voler ripetere una seconda volta lo stesso Iterable, oppure l'elenco potrebbe cambiare tra il momento in cui viene costruito l'iterabile e quando viene ripetuto. Sono sicuro che per i tuoi scopi ti assicurerai di non farlo, ma non sarebbe troppo difficile riparare il codice; o semplicemente rubare il codice da Guava (licenza Apache 2.0): code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
Kevin Bourrillion

1
Abbastanza giusto, ma anche quello di Guava è suscettibile alla stessa cosa se lo sto leggendo bene. Se un utente conservasse una copia del risultato dal retro, avrebbe lo stesso problema.
Allain Lalonde,

Risposte:


447

Prova questo:

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

4
Non male. Non utilizza l'indice, ma perde l'eleganza di per ogni sintassi. +1 comunque.
Allain Lalonde,

26
Chiamare listIterator () senza un argomento index darà un Iterator all'inizio dell'elenco e quindi hasPrevious () restituirà false alla prima chiamata.
Adamski,

2
Vuoi un indice sulla listIteratorchiamata, credo.
Tom Hawtin - tackline

1
Puoi scrivere un Iteratorche usa un ListIteratoral contrario, ma potrebbe non valerne la pena per un ciclo.
Tom Hawtin - tackline

1
Non è un loop, quindi l'ho preso e avvolto. pastebin.ca/1759041 quindi, ora posso farefor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde il

35

Guava offre Lists#reverse(List)e ImmutableList#reverse(). Come nella maggior parte dei casi per Guava, i primi delegano ai secondi se l'argomento è un ImmutableList, quindi puoi usare i primi in tutti i casi. Questi non creano nuove copie dell'elenco ma solo "viste invertite" di esso.

Esempio

List reversed = ImmutableList.copyOf(myList).reverse();

23

Non credo sia possibile usare la sintassi del ciclo for. L'unica cosa che posso suggerire è fare qualcosa del tipo:

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

... ma non direi che questo è "più pulito" dato che sarà meno efficiente.


26
e cambia anche al posto l'elenco che attraversi, il che è piuttosto un enorme effetto collaterale. (Di 'che lo avvolgi in un metodo, ogni volta che viene chiamato attraversi la lista nell'altro modo ^^)
jolivier,

Questa è la soluzione più pulita.
jfajunior, l'

14

Opzione 1: hai pensato di invertire l'elenco con le raccolte # reverse () e quindi di usare foreach?

Naturalmente, potresti anche voler riformattare il tuo codice in modo tale che l'elenco sia ordinato correttamente in modo da non doverlo invertire, che utilizza spazio / tempo extra.


MODIFICARE:

Opzione 2: in alternativa, potresti usare un Deque invece di un ArrayList? Ti permetterà di iterare avanti e indietro


MODIFICARE:

Opzione 3: come altri hanno suggerito, potresti scrivere un iteratore che scorrerà l'elenco al contrario, ecco un esempio:

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
Ci ho pensato, ma il costo per invertirlo è proibitivo. Inoltre, richiede solo questo tipo di iterazione circa la metà delle volte. Lanciandolo si sposterebbe il problema sull'altra metà.
Allain Lalonde,

3
+ per Deque; implementazioni tipiche hanno descendingIterator().
trashgod,

Ciò sarebbe terribile per gli elenchi collegati, la variante del PO è migliore.
masterxilo,

1
for eachSecondo me, usare un'espressione è la soluzione più idiomatica. È bello rendersi conto che questo è possibile se la tua lista implementa Iterable in un modo che scorre indietro. Userò questo approccio e userò la classe ReverseListIterator delle collezioni Apache Commons.
David Groomes,

11

È possibile utilizzare la classe concrete LinkedListanziché l'interfaccia generale List. Quindi hai un descendingIteratorper iterare con la direzione inversa.

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

Non so perché non c'è descendingIteratorcon ArrayList...


9

Questa è una vecchia domanda, ma manca una risposta amichevole per java8. Ecco alcuni modi per ripetere l'iterazione dell'elenco, con l'aiuto dell'API 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
molto bello, solo un cambiamento estetico: int size = list.size (); ListIterator <Integer> it = list.listIterator (size); Stream.generate (it :: precedente) .limit (dimensione) .forEach (System.out :: println);
Benez,

5

Ecco un'implementazione (non testata) di a ReverseIterable. Quando iterator()viene chiamato crea e restituisce ReverseIteratorun'implementazione privata , che mappa semplicemente le chiamate hasNext()a hasPrevious()e le chiamate a next()vengono mappate previous(). Vuol dire che potresti iterare ArrayListal contrario come segue:

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

Definizione della classe

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

Mi sembra giusto, sebbene esporlo come metodo statico anziché come costruttore pubblico eviterebbe (nella maggior parte dei casi) la necessità che il client specifichi il parametro type. Ecco come fa Guava ...
Kevin Bourrillion,

2
Questa è la migliore implementazione, tuttavia ReverseIteratormanca il costruttore necessario e il codice dovrebbe usare al Listposto di ArrayList.
Andy,

5

Se gli elenchi sono abbastanza piccoli in modo che le prestazioni non siano un vero problema, si può usare il reverse-metod della -class Listsin Google Guava. for-eachRende piuttosto il codice e l'elenco originale rimane lo stesso. Inoltre, l'elenco invertito è supportato dall'elenco originale, quindi qualsiasi modifica all'elenco originale si rifletterà in quello invertito.

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 il seguente risultato:

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

Ciò significa che l'iterazione inversa di myList può essere scritta come:

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

5

Crea un'abitudine reverseIterable.


Non so perché questo sia stato votato, potrei semplicemente creare un wrapper per l'elenco che lo fa.
Allain Lalonde,

Questo non è meno pulito di chiamare Collezioni.reverse () IMHO.
Adamski,

@Allain: concordato con riferimento a. i downvotes anche se non vedo perché visualizzi una chiamata a listIterator (list.size ()) come "non pulito". Anche se lo avvolgi devi comunque fare lo stesso metodo da qualche parte.
Adamski,

Supponiamo di no, semplicemente non disposto a prendere il colpo di prestazione, per la pulizia.
Allain Lalonde,

18
Votato in quanto non penso che consideri questa una risposta completa.
Maarten Bodewes,

3

Esempio molto semplice:

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

Per avere un codice simile al seguente:

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

Inserisci questo codice in un file chiamato "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
Questo è stato menzionato altrove nel thread, ma il listIteratorcampo deve essere all'interno Iteratordell'implementazione, non Iterabledell'implementazione.
Andy,

1
Perché usare un tipo enum, non una classe?
Aubin,

1
Rende la classe 'In' non istantanea, senza dover scrivere un costruttore predefinito privato. Buono per le classi che hanno solo metodi statici.
intrepidis,

Direi che abusare di enum solo per evitare di scrivere un costruttore predefinito privato è al massimo fonte di confusione. Che ne dici di usare enum per enum e classi per le classi? Trasformando una classe in un enum, ottiene implicitamente anche un name()metodo, un ordinal()metodo e un static valueOf()metodo, per esempio.
JHH,

1
Sì, puoi continuare con la tua opinione e anche questo funzionerà bene, ma penso il contrario. Le classi servono per istanziare oggetti e abusarne includendo un costruttore predefinito privato per inibire la loro istanza è al massimo fonte di confusione. In Java, le enumerazioni sono in realtà classi, ma quelle che non possono mai essere istanziate dal design.
intrepidis,

0

Come è stato suggerito almeno due volte, è possibile utilizzare descendingIteratorcon a Deque, in particolare con a LinkedList. Se vuoi usare il ciclo for-each (cioè, avere un Iterable), puoi costruire e usare un wrapper come questo:

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: "Non so perché non esiste un DiscendenteIteratore con ArrayList ..."

Poiché l'elenco di array non mantiene l'elenco nello stesso ordine in cui i dati sono stati aggiunti all'elenco. Quindi, non usare mai Arraylist.

L'elenco collegato manterrà i dati nello stesso ordine di AGGIUNGI all'elenco.

Quindi, sopra nel mio esempio, ho usato ArrayList () per indurre l'utente a distorcere la mente e fargli allenare qualcosa dalla propria parte.

Invece di questo

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

USO:

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

4
"Poiché l'elenco di array non mantiene l'elenco nello stesso ordine in cui i dati sono stati aggiunti all'elenco" umm, sì, almeno lo fa allo stesso modo di un elenco collegato. Puoi fornire un esempio di come non lo fanno?
corsiKa

Sì, ArrayList e LinkedList (il contratto List in generale) mantengono gli articoli in ordine. il contratto Set non è ordinato o ordinato per ordinamento (TreeSet). L'idea inversa non è male, ma ricorda che in realtà riordina l'elenco che potrebbe essere lento.
Bill K,

2
Ho votato in negativo questa risposta perché erroneamente afferma che ArrayLists non mantiene gli articoli in ordine di inserimento. Come nota @ bill-k, tutte le liste sono raccolte ordinate per definizione. Il suggerimento di utilizzare un LinkedList ha invece valore, poiché presenta il metodo descendingIterator (), ma solo se le prestazioni sono una preoccupazione maggiore della memoria e dell'astrazione.
Michael Scheper,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.