Perché non esiste un SortedList in Java?


503

In Java ci sono le interfacce SortedSete SortedMap. Entrambi appartengono al framework Java Collections e forniscono un modo ordinato per accedere agli elementi.

Tuttavia, a mio avviso, non esiste SortedListin Java. È possibile utilizzare java.util.Collections.sort()per ordinare un elenco.

Qualche idea sul perché sia ​​progettato in quel modo?



6
quindi qual è il risultato atteso quando si inserisce un elemento in mezzo all'elenco?
bestsss

4
@bestsss Sarebbe possibile avere una classe SortedList che non implementa l'interfaccia java.util.List. Leggi la domanda come una domanda perché non esiste una struttura di dati che supporti la funzionalità richiesta. Non lasciarti distrarre da dettagli insignificanti come la denominazione.
Alderath,

@Alderath, la consueta struttura per questo è un albero (rosso / nero, avl o btree) con collegamenti extra prev / next per supportare l'ordinamento. Uso la struttura simile rosso / nero con collegamenti precedenti / successivi. È un uso piuttosto di nicchia, però. L'albero può essere attraversato sia in ordine che in ordine di inserimento, ha O (logn) trova / contiene ma get (int) è O (n). Data la sua applicabilità di nicchia, suppongo che fosse lasciato agli sviluppatori implementarne uno se fosse necessario.
bestsss

3
Non rispondere alla domanda "perché", ma la soluzione più semplice per TreeSet consiste nell'utilizzare un comparatore che non restituisce mai zero, ad es. int diff = this.score - that.score; return (diff == 0) ? 1 : diff; Poiché si tratta di un trucco maleodorante , lo fornirei come argomento di costruzione anonimo piuttosto che avere qualsiasi implementabile Comparable.
earcam

Risposte:


682

Gli iteratori di elenco garantiscono innanzitutto di ottenere gli elementi dell'elenco nell'ordine interno dell'elenco (ovvero l'ordine di inserimento ). Più specificamente è nell'ordine in cui hai inserito gli elementi o su come hai manipolato l'elenco. L'ordinamento può essere visto come una manipolazione della struttura dei dati e ci sono diversi modi per ordinare l'elenco.

Ordinerò i modi in ordine di utilità come la vedo personalmente:

1. Prendi invece in considerazione l'utilizzo Seto le Bagraccolte

NOTA: ho messo questa opzione in alto perché questo è ciò che normalmente si desidera fare comunque.

Un set ordinato ordina automaticamente la raccolta al momento dell'inserimento , il che significa che esegue l'ordinamento mentre si aggiungono elementi alla raccolta. Significa anche che non è necessario ordinarlo manualmente.

Inoltre, se sei sicuro di non doverti preoccupare (o avere) degli elementi duplicati, puoi usare TreeSet<T>invece. Implementa SortedSete si NavigableSetinterfaccia e funziona come probabilmente ti aspetteresti da un elenco:

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"

Se non si desidera l'ordine naturale, è possibile utilizzare il parametro del costruttore che accetta a Comparator<T>.

In alternativa, è possibile utilizzare Multiset (noto anche come Borse ) , ovvero un Setelemento che consente elementi duplicati, mentre sono presenti implementazioni di terze parti. In particolare dalle librerie di Guava c'è un TreeMultiset, che funziona in modo molto simile al TreeSet.

2. Ordina l'elenco con Collections.sort()

Come accennato in precedenza, l'ordinamento di Lists è una manipolazione della struttura dei dati. Quindi, per le situazioni in cui hai bisogno di "una sola fonte di verità" che verrà ordinata in vari modi, l'ordinamento manuale è la strada da percorrere.

Puoi ordinare la tua lista con il java.util.Collections.sort()metodo. Ecco un esempio di codice su come:

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"

Utilizzando i comparatori

Un chiaro vantaggio è che è possibile utilizzare Comparatornel sortmetodo. Java fornisce anche alcune implementazioni Comparatorcome quella Collatorutile per le stringhe di ordinamento sensibili alle impostazioni locali. Ecco un esempio:

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

Collections.sort(strings, usCollator);

Ordinamento in ambienti simultanei

Si noti tuttavia che l'utilizzo del sortmetodo non è intuitivo in ambienti concorrenti, poiché l'istanza della raccolta verrà manipolata e si dovrebbe invece considerare l'utilizzo di raccolte immutabili. Questo è qualcosa che Guava fornisce nella Orderingclasse ed è un semplice one-liner:

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

3. Avvolgere l'elenco con java.util.PriorityQueue

Sebbene non ci sia un elenco ordinato in Java, esiste comunque una coda ordinata che probabilmente funzionerebbe altrettanto bene per te. È la java.util.PriorityQueueclasse.

Nico Haase ha collegato nei commenti una domanda correlata che risponde anche a questo.

In una raccolta ordinata molto probabilmente non vorrai manipolare la struttura interna dei dati, motivo per cui PriorityQueue non implementa l'interfaccia Elenco (perché ciò ti darebbe accesso diretto ai suoi elementi).

Avvertenza PriorityQueuesull'iteratore

La PriorityQueueclasse implementa le interfacce Iterable<E>e in Collection<E>modo che possa essere ripetuta come al solito. Tuttavia, non è garantito che l'iteratore restituisca elementi nell'ordine ordinato. Invece (come sottolinea Alderath nei commenti) è necessario che poll()la coda sia vuota.

Nota che puoi convertire un elenco in una coda prioritaria tramite il costruttore che accetta qualsiasi raccolta :

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. Scrivi la tua SortedListlezione

NOTA: non dovresti farlo.

È possibile scrivere la propria classe Elenco che ordina ogni volta che si aggiunge un nuovo elemento. Questo può diventare piuttosto pesante a seconda della tua implementazione ed è inutile , a meno che tu non voglia farlo come esercizio, per due motivi principali:

  1. Rompe il contratto che l' List<E>interfaccia ha perché i addmetodi dovrebbero garantire che l'elemento risieda nell'indice specificato dall'utente.
  2. Perché reinventare la ruota? Dovresti usare TreeSet o Multiset invece come indicato nel primo punto sopra.

Tuttavia, se vuoi farlo come esercizio qui è un esempio di codice per iniziare, usa la AbstractListclasse astratta:

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

}

Nota che se non hai ignorato i metodi di cui hai bisogno, le implementazioni predefinite AbstractListgenereranno UnsupportedOperationExceptions.


12
+1. Imo, questo è più costruttivo della risposta più votata. Viene fornito con due lati negativi minori. PriorityQueue non supporta l'accesso casuale. Non puoi fare una sbirciatina (elementIndex). Quindi non puoi fare ad es Integer maxVal = prioQueue.peek(prioQueue.size() - 1);. In secondo luogo, se si intende utilizzare PriorityQueue semplicemente come un elenco ordinato, sarà meno intuitivo vedere PriorityQueuenel codice di quanto non sarebbe stato vedere SortedList, se esistesse una tale struttura di dati.
Alderath,

12
E, dopo aver esaminato l'altra domanda che qualcuno ha collegato nei commenti, un altro grande svantaggio è che l'iteratore di PriorityQueue non è garantito per restituire elementi in un ordine specifico. Quindi, a meno che non stia trascurando qualcosa, l'unico modo per es. Stampare tutti gli oggetti in PriorityQueue per eseguire il polling () della coda ripetutamente fino a quando non è vuota. Per me, questo sembra borderline riavviato. Per stampare gli oggetti in PriorityQueue due volte, dovresti prima copiare PriorityQueue e quindi eseguire il polling () di tutti gli oggetti dalla PriorityQueue originale e quindi pol () l tutti gli oggetti dalla copia.
Alderath,

1
Hmm ... sembra che tu abbia ragione Alderath. Non è possibile utilizzare l'iteratore di PriorityQueue per ottenere gli elementi nell'ordine previsto. Sembra che devo modificare la mia risposta.
Spoike,

7
La coda di priorità è solo un heap, è possibile accedere solo alla parte superiore, anche se non appartiene alla risposta della domanda.
bestsss

1
Vale anche la pena notare che Collections.sort()consente persino di definire la funzione di confronto utilizzata per ordinare, utilizzando un Comparatoroggetto.
Mike 'Pomax' Kamermans,

71

Perché il concetto di un elenco è incompatibile con il concetto di una raccolta ordinata automaticamente. Il punto di un elenco è che dopo aver chiamato list.add(7, elem), list.get(7)tornerà una chiamata a elem. Con un elenco ordinato automaticamente, l'elemento potrebbe finire in una posizione arbitraria.


26

Poiché tutti gli elenchi sono già "ordinati" in base all'ordine in cui sono stati aggiunti gli articoli (ordinamento FIFO), è possibile "ricorrere" a un altro ordine, incluso l'ordine naturale degli elementi, utilizzando java.util.Collections.sort() .

MODIFICARE:

Gli elenchi come strutture di dati si basano su ciò che è interessante è l'ordinamento in cui gli articoli sono stati inseriti.

I set non hanno tali informazioni.

Se vuoi ordinare aggiungendo tempo, usa List. Se si desidera ordinare in base ad altri criteri, utilizzare SortedSet.


22
Il set non consente duplicati
bestsss

10
Non credo sia un'ottima risposta. Certo, gli elenchi nell'API Java hanno un ordine specifico determinato da quando / come sono stati inseriti gli articoli. Tuttavia, l'esistenza di elenchi ordinati in un modo che dipende dal metodo / tempo di inserimento non impedisce di avere altre strutture di dati in cui l'ordine è determinato in altro modo (ad esempio da un comparatore). Fondamentalmente, l'OP chiede perché non esiste una struttura di dati equivalente a un SortedSet con l'eccezione che la struttura di dati dovrebbe consentire più occorrenze di elementi uguali.
Alderath,

5
Quindi la mia domanda di follow-up sarebbe: " Perché non esiste una struttura di dati che funzioni come un SortedSet, ma che può contenere più elementi uguali? " (E per favore non rispondere " perché Set può contenere solo un elemento ")
Alderath

@Alderath: Vedere stackoverflow.com/questions/416266/sorted-collection-in-java . In breve: usa Guava's TreeMultiset
Janus Troelsen il

4
Gli elenchi NON sono "ordinati", anche se si inserisce "ordinato" tra virgolette doppie. Sono ordinati.
Koray Tugay,

21

Set e Map sono strutture di dati non lineari. L'elenco è una struttura di dati lineare.

inserisci qui la descrizione dell'immagine


La struttura SortedSete le SortedMapinterfacce dei dati dell'albero implementano TreeSete TreeMaprispettivamente utilizzano l' algoritmo di implementazione dell'albero Red-Black utilizzato. Quindi assicura che non ci siano elementi duplicati (o chiavi in ​​caso di Map).

  • List mantiene già una raccolta ordinata e una struttura di dati basata su indice, gli alberi non sono strutture di dati basati su indice.
  • Tree per definizione non può contenere duplicati.
  • In Listpossiamo avere duplicati, quindi non c'è TreeList(cioè no SortedList).
  • Elenco mantiene gli elementi in ordine di inserimento. Quindi, se vogliamo ordinare l'elenco, dobbiamo usare java.util.Collections.sort(). Ordina l'elenco specificato in ordine crescente, in base all'ordinamento naturale dei suoi elementi.

14

JavaFX SortedList

Sebbene ci sia voluto un po 'di tempo, Java 8 ha un ordinato List. http://docs.oracle.com/javase/8/javafx/api/javafx/collections/transformation/SortedList.html

Come puoi vedere nei javadocs, fa parte delle raccolte JavaFX , destinate a fornire una vista ordinata su un elenco osservabile.

Aggiornamento: si noti che con Java 11, il toolkit JavaFX si è spostato all'esterno di JDK e ora è una libreria separata. JavaFX 11 è disponibile come SDK scaricabile o da MavenCentral. Vedi https://openjfx.io


2
Sfortunatamente, questo SortedList non funziona come un normale elenco - ad esempio, non ha un costruttore predefinito (devi costruirlo usando un ObservableList, qualunque cosa significhi ...)
Erel Segal-Halevi,

La SortedList di JavaFX è chiaramente per l'uso di componenti della GUI e per via del sovraccarico NON adatto solo per avere un Elenco di oggetti ordinati. Inoltre significherebbe fare riferimento all'intero modulo FX anche se la GUI non è utilizzata nel progetto.
COBRA.cH,

1
@ COBRA.cH Sì, è vero. Un elenco ordinato più performante potrebbe probabilmente essere un wrapper sottile attorno a una TreeMap, in cui un numero intero viene utilizzato per contare i duplicati della chiave. È inoltre possibile utilizzare un TreeSet con un comparatore che non restituisce mai 0.
swpalmer

Perché i voti negativi? La domanda inizia con il presupposto che non esiste un elenco ordinato nelle librerie JDK. Questa risposta corregge tale presupposto. Sia che ti piaccia o non ti piaccia l'implementazione di questo particolare elenco ordinato non è un motivo di voto negativo. Questa risposta non raccomanda la classe, indica semplicemente la sua esistenza.
Basil Bourque,

10

Per ogni nuovo arrivato, ad aprile 2015, Android ora ha una classe SortedList nella libreria di supporto, progettata appositamente per funzionare RecyclerView. Ecco il post sul blog a riguardo.


1
Vale la pena notare che al momento di questo commento Androids SortedList manca il supporto per la funzionalità onItemMoved () con RecyclerView. Scrivere la mia SortedList che è meno efficiente è ciò che dovevo fare per aggirare i limiti.
Matteo

4

Un altro punto è la complessità temporale delle operazioni di inserimento. Per un inserto di elenco, ci si aspetta una complessità di O (1). Ma questo non può essere garantito con un elenco ordinato.

E il punto più importante è che le liste non assumono nulla dei loro elementi. Ad esempio, puoi creare elenchi di cose che non implementano equalso compare.


puoi avere la lista w / O (logn) inserisci / cancella / trova / contiene ma non w / get (int).
bestsss

3
L'ultimo punto non è davvero una buona spiegazione. Puoi fare una SortedSetdelle cose che non implementano Comparable. Vedi questo costruttore TreeSet .
Alderath,

1
@Alderath - forse la mia formulazione era troppo debole. Tuttavia, l'osservazione sostiene che gli elementi degli insiemi e delle chiavi degli alberi devono essere comparabili almeno per l'uguaglianza, mentre gli elementi dell'elenco non sono necessari. Se la relazione di ordinamento / uguaglianza per insiemi e alberi sia implementata in un comparatore o altrove è irrilevante, ma ne hai bisogno.
Ingo,

Elenchi non garantiscono O (1) inserimento , garantiscono O (1) di accesso . bigocheatsheet.com
Matt Quigley l'

1
@Betlista hai ragione! Non riesco ad aggiornare il mio commento, ma l' Listinterfaccia Java non garantisce alcuna specifica delle prestazioni sui suoi metodi.
Matt Quigley,

3

Pensate a come questo: l' Listinterfaccia ha metodi come add(int index, E element), set(int index, E element). Il contratto prevede che una volta aggiunto un elemento nella posizione X, lo troverai lì a meno che tu non aggiunga o rimuova elementi prima di esso.

Se un'implementazione dell'elenco memorizzasse elementi in un ordine diverso da quello basato sull'indice, i metodi dell'elenco sopra riportati non avrebbero alcun senso.


2

La prima riga dell'API Elenco dice che è una raccolta ordinata (nota anche come sequenza). Se si ordina l'elenco non è possibile mantenere l'ordine, quindi non esiste TreeList in Java.
Come dice l'API Java List si è ispirato a Sequence e guarda le proprietà della sequenza http://en.wikipedia.org/wiki/Sequence_(mathematics )

Ciò non significa che non è possibile ordinare l'elenco, ma Java è rigoroso per la sua definizione e non fornisce versioni ordinate di elenchi per impostazione predefinita.



2

Nel caso in cui tu stia cercando un modo per ordinare gli elementi, ma anche essere in grado di accedervi per indice in modo efficiente, puoi fare quanto segue:

  1. Utilizzare un elenco di accesso casuale per l'archiviazione (ad es. ArrayList)
  2. Assicurati che sia sempre ordinato

Quindi per aggiungere o rimuovere un elemento è possibile utilizzare Collections.binarySearchper ottenere l'indice di inserimento / rimozione. Poiché il tuo elenco implementa l'accesso casuale, puoi modificarlo in modo efficiente con l'indice determinato.

Esempio:

/**
 * @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

Prendi in considerazione l'utilizzo di una mappa ad albero indicizzata . È un TreeSet JDK migliorato che fornisce l'accesso all'elemento per indice e trova l'indice di un elemento senza iterazione o liste sottostanti nascoste che eseguono il backup dell'albero. L'algoritmo si basa sull'aggiornamento dei pesi di modifica dei nodi ogni volta che si verifica una modifica.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.