Tester si toutes les valeurs d'une liste sont uniques


90

J'ai une petite liste d'octets et je veux tester que ce sont toutes des valeurs différentes. Par exemple, j'ai ceci:

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

Quelle est la meilleure façon de vérifier si toutes les valeurs sont distinctes ou non?


2
Comme il s'agit d'une question typique de la salle de classe, je répondrai par une question. Comment feriez-vous si c'était trié?
ctrl-alt-delor

Réponses:


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

Juste curieux: quelles sont les exigences en matière d'espace et de temps?
dtb

10
@dtb doit être environ O (N) . Bien sûr, étant donné qu'il s'agit d'une "petite liste", elle sera ultra-rapide avec presque tous les algorithmes. IMO cela gagne sur la lisibilité et la concision, et comme la vitesse n'est pas un problème, cela le rend parfait.
Tim S.

2
C'est leff efficace qu'il ne pourrait l'être
Jodrell

74

Voici une autre approche plus efficace que Enumerable.Distinct+ Enumerable.Count(d'autant plus si la séquence n'est pas de type collection). Il utilise un HashSet<T>qui élimine les doublons, est très efficace dans les recherches et possède une propriété count:

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

ou une autre approche - plus subtile et efficace -:

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

HashSet<T>.Addrenvoie falsesi l'élément n'a pas pu être ajouté car il était déjà dans le HashSet. Enumerable.Alls'arrête sur le premier "faux".


1
si simple et évident, pourquoi n'y ai-je pas pensé en premier :) J'ai utilisé ce one-liner en test unitaire pour confirmer que 10 millions d'éléments générés par mon code génial sont vraiment uniques Assert.IsTrue(samples.Add(AwesomeClass.GetUnique()));. Ils étaient et sont :) +1 pour vous Tim :)
grapkulec

1
J'ai essayé votre réponse à cette question mais cela ne fonctionne pas monsieur: stackoverflow.com/questions/34941162/…
Learning-Overthinker-Confused

Devrait être ceci:bool allDifferent = theList.All(s => diffChecker.Add(s))
mike nelson

2
Non, pas nécessaire. Dans ce cas, vous pouvez passer directement le délégué
Tim Schmelter

1
@ AndréReichelt - Je viens d'ouvrir votre code et le troisième scénario ( List.All(HashSet.Add)) semble être beaucoup plus rapide que les deux autres dans presque tous les cas
Kyle Delaney le

6

D'accord, voici la méthode la plus efficace à laquelle je puisse penser en utilisant .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;
    }
}

essentiellement, quel est l'intérêt d'énumérer la séquence entière deux fois si tout ce que vous voulez faire est de trouver le premier doublon.

Je pourrais optimiser cela davantage en enveloppant spécialement une séquence d'élément vide et unique, mais cela déprécierait de la lisibilité / maintenabilité avec un gain minimal.


Agréable ajout d'une valeur en double en retour, assez utile pour la validation
Pac0

J'ai testé 3 solutions ici et c'est en effet la plus efficace sur cette page. Quelques fautes de frappe là-dedans cependant (par exemple sequencedevraient être source). Mais fonctionne très bien une fois que ceux-ci sont corrigés
Mike Nelson

@mikenelson, ça devrait être mieux
Jodrell

2
Pour la lisibilité, je pense qu'il devrait être if (!checkBuffer.Add(t)) { firstDuplicate = t; return true }dans la boucle.
tia le

2

La logique similaire à l' Distinctutilisation GroupBy:

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

Ceci est utile si vous souhaitez vérifier l'unicité d'une propriété theList.GroupBy(o => o.SomeProperty).Count() == theList.Count;alors que Distinct () ne le permet pas.
Rev1.0 le

1

On peut aussi faire: Utiliser Hashset

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

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

0

Il existe de nombreuses solutions.

Et sans doute de plus beaux avec l'utilisation de LINQ comme "juergen d" et "Tim Schmelter" mentionnés.

Mais, si vous mettez à nu la «complexité» et la vitesse, la meilleure solution sera de l'implémenter par vous-même. Une des solutions sera de créer un tableau de taille N (pour l'octet, c'est 256). Et bouclez le tableau, et à chaque itération, testera l'index numérique correspondant si la valeur est 1 si c'est le cas, cela signifie que j'incrémente déjà l'index du tableau et que le tableau n'est donc pas distinct, sinon je vais incrémenter la cellule du tableau et continuer à vérifier .


2
vous pouvez utiliser un vecteur de bits avec 256 bits = 32 octets = 8 entiers. Mais votre Big O = O (n) sera toujours le même que l'utilisation d'un Hashet proposé dans l'autre réponse.
BrokenGlass

C'est O (n) donc peut-être le plus rapide, (testez-le). La vérification des comptes au fur et à mesure ou à la fin serait-elle la plus rapide? Je soupçonne qu'à la fin, cela améliorera le pire des cas, mais au fur et à mesure que vous avancez, cela peut améliorer la moyenne et le meilleur des cas). S'il n'y a pas de doublons, ce sera le pire des cas. De plus, pour les types de données plus volumineux, cela ne fonctionnera pas bien, pour un type 16 bits, vous devrez utiliser 64 Ko de nombre, enfin 64 Ko (8 Ko), mais pour tout ce qui est plus gros, l'utilisation de la mémoire commencera à devenir idiote. Cependant, j'aime cette réponse pour les valeurs 8 bits.
ctrl-alt-delor

1
@TamusJRoyce si vous voulez stocker 4294967296 possibilités, vous avez besoin de 4 Go et non de 42 Mo (ou 512 Mo de vous utilisez le masquage de bits)
tigrou

Je ne sais pas à quoi je pensais. "Allouez plus de 42 Mo de mémoire pour contenir toutes les 4294967296 possibilités. Et utilisez de simples compteurs de compartiment. Ou utilisez même le masquage de bits xor et vérifiez si un bit est changé de vrai à faux. 42 Mo + / 8 = 5 Mo + La dépense semble trop élevée avec le matériel actuel. Mais un jour, cela peut avoir du mérite. " n'est pas vraiment un commentaire pertinent. Hashset serait le meilleur. Si vous avez affaire à des tableaux extrêmement volumineux, vous vous attendez à une mémoire extrêmement volumineuse. Mais dans un cas de bord aussi étrange, un hériste avec un algorithme CRC serait mieux. Mappez-le à un polynôme. Si proche, évaluez. Merci @tigrou!
TamusJRoyce

0

Et une autre solution, si vous voulez trouver des valeurs dupliquées.

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

Production:

duplicated value: 2
duplicated value: 7

http://rextester.com/SIDG48202


0

Je vérifie si un IEnumerable (aray, liste, etc.) est unique comme ceci:

var isUnique = someObjectsEnum.GroupBy(o => o.SomeProperty).Max(g => g.Count()) == 1;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.