Что лучше для создания отдельных структур данных: HashSet или Linq Distinct()?
Мне интересно, могу ли я получить консенсус относительно того, какой метод лучше подходит для создания отдельного набора элементов: a C# HashSet
или с помощью IEnumerable .Distinct()
, который является функцией Linq?
Скажем, я просматриваю результаты запроса из базы данных с помощью DataReader, а мои параметры - добавлять объекты, которые я создаю, в List<SomeObject>
или в HashSet<SomeObject>
. С опцией List
я завершаю что-то вроде:
myList = myList.Distinct().ToList<SomeObject>();
С HashSet
, я понимаю, что добавление к нему элементов само по себе заботится о не дублировании, предполагая, что вы переопределили методы GetHashCode()
и Equals()
в SomeObject. Я в основном обеспокоен факторами риска и эффективности параметров.
Спасибо.
Ответы
Ответ 1
"Лучше" - это сложное слово для использования - это может означать так много разных вещей для разных людей.
Для удобства чтения я бы пошел на Distinct()
, поскольку я лично считаю это более понятным.
Для производительности я подозреваю, что реализация HashSet, созданная вручную, может выполняться умеренно быстрее, но я сомневаюсь, что это будет совсем иначе, поскольку внутренняя реализация Distinct
, без сомнения, сама использует некоторую форму хэширования.
Для того, что я считаю "лучшей" реализацией... Я думаю, что вы должны использовать Distinct
, но каким-то образом переместите это на уровень базы данных, то есть измените базовую базу данных SELECT перед тем, как заполнить DataReader.
Ответ 2
Энтони Пегем сказал это лучше всего. Используйте правильный инструмент для работы. Я говорю об этом, потому что Distinct
или HashSet
не так сильно отличается от производительности. Используйте HashSet
, когда коллекция всегда должна содержать только отдельные элементы. Он также говорит программисту, что вы не можете добавить к нему дубликаты. Используйте обычные List<T>
и .Distinct()
ont, когда вам придется добавлять дубликаты и удалять дубликаты позже. Цель имеет значение.
В общем случае
a) HashSet может не принести пользы, если вы добавляете новые объекты из db, и вы не указали собственный Equals
собственный. Каждый объект из db может быть новым экземпляром для вашего хешета (если вы только новичок), и это приведет к дублированию в коллекции. В этом случае используйте обычный List<T>
.
b) Если у вас есть определитель равенства, определенный для hashset, и ваша коллекция всегда должна содержать только отдельные объекты, используйте hashset.
c) Если у вас есть сопоставитель равенства, определенный для hashset, и вам нужны только отдельные объекты из db, но коллекция не всегда должна содержать только отдельные объекты (то есть дубликаты, которые необходимо добавить позже), более быстрый подход - получить элементы от db до hashset, а затем возвращают обычный список из этого хэшета.
d) Лучшее, что вам нужно сделать, это предоставить задачу удаления дубликатов в базу данных, это правильный инструмент И этот первый класс!
Что касается различий в производительности, в моем тестировании я всегда находил HashSet быстрее, но тогда это было только маргинальным. Это очевидно, учитывая подход List, который вы должны сначала добавить, а затем сделать отчетливым.
Метод тестирования: начиная с двух общих функций,
public static void Benchmark(Action method, int iterations = 10000)
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < iterations; i++)
method();
sw.Stop();
MsgBox.ShowDialog(sw.Elapsed.TotalMilliseconds.ToString());
}
public static List<T> Repeat<T>(this ICollection<T> lst, int count)
{
if (count < 0)
throw new ArgumentOutOfRangeException("count");
var ret = Enumerable.Empty<T>();
for (var i = 0; i < count; i++)
ret = ret.Concat(lst);
return ret.ToList();
}
Реализация:
var d = Enumerable.Range(1, 100).ToList().Repeat(100);
HashSet<int> hash = new HashSet<int>();
Benchmark(() =>
{
hash.Clear();
foreach (var item in d)
{
hash.Add(item);
}
});
~ 3300 мс
var d = Enumerable.Range(1, 100).ToList().Repeat(100);
List<int> list = new List<int>();
Benchmark(() =>
{
list.Clear();
foreach (var item in d)
{
list.Add(item);
}
list = list.Distinct().ToList();
});
~ 5800 мс
Разница в 2,5 секунды не является плохим для списка из 10000 объектов при повторении 10000 раз. Для нормальных случаев разница будет едва заметной.
Лучший подход, возможно, для вас с вашим текущим дизайном:
var d = Enumerable.Range(1, 100).ToList().Repeat(100);
HashSet<int> hash = new HashSet<int>();
List<int> list = new List<int>();
Benchmark(() =>
{
hash.Clear();
foreach (var item in d)
{
hash.Add(item);
}
list = hash.ToList();
});
~ 3300 мс
Нет существенной разницы, см.
Частично несвязанный - после публикации этого ответа мне было любопытно узнать, какой лучший способ удалить дубликаты из обычного списка.
var d = Enumerable.Range(1, 100).ToList().Repeat(100);
HashSet<int> hash = new HashSet<int>();
List<int> list = new List<int>();
Benchmark(() =>
{
hash = new HashSet<int>(d);
});
~ 3900 мс
var d = Enumerable.Range(1, 100).ToList().Repeat(100);
List<int> list = new List<int>();
Benchmark(() =>
{
list = d.Distinct().ToList();
});
~ 3200 мс
Здесь правильный инструмент Distinct
быстрее, чем hackish HashSet
! Возможно, это накладные расходы на создание хэш-набора.
Я тестировал с различными другими комбинациями, такими как ссылочные типы, без дубликатов в исходном списке и т.д. Результаты согласуются.
Ответ 3
Что лучше, что наиболее выразительно в описании вашего намерения. Внутренние детали реализации более или менее одинаковы, разница заключается в том, "кто пишет код?"
Если вы намереваетесь создать с нуля отдельный набор элементов из источника, который не является совокупностью указанных элементов, я бы сказал, что для HashSet<T>
. Вы должны создать элемент, вам нужно собрать коллекцию, вы могли бы также построить правильную с самого начала.
В противном случае, если у вас уже есть коллекция элементов, и вы хотите удалить дубликаты, я бы сказал, что вы вызываете Distinct()
. У вас уже есть коллекция, вам просто нужен выразительный способ получить отдельные элементы из нее.
Ответ 4
Для больших коллекций HashSet скорее всего будет быстрее. Он полагается на хэш-код объектов, чтобы быстро определить, существует ли элемент в наборе.
На практике это (скорее всего) не будет иметь значения (но вы должны измерить, если вам интересно).
Я инстинктивно предположил, что HashSet
будет быстрее, из-за быстрой проверки хэша, которую он использует. Тем не менее, я просмотрел текущую (4.0) реализацию Distinct в исходных источниках и использует аналогичный класс Set
(который также полагается на хэширование) под обложками. Вывод; нет практических различий в производительности.
В вашем случае я бы пошел с .Distinct
для чтения - он ясно передает намерение кода. Однако я согласен с одним из других ответов, что вы, вероятно, должны выполнить эту операцию в БД, если это возможно.
Ответ 5
Если yor зацикливается на результатах DbReader, добавив ваши повторы в Hashset, было бы лучше, чем добавление его в список, а затем выполнение отличия. Вы сохранили бы одну итерацию. (Distinct внутренне использует HashSet)
Ответ 6
Реализация Distinct может использовать HashSet. Взгляните на проект Jon Skeet Edulinq.