Kann man für jede Schleife in Java eine in umgekehrter Reihenfolge machen?

147

Ich muss eine Liste in umgekehrter Reihenfolge mit Java durchlaufen.

Also, wo dies vorwärts geht:

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

Gibt es eine Möglichkeit, die stringList in umgekehrter Reihenfolge mit der für jede Syntax zu iterieren ?

Aus Gründen der Klarheit: Ich weiß, wie man eine Liste in umgekehrter Reihenfolge durchläuft, möchte aber (aus Neugier) wissen, wie man es für jeden Stil macht.

Ron Tuffin
quelle
5
Der Punkt der "for-each" -Schleife ist, dass Sie nur eine Operation für jedes Element ausführen müssen und die Reihenfolge nicht wichtig ist. For-each könnte die Elemente in völlig zufälliger Reihenfolge verarbeiten, und es würde immer noch das tun, wofür es entwickelt wurde. Wenn Sie die Elemente auf eine bestimmte Weise verarbeiten müssen, würde ich vorschlagen, dies manuell zu tun.
Muusbolla
Java-Sammlungsbibliothek. Nicht wirklich etwas mit der Sprache zu tun. Beschuldige Josh Bloch.
Tom Hawtin - Tackline
7
@muusbolla: Aber da eine Liste eine bestellte Sammlung ist, wird ihre Bestellung sicherlich trotzdem eingehalten ? Daher verarbeitet for-each die Elemente einer Liste nicht in zufälliger Reihenfolge.
Lee Kowalkowski
5
@ Muusbolla das ist nicht wahr. Möglicherweise bei Setabgeleiteten Sammlungen. foreachgarantiert die Iteration in der Reihenfolge des Iterators, der von der iterator()Methode der Sammlung zurückgegeben wird. docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html
Robert

Antworten:

151

Die Collections.reverse-Methode gibt tatsächlich eine neue Liste zurück, in die die Elemente der ursprünglichen Liste in umgekehrter Reihenfolge kopiert wurden. Dies hat also eine O (n) -Leistung in Bezug auf die Größe der ursprünglichen Liste.

Als effizientere Lösung können Sie einen Dekorateur schreiben, der eine umgekehrte Ansicht einer Liste als Iterable darstellt. Der von Ihrem Dekorateur zurückgegebene Iterator verwendet den ListIterator der dekorierten Liste, um die Elemente in umgekehrter Reihenfolge zu durchlaufen.

Beispielsweise:

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

Und Sie würden es wie folgt verwenden:

import static Reversed.reversed;

...

List<String> someStrings = getSomeStrings();
for (String s : reversed(someStrings)) {
    doSomethingWith(s);
}
Nat
quelle
22
Das ist im Grunde das, was Googles Iterables.reverse macht, ja :)
Jon Skeet
10
Ich weiß, dass es eine 'Regel' gibt, dass wir Jons Antwort akzeptieren müssen :) aber .. Ich möchte diese akzeptieren (obwohl sie im Wesentlichen gleich sind), da ich keine weitere Bibliothek eines Drittanbieters hinzufügen muss (obwohl Einige könnten argumentieren, dass dieser Grund einen der Hauptvorteile der Wiederverwendbarkeit von OO bricht.
Ron Tuffin
Kleiner Fehler: In public void remove () sollte es keine return-Anweisung geben, sondern nur: i.remove ();
Jesper
10
Collections.reverse () gibt KEINE umgekehrte Kopie zurück, sondern wirkt auf die als Parameter übergebene Liste. Wie Ihre Lösung mit dem Iterator. Wirklich elegant.
er4z0r
96

Für eine Liste können Sie die Google Guava-Bibliothek verwenden :

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

Beachten Sie, dass dies nicht die gesamte Sammlung umkehrt oder etwas Ähnliches tut - es ermöglicht lediglich Iteration und wahlfreien Zugriff in umgekehrter Reihenfolge. Dies ist effizienter, als zuerst die Sammlung umzukehren.Lists.reverse

Um eine beliebige Iterable umzukehren, müssten Sie alles lesen und dann rückwärts "wiedergeben".

(Wenn Sie nicht bereits verwenden, würde ich durchaus empfehlen Ihnen einen Blick auf die haben Guava . Es ist toll so.)

Jon Skeet
quelle
1
Unsere Codebasis verwendet in hohem Maße die generierte Version von Commons Collections, die von larvalabs ( larvalabs.com/collections ) veröffentlicht wurde. Wenn Sie sich das SVN-Repo für Apache Commons ansehen, ist klar, dass der größte Teil der Arbeit an der Veröffentlichung einer Java 5-Version von Commons Collections erledigt ist. Sie haben sie nur noch nicht veröffentlicht.
Skaffman
Ich mag das. Wenn es nicht so nützlich wäre, würde ich das einen Stecker nennen.
Geowa4
Ich frage mich, warum Jakarta sich nie die Mühe gemacht hat, Apache Commons zu aktualisieren.
Uri
3
Sie haben es aktualisiert, das sage ich. Sie haben es einfach nicht veröffentlicht.
Skaffman
23
Iterables.reverse ist veraltet. Verwenden Sie stattdessen Lists.reverse oder ImmutableList.reverse.
Garrett Hall
39

Die Liste ist (im Gegensatz zum Set) eine geordnete Sammlung, und wenn Sie sie durchlaufen, bleibt die Bestellung vertraglich erhalten. Ich hätte erwartet, dass ein Stapel in umgekehrter Reihenfolge iteriert, aber leider nicht. Die einfachste Lösung, die ich mir vorstellen kann, ist folgende:

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

Mir ist klar, dass dies keine "für jede" Schleifenlösung ist. Ich würde lieber die for-Schleife verwenden, als eine neue Bibliothek wie die Google-Sammlungen einzuführen.

Collections.reverse () erledigt den Job ebenfalls, aktualisiert jedoch die Liste, anstatt eine Kopie in umgekehrter Reihenfolge zurückzugeben.

Gopi Reddy
quelle
3
Dieser Ansatz mag für Array-basierte Listen (wie ArrayList) in Ordnung sein, wäre jedoch für verknüpfte Listen nicht optimal, da jedes Get die Liste für jedes Get von Anfang bis Ende (oder möglicherweise von Ende bis Anfang) durchlaufen müsste. Es ist besser, einen intelligenteren Iterator als in Nats Lösung zu verwenden (optimal für alle Implementierungen von List).
Chris
1
Ferner weicht es von der Anfrage im OP ab, die ausdrücklich nach der for eachSyntax fragt
Paul W
8

Dies wird mit der ursprünglichen Liste in Konflikt geraten und muss auch außerhalb der Schleife aufgerufen werden. Außerdem möchten Sie nicht jedes Mal eine Umkehrung durchführen, wenn Sie eine Schleife ausführen. Wäre das wahr, wenn eine der Iterables.reverse ideasbeiden Methoden angewendet würde?

Collections.reverse(stringList);

for(String string: stringList){
//...do something
}
Phillip Gibb
quelle
5

AFAIK: In der Standardbibliothek gibt es keinen Standard "reverse_iterator", der die Syntax für jede Syntax unterstützt, die bereits ein syntaktischer Zucker ist, den sie spät in die Sprache gebracht haben.

Sie könnten so etwas wie für (Item-Element: myList.clone (). Reverse ()) tun und den zugehörigen Preis bezahlen.

Dies scheint auch ziemlich konsistent mit dem offensichtlichen Phänomen zu sein, dass Sie keine bequemen Möglichkeiten für teure Operationen haben - da eine Liste per Definition eine O (N) -Wahlzugriffskomplexität aufweisen könnte (Sie könnten die Schnittstelle mit einer einzelnen Verbindung implementieren), umgekehrt Die Iteration könnte O (N ^ 2) sein. Wenn Sie eine ArrayList haben, zahlen Sie diesen Preis natürlich nicht.

Uri
quelle
Sie können einen ListIterator rückwärts ausführen, der in einen Iterator eingebunden werden kann.
Tom Hawtin - Tackline
@ Tom: Guter Punkt. Mit dem Iterator machen Sie jedoch immer noch den nervigen alten Stil für Schleifen, und Sie können immer noch die Kosten bezahlen, um zum letzten Element zu gelangen ... Ich habe das Qualifikationsmerkmal in meiner Antwort hinzugefügt, danke.
Uri
Deque hat einen Reverse-Iterator.
Michael Munsey
2

Dies kann eine Option sein. Ich hoffe, es gibt einen besseren Weg, um vom letzten Element zu beginnen, als bis zum Ende der while-Schleife.

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);           
    }
}
Krishna
quelle
2

Ab dem Kommentar : Sie sollten in der Lage sein, Apache Commons zu verwendenReverseListIterator

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

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

Wie @rogerdpack sagte , müssen Sie das ReverseListIteratorals einpackenIterable .

serv-inc
quelle
1

Nicht ohne einen benutzerdefinierten Code zu schreiben, der Ihnen einen Enumerator gibt, der die Elemente für Sie umkehrt.

Sie sollten dies in Java tun können, indem Sie eine benutzerdefinierte Implementierung von Iterable erstellen, die die Elemente in umgekehrter Reihenfolge zurückgibt.

Dann würden Sie den Wrapper instanziieren (oder die Methode what-have-you aufrufen), die die Iterable-Implementierung zurückgibt, die das Element in der für jede Schleife umkehrt.

casperOne
quelle
1

Sie müssten Ihre Sammlung umkehren, wenn Sie die für jede Syntax sofort verwenden und in umgekehrter Reihenfolge vorgehen möchten.

Owen
quelle
1

Alle obigen Antworten erfüllen nur die Anforderung, entweder durch Umschließen einer anderen Methode oder durch Aufrufen eines Fremdcodes außerhalb;

Hier ist die Lösung, die aus der 4. Ausgabe von Thinking in Java , Kapitel 11.13.1 AdapterMethodIdiom , kopiert wurde .

Hier ist der Code:

// 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
*///:~
qiwen li
quelle
warum ist das int current = size() - 1richtig warum nicht die int current = this.size() - 1orint current = super.size() - 1
qiwen li
1

Eine Arbeit um:

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

Oder mit Guave :

Lists.reverse(stringList).forEach(str -> ...);
EssaidiM
quelle
0

Auf jeden Fall eine späte Antwort auf diese Frage. Eine Möglichkeit besteht darin, den ListIterator in einer for-Schleife zu verwenden. Es ist nicht so sauber wie die Doppelpunktsyntax, aber es funktioniert.

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

Das Guthaben für die ListIterator-Syntax lautet "Möglichkeiten zum Durchlaufen einer Liste in Java".

ScottMichaud
quelle