Метод C# Distinct () сохраняет исходный порядок последовательности нетронутым?
Я хочу удалить дубликаты из списка, не меняя порядок уникальных элементов в списке.
Джон Скит и другие предложили использовать следующее
list = list.Distinct().ToList();
удаление дубликатов из списка C#
удалить дубликаты из списка в C#
гарантируется ли, что порядок уникальных элементов будет таким же, как и раньше? Если да, пожалуйста, дайте ссылку, которая подтверждает это, поскольку я ничего не мог найти в документации.
6 ответов:
Это не гарантировано, но это самая очевидная реализация. Было бы трудно реализовать потоковым способом (т. е. таким, чтобы он возвращал результаты как можно скорее, прочитав как можно меньше) без возвращая их по порядку.
вы можете прочитать мой пост в блоге на реализация Edulinq Distinct ().
обратите внимание, что даже если это было гарантировано для LINQ объектов (что лично я думаю, что это должны be) это ничего не значит для других поставщиков LINQ, таких как LINQ to SQL.
уровень гарантий, предоставляемых в LINQ для объектов, иногда немного противоречив, IMO. Некоторые оптимизации документированы, другие нет. Черт возьми, некоторые из документации просто неправильно.
да, в порядке первого появления в первоначальный список. Это гарантированный для .Net Framework 3.5
Я провел небольшое расследование с рефлектором. После разборки системы.Ядро.dll, Version=3.5.0.0 вы можете видеть, что Distinct () является методом расширения, который выглядит следующим образом:
public static class Emunmerable { public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source) { if (source == null) throw new ArgumentNullException("source"); return DistinctIterator<TSource>(source, null); } }Итак, интересным здесь является DistinctIterator, который реализует IEnumerable и IEnumerator. Вот упрощенная (goto и lables удалены) реализация этого IEnumerator:
private sealed class DistinctIterator<TSource> : IEnumerable<TSource>, IEnumerable, IEnumerator<TSource>, IEnumerator, IDisposable { private bool _enumeratingStarted; private IEnumerator<TSource> _sourceListEnumerator; public IEnumerable<TSource> _source; private HashSet<TSource> _hashSet; private TSource _current; private bool MoveNext() { if (!_enumeratingStarted) { _sourceListEnumerator = _source.GetEnumerator(); _hashSet = new HashSet<TSource>(); _enumeratingStarted = true; } while(_sourceListEnumerator.MoveNext()) { TSource element = _sourceListEnumerator.Current; if (!_hashSet.Add(element)) continue; _current = element; return true; } return false; } void IEnumerator.Reset() { throw new NotSupportedException(); } TSource IEnumerator<TSource>.Current { get { return _current; } } object IEnumerator.Current { get { return _current; } } }Как вы можете видеть - перечисление идет в порядке, предусмотренном источником enumerable (список, на котором мы вызываем Distinct). Hashset используется только для определения, возвращаем ли мы уже такой элемент или нет. Если нет, то мы его возвращаем, иначе-продолжаем перечислять по источнику.
таким образом, гарантируется, что Distinct() вернет элементы точно в таком же порядке, которые предоставляются коллекцией, к которой был применен Distinct.
по словам документация последовательность не упорядочена.
да, перечисляемую.Особым сохраняет порядок. Предполагая, что метод ленивый "дает различные значения, как только они видны", это следует автоматически. Думать об этом.
источник ссылок .NET подтверждает. Он возвращает подпоследовательность, первый элемент в каждом классе эквивалентности.
foreach (TSource element in source) if (set.Add(element)) yield return element;реализация.NET Core аналогично.
к сожалению, документация для перечисли.Отчетливый путается в этом пункте:
последовательность результатов неупорядочена.
Я могу только представить, что они означают "последовательность результатов не сортируется.- Ты может реализуйте Distinct, предварительно сортируя, а затем сравнивая каждый элемент с предыдущим, но это не было бы ленивым, как определено выше.
по умолчанию при использовании оператора Distinct linq используется метод Equals, но вы можете использовать свой собственный
IEqualityComparer<T>объект, чтобы указать, когда два объекта равны с пользовательской логикой реализацииGetHashCodeиEqualsметод. Помните, что:
GetHashCodeне следует использовать тяжелое сравнение ЦП ( например. используйте только некоторые очевидные базовые проверки ) и его использование в качестве первого состояния, если два объекта, безусловно, разные ( если возвращаются разные хэш-коды) или потенциально одинаковые ( один и тот же хэш-код). В этом последний случай, когда два объекта имеют один и тот же хэш-код, фреймворк будет проверять использование метода Equals в качестве окончательного решения о равенстве заданных объектов.после
MyTypeиMyTypeEqualityComparerклассы следуют коду, не гарантируя, что последовательность поддерживает свой порядок:var cmp = new MyTypeEqualityComparer(); var lst = new List<MyType>(); // add some to lst var q = lst.Distinct(cmp);в follow библиотека sci я реализовал метод расширения, чтобы гарантировать, что набор Vector3D поддерживает порядок при использовании определенного метода расширения
DistinctKeepOrder:соответствующий код ниже:
/// <summary> /// support class for DistinctKeepOrder extension /// </summary> public class Vector3DWithOrder { public int Order { get; private set; } public Vector3D Vector { get; private set; } public Vector3DWithOrder(Vector3D v, int order) { Vector = v; Order = order; } } public class Vector3DWithOrderEqualityComparer : IEqualityComparer<Vector3DWithOrder> { Vector3DEqualityComparer cmp; public Vector3DWithOrderEqualityComparer(Vector3DEqualityComparer _cmp) { cmp = _cmp; } public bool Equals(Vector3DWithOrder x, Vector3DWithOrder y) { return cmp.Equals(x.Vector, y.Vector); } public int GetHashCode(Vector3DWithOrder obj) { return cmp.GetHashCode(obj.Vector); } }короче
Vector3DWithOrderинкапсулируйте тип и целое число порядка, в то время какVector3DWithOrderEqualityComparerинкапсулирует компаратор исходного типа.и это метод помощник для обеспечения порядка поддерживается
/// <summary> /// retrieve distinct of given vector set ensuring to maintain given order /// </summary> public static IEnumerable<Vector3D> DistinctKeepOrder(this IEnumerable<Vector3D> vectors, Vector3DEqualityComparer cmp) { var ocmp = new Vector3DWithOrderEqualityComparer(cmp); return vectors .Select((w, i) => new Vector3DWithOrder(w, i)) .Distinct(ocmp) .OrderBy(w => w.Order) .Select(w => w.Vector); }Примечание : дальнейшие исследования могли бы позволить найти более общий ( использование интерфейсов ) и оптимизированный способ (без инкапсуляции объекта ).
это сильно зависит от вашего linq-провайдера. На Linq2Objects вы можете остаться на внутреннем исходном коде для
Distinct, что заставляет предположить, что исходный порядок сохраняется.однако для других поставщиков, которые разрешают какой-то SQL, например, это не обязательно так, как
ORDER BY-оператор обычно приходит после любой агрегации (например,Distinct). Так что если ваш код такой:myArray.OrderBy(x => anothercol).GroupBy(x => y.mycol);это переводится что-то похожее на следующее В SQL:
SELECT * FROM mytable GROUP BY mycol ORDER BY anothercol;это, очевидно, сначала группирует ваши данные и сортирует их впоследствии. Теперь вы застряли на собственной логике СУБД, как это выполнить. На некоторых СУБД это даже не допускается. Представьте себе следующие данные:
mycol anothercol 1 2 1 1 1 3 2 1 2 3при выполнении
myArr.OrderBy(x => x.anothercol).GroupBy(x => x.mycol)мы предполагаем следующий результат:mycol anothercol 1 1 2 1но СУБД может агрегировать anothercol-столбец таким образом, что всегда используется значение первой строки, что приводит к следующему данные:
mycol anothercol 1 2 2 1который после заказа приведет к этому:
mycol anothercol 2 1 1 2это похоже на следующее:
SELECT mycol, First(anothercol) from mytable group by mycol order by anothercol;который является полностью обратным порядком, чем то, что вы ожидали.
вы видите, что план выполнения может варьироваться в зависимости от того, что является базовым поставщиком. Вот почему нет никакой гарантии об этом в документах.
Comments