Dlaczego w Javie nie ma SortedList?


502

W Javie są interfejsy SortedSeti SortedMap. Oba należą do środowiska Java Collections i zapewniają uporządkowany sposób dostępu do elementów.

Jednak w moim rozumieniu nie ma SortedListJava. Możesz użyć java.util.Collections.sort()do sortowania listy.

Masz pojęcie, dlaczego jest tak zaprojektowany?



6
więc jaki jest twój oczekiwany wynik po wstawieniu elementu na środku listy?
bestsss

4
@bestsss W pełni możliwe byłoby posiadanie klasy SortedList, która nie implementuje interfejsu java.util.List. Przeczytaj pytanie jako pytanie, dlaczego nie ma struktury danych, która obsługuje żądaną funkcjonalność. Nie rozpraszaj się nieistotnymi szczegółami, takimi jak nazywanie.
Alderath

@Alderath, zwykłą strukturą tego jest drzewo (czerwony / czarny, avl lub btree) z dodatkowymi poprzednimi / następnymi linkami do obsługi zamawiania. Używam podobnej struktury czerwono / czarnej w / poprzedni / następny link. Jest to jednak dość niszowe zastosowanie. Drzewo można przechodzić zarówno uporządkowane, jak i wstawiane, ma O (logn) znajdź / zawiera, ale get (int) to O (n). Biorąc pod uwagę fakt, że ma ona zastosowanie niszowe, zakładam, że deweloperzy mogli wdrożyć jeden, jeśli zajdzie taka potrzeba.
bestsss

3
Nie odpowiadając na pytanie „dlaczego”, ale najprostszym obejściem dla TreeSet jest użycie Komparatora, który nigdy nie zwraca zera, np. int diff = this.score - that.score; return (diff == 0) ? 1 : diff; Ponieważ jest to śmierdzący hack, podałbym to jako anonimowy argument konstruktora, zamiast mieć jakąkolwiek implementację porównywalną.
kamera douszna

Odpowiedzi:


682

Iteratory list gwarantują przede wszystkim, że elementy listy są uporządkowane wewnętrznie (np. Kolejność wstawiania ). Mówiąc dokładniej, jest to w kolejności wstawiania elementów lub sposobu manipulowania listą. Sortowanie może być postrzegane jako manipulacja strukturą danych i istnieje kilka sposobów sortowania listy.

Zamienię sposoby w kolejności użyteczności, tak jak ja to widzę:

1. Zastanów się nad użyciem Setlub Bagkolekcjami

UWAGA: Umieszczam tę opcję na górze, ponieważ i tak zwykle chcesz to zrobić.

Posortowany zestaw automatycznie sortuje kolekcję przy wstawianiu , co oznacza, że ​​wykonuje sortowanie podczas dodawania elementów do kolekcji. Oznacza to również, że nie trzeba go ręcznie sortować.

Ponadto, jeśli masz pewność, że nie musisz się martwić (lub mieć) zduplikowanymi elementami, możesz TreeSet<T>zamiast tego użyć . Implementuje SortedSeti NavigableSetinterfejsy oraz działa tak, jak można się spodziewać po liście:

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"

Jeśli nie chcesz naturalnego uporządkowania, możesz użyć parametru konstruktora, który przyjmuje parametr Comparator<T>.

Alternatywnie możesz użyć Multisets (znanych również jako Torby ) , czyli takich, Setktóre pozwalają na duplikowanie elementów, a są one ich implementacje innych firm. Przede wszystkim z bibliotek Guava istnieje taki TreeMultiset, który działa podobnie jak TreeSet.

2. Posortuj listę za pomocą Collections.sort()

Jak wspomniano powyżej, sortowanie Lists jest manipulacją strukturą danych. Tak więc w sytuacjach, w których potrzebujesz „jednego źródła prawdy”, które zostanie posortowane na różne sposoby, a następnie posortowanie go ręcznie.

Możesz posortować listę za pomocą java.util.Collections.sort()metody. Oto przykładowy kod:

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"

Korzystanie z komparatorów

Jedną z wyraźnych korzyści jest to, że możesz użyć Comparatortej sortmetody. Java zapewnia także pewne implementacje dla Comparatortakich, Collatorktóre są przydatne do łańcuchów sortujących wrażliwych na ustawienia regionalne. Oto jeden przykład:

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

Collections.sort(strings, usCollator);

Sortowanie w współbieżnych środowiskach

Należy jednak pamiętać, że użycie tej sortmetody nie jest przyjazne w środowiskach współbieżnych, ponieważ instancja kolekcji zostanie zmanipulowana, a zamiast tego należy rozważyć użycie niezmiennych kolekcji. Jest to coś, co Guava zapewnia w Orderingklasie i jest prostym, jedno-liniowym tekstem:

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

3. Zawiń listę java.util.PriorityQueue

Chociaż w Javie nie ma posortowanej listy, istnieje jednak posortowana kolejka, która prawdopodobnie będzie dla Ciebie równie dobra. To jest java.util.PriorityQueueklasa.

Nico Haase podał w komentarzach powiązane pytanie, które również na to odpowiada.

W posortowanej kolekcji najprawdopodobniej nie chcesz manipulować wewnętrzną strukturą danych, dlatego PriorityQueue nie implementuje interfejsu List (ponieważ to dałoby ci bezpośredni dostęp do jego elementów).

Zastrzeżenie dotyczące PriorityQueueiteratora

Do PriorityQueueklasy implementuje Iterable<E>i Collection<E>interfejsy więc może należy powtórzyć, jak zwykle. Jednak nie ma gwarancji, że iterator zwróci elementy w posortowanej kolejności. Zamiast tego (jak wskazuje Alderath w komentarzach) musisz stać w poll()kolejce, aż będzie pusty.

Zauważ, że możesz przekonwertować listę do kolejki priorytetowej za pomocą konstruktora, który pobiera dowolną kolekcję :

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. Napisz własną SortedListklasę

UWAGA: Nie powinieneś tego robić.

Możesz napisać własną klasę List, która będzie sortować za każdym razem, gdy dodasz nowy element. Może to być trudne do obliczeń w zależności od implementacji i jest bezcelowe , chyba że chcesz to zrobić jako ćwiczenie, z dwóch głównych powodów:

  1. Łamie kontrakt, który List<E>ma interfejs, ponieważ addmetody powinny zapewnić, że element znajdzie się w indeksie określonym przez użytkownika.
  2. Po co wymyślać koło ponownie? Zamiast tego powinieneś używać TreeSet lub Multisets, jak wskazano w pierwszym punkcie powyżej.

Jeśli jednak chcesz to zrobić jako ćwiczenie, tutaj jest próbka kodu na początek, korzysta z AbstractListklasy abstrakcyjnej:

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

}

Zauważ, że jeśli nie przesłoniłeś potrzebnych metod, wówczas domyślne implementacje z AbstractListwyrzucą UnsupportedOperationExceptions.


12
+1. Imo, to jest bardziej konstruktywne niż najczęściej głosowana odpowiedź. Ma jednak dwie niewielkie wady. PriorityQueue nie obsługuje losowego dostępu. Nie możesz wykonać podglądu (elementIndex). Więc nie możesz zrobić np Integer maxVal = prioQueue.peek(prioQueue.size() - 1);. Po drugie, jeśli zamierzasz używać PriorityQueue po prostu jako posortowanej listy, PriorityQueuew kodzie zabrzmi mniej intuicyjnie niż w SortedListprzypadku, gdyby istniała taka struktura danych.
Alderath

12
Po przeanalizowaniu drugiego pytania, które ktoś powiązał w komentarzach, kolejną dużą wadą jest to, że iterator PriorityQueue nie gwarantuje zwrotu elementów w określonej kolejności. Tak więc, chyba że coś przeoczę, jedynym sposobem np. Wydrukowania wszystkich obiektów w PriorityQueue w celu wielokrotnego odpytywania () kolejki, aż będzie pusta. Dla mnie wydaje się, że granica została ponownie uruchomiona. Aby wydrukować obiekty w PriorityQueue dwa razy, najpierw musisz skopiować PriorityQueue, a następnie odpytać () wszystkie obiekty z pierwotnego PriorityQueue, a następnie pol () l wszystkie obiekty z kopii.
Alderath

1
Hmm ... wygląda na to, że masz rację Alderath. Nie można użyć iteratora PriorityQueue, aby uzyskać elementy w oczekiwanej kolejności. Wygląda na to, że muszę edytować swoją odpowiedź.
Spoike

7
Kolejka priorytetowa to tylko kupa, możesz uzyskać dostęp tylko do góry, imo nie należy ona do odpowiedzi na pytanie.
bestsss

1
Warto również zauważyć, że Collections.sort()pozwala nawet zdefiniować funkcję porównywania używaną do sortowania za pomocą Comparatorobiektu.
Mike 'Pomax' Kamermans

71

Ponieważ pojęcie listy jest niezgodne z pojęciem kolekcji automatycznie sortowanej. Punktem listy jest to, że po wywołaniu nastąpi powrót list.add(7, elem)do połączenia . Dzięki automatycznie posortowanej liście element może znaleźć się w dowolnej pozycji.list.get(7)elem


26

Ponieważ wszystkie listy są już „sortowane” według kolejności, w której elementy zostały dodane (kolejność FIFO), można „zastosować” je w innej kolejności, w tym naturalnej kolejności elementów java.util.Collections.sort().

EDYTOWAĆ:

Listy jako struktury danych oparte są na tym, co ciekawe, w kolejności, w której elementy zostały wstawione.

Zestawy nie zawierają tych informacji.

Jeśli chcesz zamówić, dodając czas, użyj List. Jeśli chcesz zamówić według innych kryteriów, użyj SortedSet.


22
Zestaw nie zezwala na duplikaty
bestsss

10
Nie sądzę, że to bardzo dobra odpowiedź. Jasne, listy w API java mają określoną kolejność określoną przez to, kiedy / jak elementy zostały wstawione. Jednak istnienie list uporządkowanych w sposób zależny od metody / czasu wstawiania nie uniemożliwia posiadania innych struktur danych, w których kolejność jest ustalana w inny sposób (np. Przez komparator). Zasadniczo OP pyta, dlaczego nie istnieje struktura danych równoważna SortedSet, z wyjątkiem tego, że struktura danych powinna umożliwiać wielokrotne występowanie elementów, które są równe.
Alderath

5
Moje następne pytanie brzmiałoby : „ Dlaczego nie istnieje struktura danych, która działa jak SortedSet, ale która może zawierać wiele równych elementów? ” (I proszę nie odpowiadać „ ponieważ zestawy mogą zawierać tylko jeden element ”)
Alderath

@Alderath: Zobacz stackoverflow.com/questions/416266/sorted-collection-in-java . W skrócie: użyj Guava's TreeMultiset
Janus Troelsen

4
Listy NIE są „sortowane”, nawet jeśli umieścisz „sortowane” w podwójnych cudzysłowach. Są uporządkowane.
Koray Tugay

20

Zestaw i mapa to nieliniowa struktura danych. Lista jest liniową strukturą danych.

wprowadź opis zdjęcia tutaj


Struktura danych drzewa SortedSeti SortedMapinterfejsy są implementowane TreeSeti TreeMapodpowiednio używane algorytm implementacji drzewa czerwono-czarnego . Więc upewnij się, że nie ma zduplikowanych elementów (lub kluczy w przypadku Map).

  • List utrzymuje już uporządkowaną kolekcję i strukturę danych opartą na indeksie, drzewa nie są strukturami opartymi na indeksie.
  • Tree z definicji nie może zawierać duplikatów.
  • W Listmożemy mieć duplikaty, więc nie maTreeList (tj. Nie SortedList).
  • Lista zachowuje elementy w kolejności wstawiania. Więc jeśli chcemy posortować listę, musimy użyć java.util.Collections.sort(). Sortuje określoną listę w porządku rosnącym, zgodnie z naturalnym uporządkowaniem jej elementów.

14

JavaFX SortedList

Chociaż zajęło to trochę czasu, Java 8 ma posortowane List. http://docs.oracle.com/javase/8/javafx/api/javafx/collections/transformation/SortedList.html

Jak widać w javadocs, jest to część kolekcji JavaFX , która ma zapewnić posortowany widok na ObservableList.

Aktualizacja: należy pamiętać, że w Javie 11 zestaw narzędzi JavaFX został przeniesiony poza JDK i jest teraz osobną biblioteką. JavaFX 11 jest dostępny jako pakiet SDK do pobrania lub z MavenCentral. Zobacz https://openjfx.io


2
Niestety, ta SortedList nie działa jak zwykła lista - na przykład nie ma domyślnego konstruktora (musisz ją zbudować za pomocą ObservableList, cokolwiek to znaczy ...)
Erel Segal-Halevi

SortedList z JavaFX najwyraźniej służy do korzystania ze składników GUI i ze względu na obciążenie NIE nadaje się tylko do posiadania listy posortowanych obiektów. Oznaczałoby to także odniesienie do całego modułu FX, nawet jeśli GUI nie jest używane w projekcie.
COBRA.cH

1
@ COBRA.cH Tak, to prawda. Bardziej wydajna lista posortowana może być prawdopodobnie cienkim opakowaniem wokół TreeMap, gdzie liczba całkowita jest używana do liczenia duplikatów klucza. Możesz także użyć TreeSet z komparatorem, który nigdy nie zwraca 0.
swpalmer

Dlaczego głosy zagłosowały? Pytanie zaczyna się od założenia, że ​​w bibliotekach JDK nie ma posortowanej listy. Ta odpowiedź koryguje to założenie. To, czy ci się podoba, czy nie, wdrożenie tej konkretnej posortowanej listy nie jest powodem do głosowania w dół. Ta odpowiedź nie poleca klasie, wskazuje jedynie jej istnienie.
Basil Bourque,

10

Dla wszystkich nowych użytkowników, od kwietnia 2015 r., Android ma teraz klasę SortedList w bibliotece pomocy technicznej, zaprojektowaną specjalnie do pracy RecyclerView. Oto post na ten temat na blogu .


1
Warto zauważyć, że w momencie tego komentarza Androids SortedList nie obsługuje funkcji onItemMoved () z RecyclerView. Pisanie własnej listy SortedList, która jest mniej wydajna, musiałem zrobić, aby ominąć ograniczenia.
Matthew

4

Kolejnym punktem jest złożoność czasowa operacji wstawiania. W przypadku wstawiania listy oczekuje się złożoności O (1). Nie można tego jednak zagwarantować za pomocą posortowanej listy.

Najważniejsze jest to, że listy nie zakładają niczego o swoich elementach. Na przykład możesz tworzyć listy rzeczy, które nie są implementowane equalslub compare.


możesz mieć listę w / O (logn) wstaw / usuń / znajdź / zawiera, ale nie w / get (int).
bestsss

3
Ostatni punkt nie jest tak naprawdę dobrym wytłumaczeniem. Możesz zrobić SortedSetrzeczy, których nie implementujesz Comparable. Zobacz ten konstruktor TreeSet .
Alderath,

1
@Alderath - może moje sformułowanie było zbyt słabe. Jednak obserwacja utrzymuje, że elementy zbiorów i klucze drzew muszą być porównywalne przynajmniej dla równości, podczas gdy elementy listy nie muszą. To, czy relacja kolejności / równości dla zbiorów i drzew jest zaimplementowana w Komparatorze, czy gdzie indziej, jest nieistotna - ale potrzebujesz takiego.
Ingo

Listy nie gwarantuje O (1) wprowadzenie , gwarantują O (1) dostępu . bigocheatsheet.com
Matt Quigley,

1
@Betlista masz rację! Nie mogę zaktualizować mojego komentarza, ale Listinterfejs Java nie gwarantuje żadnych specyfikacji wydajności jego metod.
Matt Quigley,

3

Pomyśl o tym w ten sposób: Listinterfejs ma takie metody add(int index, E element),set(int index, E element) . Umowa polega na tym, że po dodaniu elementu w pozycji X znajdziesz go tam, chyba że dodasz lub usuniesz elementy przed nim.

Jeśli jakakolwiek implementacja listy przechowałaby elementy w innej kolejności niż na podstawie indeksu, powyższe metody listy nie miałyby sensu.


2

Pierwszy wiersz w interfejsie API List mówi, że jest to uporządkowana kolekcja (znana również jako sekwencja). Jeśli posortujesz listę, nie możesz utrzymać kolejności, więc w Javie nie ma TreeList.
Jak mówi API, lista Java została zainspirowana sekwencją i zobacz właściwości sekwencji http://en.wikipedia.org/wiki/Sequence_(mathematics )

Nie oznacza to, że nie można sortować listy, ale Java jest ściśle zgodna z jego definicją i domyślnie nie zapewnia posortowanych wersji list.



2

Jeśli szukasz sposobu na sortowanie elementów, ale możesz także uzyskać do nich dostęp za pomocą indeksu w efektywny sposób, możesz wykonać następujące czynności:

  1. Do przechowywania użyj listy o swobodnym dostępie (np ArrayList )
  2. Upewnij się, że zawsze jest posortowane

Następnie, aby dodać lub usunąć element, którego możesz użyć Collections.binarySearch aby uzyskać indeks wstawiania / usuwania. Ponieważ twoja lista implementuje losowy dostęp, możesz skutecznie modyfikować listę za pomocą określonego indeksu.

Przykład:

/**
 * @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);
    }
}

1

Rozważ użycie indeksowanej mapy drzewa . Jest to udoskonalony TreeSet JDK, który zapewnia dostęp do elementu według indeksu i znajdowanie indeksu elementu bez iteracji lub ukrytych bazowych list, które wykonują kopię zapasową drzewa. Algorytm opiera się na aktualizacji wag zmieniających się węzłów za każdym razem, gdy następuje zmiana.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.