Sprawdź, czy wszystkie wartości na liście są unikalne


90

Mam małą listę bajtów i chcę sprawdzić, czy są to różne wartości. Na przykład mam to:

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

Jaki jest najlepszy sposób sprawdzenia, czy wszystkie wartości są różne, czy nie?


2
Ponieważ jest to typowe pytanie w klasie, odpowiem pytaniem. Jak byś to zrobił, gdyby został posortowany?
ctrl-alt-delor

Odpowiedzi:


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

Ciekawe: jakie są wymagania dotyczące przestrzeni i czasu?
dtb

10
@dtb powinno być około O (N) . Oczywiście, biorąc pod uwagę, że jest to „mała lista”, będzie ona błyskawiczna z prawie każdym algorytmem. IMO wygrywa pod względem czytelności i zwięzłości, a ponieważ szybkość nie jest problemem, to czyni ją doskonałą.
Tim S.

2
To jest bardziej wydajne niż mogłoby być
Jodrell

74

Oto inne podejście, które jest bardziej wydajne niż Enumerable.Distinct+ Enumerable.Count(tym bardziej, jeśli sekwencja nie jest typem kolekcji). Używa a, HashSet<T>który eliminuje duplikaty, jest bardzo wydajny w wyszukiwaniu i ma właściwość count:

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

lub inne - bardziej subtelne i wydajne - podejście:

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

HashSet<T>.Addzwraca, falsejeśli element nie mógł zostać dodany, ponieważ znajdował się już w HashSet. Enumerable.Allzatrzymuje się na pierwszym „fałszu”.


1
tak proste i oczywiste, dlaczego nie pomyślałem o tym najpierw :) Użyłem tego jednowierszowego testu jednostkowego, aby potwierdzić, że 10 milionów elementów wygenerowanych przez mój niesamowity kod jest naprawdę wyjątkowych Assert.IsTrue(samples.Add(AwesomeClass.GetUnique()));. Byli i są :) +1 dla Ciebie Tim :)
grapkulec

1
Wypróbowałem twoją odpowiedź na to pytanie, ale nie działa, proszę pana: stackoverflow.com/questions/34941162/ ...
Learning-Overthinker-Confused

Powinno być tak:bool allDifferent = theList.All(s => diffChecker.Add(s))
mike nelson

2
Nie, nie jest potrzebne. W takim przypadku możesz przekazać delegata bezpośrednio
Tima Schmeltera

1
@ AndréReichelt - Właśnie otworzyłem Twój kod i trzeci scenariusz ( List.All(HashSet.Add)) wydaje się być znacznie szybszy niż pozostałe dwa w prawie wszystkich przypadkach
Kyle Delaney

6

Okay, oto najbardziej wydajna metoda, jaką mogę wymyślić, używając standardowego .Net

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

Zasadniczo jaki jest sens wyliczenia całej sekwencji dwukrotnie, jeśli wszystko, co chcesz zrobić, to znaleźć pierwszy duplikat.

Mógłbym to bardziej zoptymalizować przez specjalne obudowanie pustych i pojedynczych sekwencji elementów, ale to osłabiłoby czytelność / łatwość konserwacji przy minimalnym zysku.


Fajne dodanie zduplikowanej wartości z powrotu, całkiem przydatne do walidacji
Pac0

Przetestowałem tutaj 3 rozwiązania i jest to rzeczywiście najbardziej wydajne na tej stronie. Jest tam jednak kilka literówek (np. sequencePowinno być source). Ale działa świetnie, gdy zostaną naprawione
mike nelson

@mikenelson, powinno być lepiej
Jodrell

2
Myślę, że dla czytelności powinno być if (!checkBuffer.Add(t)) { firstDuplicate = t; return true }w pętli.
tia

2

Podobna logika do Distinctużywania GroupBy:

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

Jest to przydatne, jeśli chcesz sprawdzić unikalność w odniesieniu do właściwości, theList.GroupBy(o => o.SomeProperty).Count() == theList.Count;podczas gdy Distinct () na to nie pozwala.
Wersja 1.0

1

Można też: Użyj Hashset

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

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

0

Rozwiązań jest wiele.

I bez wątpienia piękniejsze z użyciem LINQ, jak wspomnieli „juergen d” i „Tim Schmelter”.

Ale jeśli poznajesz „złożoność” i szybkość, najlepszym rozwiązaniem będzie samodzielne wdrożenie. Jednym z rozwiązań będzie utworzenie tablicy o rozmiarze N (dla bajtów to 256). I zapętl tablicę, a przy każdej iteracji przetestuje pasujący indeks liczbowy, jeśli wartość wynosi 1, jeśli tak, oznacza to, że już zwiększam indeks tablicy, a zatem tablica nie jest odrębna, w przeciwnym razie zwiększę komórkę tablicy i kontynuuję sprawdzanie .


2
możesz użyć wektora bitowego z 256 bitami = 32 bajty = 8 liczb całkowitych. Ale twoje Wielkie O = O (n) nadal będzie takie samo, jak użycie hashetu zaproponowanego w innej odpowiedzi.
BrokenGlass

To jest O (n), więc może najszybsze (przetestuj). Czy sprawdzanie liczy się na bieżąco, czy na końcu, będzie najszybsze? Podejrzewam, że w końcu poprawi się najgorszy przypadek, ale jak idziesz może poprawić średni i najlepszy przypadek). Jeśli nie ma duplikatów, będzie to najgorsza wydajność. Również w przypadku większych typów danych nie będzie to działać dobrze, dla typu 16-bitowego musiałbyś użyć 64k zliczeń, dobrze 64k bitów (8k bajtów), ale dla czegokolwiek większego zużycie pamięci zacznie się robić głupio. Jednak podoba mi się ta odpowiedź dla wartości 8-bitowych.
ctrl-alt-delor

1
@TamusJRoyce jeśli chcesz przechowywać 4294967296 możliwości, potrzebujesz 4 GB, a nie 42 MB (lub 512 MB z maskowania bitów)
tigrou

Nie jestem pewien, o czym myślę. „Przydziel 42 MB + pamięci, aby pomieścić wszystkie 4294967296 możliwości. I użyj prostych liczników zbiorczych. Lub nawet użyj maskowania bitów xor i sprawdź, czy którykolwiek bit został zmieniony z true na false. 42 MB + / 8 = 5 MB + Koszt wydaje się zbyt duży przy dzisiejszym sprzęcie. Ale pewnego dnia może się to przydać. " nie jest tak naprawdę odpowiednim komentarzem. Hashset byłby najlepszy. Jeśli masz do czynienia z bardzo dużymi tablicami, spodziewasz się wyjątkowo dużej ilości pamięci. Ale w tak dziwnym, skrajnym przypadku, herysta z algorytmem CRC byłby lepszy. Odwzoruj to na wielomian. Jeśli blisko, oceń. Dziękuję @tigrou!
TamusJRoyce

0

I inne rozwiązanie, jeśli chcesz znaleźć zduplikowane wartości.

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

Wynik:

duplicated value: 2
duplicated value: 7

http://rextester.com/SIDG48202


0

Sprawdzam, czy IEnumerable (aray, list itp.) Jest unikalny w następujący sposób:

var isUnique = someObjectsEnum.GroupBy(o => o.SomeProperty).Max(g => g.Count()) == 1;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.