Перевірте, чи всі значення у списку є унікальними


90

У мене є невеликий список байтів, і я хочу перевірити, чи всі вони різні значення. Наприклад, у мене є таке:

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

Який найкращий спосіб перевірити, чи всі значення різняться чи ні?


2
Оскільки це типове запитання в класі, я відповім запитанням. Як би ви зробили це, якби це було відсортовано?
ctrl-alt-delor

Відповіді:


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

Просто цікаво: які вимоги до простору та часу це вимагає?
dtb

10
@dtb має бути приблизно O (N) . Звичайно, враховуючи, що це "невеликий список", він буде блискавичним майже з будь-яким алгоритмом. ІМО виграє завдяки читабельності та лаконічності, а оскільки швидкість не є проблемою, це робить її ідеальною.
Тім С.

2
Це ефективно, ніж могло б бути
Джодрелл

74

Ось ще один підхід, який є більш ефективним, ніж Enumerable.Distinct+ Enumerable.Count(тим більше, якщо послідовність не є типом колекції). Він використовує a, HashSet<T>який усуває дублікати, дуже ефективний при пошуку та має властивість count:

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

або інший - більш тонкий та ефективний - підхід:

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

HashSet<T>.Addповертає, falseякщо елемент не вдалося додати, оскільки він уже був у HashSet. Enumerable.Allзупиняється на першій "помилці".


1
настільки простий і очевидний, чому я не задумався про це спочатку :) Я використав цей однокласник в модульному тесті, щоб підтвердити, що 10 мільйонів елементів, створених моїм чудовим кодом, справді унікальні Assert.IsTrue(samples.Add(AwesomeClass.GetUnique()));. Вони були і є :) +1 для вас Тім :)
grapkulec

1
я спробував свою відповідь на це питання , але він не працює , сер: stackoverflow.com/questions/34941162 / ...
Вчити-Overthinker-Confused

Має бути так:bool allDifferent = theList.All(s => diffChecker.Add(s))
Майк Нельсон

2
Ні, не потрібно. У цьому випадку ви можете передати делегата безпосередньо
Тіму Шмельтеру

1
@ AndréReichelt - Я щойно відкрив ваш код, і третій сценарій ( List.All(HashSet.Add)) здається набагато швидшим за інші два майже у всіх випадках
Кайл Делані,

6

Гаразд, ось найефективніший метод, який я можу придумати використовувати стандартний .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;
    }
}

по суті, який сенс двічі перераховувати всю послідовність, якщо все, що ви хочете зробити, це знайти перший дублікат.

Я міг би оптимізувати це більше за допомогою спеціального кожуху порожніх та одноелементних послідовностей, але це зменшило б зручність читання / ремонтопридатності з мінімальним виграшем.


Приємно додати повторне значення з повернення, досить корисне для перевірки
Pac0,

Я протестував тут 3 рішення, і це дійсно найефективніше на цій сторінці. Хоча там кілька помилок (наприклад, sequenceмають бути source). Але чудово працює, як тільки це буде виправлено
Майк Нельсон

@mikenelson, це має бути краще
Джодрелл

2
Для читабельності, я думаю, це повинно бути if (!checkBuffer.Add(t)) { firstDuplicate = t; return true }в курсі.
тіа

2

Логіка, подібна до Distinctвикористання GroupBy:

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

Це корисно, якщо ви хочете перевірити унікальність щодо властивості, theList.GroupBy(o => o.SomeProperty).Count() == theList.Count;тоді як Distinct () цього не дозволяє.
Рев. 1.0,

1

Можна також зробити: Використовувати хешсет

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

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

0

Рішень багато.

Безсумнівно, ще красивіші із використанням LINQ як "juergen d" та "Tim Schmelter".

Але якщо ви оголюєте "Складність" і швидкість, найкращим рішенням буде реалізувати це самостійно. Одним із рішень буде створення масиву розміром N (для байту це 256). І цикл масиву, і на кожній ітерації буде перевіряти відповідний індекс числа, якщо значення 1, якщо це так, це означає, що я вже збільшую індекс масиву, і тому масив не відрізняється, інакше я збільшуватиму комірку масиву і продовжуватиму перевірку .


2
ви можете використовувати бітовий вектор із 256 бітами = 32 байта = 8 цілих чисел. Але ваше Велике O = O (n) все одно буде таким же, як використання Хашета, запропонованого в іншій відповіді.
BrokenGlass

Це O (n), тож, можливо, найшвидший, (протестуйте). Чи перевірка підрахунку під час проходження або в кінці буде найшвидшою? Я підозрюю, що врешті-решт покращиться гірший випадок, але по мірі покращення може покращитися середній і найкращий випадок). Якщо дублікатів немає, це буде найгіршим результатом. Крім того, для більших типів даних це буде погано працювати, для 16-бітного типу вам доведеться використовувати 64 тис. Лічильника, ну 64 тис. Біт (8 тис. Байт), але для чогось більшого використання пам'яті почне ставати безглуздим. Однак мені подобається ця відповідь для 8-бітових значень.
ctrl-alt-delor

1
@TamusJRoyce, якщо ви хочете зберегти 4294967296 можливостей, вам знадобиться 4 ГБ, а не 42 МБ (або 512 МБ з вас використовують бітове маскування)
tigrou

Не впевнений, про що я думав. "Виділіть 42 МБ пам’яті, щоб утримувати всі 4294967296 можливостей. І використовуйте прості лічильники сегментів. Або навіть використовуйте бітове маскування xor і перевірте, чи не змінився який-небудь біт із істинного на хибний. 42 МБ + / 8 = 5 МБ + Витрати здаються занадто великими із сучасним обладнанням. Але одного разу це може мати гідність ". насправді не є відповідним коментарем. Хешсет буде найкращим. Якщо ви маєте справу з надзвичайно великими масивами, ви очікуєте надзвичайно великого обсягу пам'яті. Але в такому дивному випадку кращого був би герстик з алгоритмом CRC. Наведіть його на поліном. Якщо близько, оцініть. Дякую @tigrou!
TamusJRoyce

0

І ще одне рішення, якщо ви хочете знайти дубльовані значення.

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

Вихід:

duplicated value: 2
duplicated value: 7

http://rextester.com/SIDG48202


0

Я перевіряю, чи IEnumerable (масив, список тощо) унікальний, як це:

var isUnique = someObjectsEnum.GroupBy(o => o.SomeProperty).Max(g => g.Count()) == 1;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.