Mengapa tidak ada SortedList di Java?

503

Di Jawa ada SortedSetdan SortedMapantarmuka. Keduanya termasuk dalam kerangka Java Collections dan menyediakan cara yang diurutkan untuk mengakses elemen.

Namun, dalam pemahaman saya tidak ada SortedListdi Jawa. Anda dapat menggunakannya java.util.Collections.sort()untuk mengurutkan daftar.

Adakah yang tahu mengapa itu dirancang seperti itu?

Jijoy
sumber
6
jadi apa hasil yang Anda harapkan saat memasukkan elemen di tengah daftar?
bestsss
4
@bestsss Sangat mungkin untuk memiliki kelas SortedList yang tidak mengimplementasikan antarmuka java.util.List. Baca pertanyaan sebagai pertanyaan mengapa tidak ada struktur data yang mendukung fungsionalitas yang diminta. Jangan terganggu oleh detail yang tidak penting seperti penamaan.
Alderath
@Alderath, struktur yang biasa untuk ini adalah pohon (merah / hitam, avl atau btree) dengan tautan tambahan prev / next untuk mendukung pemesanan. Saya menggunakan struktur serupa dengan tautan merah / hitam w / prev / berikutnya. Ini adalah penggunaan yang cukup niche. Pohon dapat dilalui baik urutan dan susunan, memiliki O (logn) menemukan / mengandung tetapi mendapatkan (int) adalah O (n). Mengingat kenyataan penerapan niche, saya menganggap itu diserahkan kepada pengembang untuk menerapkan satu jika mereka perlu.
bestsss
3
Tidak menjawab pertanyaan "mengapa", tetapi solusi paling sederhana untuk TreeSet adalah dengan menggunakan Comparator yang tidak pernah mengembalikan nol, misalnya int diff = this.score - that.score; return (diff == 0) ? 1 : diff; Karena ini adalah hack yang bau, saya akan memberikan ini sebagai argumen konstruktor anonim daripada memiliki implementasi Sebanding.
earcam

Jawaban:

682

Daftar iterators menjamin pertama dan terutama bahwa Anda mendapatkan elemen daftar dalam urutan internal daftar (alias urutan penyisipan ). Lebih khusus itu dalam urutan Anda memasukkan elemen atau tentang bagaimana Anda memanipulasi daftar. Penyortiran dapat dilihat sebagai manipulasi struktur data, dan ada beberapa cara untuk menyortir daftar.

Saya akan memesan cara-cara dalam urutan kegunaan seperti yang saya pribadi melihatnya:

1. Pertimbangkan untuk menggunakan Setatau Bagkoleksi sebagai gantinya

CATATAN: Saya menempatkan opsi ini di bagian atas karena ini adalah apa yang biasanya ingin Anda lakukan.

Kumpulan yang diurutkan secara otomatis mengurutkan koleksi pada saat penyisipan , yang berarti ia melakukan penyortiran saat Anda menambahkan elemen ke dalam koleksi. Ini juga berarti Anda tidak perlu mengurutkannya secara manual.

Selain itu, jika Anda yakin tidak perlu khawatir tentang (atau memiliki) elemen duplikat, Anda bisa menggunakannya TreeSet<T>. Itu mengimplementasikan SortedSetdan NavigableSetantarmuka dan bekerja seperti yang mungkin Anda harapkan dari daftar:

TreeSet<String> set = new TreeSet<String>();
set.add("lol");
set.add("cat");
// automatically sorts natural order when adding

for (String s : set) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

Jika Anda tidak ingin pemesanan alami, Anda dapat menggunakan parameter konstruktor yang mengambil a Comparator<T>.

Atau, Anda dapat menggunakan Multiset (juga dikenal sebagai Tas ) , yaitu Setyang memungkinkan elemen duplikat, dan ada implementasi pihak ketiga. Terutama dari perpustakaan Jambu adaTreeMultiset , yang bekerja sangat mirip TreeSet.

2. Urutkan daftar Anda dengan Collections.sort()

Seperti disebutkan di atas, pengurutan List s adalah manipulasi struktur data. Jadi untuk situasi di mana Anda membutuhkan "satu sumber kebenaran" yang akan diurutkan dalam berbagai cara, maka mengurutkannya secara manual adalah cara yang harus ditempuh.

Anda dapat mengurutkan daftar Anda dengan java.util.Collections.sort()metode ini. Berikut ini contoh kode tentang caranya:

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

Collections.sort(strings);
for (String s : strings) {
    System.out.println(s);
}
// Prints out "cat" and "lol"

Menggunakan pembanding

Salah satu manfaat yang jelas adalah bahwa Anda dapat menggunakan Comparatordalam sortmetode. Java juga menyediakan beberapa implementasi untuk Comparatorseperti Collatoryang berguna untuk lokal string penyortiran sensitif. Ini salah satu contohnya:

Collator usCollator = Collator.getInstance(Locale.US);
usCollator.setStrength(Collator.PRIMARY); // ignores casing

Collections.sort(strings, usCollator);

Menyortir dalam lingkungan berbarengan

Namun perlu dicatat bahwa menggunakan sortmetode ini tidak ramah di lingkungan bersamaan, karena instance koleksi akan dimanipulasi, dan Anda sebaiknya mempertimbangkan untuk menggunakan koleksi yang tidak dapat diubah. Ini adalah sesuatu yang disediakan oleh Guava di Orderingkelas dan merupakan one-liner sederhana:

List<string> sorted = Ordering.natural().sortedCopy(strings);

3. Bungkus daftar Anda dengan java.util.PriorityQueue

Meskipun tidak ada daftar yang diurutkan di Jawa, namun ada antrian yang diurutkan yang mungkin akan bekerja dengan baik untuk Anda. Itu java.util.PriorityQueuekelasnya.

Nico Haase menautkan di komentar ke pertanyaan terkait yang juga menjawab ini.

Dalam koleksi yang diurutkan Anda kemungkinan besar tidak ingin memanipulasi struktur data internal yang mengapa PriorityQueue tidak mengimplementasikan antarmuka Daftar (karena itu akan memberi Anda akses langsung ke elemen-elemennya).

Peringatan pada PriorityQueueiterator

The PriorityQueuekelas alat yang Iterable<E>dan Collection<E>antarmuka sehingga dapat mengulangi seperti biasa. Namun, iterator tidak dijamin untuk mengembalikan elemen dalam urutan yang diurutkan. Alih-alih (seperti yang ditunjukkan Alderath dalam komentar), Anda harus poll()mengantri sampai kosong.

Perhatikan bahwa Anda dapat mengonversi daftar ke antrian prioritas melalui konstruktor yang mengambil koleksi apa pun :

List<String> strings = new ArrayList<String>()
strings.add("lol");
strings.add("cat");

PriorityQueue<String> sortedStrings = new PriorityQueue(strings);
while(!sortedStrings.isEmpty()) {
    System.out.println(sortedStrings.poll());
}
// Prints out "cat" and "lol"

4. Tulis SortedListkelas Anda sendiri

CATATAN: Anda tidak harus melakukan ini.

Anda bisa menulis kelas Daftar Anda sendiri yang mengurutkan setiap kali Anda menambahkan elemen baru. Ini bisa menjadi perhitungan yang berat tergantung pada implementasi Anda dan tidak ada gunanya , kecuali jika Anda ingin melakukannya sebagai latihan, karena dua alasan utama:

  1. Itu melanggar kontrak yang List<E>dimiliki antarmuka karena addmetode harus memastikan bahwa elemen akan berada dalam indeks yang ditentukan pengguna.
  2. Mengapa menemukan kembali roda? Anda harus menggunakan TreeSet atau Multisets sebagai gantinya seperti yang ditunjukkan pada poin pertama di atas.

Namun, jika Anda ingin melakukannya sebagai latihan di sini adalah contoh kode untuk Anda mulai, menggunakan AbstractListkelas abstrak:

public class SortedList<E> extends AbstractList<E> {

    private ArrayList<E> internalList = new ArrayList<E>();

    // Note that add(E e) in AbstractList is calling this one
    @Override 
    public void add(int position, E e) {
        internalList.add(e);
        Collections.sort(internalList, null);
    }

    @Override
    public E get(int i) {
        return internalList.get(i);
    }

    @Override
    public int size() {
        return internalList.size();
    }

}

Perhatikan bahwa jika Anda belum mengganti metode yang Anda butuhkan, maka implementasi default dari AbstractListakan membuang UnsupportedOperationException.

Spoike
sumber
12
+1. Imo, ini lebih konstruktif daripada jawaban terpilih teratas. Itu datang dengan dua kelemahan kecil sekalipun. PriorityQueue tidak mendukung akses acak. Anda tidak dapat mengintip (elementIndex). Jadi Anda tidak bisa melakukan mis Integer maxVal = prioQueue.peek(prioQueue.size() - 1);. Kedua jika Anda berniat menggunakan PriorityQueue hanya sebagai daftar yang diurutkan, itu akan terdengar kurang intuitif untuk dilihat PriorityQueuedalam kode daripada seharusnya SortedList, jika struktur data seperti itu ada.
Alderath
12
Dan, setelah melihat pertanyaan lain yang ditautkan seseorang dalam komentar, kerugian besar lainnya adalah bahwa iterator dari PriorityQueue tidak dijamin untuk mengembalikan elemen dalam urutan tertentu. Jadi, kecuali saya melihat sesuatu, satu-satunya cara untuk misalnya mencetak semua objek di PriorityQueue adalah dengan berulang kali polling () antrian sampai kosong. Bagi saya, ini terasa batas ulang. Untuk mencetak objek dalam PriorityQueue dua kali, pertama-tama Anda harus menyalin PriorityQueue dan kemudian polling () semua objek dari PriorityQueue asli dan kemudian pol () l semua objek dari copy.
Alderath
1
Hmm ... sepertinya kau benar, Alderath. Anda tidak dapat menggunakan iterator PriorityQueue untuk mendapatkan elemen dalam urutan yang diharapkan. Sepertinya saya harus mengedit jawaban saya.
Spoike
7
Antrian prioritas hanya tumpukan, Anda hanya dapat mengakses bagian atas, karena itu bukan milik jawaban pertanyaan.
bestsss
1
Juga patut dicatat adalah bahwa Collections.sort()bahkan memungkinkan Anda menentukan fungsi bandingkan yang digunakan untuk mengurutkan, dengan menggunakan Comparatorobjek.
Mike 'Pomax' Kamermans
71

Karena konsep Daftar tidak sesuai dengan konsep koleksi yang diurutkan secara otomatis. Inti dari Daftar adalah bahwa setelah memanggil list.add(7, elem), panggilan ke list.get(7)akan kembali elem. Dengan daftar yang diurutkan secara otomatis, elemen dapat berakhir pada posisi yang sewenang-wenang.

Michael Borgwardt
sumber
26

Karena semua daftar sudah "diurutkan" berdasarkan urutan item yang ditambahkan (pemesanan FIFO), Anda dapat "resor" dengan urutan lain, termasuk pemesanan elemen secara alami, menggunakan java.util.Collections.sort().

EDIT:

Daftar sebagai struktur data didasarkan pada apa yang menarik adalah urutan di mana barang-barang dimasukkan.

Set tidak memiliki informasi itu.

Jika Anda ingin memesan dengan menambahkan waktu, gunakan List. Jika Anda ingin memesan dengan kriteria lain, gunakan SortedSet.

SJuan76
sumber
22
Set tidak mengizinkan duplikat
bestsss
10
Saya kira ini bukan jawaban yang sangat bagus. Tentu, daftar di java API memang memiliki urutan tertentu ditentukan oleh kapan / bagaimana item dimasukkan. Namun, keberadaan daftar yang dipesan dengan cara yang tergantung pada metode penyisipan / waktu tidak mencegah memiliki struktur data lain di mana pesanan ditentukan dengan cara lain (misalnya oleh Pembanding). Pada dasarnya, OP menanyakan mengapa tidak ada struktur data yang setara dengan SortedSet dengan pengecualian bahwa struktur data harus memungkinkan untuk beberapa kejadian elemen yang sama.
Alderath
5
Jadi pertanyaan tindak lanjut saya adalah: " Mengapa tidak ada struktur data yang berfungsi seperti SortedSet, tetapi yang dapat berisi beberapa elemen yang sama? " (Dan tolong jangan jawab " karena Set hanya dapat berisi satu elemen ")
Alderath
@Alderath: Lihat stackoverflow.com/questions/416266/sorted-collection-in-java . Singkatnya: Gunakan TreeMultiset Guava
Janus Troelsen
4
Daftar TIDAK "diurutkan", bahkan jika Anda menempatkan "diurutkan" dalam tanda kutip ganda. Mereka diperintahkan.
Koray Tugay
21

Set dan Map adalah struktur data non-linear. Daftar adalah struktur data linier.

masukkan deskripsi gambar di sini


Struktur data pohon SortedSetdan SortedMapantarmuka mengimplementasikan TreeSetdan TreeMapmasing-masing menggunakan algoritma implementasi pohon Merah-Hitam yang digunakan. Jadi itu memastikan bahwa tidak ada item duplikat (atau kunci jika Map)

  • List sudah mempertahankan koleksi tertata dan struktur data berbasis indeks, pohon bukanlah struktur data berbasis indeks.
  • Tree menurut definisi tidak boleh mengandung duplikat.
  • Dalam Listkita dapat memiliki duplikat, jadi tidak ada TreeList(yaitu tidak SortedList).
  • Daftar mempertahankan elemen dalam urutan penyisipan. Jadi jika kita ingin mengurutkan daftar kita harus menggunakan java.util.Collections.sort(). Ini mengurutkan daftar yang ditentukan ke dalam urutan menaik, sesuai dengan urutan alami unsur-unsurnya.
Premraj
sumber
14

JavaFX SortedList

Meskipun butuh beberapa saat, Java 8 memang memiliki diurutkan List. http://docs.oracle.com/javase/8/javafx/api/javafx/collections/transformation/SortedList.html

Seperti yang Anda lihat di javadocs, itu adalah bagian dari koleksi JavaFX , dimaksudkan untuk memberikan tampilan yang diurutkan pada ObservableList.

Pembaruan: Perhatikan bahwa dengan Java 11, toolkit JavaFX telah pindah ke luar JDK dan sekarang menjadi perpustakaan yang terpisah. JavaFX 11 tersedia sebagai SDK yang dapat diunduh atau dari MavenCentral. Lihat https://openjfx.io

swpalmer
sumber
2
Sayangnya, SortedList ini tidak berfungsi seperti daftar biasa - misalnya, itu tidak memiliki konstruktor default (Anda harus membangunnya menggunakan ObservableList, apa pun artinya ...)
Erel Segal-Halevi
SortedList dari JavaFX jelas untuk penggunaan GUI-Components dan karena overhead TIDAK cocok untuk hanya memiliki Daftar objek yang diurutkan. Juga berarti merujuk seluruh Modul FX bahkan jika GUI tidak digunakan dalam proyek.
COBRA.cH
1
@ COBRA.cH Ya, itu benar. Daftar Sortir yang lebih berkinerja mungkin bisa menjadi pembungkus tipis di sekitar TreeMap, di mana integer digunakan untuk menghitung duplikat kunci. Anda juga bisa menggunakan TreeSet dengan komparator yang tidak pernah mengembalikan 0.
swpalmer
Mengapa suara turun? Pertanyaannya dimulai dengan anggapan bahwa tidak ada daftar diurutkan di perpustakaan JDK. Jawaban ini mengoreksi asumsi itu. Apakah Anda suka atau tidak menyukai penerapan daftar yang diurutkan ini bukan alasan untuk memilih. Jawaban ini tidak merekomendasikan kelas, hanya menunjukkan keberadaannya.
Basil Bourque
10

Untuk setiap pendatang baru, per April 2015, Android sekarang memiliki kelas SortedList di perpustakaan dukungan, yang dirancang khusus untuk bekerja dengannya RecyclerView. Inilah posting blog tentang itu.

Amagi82
sumber
1
Perlu dicatat bahwa pada saat komentar ini Android SortedList tidak memiliki dukungan untuk fungsionalitas onItemMoved () dengan RecyclerView. Menulis SortedList saya sendiri yang kurang efisien adalah apa yang harus saya lakukan untuk mengatasi keterbatasan.
Matius
4

Poin lainnya adalah kompleksitas waktu operasi penyisipan. Untuk memasukkan daftar, orang mengharapkan kompleksitas O (1). Tetapi ini tidak dapat dijamin dengan daftar yang diurutkan.

Dan poin yang paling penting adalah bahwa daftar tidak berasumsi tentang elemen mereka. Misalnya, Anda dapat membuat daftar hal-hal yang tidak diterapkan equalsatau compare.

Ingo
sumber
Anda dapat memiliki daftar w / O (logn) masukkan / hapus / temukan / mengandung tetapi tidak w / get (int).
bestsss
3
Poin terakhir sebenarnya bukan penjelasan yang bagus. Anda dapat membuat beberapa SortedSethal yang tidak diterapkan Comparable. Lihat konstruktor TreeSet ini .
Alderath
1
@Alderath - mungkin kata-kata saya terlalu lemah. Namun pengamatan menyatakan bahwa elemen Set dan kunci Pohon harus sebanding setidaknya untuk kesetaraan, sedangkan elemen daftar tidak perlu. Apakah hubungan pemesanan / kesetaraan untuk Set dan Pohon diterapkan dalam Pembanding atau di tempat lain tidak penting - tetapi Anda memerlukannya.
Ingo
Daftar tidak menjamin O (1) penyisipan , mereka menjamin O (1) akses . bigocheatsheet.com
Matt Quigley
1
@Betlista kamu benar! Saya tidak dapat memperbarui komentar saya, tetapi Listantarmuka Java tidak menjamin spesifikasi kinerja pada metodenya.
Matt Quigley
3

Pikirkan seperti ini: Listantarmuka memiliki metode seperti add(int index, E element),set(int index, E element) . Kontraknya adalah bahwa sekali Anda menambahkan elemen pada posisi X Anda akan menemukannya di sana kecuali Anda menambahkan atau menghapus elemen sebelumnya.

Jika implementasi daftar apa pun akan menyimpan elemen dalam urutan selain berdasarkan indeks, metode daftar di atas tidak masuk akal.

Costi Ciudatu
sumber
2

Baris pertama dalam API Daftar mengatakan itu adalah koleksi yang dipesan (juga dikenal sebagai urutan). Jika Anda mengurutkan daftar, Anda tidak dapat mempertahankan pesanan, jadi tidak ada TreeList di Jawa.
Seperti kata API, Daftar Java terinspirasi dari Sequence dan lihat properti sequence http://en.wikipedia.org/wiki/Sequence_(mathematics )

Itu tidak berarti bahwa Anda tidak dapat mengurutkan daftar, tetapi Java ketat dengan definisinya dan tidak menyediakan versi daftar yang diurutkan secara default.

Syam
sumber
2

Jika Anda mencari cara untuk mengurutkan elemen, tetapi juga dapat mengaksesnya dengan indeks secara efisien, Anda dapat melakukan hal berikut:

  1. Gunakan daftar akses acak untuk penyimpanan (mis. ArrayList)
  2. Pastikan selalu disortir

Kemudian untuk menambah atau menghapus elemen yang bisa Anda gunakan Collections.binarySearchuntuk mendapatkan indeks penyisipan / penghapusan. Karena daftar Anda menerapkan akses acak, Anda dapat secara efisien mengubah daftar dengan indeks yang ditentukan.

Contoh:

/**
 * @deprecated
 *      Only for demonstration purposes. Implementation is incomplete and does not 
 *      handle invalid arguments.
 */
@Deprecated
public class SortingList<E extends Comparable<E>> {
    private ArrayList<E> delegate;

    public SortingList() {
        delegate = new ArrayList<>();
    }

    public void add(E e) {
        int insertionIndex = Collections.binarySearch(delegate, e);

        // < 0 if element is not in the list, see Collections.binarySearch
        if (insertionIndex < 0) {
            insertionIndex = -(insertionIndex + 1);
        }
        else {
            // Insertion index is index of existing element, to add new element 
            // behind it increase index
            insertionIndex++;
        }

        delegate.add(insertionIndex, e);
    }

    public void remove(E e) {
        int index = Collections.binarySearch(delegate, e);
        delegate.remove(index);
    }

    public E get(int index) {
        return delegate.get(index);
    }
}
Marcono1234
sumber
1

Pertimbangkan untuk menggunakan indeks-pohon-peta . Ini TreeSet JDK yang disempurnakan yang menyediakan akses ke elemen dengan indeks dan menemukan indeks elemen tanpa iterasi atau daftar tersembunyi yang mendasari yang mendukung pohon. Algoritma ini didasarkan pada pembaruan bobot dari perubahan node setiap kali ada perubahan.

Vitaly Sazanovich
sumber