Por que não há SortedList em Java?

503

Em Java, há o SortedSeteSortedMap interfaces . Ambos pertencem à estrutura Java Collections e fornecem uma maneira classificada de acessar os elementos.

No entanto, no meu entendimento, não há SortedListem Java. Você pode usarjava.util.Collections.sort() para classificar uma lista.

Alguma idéia de por que é projetado assim?

Jijoy
fonte
6
então qual é o resultado esperado ao inserir um elemento no meio da lista?
bestsss 4/01/12
4
@bestsss Seria totalmente possível ter uma classe SortedList que não implemente a interface java.util.List. Leia a pergunta como uma pergunta por que não há uma estrutura de dados que suporte a funcionalidade solicitada. Não se distraia com detalhes insignificantes, como nomes.
Alderath
@ Olderath, a estrutura usual para isso é uma árvore (vermelho / preto, avl ou btree) com links prev / next extras para dar suporte à solicitação. Eu uso estrutura semelhante vermelho / preto w / prev / próximos links. É um uso bastante nicho, no entanto. A árvore pode ser percorrida tanto na ordem ordenada quanto na inserida, possui O (logn) find / contains, mas get (int) é O (n). Dado o fato de sua aplicabilidade de nicho, eu assumiria que foi deixado aos desenvolvedores implementar um, se necessário.
bestsss 4/01/12
3
Não respondendo à pergunta "por que", mas a solução mais simples para o TreeSet é usar um comparador que nunca retorne zero, por exemplo, int diff = this.score - that.score; return (diff == 0) ? 1 : diff; como é um hack fedorento, eu o forneceria como um argumento construtor anônimo, em vez de ter qualquer implementação comparável.
earcam

Respostas:

682

Os iteradores da lista garantem, em primeiro lugar, que você obtenha os elementos da lista na ordem interna da lista (também conhecida como ordem de inserção ). Mais especificamente, está na ordem em que você inseriu os elementos ou em como você manipulou a lista. A classificação pode ser vista como uma manipulação da estrutura de dados e há várias maneiras de classificar a lista.

Vou ordenar os caminhos na ordem de utilidade que eu pessoalmente vejo:

1. Considere usar Setou Bagcoleções

NOTA: Coloquei essa opção no topo, porque é isso que você normalmente deseja fazer.

Um conjunto classificado classifica automaticamente a coleção na inserção , o que significa que faz a classificação enquanto você adiciona elementos à coleção. Isso também significa que você não precisa classificá-lo manualmente.

Além disso, se você tiver certeza de que não precisa se preocupar (ou ter) elementos duplicados, poderá usá-lo TreeSet<T>. Ele implementa SortedSete NavigableSetinterfaces e obras como você provavelmente esperaria de uma lista:

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 você não deseja a ordem natural, pode usar o parâmetro construtor que recebe a Comparator<T>.

Como alternativa, você pode usar Multisets (também conhecidos como Bags ) , ou seja, Setque permite elementos duplicados, e existem implementações de terceiros. Mais notavelmente das bibliotecas do Guava, existe um TreeMultiset, que funciona muito como o TreeSet.

2. Classifique sua lista com Collections.sort()

Como mencionado acima, a classificação de Lists é uma manipulação da estrutura de dados. Portanto, para situações em que você precisa de "uma fonte de verdade" que será classificada de várias maneiras, classificá-la manualmente é o caminho a percorrer.

Você pode classificar sua lista com o java.util.Collections.sort()método Aqui está um exemplo de código sobre como:

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"

Usando comparadores

Um benefício claro é que você pode usar Comparatorno sortmétodo. Java também fornece algumas implementações para Comparatoras Collatorque são úteis para seqüências de classificação sensíveis ao código do idioma. Aqui está um exemplo:

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

Collections.sort(strings, usCollator);

Classificação em ambientes simultâneos

Observe, porém, que o uso do sortmétodo não é amigável em ambientes simultâneos, pois a instância da coleção será manipulada e, em vez disso, você deve considerar o uso de coleções imutáveis. Isso é algo que o Guava fornece na Orderingclasse e é uma linha simples:

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

3. Envolva sua lista com java.util.PriorityQueue

Embora não exista uma lista classificada em Java, existe uma fila classificada que provavelmente funcionaria bem para você. É a java.util.PriorityQueueclasse.

Nico Haase vinculou nos comentários a uma pergunta relacionada que também responde a isso.

Em uma coleção classificada, você provavelmente não deseja manipular a estrutura de dados interna, e é por isso que o PriorityQueue não implementa a interface List (porque isso lhe daria acesso direto a seus elementos).

Advertência sobre o PriorityQueueiterador

A PriorityQueueclasse implementa as interfaces Iterable<E>e Collection<E>para que possa ser iterada como de costume. No entanto, não é garantido que o iterador retorne elementos na ordem classificada. Em vez disso (como Alderath aponta nos comentários), você precisa ficar poll()na fila até ficar vazio.

Observe que você pode converter uma lista em uma fila de prioridade por meio do construtor que utiliza qualquer coleção :

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. Escreva sua própria SortedListturma

NOTA: Você não precisa fazer isso.

Você pode escrever sua própria classe List, que é classificada sempre que adicionar um novo elemento. Isso pode tornar a computação pesada, dependendo da sua implementação e não faz sentido , a menos que você queira fazer isso como um exercício, por dois motivos principais:

  1. Ele quebra o contrato que a List<E>interface possui, porque os addmétodos devem garantir que o elemento resida no índice especificado pelo usuário.
  2. Por que reinventar a roda? Você deve usar o TreeSet ou Multisets, como indicado no primeiro ponto acima.

No entanto, se você quiser fazer isso como um exercício, aqui está um exemplo de código para começar, ele usa a AbstractListclasse abstract:

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

}

Observe que se você não substituiu os métodos necessários, as implementações padrão de AbstractListirão lançar UnsupportedOperationExceptions.

Spoike
fonte
12
+1. Imo, isso é mais construtivo do que a resposta mais votada. Ele vem com duas desvantagens menores. O PriorityQueue não suporta acesso aleatório. Você não pode fazer uma espiada (elementIndex). Então você não pode fazer, por exemplo Integer maxVal = prioQueue.peek(prioQueue.size() - 1);. Em segundo lugar, se você pretende usar o PriorityQueue simplesmente como uma lista classificada, parecerá menos intuitivo ver PriorityQueueo código do que teria sido SortedList, se essa estrutura de dados existisse.
Alderath
12
E, depois de examinar a outra pergunta que alguém vinculou nos comentários, outra grande desvantagem é que o iterador do PriorityQueue não tem garantia de retornar elementos em uma ordem específica. Portanto, a menos que eu esteja ignorando alguma coisa, a única maneira de, por exemplo, imprimir todos os objetos no PriorityQueue, é pesquisar () repetidamente () a fila até que ela esteja vazia. Para mim, isso parece borderline reiniciado. Para imprimir os objetos em um PriorityQueue duas vezes, você primeiro precisa copiar o PriorityQueue e depois pesquisar () todos os objetos do PriorityQueue original e depois pol () todos os objetos da cópia.
Alderath
1
Hmm ... parece que você está certo, Alderath. Você não pode usar o iterador de PriorityQueue para obter os elementos na ordem esperada. Parece que tenho que editar minha resposta.
Spoike
7
A fila de prioridade é apenas uma pilha, você pode acessar apenas a parte superior, mas ela não pertence à resposta da pergunta.
bestsss 4/01/12
1
Também digno de nota é que Collections.sort()ainda permite definir a função de comparação usada para classificar, usando um Comparatorobjeto.
Mike 'Pomax' Kamermans
71

Como o conceito de uma lista é incompatível com o conceito de uma coleção classificada automaticamente. O objetivo de uma lista é que, após a chamada list.add(7, elem), uma chamada para list.get(7)retornará elem. Com uma lista classificada automaticamente, o elemento pode terminar em uma posição arbitrária.

Michael Borgwardt
fonte
26

Como todas as listas já estão "classificadas" pela ordem em que os itens foram adicionados (ordem FIFO), você pode "recorrer" a elas com outra ordem, incluindo a ordem natural dos elementos, usando java.util.Collections.sort().

EDITAR:

As listas como estruturas de dados são baseadas no que é interessante é a ordem na qual os itens foram inseridos.

Os conjuntos não possuem essa informação.

Se você deseja pedir adicionando tempo, use List. Se você deseja solicitar por outros critérios, use SortedSet.

SJuan76
fonte
22
O conjunto não permite duplicatas
bestsss
10
Não acho que seja uma resposta muito boa. Claro, as listas na API java têm uma ordem específica determinada por quando / como os itens foram inseridos. No entanto, a existência de listas ordenadas de uma maneira que depende do método / tempo de inserção não impede que outras estruturas de dados sejam ordenadas de outra maneira (por exemplo, por um comparador). Basicamente, o OP está perguntando por que não existe uma estrutura de dados equivalente a um SortedSet, com a exceção de que a estrutura de dados deve permitir múltiplas ocorrências de elementos iguais.
Alderath
5
Portanto, minha pergunta seguinte seria: " Por que não existe uma estrutura de dados que funcione como um SortedSet, mas que pode conter vários elementos iguais? " (E por favor não responda " porque Sets pode conter apenas um elemento ")
Alderath
@Alderath: consulte stackoverflow.com/questions/416266/sorted-collection-in-java . Resumindo: Use o TreeMultiset da goiaba
Janus Troelsen
4
As listas NÃO são "classificadas", mesmo que você coloque "classificadas" entre aspas duplas. Eles são encomendados.
Koray Tugay
21

Set e Map são estruturas de dados não lineares. Lista é estrutura de dados linear.

insira a descrição da imagem aqui


A estrutura de dados SortedSete as SortedMapinterfaces da árvore implementam TreeSete, TreeMaprespectivamente, usando o algoritmo de implementação de árvore Red-Black usado . Portanto, verifique se não há itens duplicados (ou chaves no caso de Map).

  • List já mantém uma coleção ordenada e uma estrutura de dados baseada em índice, as árvores não são estruturas de dados baseadas em índice.
  • Tree por definição, não pode conter duplicatas.
  • Em Listpodemos ter duplicatas, então não há TreeList(ou seja, não SortedList).
  • Lista mantém elementos em ordem de inserção. Então, se queremos classificar a lista, temos que usar java.util.Collections.sort(). Ele classifica a lista especificada em ordem crescente, de acordo com a ordem natural de seus elementos.
Premraj
fonte
14

JavaFX SortedList

Embora tenha demorado um pouco, o Java 8 tem uma classificação List. http://docs.oracle.com/javase/8/javafx/api/javafx/collections/transformation/SortedList.html

Como você pode ver nos javadocs, ele faz parte das coleções JavaFX , destinadas a fornecer uma exibição classificada em um ObservableList.

Atualização: Observe que, com o Java 11, o kit de ferramentas JavaFX foi movido para fora do JDK e agora é uma biblioteca separada. O JavaFX 11 está disponível como um SDK para download ou no MavenCentral. Consulte https://openjfx.io

swpalmer
fonte
2
Infelizmente, este SortedList não funciona como uma lista de costume - por exemplo, ele não tem um construtor padrão (você tem que construí-lo usando um ObservableList, o que isso significa ...)
Erel Segal-Halevi
O SortedList do JavaFX é claramente para o uso de componentes da GUI e por causa da sobrecarga NÃO é adequada apenas para ter uma lista de objetos classificados. Também significaria referenciar todo o módulo FX, mesmo que a GUI não seja usada no projeto.
COBRA.cH 23/03
1
@ COBRA.cH Sim, isso é verdade. Uma lista classificada com melhor desempenho provavelmente poderia ser um invólucro fino em torno de um TreeMap, onde um número inteiro é usado para contar duplicatas da chave. Você também pode usar um TreeSet com um comparador que nunca retorna 0.
swpalmer
Por que os votos negativos? A pergunta começa com um pressuposto de que não há lista classificada nas bibliotecas do JDK. Esta resposta corrige essa suposição. Se você gosta ou não da implementação dessa lista classificada em particular, não é motivo para menos votos. Esta resposta não recomenda a classe, apenas indica sua existência.
Basil Bourque
10

Para todos os recém-chegados, a partir de abril de 2015, o Android agora possui uma classe SortedList na biblioteca de suporte, projetada especificamente para trabalhar RecyclerView. Aqui está o post sobre isso.

Amagi82
fonte
1
Vale ressaltar que, no momento deste comentário, o Androids SortedList não oferece suporte à funcionalidade onItemMoved () com o RecyclerView. Escrever minha própria SortedList, que é menos eficiente, é o que eu tenho que fazer para contornar as limitações.
Matthew
4

Outro ponto é a complexidade do tempo das operações de inserção. Para uma inserção de lista, espera-se uma complexidade de O (1). Mas isso não pode ser garantido com uma lista classificada.

E o ponto mais importante é que as listas não assumem nada sobre seus elementos. Por exemplo, você pode fazer listas de coisas que não implementam equalsou compare.

Ingo
fonte
você pode ter a lista sem / (logn) inserir / excluir / localizar / conter, mas não com / obter (int).
bestsss 4/01/12
3
O último ponto não é realmente uma boa explicação. Você pode fazer algumas SortedSetcoisas que não implementam Comparable. Veja este construtor TreeSet .
Alderath
1
@ Olderath - talvez minha redação estivesse muito fraca. No entanto, a observação sustenta que os elementos de Conjuntos e chaves de Árvores devem ser comparáveis ​​pelo menos para a igualdade, enquanto os elementos de lista não precisam. Se a relação de ordem / igualdade para Conjuntos e Árvores é implementada em um Comparador ou em outro local é irrelevante - mas você precisa de um.
Ingo
Listas não garantem O (1) de inserção , eles garantem O (1) acesso . bigocheatsheet.com
Matt Quigley
1
@ Betlista você está certo! Não consigo atualizar meu comentário, mas a Listinterface Java não garante nenhuma especificação de desempenho em seus métodos.
Matt Quigley
3

Pense nisso como este: a Listinterface possui métodos como add(int index, E element), set(int index, E element). O contrato é que, depois de adicionar um elemento na posição X, você o encontrará, a menos que adicione ou remova elementos antes dele.

Se alguma implementação de lista armazenasse elementos em alguma ordem que não fosse o índice, os métodos de lista acima não teriam sentido.

Costi Ciudatu
fonte
2

A primeira linha da API da lista diz que é uma coleção ordenada (também conhecida como sequência). Se você classificar a lista, não poderá manter a ordem, portanto, não haverá TreeList em Java.
Como a API diz, a lista Java foi inspirada na sequência e veja as propriedades da sequência http://en.wikipedia.org/wiki/Sequence_(mathematics )

Isso não significa que você não pode classificar a lista, mas o Java é estrito à sua definição e não fornece versões classificadas das listas por padrão.

Syam
fonte
2

Caso você esteja procurando uma maneira de classificar elementos, mas também possa acessá-los por índice de maneira eficiente, faça o seguinte:

  1. Use uma lista de acesso aleatório para armazenamento (por exemplo ArrayList)
  2. Verifique se ele está sempre classificado

Em seguida, para adicionar ou remover um elemento, você pode usar Collections.binarySearchpara obter o índice de inserção / remoção. Como sua lista implementa o acesso aleatório, você pode modificar eficientemente a lista com o índice determinado.

Exemplo:

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

Considere usar o mapa da árvore indexada . É um TreeSet aprimorado do JDK que fornece acesso ao elemento por índice e localiza o índice de um elemento sem iteração ou listas subjacentes ocultas que fazem backup da árvore. O algoritmo é baseado na atualização de pesos dos nós alterados toda vez que há uma alteração.

Vitaly Sazanovich
fonte