特定のプロパティに対するLINQのDistinct()


1095

私はそれについて学ぶためにLINQで遊んでDistinctいますが、単純なリストがない場合の使用方法がわかりません(整数の単純なリストは非常に簡単です。これは問題ではありません)。オブジェクトの1以上のプロパティのオブジェクトのリストでDistinctを使用したい場合はどうすればよいですか?

例:オブジェクトがの場合、PersonPropertyを使用しIdます。どのようにすればすべてのPersonを取得し、オブジェクトのDistinctプロパティIdでそれらを使用できますか?

Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"

どのように私は得ることができますPerson1Person3?それは可能ですか?

LINQでそれができない場合Person、.NET 3.5の一部のプロパティに応じてリストを作成する最良の方法は何ですか?

回答:


1247

編集:これは、MoreLINQの一部になりました

必要なのは、効果的に「個別」です。書くのはかなり簡単ですが、それが現状ではLINQの一部であるとは思いません。

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> seenKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

したがって、Idプロパティのみを使用して個別の値を見つけるには、次のように使用できます。

var query = people.DistinctBy(p => p.Id);

そして、複数のプロパティを使用するために、等価性を適切に実装する匿名型を使用できます。

var query = people.DistinctBy(p => new { p.Id, p.Name });

テストされていませんが、動作するはずです(少なくとも現在はコンパイルされています)。

ただし、キーのデフォルトの比較子を想定しています。等価比較子を渡したい場合は、HashSetコンストラクタに渡してください。



1
@ ashes999:どういう意味かわかりません。コードは回答ライブラリに含まれています-依存関係を喜んで受け入れるかどうかによって異なります。
Jon Skeet

10
@ ashes999:これを1か所でのみ行う場合は、確かに、使用する方GroupByが簡単です。複数の場所で必要な場合は、意図をカプセル化するほうがはるかにクリーン(IMO)です。
Jon Skeet

5
@MatthewWhited:IQueryable<T>ここでの言及がないことを考えると、それがどのように関連しているかはわかりません。これはEFなどには適していないことに同意しますが、LINQ to Objects内では、より適切だと思いますGroupBy。質問のコンテキストは常に重要です。
Jon Skeet 2017年

7
プロジェクトはgithubに移動しました。DistinctByの
Phate01

1858

1以上のプロパティに基づいて個別のリストを取得したい場合はどうなりますか?

シンプル!それらをグループ化し、グループから勝者を選びます。

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();

複数のプロパティでグループを定義する場合は、次のようになります。

List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.First())
  .ToList();

1
@ErenErsonmez確かに。投稿したコードで、遅延実行が必要な場合は、ToList呼び出しを省略します。
エイミーB

5
とてもいい答えです!Reallllllyは、ビューを変更できないSQLビューから駆動されるLinq-to-Entitiesで私を助けました。First()ではなくFirstOrDefault()を使用する必要がありました。すべて問題ありません。
Alex KeySmith、2012年

8
私は試してみましたが、Select(g => g.FirstOrDefault())に変更されるはずです

26
@ChocapicSzいいえ。どちらSingle()SingleOrDefault()ソースが複数の項目があり、それぞれスロー。この操作では、各グループに複数のアイテムが含まれる可能性があると予想されます。さらに言えば、各グループには少なくとも1つのメンバーが必要なので、First()が優先されFirstOrDefault()ますFirstOrDefault()
Amy B

2
FirstOrDefault() github.com/dotnet/efcore/issues/12088を使用していても、EF Coreでは現在サポートされていないようです。3.1を使用していて、「翻訳できません」というエラーが表示されます。
コリンMバレット

78

使用する:

List<Person> pList = new List<Person>();
/* Fill list */

var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());

whereは、エントリ(より複雑になる可能性があります)とをフィルタリングし、個別の機能groupbyselect実行するのに役立ちます。


1
完璧で、Linqを拡張したり、別の依存関係を使用したりすることなく機能します。
DavidScherer

77

すべてのLINQのように表示する場合は、クエリ構文を使用することもできます。

var uniquePeople = from p in people
                   group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}
                   into mygroup
                   select mygroup.FirstOrDefault();

4
うーん私の考えは、クエリ構文と流暢なAPI構文の両方が、お互いと同じようにLINQであり、人々が使用するものよりも好みです。私自身も流暢なAPIを好むので、もっとリンクのように考えますが、それは主観的なものだと思います
Max Carroll

LINQライクは設定とは何の関係もありません。 "LINQライク"とは、C#に埋め込まれている別のクエリ言語のように見えることに関係しています。Javaストリームからの流れるようなインターフェイスを好みますが、それはLINQライクではありません。
Ryan The Leach

優秀な!!あなたは私のヒーローです!
Farzin Kanzi

63

私はそれで十分だと思います:

list.Select(s => s.MyField).Distinct();

43
特定のフィールドだけでなく、オブジェクト全体を戻す必要がある場合はどうなりますか?
フェスティムカハニ2015

1
同じプロパティ値を持ついくつかのオブジェクトのうち、正確には何ですか?
donRumatta

40

ソリューションはまずフィールドでグループ化し、次にfirstordefaultアイテムを選択します。

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();

26

これは標準で行うことができますLinq.ToLookup()。これにより、一意のキーごとに値のコレクションが作成されます。コレクションの最初のアイテムを選択するだけです

Persons.ToLookup(p => p.Id).Select(coll => coll.First());

17

次のコードはJon Skeetの回答と機能的に同等です。

.NET 4.5でテストされ、以前のバージョンのLINQで動作するはずです。

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
  this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
  HashSet<TKey> seenKeys = new HashSet<TKey>();
  return source.Where(element => seenKeys.Add(keySelector(element)));
}

偶然にも、Google CodeでJon SkeetのDistinctBy.csの最新バージョンをチェックしてください。


3
これは「シーケンスに値エラーはありません」を与えましたが、スキートの答えは正しい結果をもたらしました。
クールなものは

10

Distinct関数を拡張して次のようにする方法を説明する記事を書きました。

var people = new List<Person>();

people.Add(new Person(1, "a", "b"));
people.Add(new Person(2, "c", "d"));
people.Add(new Person(1, "a", "b"));

foreach (var person in people.Distinct(p => p.ID))
    // Do stuff with unique list here.

これが記事です。LINQの拡張-Distinct関数でのプロパティの指定


3
記事にエラーがあります。Distinctの後に<T>があるはずです:public static IEnumerable <T> Distinct(this ...また、1つ以上のプロパティ(つまり、および姓
。– row1

2
+1、マイナーなエラーは反対票を投じる十分な理由ではありません。そのため、ばかげて、タイプミスを頻繁に呼びました。そして、私はまだ、任意の数のプロパティで機能する一般的な関数を見ていません!私も、反対投票者がこのスレッドで他のすべての回答に反対投票したことを願っています。しかし、ねえ、この2番目のタイプはオブジェクトですか?私は反対します!
nawfal

4
リンクが壊れています
Tom Lint、

7

個人的に私は次のクラスを使用します:

public class LambdaEqualityComparer<TSource, TDest> : 
    IEqualityComparer<TSource>
{
    private Func<TSource, TDest> _selector;

    public LambdaEqualityComparer(Func<TSource, TDest> selector)
    {
        _selector = selector;
    }

    public bool Equals(TSource obj, TSource other)
    {
        return _selector(obj).Equals(_selector(other));
    }

    public int GetHashCode(TSource obj)
    {
        return _selector(obj).GetHashCode();
    }
}

次に、拡張メソッド:

public static IEnumerable<TSource> Distinct<TSource, TCompare>(
    this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
    return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}

最後に、意図した使用法:

var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);

このアプローチを使用して見つけた利点は、LambdaEqualityComparerを受け入れる他のメソッドでクラスを再利用することIEqualityComparerです。(ああ、私はyieldものを元のLINQ実装に任せます...)


5

複数のプロパティでDistinctメソッドが必要な場合は、私のPowerfulExtensionsライブラリをチェックしてください。現在は非常に若い段階にありますが、すでに多数のプロパティでDistinct、Union、Intersect、Exceptなどのメソッドを使用できます。

これはあなたがそれを使う方法です:

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);

5

プロジェクトでこのようなタスクに直面したとき、コンパレーターを構成する小さなAPIを定義しました。

したがって、ユースケースは次のようになります。

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);

API自体は次のようになります。

using System;
using System.Collections;
using System.Collections.Generic;

public static class KeyEqualityComparer
{
    public static IEqualityComparer<T> Null<T>()
    {
        return null;
    }

    public static IEqualityComparer<T> EqualityComparerBy<T, K>(
        this IEnumerable<T> source,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc);
    }

    public static KeyEqualityComparer<T, K> ThenBy<T, K>(
        this IEqualityComparer<T> equalityComparer,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc, equalityComparer);
    }
}

public struct KeyEqualityComparer<T, K>: IEqualityComparer<T>
{
    public KeyEqualityComparer(
        Func<T, K> keyFunc,
        IEqualityComparer<T> equalityComparer = null)
    {
        KeyFunc = keyFunc;
        EqualityComparer = equalityComparer;
    }

    public bool Equals(T x, T y)
    {
        return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) &&
                EqualityComparer<K>.Default.Equals(KeyFunc(x), KeyFunc(y));
    }

    public int GetHashCode(T obj)
    {
        var hash = EqualityComparer<K>.Default.GetHashCode(KeyFunc(obj));

        if (EqualityComparer != null)
        {
            var hash2 = EqualityComparer.GetHashCode(obj);

            hash ^= (hash2 << 5) + hash2;
        }

        return hash;
    }

    public readonly Func<T, K> KeyFunc;
    public readonly IEqualityComparer<T> EqualityComparer;
}

詳細については、LINQのIEqualityComparerのサイトをご覧ください。


5

DistinctBy()を使用して、オブジェクトプロパティでDistinctレコードを取得できます。使用する前に、次のステートメントを追加してください。

Microsoft.Ajax.Utilitiesを使用します。

そしてそれを次のように使用します:

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

ここで、「インデックス」は、データを区別したいプロパティです。


4

あなたはそれを次のように行うことができます(すぐにではありませんが):

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

つまり、「リストに同じIDの別の人がいないすべての人を選択します。」

ちなみに、あなたの例では、それは人3を選択するだけです。前の2つのうち、どちらを望むかをどのように伝えるかわかりません。


4

DistinctBy機能を取得するためだけにMoreLinqライブラリをプロジェクトに追加したくない場合は、引数を受け取るLinqのDistinctメソッドのオーバーロードを使用して同じ最終結果を取得できIEqualityComparerます。

まず、ラムダ構文を使用してジェネリッククラスの2つのインスタンスのカスタム比較を実行するジェネリックカスタム等価比較クラスを作成します。

public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> _comparison;
    Func<T, int> _hashCodeFactory;

    public CustomEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCodeFactory)
    {
        _comparison = comparison;
        _hashCodeFactory = hashCodeFactory;
    }

    public bool Equals(T x, T y)
    {
        return _comparison(x, y);
    }

    public int GetHashCode(T obj)
    {
        return _hashCodeFactory(obj);
    }
}

次に、メインコードで次のように使用します。

Func<Person, Person, bool> areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id);

Func<Person, int> getHashCode = (p) => p.Id.GetHashCode();

var query = people.Distinct(new CustomEqualityComparer<Person>(areEqual, getHashCode));

出来上がり!:)

上記は次のことを前提としています。

  • プロパティPerson.Idはタイプですint
  • peopleコレクションは、任意のヌル要素が含まれていません

コレクションにnullが含まれる可能性がある場合は、ラムダを書き直してnullをチェックします。例:

Func<Person, Person, bool> areEqual = (p1, p2) => 
{
    return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};

編集する

このアプローチは、ウラジミールネステロフスキーの回答に似ていますが、より単純です。

また、Joelの回答と似ていますが、複数のプロパティを含む複雑な比較ロジックが可能です。

あなたのオブジェクトがしかによって異なることができればしかし、Idその後、別のユーザーは、あなたがする必要があるすべては、デフォルトの実装でオーバーライドしていること、正しい答えを与えたGetHashCode()し、Equals()自分の中にPersonクラスをして、ちょうどすぐに使うDistinct()フィルターにLINQの方法を重複を除外します。


辞書にある一意のアイテムのみを取得したいので、助けてください、このコードを使用しています。 y.SafeField(fldParamValue11、NULL_ID_VALUE))
RSB

2

他の.NETバージョンと互換性のあるこれを行うための最良の方法は、EqualsとGetHashをオーバーライドしてこれを処理することです(スタックオーバーフローの質問を参照してくださいこのコードは異なる値を返します。ただし、強く型付けされたコレクションを返すのではなく、匿名型)ですが、コード全体で汎用的なものが必要な場合は、この記事のソリューションが最適です。


1
List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();

あなたはにもしかしてSelect() new Person代わりにnew Player?ただし、順序付けを行っているという事実は、一意性の判断にそのプロパティを使用するようにID通知Distinct()するわけではないため、これは機能しません。
BACON

1

Equals(object obj)およびGetHashCode()メソッドをオーバーライドします。

class Person
{
    public int Id { get; set; }
    public int Name { get; set; }

    public override bool Equals(object obj)
    {
        return ((Person)obj).Id == Id;
        // or: 
        // var o = (Person)obj;
        // return o.Id == Id && o.Name == Name;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

次に呼び出すだけです:

List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();

ただし、GetHashCode()は(名前もカウントするために)より高度なものにする必要があります。この答えはおそらく私の意見では最良です。実際、ターゲットロジックをアーカイブするには、GetHashCode()をオーバーライドする必要はありません。Equals()で十分ですが、パフォーマンスが必要な場合はオーバーライドする必要があります。すべての比較アルゴリズム、最初にハッシュをチェックし、それらが等しい場合はEquals()を呼び出します。
Oleg Skripnyak 2018年

また、Equals()の最初の行は「if(!(obj is Person))return false」である必要があります。しかし、ベストプラクティスは、「var o = obj as Person; if(o == null)return false;」のように、型にキャストされた個別のオブジェクトを使用することです。次に、キャストせずにoと等しいかどうかを確認します
Oleg Skripnyak '28年

1
このようにEqualsをオーバーライドすることは、Person's Equalityが複数のプロパティで決定されることを期待する他のプログラマーに意図しない結果をもたらす可能性があるため、良い考えではありません。
B2K

0

Person.idでEqualsを実際に実行するには、Equals on personをオーバーライドできる必要があります。これはあなたが望む行動をもたらすはずです。


-5

以下のコードで試してみてください。

var Item = GetAll().GroupBy(x => x .Id).ToList();

3
簡単な回答を歓迎しますが、問題の背後で何が起こっているのかを理解しようとしている後者のユーザーには、あまり価値はありません。問題を引き起こす本当の問題とその解決方法を説明するために、少し時間を割いてください。ありがとう〜
Hearen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.