リスト内の一意の要素の順序を変更せずに、リストから重複を削除したい。
JonSkeetなどが以下の使用を提案しています
list = list.Distinct().ToList();
一意の要素の順序が以前と同じになることは保証されていますか?はいの場合、ドキュメントに何も見つからなかったので、これを確認するリファレンスを提供してください。
リスト内の一意の要素の順序を変更せずに、リストから重複を削除したい。
JonSkeetなどが以下の使用を提案しています
list = list.Distinct().ToList();
一意の要素の順序が以前と同じになることは保証されていますか?はいの場合、ドキュメントに何も見つからなかったので、これを確認するリファレンスを提供してください。
回答:
保証はされていませんが、最も明白な実装です。ストリーミング方式で(つまり、結果をできるだけ早く返し、読み取りをできるだけ少なくして)、順番に返すことなく実装するのは困難です。
Distinct()のEdulinq実装に関する私のブログ投稿を読むことをお勧めします。
これがLINQto Objects(個人的にはそうあるべきだと思います)で保証されていたとしても、LINQ toSQLなどの他のLINQプロバイダーにとっては何の意味もないことに注意してください。
LINQ to Objects内で提供される保証のレベルは、IMOによって少し一貫性がない場合があります。一部の最適化は文書化されていますが、そうでないものもあります。一体、ドキュメントのいくつかは完全に間違っています。
.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()
要素をまったく同じ順序で返すことが保証されています。
ドキュメントによると、シーケンスは順序付けられていません。
はい、Enumerable.Distinctは順序を保持します。メソッドが怠惰であると仮定すると、「明確な値が見られるとすぐに得られます」、それは自動的に続きます。考えてみてください。
.NETリファレンスソースを確認します。各等価クラスの最初の要素であるサブシーケンスを返します。
foreach (TSource element in source)
if (set.Add(element)) yield return element;
.NETのコアの実装は似ています。
苛立たしいことに、Enumerable.Distinctのドキュメントはこの点で混乱しています。
結果のシーケンスは順序付けられていません。
「結果シーケンスがソートされていない」という意味だと想像できます。事前に並べ替えてから各要素を前の要素と比較することでDistinctを実装できますが、これは上記で定義したように怠惰ではありません。
dbQuery.OrderBy(...).Distinct().ToList()
が述語による順序で指定された順序でリストを返さないのを見てきました-Distinct(たまたま冗長でした)を削除すると、私の場合のバグが修正されました
デフォルトでは、Distinct linq演算子はEqualsメソッドを使用しますが、独自のIEqualityComparer<T>
オブジェクトを使用して、カスタムロジックの実装GetHashCode
とEquals
メソッドで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);
}
注:さらなる調査により、より一般的(インターフェースの使用)および最適化された方法(オブジェクトをカプセル化せずに)を見つけることができる可能性があります。
これは、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;
これは、予想とは完全に逆の順序です。
実行プランは、基盤となるプロバイダーが何であるかによって異なる場合があります。これが、ドキュメントでそれについての保証がない理由です。