Лучший способ узнать, имеет ли IEnumerable <> уникальные значения

У меня есть много кода, в котором я делаю что-то вроде этого

bool GetIsUnique(IEnumerable<T> values)
{
    return values.Count() == values.Distinct().Count;
}

Есть ли более быстрый более удобный способ сделать это?

Ответ 1

Ваш метод должен повторять последовательность в два раза с несколькими потенциальными недостатками:

  • Итерация дважды будет медленнее, чем повторение для последовательностей любого значительного размера.
  • Некоторые последовательности генерируют исключение, если вы пытаетесь повторить их несколько раз; другие могут возвращать разные результаты для последующих итераций.
  • В вашем методе используется Count, который должен каждый раз перебирать всю последовательность. Нет причин, по которым вам не следует начинать рано, как только вы знаете, что есть дублирующее значение.

Следующий метод требует только повторения последовательности по порядку один раз и будет разорваться раньше, как только встретится какое-то повторяющееся значение:

bool GetIsUnique<T>(IEnumerable<T> values)
{
    var set = new HashSet<T>();

    foreach (T item in values)
    {
        if (!set.Add(item))
            return false;
    }
    return true;
}

Ответ 2

Я бы сделал это хорошим методом расширения

public static bool IsUnique<T>(this IEnumerable<T> list)
{
    var hs = new HashSet<T>();
    return list.All(hs.Add);  
}

Проверяет, что все элементы могут быть добавлены в HashSet.

Ответ 3

Я думаю, это зависит от того, что вы хотите сделать, если есть уникальные значения. @Jamiec или ответ @LukeH - отличные ответы и, вероятно, лучше всего подходят для чистой скорости, но он не может сказать вам, где проблемы.

Вы также можете рассмотреть что-то вроде

var group = values.GroupBy(x => x);
return group.Any(g => g.Count() > 1);

На самом деле это хуже, чем реализация HashSet. Но если вы держите эту группу вокруг, вы можете найти, какие элементы дублируются.

var group = values.GroupBy(x => x);
return group.Where(g => g.Count() > 1);

или

var group = values.GroupBy(x => x);
return group.Where(g => g.Count() > 1).Select(g => g.Key);

Размышление об этом с помощью GroupBy позволяет вам открывать свои возможности для того, что делать дальше. Но если все, о чем вы заботитесь, это знать, являются ли все значения уникальными, я бы пошел с HashSet

Ответ 4

Вы бы делали две петли через данные для выше - один раз, чтобы получить счет, один раз, чтобы получить отчетный счет. Особенно плохо, если первые два предмета идентичны! Попробуйте что-то вроде этого:

bool GetIsUnique<T>(IEnumerable<T> values)
{
    HashSet<T> hashSet = new HashSet<T>();
    foreach(var value in values)
    {
        if (hashSet.Contains(value))
        {
            return false;
        }
        hashSet.Add(value);
    }
    return true;
}

Этот будет завершен, как только он найдет дубликат. Очевидно, что на скорости поиска хэша, но учитывая, что Distinct использует набор внутри, я бы все еще ожидал, что он будет быстрее.

Ответ 5

Два основных правила:

  • Самый простой способ читать и понимать - это всегда лучший способ кодировать что-то. Этот код очень легко читается, поэтому я бы сказал, что вам хорошо.
  • Производительность ( "быстрее" ) должна быть только проблемой, если вы можете доказать, что это метод, замедляющий вашу программу, или если вы создаете библиотеку, к которой у других людей будет доступ, без доступа к источнику код.

Другие методы будут быстрее (они будут замыкаться на короткое замыкание при обнаружении повторяющегося значения, возвращая false), но я все равно буду придерживаться вашей версии, если бы это был мой код.

Ответ 6

Быстрый поиск первого дубликата, если он есть, следующий:

public static bool TryFindFirstDuplicate<T>(this IEnumerable<T> source, out T duplicate)
{
    var set = new HashSet<T>();
    foreach (var item in source)
    {
        if (!set.Add(item))
        {
            duplicate = item;
            return true;
        }
    }
    duplicate = default(T);
    return false;
}