Teste se todos os valores em uma lista são únicos


91

Eu tenho uma pequena lista de bytes e quero testar se são todos valores diferentes. Por exemplo, eu tenho este:

List<byte> theList = new List<byte> { 1,4,3,6,1 };

Qual é a melhor maneira de verificar se todos os valores são distintos ou não?


2
Como esta é uma pergunta típica de sala de aula, responderei com uma pergunta. Como você faria se fosse classificado?
ctrl-alt-delor

Respostas:


171
bool isUnique = theList.Distinct().Count() == theList.Count();

Só por curiosidade: quais são os requisitos de espaço e tempo disso?
dtb

10
@dtb deve ser cerca de O (N) . Claro, considerando que esta é uma "pequena lista", será muito rápido com quase qualquer algoritmo. IMO, isso ganha em legibilidade e concisão, e como a velocidade não é um problema, isso o torna perfeito.
Tim S.

2
Isso é mais eficiente do que poderia ser
Jodrell

74

Aqui está outra abordagem que é mais eficiente do que Enumerable.Distinct+ Enumerable.Count(ainda mais se a sequência não for um tipo de coleção). Ele usa um HashSet<T>que elimina duplicatas, é muito eficiente em pesquisas e tem uma propriedade de contagem:

var distinctBytes = new HashSet<byte>(theList);
bool allDifferent = distinctBytes.Count == theList.Count;

ou outra abordagem - mais sutil e eficiente:

var diffChecker = new HashSet<byte>();
bool allDifferent = theList.All(diffChecker.Add);

HashSet<T>.Addretorna falsese o elemento não pôde ser adicionado, pois já estava no HashSet. Enumerable.Allpára no primeiro "falso".


1
tão simples e óbvio, por que não pensei nisso primeiro :) Eu usei essa linha em teste de unidade para confirmar que 10 milhões de elementos gerados pelo meu código incrível são realmente únicos Assert.IsTrue(samples.Add(AwesomeClass.GetUnique()));. Eles foram e são :) +1 para você Tim :)
grapkulec

1
Tentei sua resposta para esta pergunta, mas não está funcionando, senhor: stackoverflow.com/questions/34941162/…
Learning-Overthinker-Confused

Deve ser este:bool allDifferent = theList.All(s => diffChecker.Add(s))
mike nelson

2
Não, não é necessário. Neste caso, você pode passar o delegado diretamente
Tim Schmelter

1
@ AndréReichelt - Acabei de abrir seu código e o terceiro cenário ( List.All(HashSet.Add)) parece ser muito mais rápido que os outros dois em quase todos os casos
Kyle Delaney

7

Ok, aqui está o método mais eficiente que posso pensar em usar .Net padrão

using System;
using System.Collections.Generic;

public static class Extension
{
    public static bool HasDuplicate<T>(
        this IEnumerable<T> source,
        out T firstDuplicate)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }

        var checkBuffer = new HashSet<T>();
        foreach (var t in source)
        {
            if (checkBuffer.Add(t))
            {
                continue;
            }

            firstDuplicate = t;
            return true;
        }

        firstDuplicate = default(T);
        return false;
    }
}

essencialmente, qual é o ponto de enumerar toda a sequência duas vezes se tudo o que você deseja fazer é encontrar a primeira duplicata.

Eu poderia otimizar isso mais pelo caso especial de sequências de elemento vazio e único, mas isso depreciaria a legibilidade / manutenção com ganho mínimo.


Bom adicionar um valor duplicado no retorno, bastante útil para validação
Pac0

Eu testei 3 soluções aqui e esta é realmente a mais eficiente nesta página. Alguns erros de digitação lá (por exemplo, sequencedeveriam estar source). Mas funciona muito bem depois de consertados
mike nelson

@mikenelson, isso deve ser melhor
Jodrell

2
Para facilitar a leitura, acho que deveria estar if (!checkBuffer.Add(t)) { firstDuplicate = t; return true }no loop.
tia

2

A lógica semelhante ao Distinctuso de GroupBy:

var isUnique = theList.GroupBy(i => i).Count() == theList.Count;

Isso é útil se você deseja verificar a exclusividade em relação a uma propriedade theList.GroupBy(o => o.SomeProperty).Count() == theList.Count;enquanto Distinct () não permite isso.
Rev1.0

1

Também se pode fazer: Usar Hashset

var uniqueIds = new HashSet<long>(originalList.Select(item => item.Id));

            if (uniqueIds.Count != originalList.Count)
            {
            }

0

Existem muitas soluções.

E sem dúvida outros mais bonitos com o uso do LINQ como "juergen d" e "Tim Schmelter" mencionados.

Mas, se você tiver "Complexidade" e velocidade, a melhor solução será implementá-la sozinho. Uma das soluções será criar um array de tamanho N (para byte é 256). E fazer um loop no array, e em cada iteração testará o índice de número correspondente se o valor for 1 se for o caso, isso significa que eu já incremento o índice do array e, portanto, o array não é distinto, caso contrário irei incrementar a célula do array e continuar verificando .


2
você pode usar um vetor de bits com 256 bits = 32 bytes = 8 inteiros. Mas seu Big O = O (n) ainda será o mesmo que usar um Hashet proposto na outra resposta.
BrokenGlass

Este é O (n) então talvez o mais rápido, (teste). Verificar contagens conforme você avança ou no final é o mais rápido? Eu suspeito que no final irá melhorar o pior caso, mas conforme você avança pode melhorar a média e o melhor caso). Se não houver duplicatas, este será o pior caso de desempenho. Também para tipos de dados maiores isso não funcionará bem, para um tipo de 16 bits você teria que usar 64k de contagem, bem 64k bits (8k bytes), mas para qualquer coisa maior o uso de memória começará a ficar bobo. No entanto, gosto dessa resposta para valores de 8 bits.
ctrl-alt-delor

1
@TamusJRoyce se você quiser armazenar 4294967296 possibilidades, você precisa de 4 GB, não 42 MB (ou 512 MB de usar mascaramento de bits)
tigrou

Não tenho certeza do que estava pensando. "Aloque 42 MB + de memória para armazenar todas as possibilidades 4294967296. E use contadores de balde simples. Ou até mesmo use o mascaramento de bits xor e verifique se algum bit foi alterado de verdadeiro para falso. 42 MB + / 8 = 5 MB + A despesa parece muito grande com o hardware de hoje. Mas um dia isso pode ter mérito. " não é realmente um comentário relevante. Hashset seria o melhor. Se você está lidando com matrizes extremamente grandes, espera uma parte extremamente grande de memória. Mas em um caso tão estranho, um herístico com um algoritmo CRC seria melhor. Mapeie-o para um polinômio. Se fechar, avalie. Obrigado @tigrou!
TamusJRoyce 01 de

0

E outra solução, se você quiser encontrar valores duplicados.

var values = new [] { 9, 7, 2, 6, 7, 3, 8, 2 };

var sorted = values.ToList();
sorted.Sort();
for (var index = 1; index < sorted.Count; index++)
{
    var previous = sorted[index - 1];
    var current = sorted[index];
    if (current == previous)
        Console.WriteLine(string.Format("duplicated value: {0}", current));
}

Resultado:

duplicated value: 2
duplicated value: 7

http://rextester.com/SIDG48202


0

Eu verifico se um IEnumerable (aray, list, etc) é único como este:

var isUnique = someObjectsEnum.GroupBy(o => o.SomeProperty).Max(g => g.Count()) == 1;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.