web-dev-qa-db-ja.com

C#Distinct()メソッドはシーケンスの元の順序をそのまま維持しますか?

リスト内の一意の要素の順序を変更せずに、リストから重複を削除したい。

Jon Skeet&他は以下を使用することを提案しています

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

リストからの重複の削除C#

C#のList <T>から重複を削除

一意の要素の順序が以前と同じであることは保証されていますか? 「はい」の場合、ドキュメントでは何も見つからなかったため、これを確認するためのリファレンスを提供してください。

78
Nitesh

保証はありませんが、最も明白な実装です。ストリーミング方式で実装するのは困難です(つまり、可能な限り早く結果を返し、可能な限り読み取りを行わない)withoutそれらを順番に返す。

EtinlinqのDistinct()の実装 に関する私のブログ投稿を読むことをお勧めします。

これがLINQ to Objects(個人的にはである必要があると考えているであると保証されている)場合でも、次のような他のLINQプロバイダーには何の意味もないことに注意してくださいLINQ to SQL。

LINQ to Objects内で提供される保証のレベルは、IMOのように、少し矛盾することがあります。ドキュメント化されている最適化もあれば、そうでないものもあります。確かに、一部のドキュメントは完全にwrongです。

63
Jon Skeet

はい、元のリストで最初に出現した順に。 .Net Framework 3.5の場合保証

リフレクターで少し調べました。 System.Core.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);
    }
}

したがって、ここで興味深いのは、IEnumerableとIEnumeratorを実装するDistinctIteratorです。このIEnumeratorの実装が簡略化されています(gotoとlablesが削除されています)。

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を呼び出します)によって提供される順序で行われます。ハッシュセットは、そのような要素がすでに返されたかどうかを判断するためだけに使用されます。そうでない場合は、それを返します。そうでない場合は、ソースで列挙を続けます。

したがって、Distinct()が要素を返すことが保証されています正確に同じ順序で。これは、Distinctが適用されたコレクションによって提供されます。

26

documentation によると、シーケンスは順序付けされていません。

12
mgronber

はい、Enumerable.Distinctは順序を保持します。メソッドが「明確な値が得られるとすぐに得られる」のが遅延であると仮定すると、自動的にそれに従います。それについて考えてください。

。NET参照ソース は確認します。各等価クラスの最初の要素であるサブシーケンスを返します。

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

。NET Core実装 も同様です。

苛立たしいことに、 Enumerable.Distinct のドキュメントはこの点で混乱しています:

結果のシーケンスは順不同です。

「結果のシーケンスはソートされていない」という意味だと想像できます。 could事前にソートしてから各要素を前の要素と比較することによりDistinctを実装しますが、これは上で定義したように遅延ではありません。

5
Colonel Panic

デフォルトでは、Distinct linq演算子はEqualsメソッドを使用しますが、独自のIEqualityComparer<T>オブジェクトを使用して、GetHashCodeおよびEqualsメソッドを実装するカスタムロジックで2つのオブジェクトが等しい場合を指定できます。覚えておいてください:

GetHashCodeは重いCPU比較を使用してはならず(たとえば、いくつかの明らかな基本チェックのみを使用)、2つのオブジェクトが確かに異なる(異なるハッシュコードが返される)か、または同じ(潜在的に同じ)かを最初に示すコード)。この最新のケースでは、2つのオブジェクトが同じハッシュコードを持っている場合、フレームワークは、指定されたオブジェクトの等価性に関する最終決定としてEqualsメソッドを使用してチェックするようになります。

MyTypeMyTypeEqualityComparerのクラスを実行した後、コードがシーケンスを維持していることを確認しないでください。

var cmp = new MyTypeEqualityComparer();
var lst = new List<MyType>();
// add some to lst
var q = lst.Distinct(cmp);

以下 sciライブラリ 特定の拡張メソッドDistinctKeepOrderを使用するときにVector3Dセットが順序を維持するように拡張メソッドを実装しました:

関連するコードは次のとおりです。

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

:さらなる調査により、より一般的な(インターフェースの使用)および最適化された方法(オブジェクトをカプセル化することなく)を見つけることができます。

1
Lorenzo Delana

これはlinqプロバイダーに大きく依存します。 Linq2Objectsでは、Distinctの内部ソースコードをそのまま使用できます。これにより、元の順序が保持されていると見なされます。

ただし、たとえば、ある種のSQLに解決する他のプロバイダーの場合、_ORDER BY_- statementは通常、集計(Distinctなど)の後に来るため、必ずしもそうであるとは限りません。したがって、コードが次の場合:

_myArray.OrderBy(x => anothercol).GroupBy(x => y.mycol);
_

これは、SQLでは次のようなものに変換されます。

_SELECT * FROM mytable GROUP BY mycol ORDER BY anothercol;
_

これは明らかに最初にデータをグループ化し、後でソートします。これで、DBMS独自のロジックの実行方法に行き詰まりました。一部のDBMSでは、これは許可されていません。次のデータを想像してください。

_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
_

ただし、DBMSはanothercol-columnを集約する場合があるため、常に最初の行の値が使用され、次のデータが生成されます。

_mycol anothercol
1    2
2    1
_

注文後は次のようになります:

_mycol anothercol
2    1
1    2
_

これは次のようになります。

_SELECT mycol, First(anothercol) from mytable group by mycol order by anothercol;
_

これは、予想とはまったく逆の順序です。

実行プランは、基になるプロバイダーが何であるかによって異なる場合があります。これが、ドキュメントにそれについての保証がない理由です。

0
HimBromBeere