Si può fare un per ogni loop in Java in ordine inverso?


148

Devo scorrere una lista in ordine inverso usando Java.

Quindi, dove questo lo fa in avanti:

for(String string: stringList){
//...do something
}

Esiste un modo per iterare la stringList in ordine inverso usando la per ogni sintassi?

Per chiarezza: so come ripetere un elenco in ordine inverso, ma vorrei sapere (per amor di curiosità) come farlo in ogni stile.


5
Il punto del ciclo "per ogni" è che devi solo eseguire un'operazione su ciascun elemento e l'ordine non è importante. Perché ognuno potrebbe elaborare gli elementi in ordine completamente casuale e farebbe comunque quello per cui è stato progettato. Se hai bisogno di elaborare gli elementi in un modo particolare, ti suggerirei di farlo manualmente.
muusbolla,

Libreria di raccolte Java. Non c'è davvero niente a che fare con la lingua. Colpa di Josh Bloch.
Tom Hawtin - tackline

7
@muusbolla: Ma poiché una Lista è una raccolta ordinata , sicuramente il suo ordine sarà onorato, indipendentemente? Pertanto for-each non elaborerà gli elementi di un elenco in un ordine casuale.
Lee Kowalkowski il

5
@muusbolla non è vero. Forse nel caso di Setraccolte derivate. foreachgarantisce l'iterazione nell'ordine dell'iteratore restituito dal iterator()metodo di raccolta. docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html
robert

Risposte:


151

Il metodo Collections.reverse restituisce effettivamente un nuovo elenco con gli elementi dell'elenco originale copiati in esso in ordine inverso, quindi questo ha prestazioni O (n) rispetto alla dimensione dell'elenco originale.

Come soluzione più efficiente, è possibile scrivere un decoratore che presenta una vista inversa di un elenco come Iterable. L'iteratore restituito dal tuo decoratore userebbe il ListIterator dell'elenco decorato per camminare sugli elementi in ordine inverso.

Per esempio:

public class Reversed<T> implements Iterable<T> {
    private final List<T> original;

    public Reversed(List<T> original) {
        this.original = original;
    }

    public Iterator<T> iterator() {
        final ListIterator<T> i = original.listIterator(original.size());

        return new Iterator<T>() {
            public boolean hasNext() { return i.hasPrevious(); }
            public T next() { return i.previous(); }
            public void remove() { i.remove(); }
        };
    }

    public static <T> Reversed<T> reversed(List<T> original) {
        return new Reversed<T>(original);
    }
}

E lo useresti come:

import static Reversed.reversed;

...

List<String> someStrings = getSomeStrings();
for (String s : reversed(someStrings)) {
    doSomethingWith(s);
}

22
Questo è fondamentalmente ciò che fa Iterables.reverse di Google, sì :)
Jon Skeet

10
So che esiste una 'regola' che dobbiamo accettare la risposta di Jon :) ma .. Voglio accettarlo (anche se sono essenzialmente gli stessi) perché non mi richiede di includere un'altra libreria di terze parti (anche se alcuni potrebbero sostenere che questa ragione rompe uno dei principali vantaggi dell'OO - la riusabilità).
Ron Tuffin,

Piccolo errore: nel vuoto pubblico remove (), non ci dovrebbe essere un'istruzione return, dovrebbe essere solo: i.remove ();
Jesper,

10
Collections.reverse () NON restituisce una copia invertita ma agisce sull'Elenco passato ad esso come parametro. Come la tua soluzione con l'iteratore, però. Davvero elegante.
er4z0r

96

Per un elenco, è possibile utilizzare la libreria di Google Guava :

for (String item : Lists.reverse(stringList))
{
    // ...
}

Nota che non inverte l'intera raccolta o fa nulla di simile: consente solo l'iterazione e l'accesso casuale, nell'ordine inverso. Questo è più efficiente che invertire prima la raccolta.Lists.reverse

Per invertire un iterabile arbitrario, dovresti leggerlo tutto e "rigiocarlo" al contrario.

(Se non hai già utilizza, avrei accuratamente consigliamo di dare un'occhiata alla Guava . E 'roba grande.)


1
La nostra base di codice fa ampio uso della versione generata di Commons Collections rilasciata da larvalabs ( larvalabs.com/collections ). Guardando nel repository SVN per Apache Commons, è chiaro che la maggior parte del lavoro nel rilascio di una versione java 5 di Commons Collections è stata eseguita, ma non l'hanno ancora rilasciata.
Skaffman,

Mi piace. Se non fosse così utile, lo definirei una spina.
geowa4,

Mi chiedo perché Jakarta non si sia mai preoccupato di aggiornare Apache Commons.
Uri,

3
L'hanno aggiornato, è quello che sto dicendo. Non l'hanno rilasciato.
Skaffman,

23
Iterables.reverse è stato deprecato, utilizzare invece Lists.reverse o ImmutableList.reverse.
Garrett Hall,

39

L'elenco (a differenza del set) è una raccolta ordinata e iterando su di essa preserva l'ordine per contratto. Mi sarei aspettato che uno Stack si ripetesse nell'ordine inverso, ma sfortunatamente non lo è. Quindi la soluzione più semplice che mi viene in mente è questa:

for (int i = stack.size() - 1; i >= 0; i--) {
    System.out.println(stack.get(i));
}

Mi rendo conto che questa non è una soluzione loop "per ogni". Preferirei usare il ciclo for piuttosto che introdurre una nuova libreria come Google Collections.

Anche Collections.reverse () esegue il lavoro ma aggiorna l'elenco anziché restituire una copia in ordine inverso.


3
Questo approccio può andare bene per gli elenchi basati su array (come ArrayList) ma sarebbe subottimale per gli elenchi collegati poiché ogni get dovrebbe attraversare l'elenco dall'inizio alla fine (o eventualmente dalla fine all'inizio) per ogni get. È meglio usare un iteratore più intelligente come nella soluzione di Nat (ottimale per tutte le implementazioni di List).
Chris,

1
Inoltre, si discosta dalla richiesta nel PO che chiede esplicitamente la for eachsintassi
Paul W

8

Questo rovinerà l'elenco originale e dovrà essere chiamato anche al di fuori del ciclo. Inoltre, non vuoi eseguire un reverse ogni volta che esegui il loop: sarebbe vero se uno dei Iterables.reverse ideasfosse applicato?

Collections.reverse(stringList);

for(String string: stringList){
//...do something
}

5

AFAIK non esiste una sorta di "reverse_iterator" standard nella libreria standard che supporti la sintassi for-each che è già uno zucchero sintattico che hanno portato tardi nella lingua.

Potresti fare qualcosa del genere per (Elemento elemento: myList.clone (). Reverse ()) e pagare il prezzo associato.

Ciò sembra anche abbastanza coerente con l'apparente fenomeno di non fornirti modi convenienti per eseguire operazioni costose - poiché un elenco, per definizione, potrebbe avere una complessità di accesso casuale O (N) (potresti implementare l'interfaccia con un singolo collegamento), viceversa l'iterazione potrebbe finire per essere O (N ^ 2). Naturalmente, se hai una ArrayList, non paghi quel prezzo.


È possibile eseguire un ListIterator all'indietro, che può essere racchiuso in un Iterator.
Tom Hawtin - tackline

@ Tom: buon punto. Tuttavia, con l'iteratore stai ancora facendo il fastidioso vecchio stile per i loop e potresti ancora pagare il costo per arrivare all'ultimo elemento per cominciare ... Ho aggiunto il qualificatore nella mia risposta, però, grazie.
Uri,

Deque ha un iteratore inverso.
Michael Munsey,

2

Questa potrebbe essere un'opzione. Spero che ci sia un modo migliore per iniziare dall'ultimo elemento rispetto a while loop fino alla fine.

public static void main(String[] args) {        
    List<String> a = new ArrayList<String>();
    a.add("1");a.add("2");a.add("3");a.add("4");a.add("5");

    ListIterator<String> aIter=a.listIterator();        
    while(aIter.hasNext()) aIter.next();

    for (;aIter.hasPrevious();)
    {
        String aVal = aIter.previous();
        System.out.println(aVal);           
    }
}

2

A partire dal commento : dovresti essere in grado di utilizzare Apache CommonsReverseListIterator

Iterable<String> reverse 
    = new IteratorIterable(new ReverseListIterator(stringList));

for(String string: reverse ){
    //...do something
}

Come ha detto @rogerdpack , è necessario racchiuderlo ReverseListIteratorcome Iterable.


1

Non senza scrivere un codice personalizzato che ti darà un enumeratore che invertirà gli elementi per te.

Dovresti essere in grado di farlo in Java creando un'implementazione personalizzata di Iterable che restituirà gli elementi in ordine inverso.

Quindi, dovresti istanziare il wrapper (o chiamare il metodo, what-have-you) che restituirebbe l'implementazione Iterable che inverte l'elemento in per ogni ciclo.



1

Dovresti invertire la tua raccolta se vuoi usare il per ogni sintassi fuori dalla scatola e andare in ordine inverso.


1

Tutte le risposte sopra soddisfano solo il requisito, avvolgendo un altro metodo o chiamando un codice esterno all'esterno;

Ecco la soluzione copiata dalla quarta edizione di Thinking in Java , capitolo 11.13.1 AdapterMethodIdiom ;

Ecco il codice:

// The "Adapter Method" idiom allows you to use foreach
// with additional kinds of Iterables.
package holding;
import java.util.*;

@SuppressWarnings("serial")
class ReversibleArrayList<T> extends ArrayList<T> {
  public ReversibleArrayList(Collection<T> c) { super(c); }
  public Iterable<T> reversed() {
    return new Iterable<T>() {
      public Iterator<T> iterator() {
        return new Iterator<T>() {
          int current = size() - 1; //why this.size() or super.size() wrong?
          public boolean hasNext() { return current > -1; }
          public T next() { return get(current--); }
          public void remove() { // Not implemented
            throw new UnsupportedOperationException();
          }
        };
      }
    };
  }
}   

public class AdapterMethodIdiom {
  public static void main(String[] args) {
    ReversibleArrayList<String> ral =
      new ReversibleArrayList<String>(
        Arrays.asList("To be or not to be".split(" ")));
    // Grabs the ordinary iterator via iterator():
    for(String s : ral)
      System.out.print(s + " ");
    System.out.println();
    // Hand it the Iterable of your choice
    for(String s : ral.reversed())
      System.out.print(s + " ");
  }
} /* Output:
To be or not to be
be to not or be To
*///:~

perché è int current = size() - 1giusto? perché no int current = this.size() - 1oint current = super.size() - 1
qiwen li

1

Un'opera intorno:

Collections.reverse(stringList).forEach(str -> ...);

O con guava :

Lists.reverse(stringList).forEach(str -> ...);

0

Sicuramente una risposta tardiva a questa domanda. Una possibilità è usare ListIterator in un ciclo for. Non è pulito come la sintassi del colon, ma funziona.

List<String> exampleList = new ArrayList<>();
exampleList.add("One");
exampleList.add("Two");
exampleList.add("Three");

//Forward iteration
for (String currentString : exampleList) {
    System.out.println(currentString); 
}

//Reverse iteration
for (ListIterator<String> itr = exampleList.listIterator(exampleList.size()); itr.hasPrevious(); /*no-op*/ ) {
    String currentString = itr.previous();
    System.out.println(currentString); 
}

Il credito per la sintassi di ListIterator va a "Modi per scorrere su un elenco in Java"

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.