LINQでそれを学ぶために遊んでいますが、単純なリストがない場合にDistinctを使用する方法を理解することはできません(単純な整数のリストを作成するのは簡単です。これは問題ではありません)。 one または more オブジェクトのプロパティ上のオブジェクトのリストに Distinct を使用したい場合はどうすればよいですか。
例:オブジェクトがPerson
で、プロパティがId
の場合。どのようにしてすべてのPersonを取得し、オブジェクトのプロパティDistinct
を使用してそれらにId
を使用することができますか?
Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"
Person1とPerson3だけを取得するにはどうすればいいですか?それは可能ですか?
それがLINQで不可能であるならば、.NET 3.5のその特性のいくつかに従ってPerson
のリストを持つための最良の方法は何でしょうか?
_ edit _ :これは MoreLINQ の一部になりました。
あなたが必要としているのは、効果的に "distinct-by"です。それは現時点では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
コンストラクタに渡してください。
one または more プロパティに基づいて個別のリストを取得する場合はどうすればよいですか。
シンプル!あなたはそれらをグループ化し、そのグループから勝者を選びたいのです。
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();
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();
つかいます:
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
はエントリをフィルタリングするのに役立ち(より複雑かもしれません)、groupby
とselect
は異なる機能を実行します。
私はそれで十分だと思います:
list.Select(s => s.MyField).Distinct();
最初のフィールドでソリューションをグループ化してから、firstまたはdefault itemを選択します。
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.FirstOrDefault())
.ToList();
標準の Linq.ToLookup()
でこれを行うことができます。これにより、一意のキーごとに値のコレクションが作成されます。コレクションの最初の項目を選択するだけです
Persons.ToLookup(p => p.Id).Select(coll => coll.First());
次のコードは、 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)));
}
偶然にも、 Jon Skeetの最新バージョンのDistinctBy.csをGoogle Codeでチェックしてください 。
私はあなたが以下のようにすることができるように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を拡張する - 特殊な関数でプロパティを指定する
あなたはそれをすることができます(すぐに落雷ではないにしても):
people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));
つまり、「同じIDを持つ別の人がリストに含まれていないすべての人を選択する」ということです。
あなたの例では、それはちょうど人3を選択することになることを念頭に置いてください。
複数のプロパティに対してDistinctメソッドが必要な場合は、my PowerfulExtensions libraryを調べてください。現在はまだ非常に若い段階にありますが、既にDistinct、Union、Intersectなどのメソッドをいくつでも使用できます。
これはあなたがそれをどのように使うかです:
using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
私は個人的には次のクラスを使います。
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実装に任せています...)
私たちのプロジェクトでそのようなタスクに直面したとき、コンパレーターを構成するための小さな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。
DistinctBy
機能を取得するためだけにMoreLinqライブラリをプロジェクトに追加したくない場合は、Distinct
引数を取るLinqの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が含まれる可能性がある場合は、単にnullをチェックするようにラムダを書き換えます。
Func<Person, Person, bool> areEqual = (p1, p2) =>
{
return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};
_編集_
このアプローチは、Vladimir Nesterovskyの答えにあるものと似ていますが、より単純です。
これもJoelの答えにあるものと似ていますが、複数のプロパティを含む複雑な比較ロジックを可能にします。
しかし、あなたのオブジェクトがId
だけ異なることができるのであれば、あなたのPerson
クラスのGetHashCode()
とEquals()
のデフォルト実装をオーバーライドして、そのまま使用できるDistinct()
を使用することが他のユーザーから正しい答えを得ました。重複を除外するLinqの方法。
List<Person>lst=new List<Person>
var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();
オーバーライド 等しい(object obj) および GetHashCode() methods:
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();
他の.NETバージョンと互換性があるようにするには、EqualsとGetHashをオーバーライドしてこれを処理するのが最善の方法です(Stack Overflow questionを参照)。このコードでは異なる値を返します。匿名型とは対照的な型付きコレクション)ですが、コード全体を通して一般的なものが必要な場合は、この記事の解決策が最適です。
DistinctBy()を使用すると、オブジェクトプロパティによって個別のレコードを取得できます。使用する前に次の文を追加するだけです。
microsoft.Ajax.Utilitiesを使用する。
そしてそれを次のように使います。
var listToReturn = responseList.DistinctBy(x => x.Index).ToList();
ここで 'インデックス'は私がデータを区別したいプロパティです。
実際にEquals on Person.idを実行するには、PersonのEqualsをオーバーライドする必要があります。これはあなたが期待している振る舞いをもたらすはずです。
List<string> colors = new List<string> { "blue", "red", "black", "blue", "yellow", "blue" };
IEnumerable<string> distinctColors = colors.Distinct();