Verifica se tutti i valori in un elenco sono univoci


91

Ho un piccolo elenco di byte e voglio verificare che siano tutti valori diversi. Ad esempio, ho questo:

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

Qual è il modo migliore per verificare se tutti i valori sono distinti o meno?


2
Poiché questa è una tipica domanda in classe, risponderò con una domanda. Come lo faresti se fosse ordinato?
ctrl-alt-delor

Risposte:


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

Solo curioso: quali sono i requisiti di spazio e tempo di questo?
dtb

10
@dtb dovrebbe essere circa O (N) . Ovviamente, considerando che questo è un "piccolo elenco", sarà velocissimo con quasi tutti gli algoritmi. IMO questo vince in termini di leggibilità e concisione, e poiché la velocità non è un problema, questo lo rende perfetto.
Tim S.

2
Questo è più efficiente di quanto potrebbe essere
Jodrell

74

Ecco un altro approccio più efficiente di Enumerable.Distinct+ Enumerable.Count(tanto più se la sequenza non è un tipo di raccolta). Usa un HashSet<T>che elimina i duplicati, è molto efficiente nelle ricerche e ha una proprietà count:

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

o un altro approccio, più sottile ed efficiente:

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

HashSet<T>.Addrestituisce falsese l'elemento non può essere aggiunto poiché era già nel file HashSet. Enumerable.Allsi ferma sul primo "falso".


1
così semplice e ovvio, perché non ci ho pensato prima :) Ho usato questa riga in un test unitario per confermare che 10 milioni di elementi generati dal mio fantastico codice sono davvero unici Assert.IsTrue(samples.Add(AwesomeClass.GetUnique()));. Erano e sono :) +1 per te Tim :)
grapkulec

1
ho provato la tua risposta su questa questione ma non è signore di lavoro: stackoverflow.com/questions/34941162/...
Learning-Overthinker-Confuso

Dovrebbe essere questo:bool allDifferent = theList.All(s => diffChecker.Add(s))
mike nelson

2
No, non necessario. In questo caso puoi passare direttamente il delegato
Tim Schmelter

1
@ AndréReichelt - Ho appena aperto il tuo codice e il terzo scenario ( List.All(HashSet.Add)) sembra essere molto più veloce degli altri due in quasi tutti i casi
Kyle Delaney

7

Ok, ecco il metodo più efficiente che riesco a pensare di usare .Net standard

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

essenzialmente, qual è il punto di enumerare l'intera sequenza due volte se tutto ciò che vuoi fare è trovare il primo duplicato.

Potrei ottimizzarlo di più inserendo in maiuscolo una sequenza di elementi vuoti e singoli, ma ciò si deprezzerebbe dalla leggibilità / manutenibilità con un guadagno minimo.


Bello aggiungere un valore duplicato in uscita, abbastanza utile per la convalida
Pac0

Ho testato 3 soluzioni qui e questa è davvero la più efficiente in questa pagina. Alcuni errori di battitura in là però (ad esempio sequencedovrebbe essere source). Ma funziona benissimo una volta che questi sono stati risolti
mike nelson

@mikenelson, dovrebbe essere migliore
Jodrell

2
Per la leggibilità, penso che dovrebbe essere if (!checkBuffer.Add(t)) { firstDuplicate = t; return true }nel ciclo.
tia

2

La logica simile Distinctall'utilizzo di GroupBy:

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

Questo è utile se vuoi controllare l'unicità rispetto a una proprietà theList.GroupBy(o => o.SomeProperty).Count() == theList.Count;mentre Distinct () non lo consente.
Rev1.0

1

Si può anche fare: Usa Hashset

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

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

0

Ci sono molte soluzioni.

E senza dubbio quelli più belli con l'uso di LINQ come "juergen d" e "Tim Schmelter" menzionati.

Ma, se metti a nudo "Complessità" e velocità, la soluzione migliore sarà implementarla da solo. Una delle soluzioni sarà creare un array di dimensione N (per byte è 256). E il ciclo dell'array, e ad ogni iterazione testerà l'indice del numero corrispondente se il valore è 1 se lo fa, ciò significa che ho già incrementato l'indice dell'array e quindi l'array non è distinto altrimenti incrementerò la cella dell'array e continuerò a controllare .


2
puoi usare un vettore di bit con 256 bit = 32 byte = 8 numeri interi. Ma il tuo Big O = O (n) sarà ancora lo stesso dell'uso di un Hashet proposto nell'altra risposta.
BrokenGlass

Questo è O (n) quindi forse il più veloce, (provalo). Il controllo conta man mano che procedi o alla fine sarebbe il più veloce? Ho il sospetto che alla fine migliorerà il caso peggiore, ma man mano che si procede potrebbe migliorare il caso medio e migliore). Se non ci sono duplicati, questo sarà il peggior rendimento. Anche per tipi di dati più grandi questo non funzionerà bene, per un tipo a 16 bit dovresti usare 64k di conteggio, ben 64k bit (8k byte), ma per qualsiasi cosa più grande l'utilizzo della memoria inizierà a diventare sciocco. Tuttavia mi piace questa risposta per i valori a 8 bit.
ctrl-alt-delor

1
@TamusJRoyce se vuoi memorizzare 4294967296 possibilità, hai bisogno di 4 GB non 42 MB (o 512 MB di cui usi il mascheramento dei bit)
tigrou

Non sono sicuro di quello che stavo pensando. "Alloca 42 MB + di memoria per contenere tutte le 4294967296 possibilità. E utilizza semplici contatori di bucket. Oppure utilizza il mascheramento dei bit xor e controlla se qualche bit è cambiato da true a false. 42 MB + / 8 = 5 MB + La spesa sembra troppo grande con l'hardware di oggi. Ma un giorno questo potrebbe avere dei meriti ". non è davvero un commento rilevante. L'hashset sarebbe il migliore. Se hai a che fare con array estremamente grandi, ti aspetti una memoria estremamente grande. Ma in un caso limite così strano, un eristico con un algoritmo CRC sarebbe meglio. Mappalo su un polinomio. Se vicino, valuta. Grazie @tigrou!
TamusJRoyce

0

E un'altra soluzione, se vuoi trovare valori duplicati.

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

Produzione:

duplicated value: 2
duplicated value: 7

http://rextester.com/SIDG48202


0

Controllo se un IEnumerable (aray, list, ecc.) È unico in questo modo:

var isUnique = someObjectsEnum.GroupBy(o => o.SomeProperty).Max(g => g.Count()) == 1;
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.