Iterasi melalui daftar dalam urutan terbalik di java


251

Saya memigrasi sepotong kode untuk menggunakan obat generik. Salah satu argumen untuk melakukannya adalah bahwa for loop jauh lebih bersih daripada melacak indeks, atau menggunakan iterator eksplisit.

Dalam sekitar setengah kasus, daftar (sebuah ArrayList) sedang diulang dalam urutan terbalik dengan menggunakan indeks hari ini.

Dapatkah seseorang menyarankan cara yang lebih bersih untuk melakukan ini (karena saya tidak suka indexed for loopketika bekerja dengan koleksi), meskipun itu berhasil?

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

Catatan: Saya tidak bisa menambahkan dependensi baru di luar JDK.


10
Apa yang sangat buruk tentang penggunaan indeks eksplisit untuk beralih pada struktur data yang diindeks? Setidaknya itu menunjukkan kepada Anda apa yang sebenarnya terjadi. Untuk iterating backwards saya selalu idiom yang sedikit lebih pendek:for (int i = nodes.size(); --i >= 0;)
x4u

4
Tidak ada yang khusus, saya lebih suka memprogram ke antarmuka dan tidak tahu tentang jenis daftar yang saya gunakan. Meskipun saya sangat menyukai tangan pendek Anda. (+1 beri komentar)
Allain Lalonde

1
@ x4u: Tidak ada banyak di dalamnya meskipun Iterator gagal-cepat dan juga memungkinkan elemen-elemen mudah dihapus selama iterasi.
Adamski

2
Kelas ini rusak, karena pengguna mungkin ingin mengulangi yang kedua kali dari Iterable yang sama, atau daftar mungkin berubah antara ketika iterable dibangun dan ketika iterated. Saya yakin untuk keperluan Anda, Anda hanya perlu memastikan tidak melakukan itu, tetapi tidak akan terlalu sulit untuk memperbaiki kode; atau hanya mencuri kode dari Guava (lisensi Apache 2.0): code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
Kevin Bourrillion

1
Cukup adil, tetapi bahkan satu jambu biji pun rentan terhadap hal yang sama jika saya membacanya dengan benar. Jika pengguna menyimpan salinan hasil dari kebalikannya, itu akan memiliki masalah yang sama.
Allain Lalonde

Jawaban:


447

Coba ini:

// 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
Tidak buruk. Tidak menggunakan indeks, tetapi kehilangan keanggunan dari untuk setiap sintaks. Tetap memberi +1.
Allain Lalonde

26
Memanggil listIterator () tanpa argumen indeks akan memberikan Iterator di awal daftar dan hasPrevious () akan mengembalikan false pada panggilan pertama.
Adamski

2
Anda ingin indeks di listIteratortelepon, saya pikir.
Tom Hawtin - tackline

1
Anda dapat menulis Iteratoryang menggunakan ListIteratorterbalik, tetapi itu mungkin tidak layak untuk satu loop.
Tom Hawtin - tackline

1
Ini bukan satu loop, jadi saya mengambil ini dan membungkusnya. pastebin.ca/1759041 jadi, sekarang saya bisa melakukanfor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde

35

Jambu biji menawarkan Lists#reverse(List)dan ImmutableList#reverse(). Seperti dalam kebanyakan kasus untuk Guava, mantan delegasi ke yang terakhir jika argumennya adalahImmutableList , sehingga Anda dapat menggunakan yang pertama dalam semua kasus. Ini tidak membuat salinan baru dari daftar tetapi hanya "pandangan terbalik" darinya.

Contoh

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

23

Saya tidak berpikir itu mungkin menggunakan sintaks for loop. Satu-satunya hal yang dapat saya sarankan adalah melakukan sesuatu seperti:

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

... tapi saya tidak akan mengatakan ini "bersih" mengingat itu akan menjadi kurang efisien.


26
dan itu juga mengubah di tempat daftar yang Anda lalui, yang merupakan efek samping yang sangat besar. (Katakan Anda membungkus ini dalam suatu metode, setiap kali itu disebut Anda melintasi daftar dengan cara lain ^^)
jolivier

Ini adalah solusi terbersih.
jfajunior

14

Opsi 1: Sudahkah Anda berpikir untuk membalikkan Daftar dengan Koleksi # reverse () dan kemudian menggunakan foreach?

Tentu saja, Anda mungkin juga ingin memperbaiki kode Anda sehingga daftar tersebut dipesan dengan benar sehingga Anda tidak perlu membalikkannya, yang menggunakan ruang / waktu ekstra.


EDIT:

Opsi 2: Atau, dapatkah Anda menggunakan Deque alih-alih ArrayList? Ini akan memungkinkan Anda untuk beralih ke depan dan ke belakang


EDIT:

Opsi 3: Seperti yang disarankan orang lain, Anda bisa menulis Iterator yang akan masuk daftar secara terbalik, berikut adalah contohnya:

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
Sudah memikirkannya, tetapi ada biaya untuk membalikkan itu mahal. Juga, hanya membutuhkan iterasi semacam ini sekitar separuh waktu. Membaliknya hanya akan memindahkan masalah ke setengah lainnya.
Allain Lalonde

3
+ untuk Deque; implementasi khas miliki descendingIterator().
trashgod

Ini akan sangat buruk untuk daftar tertaut, varian OP lebih baik.
masterxilo

1
Menggunakan for eachekspresi adalah solusi paling idiomatis menurut saya. Sangat menyenangkan untuk menyadari bahwa ini dimungkinkan jika Daftar Anda menerapkan Iterable dengan cara yang berulang. Saya akan menggunakan pendekatan ini dan menggunakan kelas ReverseListIterator dari Apache Commons Collections.
David Groomes

11

Anda bisa menggunakan kelas konkret LinkedListalih-alih antarmuka umum List. Maka Anda memiliki descendingIteratoruntuk iterasi dengan arah sebaliknya.

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

Tidak tahu mengapa tidak ada descendingIteratordengan ArrayList...


9

Ini adalah pertanyaan lama, tetapi tidak memiliki jawaban ramah java8. Berikut adalah beberapa cara membalikkan daftar, dengan bantuan Streaming API:

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
sangat bagus, hanya perubahan kosmetik: int size = list.size (); ListIterator <Integer> it = list.listIterator (size); Stream.generate (it :: sebelumnya) .limit (size) .forEach (System.out :: println);
benez

5

Berikut adalah implementasi (belum diuji) dari a ReverseIterable. Ketika iterator()dipanggil itu membuat dan mengembalikan ReverseIteratorimplementasi pribadi , yang hanya memetakan panggilan hasNext()ke hasPrevious()dan panggilan ke next()dipetakan ke previous(). Ini berarti Anda dapat beralih secara ArrayListterbalik sebagai berikut:

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

Definisi kelas

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

Tampak tepat bagi saya, meskipun mengeksposnya sebagai metode statis bukan konstruktor publik akan (dalam kebanyakan kasus) menghindari kebutuhan klien untuk menentukan parameter tipe. Ini adalah bagaimana Guava melakukannya ..
Kevin Bourrillion

2
Ini adalah implementasi terbaik, namun ReverseIteratortidak ada konstruktor yang diperlukan dan kode harus digunakan Listsebagai gantinya ArrayList.
Andy

5

Jika daftar tersebut cukup kecil sehingga kinerja bukanlah masalah nyata, orang dapat menggunakan reverse-metod dari -class Listsin Google Guava. Menghasilkan for-eachkode- cantik , dan daftar asli tetap sama. Juga, daftar terbalik didukung oleh daftar asli, sehingga setiap perubahan ke daftar asli akan tercermin dalam daftar terbalik.

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

Menghasilkan hasil sebagai berikut:

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

Yang berarti bahwa iterasi balik dari myList dapat ditulis sebagai:

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

5

Buat custom reverseIterable.


Tidak tahu mengapa ini dipilih, saya mungkin hanya membuat pembungkus untuk daftar yang melakukan ini.
Allain Lalonde

Ini tidak kalah bersih daripada memanggil Collections.reverse () IMHO.
Adamski

@ Allain: Setuju ulang. downvotes meskipun saya tidak melihat mengapa Anda melihat panggilan ke listIterator (list.size ()) sebagai "tidak bersih". Bahkan jika Anda membungkusnya Anda masih harus membuat panggilan metode yang sama di suatu tempat.
Adamski

Misalkan tidak, hanya tidak mau mengambil hit kinerja, untuk kebersihan.
Allain Lalonde

18
Dipilih karena saya pikir ini bukan jawaban yang lengkap.
Maarten Bodewes

3

Contoh yang sangat sederhana:

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

Untuk memiliki kode yang terlihat seperti ini:

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

Masukkan kode ini ke file yang disebut "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
Ini telah disebutkan di tempat lain di utas, tetapi listIteratorlapangan harus di dalam Iteratorimplementasi, bukan Iterableimplementasi.
Andy

1
Mengapa menggunakan tipe enum, bukan kelas?
Aubin

1
Itu membuat kelas 'Dalam' tidak dapat dipakai, tanpa harus menulis konstruktor default pribadi. Bagus untuk kelas yang hanya memiliki metode statis.
intrepidis

Saya akan mengatakan bahwa penyalahgunaan enums hanya untuk menghindari penulisan konstruktor default pribadi paling membingungkan. Bagaimana dengan menggunakan enum untuk enum dan kelas untuk kelas? Dengan membuat kelas menjadi enum, ia juga secara implisit mendapat name()metode, metode, ordinal()dan static valueOf()metode, misalnya.
JHH

1
Ya, Anda dapat melanjutkan dengan pendapat Anda dan itu akan bekerja dengan baik juga, tapi saya pikir sebaliknya. Kelas adalah untuk membuat instance objek dan menyalahgunakannya dengan memasukkan konstruktor default pribadi untuk menghambat instantiasi mereka membingungkan. Di Jawa, enum sebenarnya adalah kelas, tetapi yang tidak pernah dapat dibuat oleh desain.
intrepidis

0

Seperti yang telah disarankan setidaknya dua kali, Anda dapat menggunakan descendingIterator dengan Deque, khususnya dengan a LinkedList. Jika Anda ingin menggunakan untuk-setiap loop (yaitu, memiliki Iterable), Anda dapat membuat dan menggunakan pembungkus seperti ini:

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

Alasan: "Tidak tahu mengapa tidak ada descendingIterator dengan ArrayList ..."

Karena daftar array tidak menyimpan daftar dalam urutan yang sama seperti data telah ditambahkan ke daftar. Jadi, jangan pernah gunakan Arraylist.

Daftar tertaut akan menyimpan data dalam urutan ADD yang sama.

Jadi, di atas dalam contoh saya, saya menggunakan ArrayList () untuk membuat pengguna memutar pikiran mereka dan membuat mereka berolahraga sesuatu dari sisi mereka.

Alih-alih ini

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

MENGGUNAKAN:

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
"Karena daftar array tidak menyimpan daftar dalam urutan yang sama seperti data telah ditambahkan ke daftar" umm, ya itu, setidaknya itu dengan cara yang sama seperti daftar tertaut. Bisakah Anda memberikan contoh bagaimana mereka tidak melakukannya?
corsiKa

Ya, ArrayList dan LinkedList (Kontrak Daftar pada umumnya) menyimpan item dalam penyisipan secara berurutan. kontrak Set tidak diurutkan atau dipesan berdasarkan sortir (TreeSet). Gagasan sebaliknya tidak buruk tetapi ingat bahwa itu sebenarnya menata ulang daftar yang bisa lambat.
Bill K

2
Saya menurunkan jawaban ini karena salah menyatakan bahwa ArrayLists tidak menyimpan item dalam urutan penyisipan. Sebagai @ bill-k mencatat, semua Daftar dipesan koleksi berdasarkan definisi. Saran untuk menggunakan LinkedList sebagai gantinya memiliki nilai, karena fitur metode descendingIterator (), tetapi hanya jika kinerja merupakan masalah yang lebih besar daripada memori dan abstraksi.
Michael Scheper
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.