Distinct () yöntemi, dizinin orijinal sırasını olduğu gibi tutar mı?


84

Listedeki benzersiz öğelerin sırasını değiştirmeden kopyaları listeden kaldırmak istiyorum.

Jon Skeet ve diğerleri aşağıdakileri kullanmayı önerdi:

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

Referans:

Benzersiz öğelerin sırasının öncekiyle aynı olacağı garanti ediliyor mu? Varsa, belgelerde hiçbir şey bulamadığım için lütfen bunu doğrulayan bir referans verin.


5
@ColonelPanic - resmi belge burada msdn.microsoft.com/en-us/library/bb348436(v=vs.110).aspx açıkça "Distinct () yöntemi yinelenen değerler içermeyen sırasız bir dizi döndürür" ifadesini belirtir .
Evk

@Evk 'Sırasız sıra', 'orijinal sıra sıralaması' ile aynı değildir.
Nitesh

3
"Sekanslanmamış" ifadesinin "belirli bir sıra yok" anlamına geldiğini düşünüyorum, bu da "orijinal sıra sırasında gerekli değil" anlamına gelir.
Evk

Oracle12 Entity Framework 6 ile ilgili bir sorun yaşadım. Benim durumumda, linq cümlesimde kaybolmadan önce orderby vardı ve sipariş gitmişti. select (). OrderBy (). Distinct (). ToList (), select (). OrderBy (). Distinct (). ToList () çalıştı.
Karl

2
@Karl, bu ifadeler aynı. :)
pvgoran

Yanıtlar:


77

Garantili değil ama en bariz uygulama. (Bunu, yakında geldiğince sonuç vermedi elinden kadar az okumak zorunda, öyle ki yani) bir akış şekilde uygulamak zor olacağını olmadan bunları sırayla dönüyor.

Distinct () 'in Edulinq uygulamasıyla ilgili blog yazımı okumak isteyebilirsiniz .

Bu LINQ to Objects için garanti edilmiş olsa bile (ki şahsen öyle olması gerektiğini düşünüyorum), LINQ to SQL gibi diğer LINQ sağlayıcıları için hiçbir şey ifade etmeyeceğini unutmayın.

LINQ to Objects içinde sağlanan garantilerin seviyesi bazen biraz tutarsızdır, IMO. Bazı optimizasyonlar belgelenir, bazıları değildir. Heck, bazı belgeler tamamen yanlış .


Kabul ediyorum çünkü 1) Garantili olup olmadığına dair endişelerimi net bir şekilde yanıtlıyor 2) Bağlantılı gönderi, Ayırt Edici'nin belgelenmemiş yönlerini daha derinlemesine araştırıyor 3) Bağlantılı gönderi ayrıca bir Ayırt etme Bu garantili listeler.
Nitesh

25

.NET Framework 3.5'te, Linq-to-Objects uygulamasının CIL'sinin sökülmesi, Distinct()öğelerin sırasının korunduğunu gösterir - ancak bu belgelenmiş bir davranış değildir.

Reflector ile küçük bir araştırma yaptım. System.Core.dll, Version = 3.5.0.0'ı söktükten sonra, Distinct () 'nin aşağıdaki gibi görünen bir uzantı yöntemi olduğunu görebilirsiniz:

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

İşte ilginç olan, IEnumerable ve IEnumerator'ü uygulayan DistinctIterator. İşte bu IEnumerator'ın basitleştirilmiş (git ve etiketleri kaldırıldı) uygulaması:

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

Gördüğünüz gibi - numaralandırma, kaynak numaralandırılabilir (çağırdığımız liste) tarafından sağlanan sırayla ilerler Distinct. Hashsetyalnızca böyle bir öğeyi zaten döndürüp döndürmediğimizi belirlemek için kullanılır. Değilse, onu iade ediyoruz, aksi takdirde - kaynakta listelemeye devam edin.

Böylelikle, Distinct'in uygulandığı koleksiyon tarafından sağlanan Distinct()öğeleri tam olarak aynı sırada döndüreceği garanti edilir.


8
İyi belgelenmiş bir davranış mı?
abatishchev

4
Bağlantılı yanıt, "Sonuç dizisi sıralanmamış" yazan belgelere bir referans içerir.
mgronber

5
@lazyberezovsky: Soru , ortak uygulamaları değil, garantileri soruyor . (Daha önce de söylediğim gibi, uygulamanın platformlar / sürümler arasında değişmesi durumunda şaşırırdım, ancak bu bir garanti sağlamaz.)
LukeH 19'11

5
@lazyberezovsky: Bir çok şeyin tanımsız olduğu ve bir şeyin garantili olup olmadığını sormanın çok yaygın olduğu C \ C ++ 'dan geliyorum. Ayrıca hem Mac hem de Windows'ta olan bir Silverlight uygulamasında Distinct () kullanıyorum, bu yüzden 'genel uygulamaya' karar veremiyoruz bunun garanti edilmesi gerekiyor.
Nitesh

43
@lazyberezovsky: İnsanlar garantilerden bahsettiklerinde, normalde güvenilmesi makul olan belgelenmiş davranışları kastediyorlar . Örneğin, GroupBy için dokümanlar yok davranışını belirtir, ancak Farklı için dokümanlar yok .
Jon Skeet


6

Evet , Enumerable.Distinct düzeni korur. Yöntemin tembel olduğunu varsayarsak, "farklı değerler görüldükleri anda ortaya çıkarlar", otomatik olarak izler. Bunu düşün.

.NET Referans kaynak doğruladı. Her eşdeğerlik sınıfındaki ilk öğe olan bir alt dizi döndürür.

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

.NET Çekirdek uygulama benzerdir.

Sinir bozucu bir şekilde, Enumerable.Distinct dokümantasyonu bu noktada karıştırılır:

Sonuç dizisi sıralanmamış.

Sadece "sonuç dizisi sıralanmamış" demek istediklerini hayal edebiliyorum. Sen olabilir önceki her eleman karşılaştırarak sonra ön düzenlemesine göre Farklı uygulamak, fakat yukarıda tanımlandığı gibi bu tembel olmaz.


7
Kaynak, spesifikasyon değildir. Bulduğunuz şey bir tesadüf ve bir sonraki güncellemeden sonra geçersiz olabilir.
Henk Holterman

@HenkHolterman Genel olarak kabul ediyorum, uygulamalar değişebilir. Örneğin, .NET 4.5 , Array.Sort'un arkasındaki sıralama algoritmasını değiştirdi . Bununla birlikte, bu özel durumda, Enumerable.Distinct'in herhangi bir mantıklı uygulaması kesinlikle tembel olacaktır ("farklı değerler, görüldükleri anda ortaya çıkar") ve bunu, sipariş koruma özelliği izler. Tembel değerlendirme, LINQ to Objects'in temel ilkelerinden biridir; onu iptal etmek düşünülemez.
Albay Panic

1
dbQuery.OrderBy(...).Distinct().ToList()Aramanın yüklem tarafından belirtilen sırada bir liste döndürmediği .net 4.6 kullanan uygulamalar gördüm - Distinct'i (gereksiz olan) kaldırmak benim durumumdaki hatayı düzeltti
Rowland Shaw

1

Varsayılan olarak, Distinct linq operatörü kullanıldığında Equals yöntemini kullanır, ancak IEqualityComparer<T>iki nesnenin özel bir mantık uygulaması GetHashCodeve Equalsyöntemiyle ne zaman eşit olduğunu belirtmek için kendi nesnenizi kullanabilirsiniz . Bunu hatırla:

GetHashCodeağır cpu karşılaştırması kullanılmamalıdır (örneğin, yalnızca bazı açık temel kontrolleri kullanın) ve iki nesnenin kesinlikle farklı (farklı karma kod döndürülürse) veya potansiyel olarak aynı (aynı karma kod) olup olmadığını belirtmek için ilk olarak kullanılmalıdır. Bu son durumda, iki nesne aynı hashcode'a sahip olduğunda, çerçeve, verilen nesnelerin eşitliği hakkında nihai bir karar olarak Equals yöntemini kullanarak kontrol etmeye başlayacaktır.

Siz MyTypeve bir MyTypeEqualityComparersınıf takip ettikten sonra , dizinin sırasını korumasını sağlamayın:

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

Follow sci kitaplığında, Vector3D setinin belirli bir uzantı yöntemi kullanırken sırayı korumasını sağlamak için bir uzantı yöntemi uyguladım DistinctKeepOrder:

ilgili kod şöyledir:

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

Kısacası Vector3DWithOrder, Vector3DWithOrderEqualityComparerorijinal tür karşılaştırıcısını kapsüllerken, türü ve bir sıra tamsayısını kapsülleyin.

ve bu, düzenin korunmasını sağlamak için yöntem yardımcısıdır

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

Not : Daha fazla araştırma, daha genel (arayüz kullanımları) ve optimize edilmiş bir yol (nesneyi kapsüllemeden) bulmaya izin verebilir.


1

Bu büyük ölçüde linq sağlayıcınıza bağlıdır. Linq2Objects üzerinde dahili kaynak kodunda kalabilirsiniz Distinct, bu da orijinal sıranın korunduğunu varsayar.

Bununla birlikte, örneğin bir tür SQL'e çözümlenen diğer sağlayıcılar için, bu ORDER BYzorunlu değildir , çünkü bir- ifadesi genellikle herhangi bir toplamadan sonra gelir (örneğin Distinct). Yani kodunuz bu ise:

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

bu, SQL'de aşağıdakine benzer bir şeye çevrilir:

SELECT * FROM mytable GROUP BY mycol ORDER BY anothercol;

Bu tabii ki önce verilerinizi gruplar ve daha sonra sıralar. Şimdi, DBMS'yi nasıl çalıştıracağınıza dair kendi mantığına takılı kaldınız. Bazı DBMS'de buna izin bile verilmez. Aşağıdaki verileri hayal edin:

mycol anothercol
1     2
1     1
1     3
2     1
2     3

yürütürken myArr.OrderBy(x => x.anothercol).GroupBy(x => x.mycol)aşağıdaki sonucu varsayıyoruz:

mycol anothercol
1     1
2     1

Ancak DBMS, başka bir sütun sütununu toplayabilir, böylece her zaman ilk satırın değeri kullanılır ve aşağıdaki veriler elde edilir:

mycol anothercol
1    2
2    1

sipariş verdikten sonra bununla sonuçlanacaktır:

mycol anothercol
2    1
1    2

Bu, aşağıdakine benzer:

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

bu beklediğinizden tamamen ters bir sıradır.

Yürütme planının, temel sağlayıcıya bağlı olarak değişebileceğini görüyorsunuz. Bu nedenle belgelerde bununla ilgili hiçbir garanti yok.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.