Метод С# Distinct() сохраняет исходный порядок последовательности неповрежденным?

Я хочу удалить дубликаты из списка, не изменяя порядок уникальных элементов в списке.

Джон Скит и другие предложили использовать следующие

list = list.Distinct().ToList();

удаление дубликатов из списка С#

Удалить дубликаты из списка <T> в С#

Гарантировано ли, что порядок уникальных элементов будет таким же, как раньше? Если да, просьба дать ссылку, подтверждающую это, поскольку я не мог найти что-либо в нем в документации.

Ответ 1

Это не гарантировано, но это самая очевидная реализация. Было бы трудно реализовать потоковым способом (то есть, чтобы он возвращал результаты, как только мог, прочитав как можно меньше), не возвращая их по порядку.

Возможно, вы захотите прочитать мой пост в блоге Edulinq для реализации Distinct().

Обратите внимание, что даже если это гарантировано для LINQ to Objects (что лично я думаю, что это должно быть), это ничего не значит для других поставщиков LINQ, таких как LINQ to SQL.

Уровень гарантий, предоставляемых в LINQ для объектов, иногда немного непоследователен, IMO. Некоторые оптимизации документированы, другие - нет. Хек, некоторые из документации не соответствуют действительности.

Ответ 2

Да, в порядке первого появления в исходном списке. Он гарантирован для .Net Framework 3.5

Я сделал небольшое исследование с Reflector. После дизассемблирования System.Core.dll, версии = 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. Здесь упрощается (с удалением и удалением) реализация этого 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; }
    }
}

Как вы можете видеть - перечисление идет в порядке, предоставляемом источником, перечислимым (список, по которому мы вызываем Distinct). Hashset используется только для определения того, мы уже вернули такой элемент или нет. Если нет, мы возвращаем его, иначе - продолжайте перечисление по источнику.

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

Ответ 4

Да, Enumerable.Distinct сохраняет порядок. Предполагая, что метод ленив, "дает определенные значения, как только они видны", это следует автоматически. Подумайте об этом.

. Исходный источник .NET подтверждает. Он возвращает подпоследовательность, первый элемент в каждом классе эквивалентности.

foreach (TSource element in source)
    if (set.Add(element)) yield return element;

. Реализация NET Core похожа.

Разочарочно, документация для Enumerable.Distinct запуталась в этой точке:

Последовательность результатов неупорядочена.

Я могу только представить себе, что они означают "последовательность результатов не сортируется". Вы могли бы реализовать Distinct, предварительно предваряя сравнение каждого элемента с предыдущим, но это не было бы ленивым, как определено выше.

Ответ 5

По умолчанию при использовании 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);

Следуйте библиотека 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);
}

Примечание: дальнейшее исследование может позволить найти более общий (использование интерфейсов) и оптимизированный способ (без инкапсуляции объекта).