Czy można wykonać pętlę for each w Javie w odwrotnej kolejności?

148

Muszę przejrzeć listę w odwrotnej kolejności, używając języka Java.

Więc gdzie to robi do przodu:

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

Czy istnieje sposób na iterację stringList w odwrotnej kolejności przy użyciu for each syntax?

Dla jasności: wiem, jak powtórzyć listę w odwrotnej kolejności, ale chciałbym wiedzieć (ze względu na ciekawość), jak to zrobić w każdym stylu.

Ron Tuffin
źródło
5
Istotą pętli „for-each” jest to, że wystarczy wykonać operację na każdym elemencie, a kolejność nie jest ważna. Każdy mógł przetwarzać elementy w całkowicie przypadkowej kolejności i nadal robiłby to, do czego został zaprojektowany. Jeśli potrzebujesz przetworzyć elementy w określony sposób, proponuję zrobić to ręcznie.
muusbolla
Biblioteka kolekcji Java. Nie ma to nic wspólnego z językiem. Winić Josha Blocha.
Tom Hawtin - tackline
7
@muusbolla: Ale skoro lista jest uporządkowaną kolekcją, na pewno jej zamówienie będzie honorowane, niezależnie od tego? Dlatego for-each nie przetworzy elementów listy w losowej kolejności.
Lee Kowalkowski
5
@muusbolla to nieprawda. Może w przypadku Setkolekcji pochodnych. foreachgwarantuje iterację w kolejności iteratora zwróconej z iterator()metody kolekcji. docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html
robert

Odpowiedzi:

151

Metoda Collections.reverse w rzeczywistości zwraca nową listę z elementami oryginalnej listy skopiowanymi do niej w odwrotnej kolejności, dzięki czemu uzyskuje się wydajność O (n) w odniesieniu do rozmiaru oryginalnej listy.

Bardziej wydajnym rozwiązaniem jest napisanie dekoratora, który prezentuje odwrócony widok listy jako iterowalnej. Iterator zwrócony przez dekoratora użyje elementu ListIterator listy dekorowanej, aby przejść przez elementy w odwrotnej kolejności.

Na przykład:

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

Używałbyś go tak:

import static Reversed.reversed;

...

List<String> someStrings = getSomeStrings();
for (String s : reversed(someStrings)) {
    doSomethingWith(s);
}
Nat
źródło
22
Tak właśnie działa Google Iterables.reverse, tak :)
Jon Skeet,
10
Wiem, że istnieje „zasada”, że musimy zaakceptować odpowiedź Jona :) ale .. chcę zaakceptować tę (mimo że są one zasadniczo takie same), ponieważ nie wymaga ona dołączenia innej biblioteki innej firmy (nawet jeśli niektórzy mogą argumentować, że ten powód łamie jedną z głównych zalet OO - możliwość ponownego użycia).
Ron Tuffin,
Mały błąd: w public void remove () nie powinno być instrukcji return, powinno to być po prostu: i.remove ();
Jesper
10
Collections.reverse () NIE zwraca odwróconej kopii, ale działa na liście przekazanej do niej jako parametr. Jednak podoba mi się twoje rozwiązanie z iteratorem. Naprawdę eleganckie.
er4z0r
96

Aby uzyskać listę, możesz skorzystać z biblioteki Google Guava :

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

Zauważ, że nie odwraca całej kolekcji ani nie robi czegoś podobnego - po prostu umożliwia iterację i losowy dostęp w odwrotnej kolejności. Jest to bardziej wydajne niż najpierw odwracanie kolekcji.Lists.reverse

Aby odwrócić dowolną iterację, musiałbyś przeczytać wszystko, a następnie „odtworzyć” to wstecz.

(Jeśli nie masz już go używać, ja dokładnie polecam rzucić okiem na guawa . To świetna sprawa).

Jon Skeet
źródło
1
Nasza baza kodów w dużym stopniu korzysta z uogólnionej wersji Commons Collections wydanej przez larvalabs ( larvalabs.com/collections ). Przeglądając repozytorium SVN dla Apache Commons, widać, że większość prac związanych z wydaniem wersji Commons Collections w java 5 jest już zakończona, po prostu jeszcze jej nie wydali.
skaffman
Lubię to. Gdyby to nie było tak przydatne, nazwałbym to wtyczką.
geowa4
Zastanawiam się, dlaczego Dżakarta nigdy nie zadała sobie trudu, aby zaktualizować Apache Commons.
Uri
3
Zaktualizowali to, to właśnie mówię. Po prostu go nie wydali.
skaffman
23
Iterables.reverse jest przestarzałe, zamiast tego użyj Lists.reverse lub ImmutableList.reverse.
Garrett Hall
39

Lista (w przeciwieństwie do zestawu) jest uporządkowaną kolekcją i iteracja po niej zachowuje kolejność według kontraktu. Spodziewałbym się, że stos będzie iterował w odwrotnej kolejności, ale niestety tak nie jest. Najprostszym rozwiązaniem, jakie przychodzi mi do głowy, jest to:

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

Zdaję sobie sprawę, że nie jest to rozwiązanie pętli „dla każdego”. Wolę używać pętli for niż wprowadzać nową bibliotekę, taką jak Kolekcje Google.

Collections.reverse () również wykonuje to zadanie, ale aktualizuje listę w przeciwieństwie do zwracania kopii w odwrotnej kolejności.

Gopi Reddy
źródło
3
Takie podejście może być dobre dla list opartych na tablicach (takich jak ArrayList), ale byłoby nieoptymalne dla list połączonych, ponieważ każde get musiałoby przejść przez listę od początku do końca (lub prawdopodobnie od końca do początku) dla każdego get. Lepiej jest użyć mądrzejszego iteratora, jak w rozwiązaniu Nat (optymalnego dla wszystkich implementacji List).
Chris
1
Co więcej, odbiega od żądania w PO, które wyraźnie prosi o for eachskładnię
Paul W
8

Spowoduje to zepsucie oryginalnej listy i musi być wywoływane poza pętlą. Nie chcesz również wykonywać odwrotności za każdym razem, gdy wykonujesz pętlę - czy byłoby to prawdą, gdyby jeden z nich Iterables.reverse ideaszostał zastosowany?

Collections.reverse(stringList);

for(String string: stringList){
//...do something
}
Phillip Gibb
źródło
5

AFAIK, w standardowej bibliotece nie ma standardowego typu „reverse_iterator”, który obsługuje składnię for-each, która jest już cukrem składniowym, który wprowadzili późno do języka.

Możesz zrobić coś takiego jak (element pozycji: myList.clone (). Reverse ()) i zapłacić odpowiednią cenę.

Wydaje się to również dość zgodne z widocznym zjawiskiem polegającym na braku wygodnych sposobów wykonywania kosztownych operacji - ponieważ lista z definicji może mieć złożoność dostępu swobodnego O (N) (można zaimplementować interfejs z pojedynczym łączem), odwrotnie iteracja może zakończyć się O (N ^ 2). Oczywiście, jeśli masz ArrayList, nie płacisz tej ceny.

Uri
źródło
Możesz uruchomić ListIterator wstecz, co można zawinąć w Iteratorze.
Tom Hawtin - tackline
@Tom: Słuszna uwaga. Jednak z iteratorem nadal robisz irytujące pętle w starym stylu i możesz nadal płacić za dostanie się do ostatniego elementu na początku ... Jednak dodałem kwalifikator w mojej odpowiedzi, dziękuję.
Uri
Deque ma odwrotny iterator.
Michael Munsey
2

To może być opcja. Mam nadzieję, że jest lepszy sposób na rozpoczęcie od ostatniego elementu niż pętla while do końca.

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);           
    }
}
Kryszna
źródło
2

Od komentarza : Powinieneś być w stanie korzystać z Apache CommonsReverseListIterator

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

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

Jak powiedział @rogerdpack , musisz opakować ReverseListIteratorplik jako Iterable.

serv-inc
źródło
1

Nie bez napisania niestandardowego kodu, który da ci moduł wyliczający, który odwróci elementy za Ciebie.

Powinieneś móc to zrobić w Javie, tworząc niestandardową implementację Iterable, która zwróci elementy w odwrotnej kolejności.

Następnie należy utworzyć instancję opakowania (lub wywołać metodę what-have-you), która zwróci Iterowalną implementację, która odwraca element w pętli for.

casperOne
źródło
1

Będziesz musiał odwrócić swoją kolekcję, jeśli chcesz użyć dla każdej składni po wyjęciu z pudełka i postępować w odwrotnej kolejności.

piekarnik
źródło
1

Wszystkie powyższe odpowiedzi spełniają tylko to wymaganie, albo zawijając inną metodę, albo wywołując jakiś obcy kod na zewnątrz;

Oto rozwiązanie skopiowane z wydania Thinking in Java 4 , rozdział 11.13.1 AdapterMethodIdiom ;

Oto kod:

// 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
źródło
dlaczego ma int current = size() - 1rację? dlaczego nie int current = this.size() - 1orint current = super.size() - 1
qiwen li
1

Praca wokół:

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

Lub z guawą :

Lists.reverse(stringList).forEach(str -> ...);
EssaidiM
źródło
0

Zdecydowanie późna odpowiedź na to pytanie. Jedną z możliwości jest użycie elementu ListIterator w pętli for. Nie jest tak czysty, jak składnia dwukropka, ale działa.

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

Kredyt za składnię ListIterator przypada na „Sposoby iteracji listy w Javie”

ScottMichaud
źródło