Existe uma implementação de lista não duplicada lá fora?

86

Eu sei SortedSet, mas no meu caso preciso de algo que implemente List, e não Set. Então, há uma implementação lá fora, na API ou em outro lugar?

Não deve ser difícil de implementar, mas imaginei por que não perguntar às pessoas aqui primeiro?

Yuval
fonte
1
Por que ele precisa implementar o List? Os conjuntos são iteráveis, como as listas, então suponho que o método de recebimento esteja impondo List por algum outro motivo.
Rob
@Rob Isso mesmo, é uma demanda externa, e a estrutura de dados inclui muito mais de uma lista.
Yuval
Se o usuário quiser uma LIST, então está claro que precisa de métodos da interface LIST que não estão presentes na interface SET ...
marcolopes

Respostas:

92

Não há coleção Java na biblioteca padrão para fazer isso. LinkedHashSet<E>preserva a ordenação de forma semelhante a a List, entretanto, se você envolver seu conjunto em a Listquando quiser usá-lo como a, Listobterá a semântica desejada.

Alternativamente, o Commons Collections (ou commons-collections4, para a versão genérica) tem um Listque faz o que você já quer: SetUniqueList/ SetUniqueList<E>.

Calum
fonte
5
A classe Commons é exatamente o que eu preciso, mas meu chefe me disse para implementá-la sozinho eventualmente. 10x de qualquer maneira!
Yuval
5
Bem, nada como reinventar a roda! Você saberá agora se a necessidade surgir novamente, de qualquer maneira. coleções15 é uma coisa muito útil para se ter por aí; Os MultiMaps, em particular, aliviam a dor de algo que a gente acaba implementando muito.
Calum
19
@skaffman: ele não é realmente um idiota, mas às vezes ele faz movimentos que são ... bem, estranhos. De qualquer forma, não vou introduzir bugs no produto. No mercado de hoje, estou feliz com meu trabalho e não procuro bater portas e queimar pontes, se é que você me entende.
Yuval
3
Estou bastante surpreso quando SetUniqueList não tem tipo parametrizado.
emeraldhieu
2
Jeffrey: Em plataformas móveis, o sistema geralmente removerá classes não utilizadas, mas com certeza, há muitos motivos pelos quais você pode não escolher uma dessas soluções "normais". Sempre há alguma compensação a ser feita e nenhuma solução resolverá todos os casos.
Calum
14

Aqui está o que eu fiz e funciona.

Supondo que eu tenha um ArrayListcom o qual trabalhar, a primeira coisa que fiz foi criar um novo LinkedHashMap.

LinkedHashSet<E> hashSet = new LinkedHashSet<E>()

Em seguida, tento adicionar meu novo elemento ao LinkedHashSet. O método add não altera o LinkedHasSete retorna false se o novo elemento for uma duplicata. Portanto, essa se torna uma condição que posso testar antes de adicionar ao ArrayList.

if (hashSet.add(E)) arrayList.add(E);

Esta é uma maneira simples e elegante de evitar que duplicatas sejam adicionadas a uma lista de arrays. Se você quiser, pode encapsulá-lo e sobrescrever o método add em uma classe que estende o ArrayList. Apenas lembre-se de lidar com isso addAllpercorrendo os elementos e chamando o método add.

user3570018
fonte
1
Sim, eu acho que essa é a melhor solução para isso, você também pode simplesmente usar um HashSet normal, não um Linked, e então você pode usar sua lista como quiser, você também pode decidir o que fazer em algumas situações, como em adicionando um elemento dentro de uma lista antes de um índice específico, você pode decidir que deseja mover o item duplicado para esta posição ou não.
Gyurix
Melhor solução aqui ... Vou postar meu código de classe
UniqueList
Isso funcionou para mim, em meu algoritmo BFS Graph. Porque eu tinha alguns nós que adicionei a uma Queue (LinkedList) apenas se eles ainda não estivessem.
Jeancarlo Fontalvo
11

Então aqui está o que eu fiz eventualmente. Espero que isto ajude alguém.

class NoDuplicatesList<E> extends LinkedList<E> {
    @Override
    public boolean add(E e) {
        if (this.contains(e)) {
            return false;
        }
        else {
            return super.add(e);
        }
    }

    @Override
    public boolean addAll(Collection<? extends E> collection) {
        Collection<E> copy = new LinkedList<E>(collection);
        copy.removeAll(this);
        return super.addAll(copy);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> collection) {
        Collection<E> copy = new LinkedList<E>(collection);
        copy.removeAll(this);
        return super.addAll(index, copy);
    }

    @Override
    public void add(int index, E element) {
        if (this.contains(element)) {
            return;
        }
        else {
            super.add(index, element);
        }
    }
}   
Yuval
fonte
10
Tenha cuidado - LinkedList.contains () precisa verificar a lista inteira para determinar se um objeto está contido na Lista. Isso significa que quando você adiciona objetos a uma Lista grande, toda a Lista é verificada para cada operação de adição (no pior caso). Isso pode acabar sendo LENTO.
matt b
8
Além disso, sua substituição addAll não verifica se há duplicatas na coleção que está sendo passada para addAll ().
matt b
@mattb Como você resolveria esse problema então: No Android, ao vincular objetos a uma visualização de item de lista, é fornecida a posição do item no adaptador de visualização. Como os conjuntos não têm índice, a única maneira é verificar se o objeto existe ou não ao usar listas é iterar e procurar uma cópia existente.
TheRealChx101
6

Por que não encapsular um conjunto com uma lista, classificar como:

new ArrayList( new LinkedHashSet() )

Isso deixa a outra implementação para alguém que é um verdadeiro mestre em coleções ;-)

Daniel Hiller
fonte
4
Este construtor copia o conteúdo do Conjunto para a nova Lista, em vez de envolvê-lo.
Calum
@Calum, isso é correto, mas em vez de se preocupar em não adicionar duplicatas a uma lista, ele pode adicionar seus objetos a um conjunto (e deixar o conjunto se preocupar em filtrar duplicatas) e apenas embrulhar esse conjunto em uma lista ao passá-lo para o método externo.
matt b
4
Isso copia um conjunto para uma lista, mas você não tem uma ordem conhecida. Mas é disso que se trata.
Janeiro
4

Você deve considerar seriamente a resposta de dhiller:

  1. Em vez de se preocupar em adicionar seus objetos a uma Lista sem duplicatas, adicione-os a um Conjunto (qualquer implementação), que, por natureza, filtrará as duplicatas.
  2. Quando você precisar chamar o método que requer um List, envolva-o em a new ArrayList(set)(ou a new LinkedList(set), qualquer coisa).

Acho que a solução que você postou com o NoDuplicatesListtem alguns problemas, principalmente com o contains()método, além de sua classe não lidar com a verificação de duplicatas na coleção passada para o seu addAll()método.

matt b
fonte
Adoraria aprender sobre esses problemas de contains (). Quanto ao addAll (), eu crio uma cópia da coleção dada e removo todos os objetos que já estão 'this'. Como isso não lida com duplicatas?
Yuval
Como mencionei em meu comentário sobre a postagem da sua turma, contains () precisa examinar a lista inteira (no pior caso) para descobrir se o objeto está contido na lista. Se você tiver uma lista de 1 milhão de itens e adicionar 10 individualmente, então (no pior caso) mais de dez milhões de itens serão verificados.
matt b
Quanto a addAll (), se a coleção passada para addAll contiver duplicatas, elas não serão detectadas. Por exemplo: sua lista {A, B, C, D} lista de parâmetros {B, D, E, E, E}. Você cria uma cópia do parâmetro e, após removeAll, ele contém {E, E, E}.
matt b
O problema addAll () não é realmente relevante para mim, pois uso NoDuplicatesList em todo o procedimento e addAll () deve receber outra NoDuplicatesList como parâmetro. O que você sugere para melhorar o desempenho de contains ()?
Yuval
3

Eu precisava de algo assim, então fui às coleções comuns e usei o SetUniqueList, mas quando executei alguns testes de desempenho, descobri que parece não estar otimizado em comparação com o caso se eu quiser usar um Sete obter um Arrayusando o Set.toArray()método.

O SetUniqueTesttomou 20: 1 tempo para preencher e depois transversal 100.000 Cordas comparando à outra implementação, o que é uma grande diferença negócio.

Então, se você se preocupa com o desempenho, eu recomendo que você use Set and Get an Array em vez de usar o SetUniqueList, a menos que você realmente precise da lógica de SetUniqueList, então você precisará verificar outras soluções ...

Método principal do código de teste :

public static void main(String[] args) {


SetUniqueList pq = SetUniqueList.decorate(new ArrayList());
Set s = new TreeSet();

long t1 = 0L;
long t2 = 0L;
String t;


t1 = System.nanoTime();
for (int i = 0; i < 200000; i++) {
    pq.add("a" + Math.random());
}
while (!pq.isEmpty()) {
    t = (String) pq.remove(0);
}
t1 = System.nanoTime() - t1;

t2 = System.nanoTime();
for (int i = 0; i < 200000; i++) {
    s.add("a" + Math.random());
}

s.clear();
String[] d = (String[]) s.toArray(new String[0]);
s.clear();
for (int i = 0; i < d.length; i++) {
    t = d[i];

}
t2 = System.nanoTime() - t2;

System.out.println((double)t1/1000/1000/1000); //seconds
System.out.println((double)t2/1000/1000/1000); //seconds
System.out.println(((double) t1) / t2);        //comparing results

}

Atenciosamente, Mohammed Sleem

Grandtour
fonte
1

NOTA: não leva em consideração a implementação de subList .

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

public class UniqueList<T> extends ArrayList<T> {

    private static final long serialVersionUID = 1L;

    /** Unique elements SET */
    private final Set<T> set=new HashSet();

    /** Used by addAll methods */
    private Collection<T> addUnique(Collection<? extends T> col) {
        Collection<T> unique=new ArrayList();
        for(T e: col){
            if (set.add(e)) unique.add(e);
        }
        return unique;
    }

    @Override
    public boolean add(T e) {
        return set.add(e) ? super.add(e) : false;
    }

    @Override
    public boolean addAll(Collection<? extends T> col) {
        return super.addAll(addUnique(col));
    }

    @Override
    public void add(int index, T e) {
        if (set.add(e)) super.add(index, e);
    }

    @Override
    public boolean addAll(int index, Collection<? extends T> col) {
        return super.addAll(index, addUnique(col));
    }

}
marcolopes
fonte
0

A documentação para interfaces de coleção diz:

Conjunto - uma coleção que não pode conter elementos duplicados.
Lista - uma coleção ordenada (às vezes chamada de sequência). As listas podem conter elementos duplicados.

Portanto, se você não quiser duplicatas, provavelmente não deve usar uma lista.

Hauch
fonte
Mencionei especificamente que preciso de uma implementação de List. Acredite em mim, há um motivo.
Yuval
A razão é porque você está interagindo com uma API que está tomando uma lista como parâmetro (em vez de uma coleção)? Isso é um pouco chato de se lidar
matt b
Na verdade, a API usa um Map <AccountType, Map <AccountType, List <Account> >>, o que significa conter algo em torno de dezenas a centenas de listas ... bah.
Yuval
A construção de funções de probabilidade com pares de probabilidade de elemento pode envolver não ter duplicatas, embora elementos duplicados possam apenas ser mesclados.
Al G Johnston
-1

no addmétodo, por que não usar HashSet.add()para verificar duplicatas em vez de HashSet.consist(). HashSet.add()retornará truese não houver duplicata e falsecaso contrário.

neoreprast
fonte
O que é HashSet#consist()?
naXa
-1

No topo da minha cabeça, as listas permitem duplicatas. Você pode implementar rapidamente ae UniqueArrayListsubstituir todas as funções add/ insertpara verificar contains()antes de chamar os métodos herdados. Para uso pessoal, você só poderia implementar o addmétodo que usa e substituir os outros para lançar uma exceção no caso de futuros programadores tentarem usar a lista de uma maneira diferente.

Kieveli
fonte
Eu estava pronto para voltar a essa ideia (o que eventualmente eu tive que fazer) se ninguém sugerisse nada melhor = 8-) Veja minha própria resposta acima.
Yuval
-3

Acabei de fazer minha própria UniqueList em minha pequena biblioteca como esta:

package com.bprog.collections;//my own little set of useful utilities and classes

import java.util.HashSet;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Jonathan
*/
public class UniqueList {

private HashSet masterSet = new HashSet();
private ArrayList growableUniques;
private Object[] returnable;

public UniqueList() {
    growableUniques = new ArrayList();
}

public UniqueList(int size) {
    growableUniques = new ArrayList(size);
}

public void add(Object thing) {
    if (!masterSet.contains(thing)) {
        masterSet.add(thing);
        growableUniques.add(thing);
    }
}

/**
 * Casts to an ArrayList of unique values
 * @return 
 */
public List getList(){
    return growableUniques;
}

public Object get(int index) {
    return growableUniques.get(index);
}

public Object[] toObjectArray() {
    int size = growableUniques.size();
    returnable = new Object[size];
    for (int i = 0; i < size; i++) {
        returnable[i] = growableUniques.get(i);
    }
    return returnable;
    }
}

Eu tenho uma classe TestCollections parecida com esta:

package com.bprog.collections;
import com.bprog.out.Out;
/**
*
* @author Jonathan
*/
public class TestCollections {
    public static void main(String[] args){
        UniqueList ul = new UniqueList();
        ul.add("Test");
        ul.add("Test");
        ul.add("Not a copy");
        ul.add("Test"); 
        //should only contain two things
        Object[] content = ul.toObjectArray();
        Out.pl("Array Content",content);
    }
}

Funciona bem. Tudo o que ele faz é adicionar a um conjunto se ainda não o tiver e houver um Arraylist que pode ser retornado, bem como um array de objetos.

Jonathan
fonte
Sim, você deve adicionar um pouco mais de métodos para implementar a interface List.
Gyurix