Testen Sie, ob alle Werte in einer Liste eindeutig sind


88

Ich habe eine kleine Liste von Bytes und möchte testen, ob sie alle unterschiedliche Werte haben. Zum Beispiel habe ich Folgendes:

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

Wie kann am besten überprüft werden, ob alle Werte unterschiedlich sind oder nicht?


2
Da dies eine typische Unterrichtsfrage ist, werde ich sie mit einer Frage beantworten. Wie würden Sie es tun, wenn es sortiert wäre?
Strg-Alt-Delor

Antworten:


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

Nur neugierig: Was sind die räumlichen und zeitlichen Anforderungen dafür?
dtb

10
@dtb sollte ungefähr O (N) sein . Wenn man bedenkt, dass dies eine "kleine Liste" ist, ist sie mit fast jedem Algorithmus blitzschnell. IMO gewinnt dies an Lesbarkeit und Prägnanz, und da Geschwindigkeit kein Problem ist, macht es das perfekt.
Tim S.

2
Dies ist leff effizient als es sein könnte
Jodrell

72

Hier ist ein anderer Ansatz, der effizienter als Enumerable.Distinct+ ist Enumerable.Count(umso mehr, wenn die Sequenz kein Sammlungstyp ist). Es verwendet a, HashSet<T>das Duplikate eliminiert, bei der Suche sehr effizient ist und eine count-Eigenschaft hat:

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

oder ein anderer - subtilerer und effizienterer - Ansatz:

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

HashSet<T>.AddGibt zurück, falsewenn das Element nicht hinzugefügt werden konnte, da es bereits in der enthalten war HashSet. Enumerable.Allstoppt beim ersten "false".


1
So einfach und offensichtlich, warum habe ich nicht zuerst darüber nachgedacht :) Ich habe diesen Einzeiler im Unit-Test verwendet, um zu bestätigen, dass 10 Millionen Elemente, die durch meinen fantastischen Code generiert wurden, wirklich einzigartig sind Assert.IsTrue(samples.Add(AwesomeClass.GetUnique()));. Sie waren und sind :) +1 für dich Tim :)
grapkulec

1
Ich habe Ihre Antwort auf diese Frage versucht, aber es funktioniert nicht, Sir: stackoverflow.com/questions/34941162/…
Learning-Overthinker-Confused

Sollte dies sein:bool allDifferent = theList.All(s => diffChecker.Add(s))
Mike Nelson

2
Nein, nicht benötigt. In diesem Fall können Sie den Delegierten direkt übergeben
Tim Schmelter

1
@ AndréReichelt - Ich habe gerade Ihren Code geöffnet und das dritte Szenario ( List.All(HashSet.Add)) scheint in fast allen Fällen viel schneller zu sein als die beiden anderen
Kyle Delaney

6

Okay, hier ist die effizienteste Methode, die ich mir vorstellen kann, Standard .Net zu verwenden

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

Im Wesentlichen müssen Sie die gesamte Sequenz zweimal aufzählen, wenn Sie nur das erste Duplikat finden möchten.

Ich könnte dies weiter optimieren, indem ich eine leere und eine einzelne Elementsequenz speziell umhülle, aber dies würde die Lesbarkeit / Wartbarkeit mit minimalem Gewinn beeinträchtigen.


Schön, einen doppelten Wert aus der Rückgabe hinzuzufügen, sehr nützlich für die Validierung
Pac0

Ich habe hier 3 Lösungen getestet und dies ist in der Tat die effizienteste auf dieser Seite. Ein paar Tippfehler drin (zB sequencesollte es sein source). Aber funktioniert großartig, wenn diese behoben sind
Mike Nelson

@mikenelson, das sollte besser sein
Jodrell

2
Aus Gründen der Lesbarkeit denke ich, dass es if (!checkBuffer.Add(t)) { firstDuplicate = t; return true }in der Schleife sein sollte.
Tia

1

Die ähnliche Logik wie bei der DistinctVerwendung von GroupBy:

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

1

Man kann auch: Hashset verwenden

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

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

0

Es gibt viele Lösungen.

Und zweifellos schönere mit der Verwendung von LINQ als "juergen d" und "Tim Schmelter" erwähnt.

Wenn Sie jedoch "Komplexität" und Geschwindigkeit haben, ist es die beste Lösung, diese selbst zu implementieren. Eine der Lösungen besteht darin, ein Array mit einer Größe von N zu erstellen (für Byte sind es 256). Und Schleife des Arrays, und bei jeder Iteration wird der Index der übereinstimmenden Zahlen getestet, wenn der Wert 1 ist, wenn dies der Fall ist. Das bedeutet, dass ich den Array-Index bereits inkrementiere und daher das Array nicht eindeutig ist, andernfalls inkrementiere ich die Array-Zelle und setze die Überprüfung fort .


2
Sie können einen Bitvektor mit 256 Bits = 32 Byte = 8 Ganzzahlen verwenden. Ihr großes O = O (n) entspricht jedoch immer noch der Verwendung eines in der anderen Antwort vorgeschlagenen Hashets.
BrokenGlass

Dies ist O (n), also vielleicht am schnellsten (teste es). Wäre die Überprüfung am schnellsten oder am Ende am schnellsten? Ich vermute, dass sich am Ende der Worst-Case verbessern wird, aber im Laufe der Zeit kann sich der Durchschnitt und der Best-Case verbessern. Wenn keine Duplikate vorhanden sind, ist dies die schlechteste Leistung. Auch für größere Datentypen funktioniert dies nicht gut. Für einen 16-Bit-Typ müssten Sie 64 KB zählen, also 64 KBit (8 KByte), aber für alles, was größer ist, wird die Speichernutzung langsam albern. Diese Antwort gefällt mir jedoch für 8-Bit-Werte.
Strg-Alt-Delor

1
@ TamusJRoyce Wenn Sie 4294967296 Möglichkeiten speichern möchten, benötigen Sie 4 GB nicht 42 MB (oder 512 MB von Ihnen verwenden
Bitmaskierung

Ich bin mir nicht sicher, was ich dachte. "Weisen Sie 42 MB + Speicher zu, um alle 4294967296-Möglichkeiten aufzunehmen. Verwenden Sie einfache Bucket-Zähler. Oder verwenden Sie sogar die Bitmaskierung xor und prüfen Sie, ob ein Bit von true in false geändert wurde. 42 MB + / 8 = 5 MB + Die Kosten scheinen mit der heutigen Hardware zu hoch. Aber eines Tages könnte dies Verdienst haben. " ist nicht wirklich ein relevanter Kommentar. Hashset wäre am besten. Wenn Sie mit extrem großen Arrays arbeiten, erwarten Sie extrem viel Speicher. Aber in solch einem seltsamen Randfall wäre ein Herist mit einem CRC-Algorithmus besser. Ordnen Sie es einem Polynom zu. Wenn nahe, bewerten. Danke @tigrou!
TamusJRoyce

0

Und eine andere Lösung, wenn Sie doppelte Werte finden möchten.

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

Ausgabe:

duplicated value: 2
duplicated value: 7

http://rextester.com/SIDG48202


0

Ich überprüfe, ob eine IEnumerable (aray, list, etc) wie folgt eindeutig ist:

var isUnique = someObjectsEnum.GroupBy(o => o.SomeProperty).Max(g => g.Count()) == 1;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.