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


82

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

JonSkeetなどが以下の使用を提案しています

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

リストから重複を削除するC#

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

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


5
@ ColonelPanic-公式ドキュメントはこちらmsdn.microsoft.com/en-us/library/bb348436(v=vs.110).aspxは、「Distinct()メソッドは重複する値を含まない順序付けられていないシーケンスを返す」と明示的に述べています。
evk 2017年

@Evk '順序付けされていないシーケンス'は 'シーケンスの元の順序付け'と同じではありません。
Nitesh 2017年

3
「未編集」とは「順不同」を意味し、「元の順序では不要」という意味でもあります。
evk 2017年

oracle12 Entity Framework 6との区別に関して問題が発生しました。私の場合、linq句でdisinctする前にorderbyがあり、注文がなくなりました。select()。OrderBy()。Distinct()。ToList()が機能している間、select()。OrderBy()。Distinct()。ToList()は機能しませんでした。
カール

2
@Karl、これらの式は同じです。:)
pvgoran 2018

回答:


75

保証はされていませんが、最も明白な実装です。ストリーミング方式で(つまり、結果をできるだけ早く返し、読み取りをできるだけ少なくして)、順番に返すことなく実装するのは困難です。

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

これがLINQto Objects(個人的にはそうあるべきだと思います)で保証されていたとしても、LINQ toSQLなどの他のLINQプロバイダーにとっては何の意味もないことに注意してください。

LINQ to Objects内で提供される保証のレベルは、IMOによって少し一貫性がない場合があります。一部の最適化は文書化されていますが、そうでないものもあります。一体、ドキュメントのいくつかは完全に間違っます。


私はそれを受け入れています。1)保証されているかどうかという私の懸念に明確に答えている2)リンクされた投稿はDistinctの文書化されていない側面を深く掘り下げている3)リンクされた投稿にはDistinctを実装するための参照として使用できるサンプル実装もありますその保証付きのリスト。
nitesh 2011年

26

.NET Framework 3.5では、のLinq-to-Objects実装のCILを分解Distinct()すると、要素の順序が保持されていることが示されますが、これは文書化された動作ではありません。

私はReflectorで少し調査しました。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)によって提供される順序で行われます。Hashsetそのような要素をすでに返したかどうかを判断するためにのみ使用されます。そうでない場合は、それを返します。そうでない場合は、ソースで列挙を続けます。

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


8
それは十分に文書化された動作ですか?
abatishchev 2011年

4
リンクされた回答には、「結果シーケンスは順序付けられていません」というドキュメントへの参照が含まれています。
mgronber 2011年

4
@lazyberezovsky:質問は、一般的な実装ではなく、保証について尋ねます。(すでに述べたように、実装がプラットフォーム/バージョン間で変更された場合は驚きますが、それは保証にはなりません。)
LukeH 2011年

5
@lazyberezovsky:私はC \ C ++の出身で、多くのことが未定義であり、何かが保証されているかどうかを尋ねるのは非常に一般的です。また、MacとWindowsの両方にあるSilverlightアプリケーションでDistinct()を使用しているため、「一般的な実装」を保証する必要があります。
nitesh 2011年

42
@lazyberezovsky:人々が保証について話すとき、彼らは通常、信頼するのに合理的な文書化された行動を意味します。たとえば、GroupByのドキュメントは動作を指定ますが、Distinctのドキュメント指定しません
Jon Skeet

13

ドキュメントによると、シーケンスは順序付けられていません。


2
それを見つけるための追加情報:リンクで、「備考」セクションを参照してください。「結果シーケンスは順序付けられていません。」
Curtis Yallop 2014年

6

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

.NETリファレンスソースを確認します。各等価クラスの最初の要素であるサブシーケンスを返します。

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

.NETのコアの実装は似ています。

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

結果のシーケンスは順序付けられていません。

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


6
ソースは仕様ではありません。あなたが見つけたのは偶然であり、次の更新後に無効になる可能性があります。
ヘンクホルターマン2017年

@HenkHolterman一般的に、実装は変更される可能性があることに同意します。たとえば、.NET 4.5は、 Array.Sortの背後にある並べ替えアルゴリズムを変更しました。ただし、この特定のケースでは、Enumerable.Distinctの適切な実装は確実に怠惰になり(「異なる値が表示されるとすぐに生成されます」)、順序保持プロパティがそれに続きます。遅延評価は、LINQ toObjectsの中心的な信条です。取り消すことは考えられないでしょう。
パニック大佐2017年

1
.net 4.6を使用した実装では、呼び出しdbQuery.OrderBy(...).Distinct().ToList()が述語による順序で指定された順序でリストを返さないのを見てきました-Distinct(たまたま冗長でした)を削除すると、私の場合のバグが修正されました
Rowland Shaw

1

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

GetHashCode重いCPU比較を使用しないでください(たとえば、いくつかの明白な基本チェックのみを使用してください)。2つのオブジェクトが確かに異なるか(異なるハッシュコードが返される場合)、または潜在的に同じか(同じハッシュコード)を示すために最初に使用されます。この最新のケースでは、2つのオブジェクトが同じハッシュコードを持っている場合、フレームワークは、指定されたオブジェクトの同等性に関する最終決定として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);
}

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


1

これは、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;

これは明らかに最初にデータをグループ化し、後でソートします。今、あなたはそれを実行する方法の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;

これは、予想とは完全に逆の順序です。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.