Iterowanie po liście w odwrotnej kolejności w java

251

Przeprowadzam migrację fragmentu kodu, aby korzystać z ogólnych. Jednym z argumentów przemawiających za tym jest to, że pętla for jest znacznie bardziej przejrzysta niż śledzenie indeksów lub używanie jawnego iteratora.

W około połowie przypadków lista (ArrayList) jest iterowana w odwrotnej kolejności przy użyciu dzisiaj indeksu.

Czy ktoś może zasugerować bardziej przejrzysty sposób (ponieważ nie lubię indexed for looppracy z kolekcjami), chociaż to działa?

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

Uwaga: Nie mogę dodać żadnych nowych zależności poza JDK.

Allain Lalonde
źródło
10
Co jest takiego złego w korzystaniu z jawnego indeksu w celu iteracji indeksowanej struktury danych? Przynajmniej pokazuje, co się właściwie dzieje. Do iteracji wstecz zawsze używam następującego nieco krótszego idiomu:for (int i = nodes.size(); --i >= 0;)
x4u
4
Nic szczególnego, wolałbym raczej zaprogramować interfejs i nie wiedzieć, jakiego rodzaju listy używam. Chociaż bardzo podoba mi się twoja krótka ręka. (+1 komentarz)
Allain Lalonde,
1
@ x4u: Nie ma w tym wiele, chociaż Iterator działa szybko i pozwala na łatwe usuwanie elementów podczas iteracji.
Adamski
2
Ta klasa jest zepsuta, ponieważ użytkownik może chcieć powtórzyć iterację po tej samej iteracji, lub lista może się zmieniać między momentem, w którym iteracja jest konstruowana, a iteracją. Jestem pewien, że dla twoich celów po prostu upewnisz się, że tego nie zrobisz, ale naprawienie kodu nie byłoby zbyt trudne; lub po prostu ukraść kod z Guava (licencja Apache 2.0): code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
Kevin Bourrillion 20'10
1
W porządku, ale nawet Guawa jest podatna na to samo, jeśli dobrze ją czytam. Gdyby użytkownik zachował kopię wyniku od tyłu, miałby ten sam problem.
Allain Lalonde

Odpowiedzi:

446

Spróbuj tego:

// 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());
}
John Feminella
źródło
4
Nie jest zły. Nie używa indeksu, ale traci elegancję każdej składni. W każdym razie +1.
Allain Lalonde,
26
Wywołanie listIterator () bez argumentu indeksu da Iterator na początku listy, a zatem hasPrevious () zwróci false przy pierwszym wywołaniu.
Adamski
2
listIteratorMyślę, że potrzebujesz indeksu w połączeniu.
Tom Hawtin - tackline
1
Możesz napisać, Iteratorże używa ListIteratorodwrotnej strony, ale może to nie być warte jednej pętli.
Tom Hawtin - tackline
1
To nie jest jedna pętla, więc wziąłem to i owinąłem. pastebin.ca/1759041, więc teraz mogę to zrobićfor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde
35

Oferty GuavaLists#reverse(List) i ImmutableList#reverse(). Jak w większości przypadków w przypadku Guawy, ten pierwszy przekazuje go drugiemu, jeśli argumentem jestImmutableList , więc możesz użyć tego pierwszego we wszystkich przypadkach. Nie tworzą one nowych kopii listy, a jedynie „odwrócone widoki”.

Przykład

List reversed = ImmutableList.copyOf(myList).reverse();
Geoffrey Zheng
źródło
23

Nie sądzę, że jest to możliwe przy użyciu składni pętli for. Jedyne, co mogę zasugerować, to zrobić coś takiego:

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

... ale nie powiedziałbym, że jest to „czystsze”, biorąc pod uwagę, że będzie mniej wydajne.

Adamski
źródło
26
zmienia się także miejsce, przez które przechodzisz listę, co jest dość dużym efektem ubocznym. (Załóżmy, że zawijasz to w metodzie, za każdym razem, gdy jest wywoływana, przeglądasz listę w drugą stronę ^^)
Jolivier
To najczystsze rozwiązanie.
jfajunior
14

Opcja 1: Czy zastanawiałeś się nad odwróceniem listy za pomocą kolekcji # reverse (), a następnie za pomocą foreach?

Oczywiście możesz również chcieć zmienić kod tak, aby lista była uporządkowana poprawnie, abyś nie musiał jej odwracać, co zużywa więcej miejsca / czasu.


EDYTOWAĆ:

Opcja 2: Alternatywnie, czy możesz użyć Deque zamiast ArrayList? Umożliwi to iterację do przodu i do tyłu


EDYTOWAĆ:

Opcja 3: Jak sugerowali inni, możesz napisać iterator, który przejdzie listę w odwrotnej kolejności, oto przykład:

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);
}
Kevin
źródło
1
Zastanawiałem się nad tym, ale ponoszenie kosztów jego cofnięcia jest wygórowane. Ponadto wymaga tylko tego rodzaju iteracji około połowy czasu. Odwrócenie go spowoduje po prostu przeniesienie problemu na drugą połowę.
Allain Lalonde,
3
+ dla Deque; typowe implementacje mają descendingIterator().
trashgod
To by działało okropnie dla połączonych list, wariant PO jest lepszy.
masterxilo
1
for eachMoim zdaniem użycie wyrażenia jest najbardziej idiomatycznym rozwiązaniem. Miło jest zdawać sobie sprawę, że jest to możliwe, jeśli twoja lista implementuje Iterable w sposób, który iteruje wstecz. Mam zamiar użyć tego podejścia i użyć klasy ReverseListIterator z kolekcji Apache Commons.
David Groomes
11

Możesz użyć konkretnej klasy LinkedListzamiast ogólnego interfejsu List. Potem masz descendingIteratoriterację w odwrotnym kierunku.

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

Nie wiem, dlaczego nie ma descendingIteratorz ArrayList...

tangens
źródło
9

To stare pytanie, ale brakuje w nim odpowiedzi przyjaznej dla java8. Oto kilka sposobów iteracji listy za pomocą interfejsu API przesyłania strumieniowego:

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
Federico Peralta Schaffner
źródło
2
bardzo ładna, tylko kosmetyczna zmiana: int size = list.size (); ListIterator <Integer> it = list.listIterator (rozmiar); Stream.generate (it :: previous) .limit (size) .forEach (System.out :: println);
benez
5

Oto (nie przetestowana) implementacja ReverseIterable. Po iterator()wywołaniu tworzy i zwraca prywatną ReverseIteratorimplementację, która po prostu odwzorowuje połączenia hasNext()na hasPrevious()i połączenia na next()są mapowane previous(). Oznacza to, że możesz iterować ArrayListw odwrotnej kolejności w następujący sposób:

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

Definicja klasy

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()));
  }
}
Adamski
źródło
Wydaje mi się słuszne, ale ujawnienie go jako metody statycznej zamiast publicznego konstruktora pozwoliłoby (w większości przypadków) uniknąć konieczności określania parametru typu przez klienta. Tak to robi Guava ..
Kevin Bourrillion,
2
Jest to najlepsza implementacja, jednak ReverseIteratorbrakuje w niej niezbędnego konstruktora i Listzamiast tego należy użyć kodu ArrayList.
Andy
5

Jeśli listy są dość małe, więc wydajność nie jest prawdziwym problemem, można użyć opcji reverse-metod Listsklasy-in Google Guava. for-eachDaje całkiem kod, a oryginalna lista pozostaje taka sama. Ponadto lista odwrócona jest wspierana przez listę oryginalną, więc wszelkie zmiany na liście oryginalnej zostaną odzwierciedlone na liście odwróconej.

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

Daje następujący wynik:

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

Co oznacza, że ​​odwrotną iterację mojej listy można zapisać jako:

for (final String someString : Lists.reverse(myList)) {
    //do something
}
Tobb
źródło
5

Utwórz niestandardowy reverseIterable.

nanda
źródło
Nie wiem, dlaczego jest to przegłosowane, mógłbym po prostu zawinąć listę, która to robi.
Allain Lalonde,
Jest to nie mniej czyste niż wywołanie IM. Collections.reverse ().
Adamski
@Allain: Uzgodnione ponownie. opinie negatywne, chociaż nie rozumiem, dlaczego przeglądasz wywołanie listIterator (list.size ()) jako „nie czyste”. Nawet jeśli go owiniesz, nadal musisz gdzieś wywołać tę samą metodę.
Adamski
Załóżmy, że nie, po prostu nie chcąc wziąć hitu wydajności, dla czystości.
Allain Lalonde,
18
Głosowałem w dół, ponieważ nie uważam, że jest to pełna odpowiedź.
Maarten Bodewes
3

Bardzo prosty przykład:

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) {
  ...
}
Ravi Kant Soni
źródło
2

Znaleziono także metodę odwrotną do kolekcji Google .

Allain Lalonde
źródło
Która wersja kolekcji Google? twój link już nie istnieje.
AaA
1

Aby mieć kod, który wygląda następująco:

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

Umieść ten kod w pliku o nazwie „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();
                }
            };
        }
    }
}
intrepidis
źródło
1
Zostało to wspomniane gdzie indziej w tym wątku, ale listIteratorpole musi znajdować się wewnątrz Iteratorimplementacji, a nie Iterableimplementacji.
Andy
1
Po co używać typu wyliczeniowego, a nie klasy?
Aubin
1
To sprawia, że ​​klasa „In” nie jest możliwa do uruchomienia, bez konieczności pisania prywatnego domyślnego konstruktora. Dobry dla klas, które mają tylko metody statyczne.
intrepidis,
Powiedziałbym, że nadużywanie wyliczeń tylko po to, by uniknąć pisania prywatnego domyślnego konstruktora, jest w najlepszym razie mylące. Co powiesz na użycie wyliczeń dla wyliczeń i klas dla klas? Zmieniając klasę w wyliczenie, domyślnie otrzymuje również na przykład name()metodę, ordinal()metodę i static valueOf()metodę.
JHH
1
Tak, możesz kontynuować swoją opinię i to też będzie dobrze działać, ale myślę wręcz przeciwnie. Klasy służą do tworzenia instancji obiektów i nadużywania ich przez włączenie prywatnego domyślnego konstruktora, aby zahamować ich tworzenie, co najwyżej mylące. W Javie wyliczenia w rzeczywistości są klasami, ale takimi, których projekt nigdy nie może utworzyć.
intrepidis,
0

Jak zasugerowano co najmniej dwa razy, możesz używać descendingIteratorz Deque, w szczególności z LinkedList. Jeśli chcesz użyć pętli dla każdego (tj. Mieć an Iterable), możesz zbudować i użyć otoki w następujący sposób:

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

Powód: „Nie wiem, dlaczego nie ma malejącego interfejsu z ArrayList ...”

Ponieważ lista tablic nie utrzymuje listy w tej samej kolejności, w jakiej dane zostały dodane do listy. Nigdy nie używaj Arraylist.

Lista połączona utrzyma dane w tej samej kolejności ADD do listy.

Tak więc powyżej w moim przykładzie użyłem ArrayList (), aby zmusić użytkownika do skręcenia umysłu i zmuszenia go do ćwiczenia czegoś ze swojej strony.

Zamiast tego

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

POSŁUGIWAĆ SIĘ:

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) {
  ...
}
Ravi Kant Soni
źródło
4
„Ponieważ lista tablic nie utrzymuje listy w tej samej kolejności, w jakiej dane zostały dodane do listy” umm, tak, przynajmniej działa w taki sam sposób jak lista połączona. Czy możesz podać przykład, jak oni tego nie robią?
corsiKa
Tak, ArrayList i LinkedList (ogólnie umowa List) utrzymują elementy w kolejności wstawiania. umowa Set jest nieuporządkowana lub uporządkowana według sortowania (TreeSet). Odwrotny pomysł nie jest zły, ale pamiętaj, że faktycznie porządkuje listę, która może być powolna.
Bill K
2
Poparłem tę odpowiedź, ponieważ błędnie stwierdza, że ​​ArrayLists nie utrzymują elementów w kolejności wstawiania. Jak zauważa @ bill-k, wszystkie listy są uporządkowane według definicji. Sugestia użycia zamiast tego LinkedList ma tę zaletę, że zawiera metodę descendingIterator (), ale tylko wtedy, gdy wydajność jest ważniejsza niż pamięć i abstrakcja.
Michael Scheper