C # Sırala ve Sırala Karşılaştırmaya göre


105

Sort veya OrderBy kullanarak bir listeyi sıralayabilirim. Hangisi daha hızlı? İkisi de aynı algoritma üzerinde mi çalışıyor?

List<Person> persons = new List<Person>();
persons.Add(new Person("P005", "Janson"));
persons.Add(new Person("P002", "Aravind"));
persons.Add(new Person("P007", "Kazhal"));

1.

persons.Sort((p1,p2)=>string.Compare(p1.Name,p2.Name,true));

2.

var query = persons.OrderBy(n => n.Name, new NameComparer());

class NameComparer : IComparer<string>
{
    public int Compare(string x,string y)
    {
      return  string.Compare(x, y, true);
    }
}

22
Cevapların hiçbirinin bundan bahsetmediğine inanamıyorum, ancak en büyük fark şudur: OrderBy, Dizi veya Listenin sıralı bir kopyasını oluştururken, Sırala aslında onu yerinde sıralar.
PRMan

2
Başlığın karşılaştırma dediği gibi, OrderBy'nin kararlı olduğunu ve sıralamanın 16 öğeye kadar kararlı olduğunu eklemek isterim, çünkü öğeler bundan daha fazlaysa 16 öğeye kadar ekleme sıralaması kullanılır, ardından diğer kararsız algoritmalara geçer. aynı anahtara sahip öğeler.
Eklavyaa

@PRMan Hayır, OrderBy tembel bir numaralandırma oluşturur. Yalnızca döndürülen numaralandırmada ToList gibi bir yöntemi çağırırsanız, sıralanmış bir kopya alırsınız.
Stewart

1
@Stewart, System.Core / System / Linq / Enumerable.cs içindeki Buffer içindeki Array.Copy veya Collection.Copy into TElement [] öğesinin bir kopya olduğunu düşünmüyor musunuz? Ve IEnumerable'da ToList'i çağırırsanız, aynı anda 3 kopyaya sahip olabilirsiniz. Bu, benim açımdan önemli olan çok büyük diziler için bir sorundur. Ayrıca, aynı sıralı düzene birden çok kez ihtiyacınız varsa, Yerinde sırala işlevini bir kez çağırmak, kalıcılığı nedeniyle Listeyi tekrar tekrar sıralamaktan çok daha etkilidir.
PRMan

1
@PRMan Oh, sıralı bir kopyanın dahili olarak oluşturulduğunu kastettiniz. Yine de bu yanlış, çünkü OrderBy kopyayı oluşturmuyor - görebildiğim kadarıyla bu, koleksiyonda gerçekten döngü oluşturmaya başladığınızda GetEnumerator yöntemi tarafından yapılıyor. Kodumun üzerinden geçmeyi denedim ve bir LINQ ifadesinden bir değişkeni dolduran kodun neredeyse anında çalıştığını, ancak foreach döngüsüne girdiğinizde onu sıralamak için zaman harcadığını gördüm. Sanırım biraz daha zamanım olduğunda, perde arkasında nasıl çalıştığını anlamaya çalışmak için biraz zaman harcamalıyım.
Stewart

Yanıtlar:


90

Neden ölçmeyelim:

class Program
{
    class NameComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return string.Compare(x, y, true);
        }
    }

    class Person
    {
        public Person(string id, string name)
        {
            Id = id;
            Name = name;
        }
        public string Id { get; set; }
        public string Name { get; set; }
    }

    static void Main()
    {
        List<Person> persons = new List<Person>();
        persons.Add(new Person("P005", "Janson"));
        persons.Add(new Person("P002", "Aravind"));
        persons.Add(new Person("P007", "Kazhal"));

        Sort(persons);
        OrderBy(persons);

        const int COUNT = 1000000;
        Stopwatch watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        {
            Sort(persons);
        }
        watch.Stop();
        Console.WriteLine("Sort: {0}ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        {
            OrderBy(persons);
        }
        watch.Stop();
        Console.WriteLine("OrderBy: {0}ms", watch.ElapsedMilliseconds);
    }

    static void Sort(List<Person> list)
    {
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    }

    static void OrderBy(List<Person> list)
    {
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    }
}

Yayın modunda derlendiğinde bilgisayarımda bu program şunları yazdırır:

Sort: 1162ms
OrderBy: 1269ms

GÜNCELLEME:

@Stefan tarafından önerildiği gibi, burada büyük bir listeyi daha az sayıda sıralamanın sonuçları verilmiştir:

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)
{
    persons.Add(new Person("P" + i.ToString(), "Janson" + i.ToString()));
}

Sort(persons);
OrderBy(persons);

const int COUNT = 30;
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
    Sort(persons);
}
watch.Stop();
Console.WriteLine("Sort: {0}ms", watch.ElapsedMilliseconds);

watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
    OrderBy(persons);
}
watch.Stop();
Console.WriteLine("OrderBy: {0}ms", watch.ElapsedMilliseconds);

Baskılar:

Sort: 8965ms
OrderBy: 8460ms

Bu senaryoda, OrderBy daha iyi performans gösteriyor gibi görünüyor.


UPDATE2:

Ve rastgele isimler kullanarak:

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)
{
    persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
}

Nerede:

private static Random randomSeed = new Random();
public static string RandomString(int size, bool lowerCase)
{
    var sb = new StringBuilder(size);
    int start = (lowerCase) ? 97 : 65;
    for (int i = 0; i < size; i++)
    {
        sb.Append((char)(26 * randomSeed.NextDouble() + start));
    }
    return sb.ToString();
}

Getiri:

Sort: 8968ms
OrderBy: 8728ms

Still OrderBy daha hızlı


2
Bence, çok küçük bir listeyi (3 öğe) 1000000 kez veya çok büyük bir listeyi (1000000 öğe) birkaç kez sıralayarak çok farklı. Her ikisi de çok alakalı. Pratikte orta boy liste (orta büyüklükte olan nedir? ... diyelim ki şimdilik 1000 öğe) en ilginç olanıdır. IMHO, listeleri 3 maddeli sıralamak çok anlamlı değil.
Stefan Steinegger

25
"Daha hızlı" ve "fark edilir derecede daha hızlı" arasında bir fark olduğunu unutmayın. Son örneğinizde fark saniyenin çeyreği kadardı. Kullanıcı fark edecek mi? Sonuç için kullanıcının neredeyse dokuz saniye beklemesi kabul edilemez mi? Her iki sorunun da cevabı "hayır" ise, performans açısından hangisini seçtiğin gerçekten önemli değil.
Eric Lippert

12
Ayrıca, buradaki testin kronometreyi başlatmadan önce listeyi sıraladığını unutmayın, bu nedenle iki algoritmanın sıralı girişle karşılaştıklarında nasıl karşılaştırdıklarını karşılaştırıyoruz. Bu, sıralanmamış girdilerle göreli performanslarından oldukça farklı olabilir.
phoog

3
LINQYerinde List<T>.Sortuygulamaya kıyasla ek bellek harcamak zorunda olduğu gerçeği göz önüne alındığında, bu sonuçlar oldukça şaşırtıcı IMHO . Bunu daha yeni .NET sürümlerinde iyileştirip geliştirmediklerinden emin değilim, ancak makinemde (i7 3. nesil 64-bit .NET 4.5 sürümü) her durumda Sortdaha iyi performans gösteriyor OrderBy. Ayrıca, OrderedEnumerable<T>kaynak koduna bakarak, sonunda indis dizisini yerinde sıralamak için Quicksort'u çağırmadan önce üç ek dizi (önce a Buffer<T>, sonra bir dizi öngörülen anahtarlar, sonra bir dizi indeks) oluşturduğu görülmektedir.
Groo

2
... ve tüm ToArraybunlardan sonra, ortaya çıkan diziyi yaratan çağrı var. Bellek işlemleri ve dizi indeksleme inanılmaz derecede hızlı işlemlerdir, ancak yine de bu sonuçların arkasındaki mantığı bulamıyorum.
Groo

121

Hayır, aynı algoritma değiller. Yeni başlayanlar için, LINQ kararlıOrderBy olarak belgelenmiştir (yani, iki öğe aynıysa , orijinal sıralarında görüneceklerdir).Name

Ayrıca, sorguyu arabelleğe alıp almamanıza veya birkaç kez yinelemenize de bağlıdır (LINQ-to-Objects, sonucu arabelleğe almadıkça, başına yeniden sıralanır foreach).

İçin OrderBysorguda, ben de kullanımına cazip olacaktır:

OrderBy(n => n.Name, StringComparer.{yourchoice}IgnoreCase);

(yönelik {yourchoice}bir CurrentCulture, Ordinalya da InvariantCulture).

List<T>.Sort

Bu yöntem, QuickSort algoritmasını kullanan Array.Sort kullanır. Bu uygulama, kararsız bir sıralama gerçekleştirir; yani, iki unsur eşitse, sıraları korunamayabilir. Buna karşılık, kararlı bir sıralama, eşit olan öğelerin sırasını korur.

Enumerable.OrderBy

Bu yöntem kararlı bir sıralama gerçekleştirir; yani, iki elemanın anahtarları eşitse, elemanların sırası korunur. Buna karşılık, kararsız bir sıralama, aynı anahtara sahip öğelerin sırasını korumaz. çeşit; yani, iki unsur eşitse, sıraları korunamayabilir. Buna karşılık, kararlı bir sıralama, eşit olan öğelerin sırasını korur.


5
.NET Reflector'ı veya ILSpy'ı Enumerable.OrderBykendi iç uygulamalarını açmak ve derinlemesine incelemek için kullanırsanız, OrderBy sıralama algoritmasının kararlı bir sıralama yapan bir QuickSort varyantı olduğunu görebilirsiniz. (Bkz System.Linq.EnumerableSorter<TElement>.) Bu durumda, Array.Sortve Enumerable.OrderByher ikisi de beklenebilir O (N günlük K) burada, yürütme kez K toplama elemanlarının sayısıdır.
John Beyer

@Marc İki unsur eşit olsaydı ve sıraları korunmasaydı farkın ne olacağını tam olarak takip edemiyorum. Bu, ilkel veri türleri için kesinlikle bir sorun gibi görünmüyor. Ama bir referans türü için bile, sıralayacak olursam, Marc Gravell adlı bir kişinin, Marc Gravell adında başka bir kişinin önünde görünmesi neden önemli olsun (örneğin :))? Cevabınızı / bilginizi sorgulamıyorum, bu senaryonun bir uygulamasını arıyorum.
Mukus

4
@Mukus, bir şirketin adres defterini isme göre (veya aslında doğum tarihine göre) sıraladığınızı hayal edin - kaçınılmaz olarak kopyalar olacaktır. Nihayetinde soru şudur: onlar için ne olur? Alt sipariş tanımlanmış mı?
Marc Gravell

55

Darin Dimitrov'un cevabı, bunun halihazırda sıralanmış girdilerle karşılaştığından OrderBybiraz daha hızlı olduğunu gösteriyor List.Sort. Kodunu değiştirdim, böylece sıralanmamış verileri defalarca sıralar ve OrderByçoğu durumda biraz daha yavaş olur.

Ayrıca, OrderBytest ToArrayLinq numaralandırıcısını zorlamak için kullanır , ancak bu açıkça Person[]girdi türünden ( List<Person>) farklı bir tür ( ) döndürür . Bu nedenle testi ToListyerine kullanarak yeniden çalıştırdım ve daha ToArrayda büyük bir fark elde ettim :

Sort: 25175ms
OrderBy: 30259ms
OrderByWithToList: 31458ms

Kod:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

class Program
{
    class NameComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return string.Compare(x, y, true);
        }
    }

    class Person
    {
        public Person(string id, string name)
        {
            Id = id;
            Name = name;
        }
        public string Id { get; set; }
        public string Name { get; set; }
        public override string ToString()
        {
            return Id + ": " + Name;
        }
    }

    private static Random randomSeed = new Random();
    public static string RandomString(int size, bool lowerCase)
    {
        var sb = new StringBuilder(size);
        int start = (lowerCase) ? 97 : 65;
        for (int i = 0; i < size; i++)
        {
            sb.Append((char)(26 * randomSeed.NextDouble() + start));
        }
        return sb.ToString();
    }

    private class PersonList : List<Person>
    {
        public PersonList(IEnumerable<Person> persons)
           : base(persons)
        {
        }

        public PersonList()
        {
        }

        public override string ToString()
        {
            var names = Math.Min(Count, 5);
            var builder = new StringBuilder();
            for (var i = 0; i < names; i++)
                builder.Append(this[i]).Append(", ");
            return builder.ToString();
        }
    }

    static void Main()
    {
        var persons = new PersonList();
        for (int i = 0; i < 100000; i++)
        {
            persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
        } 

        var unsortedPersons = new PersonList(persons);

        const int COUNT = 30;
        Stopwatch watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        {
            watch.Start();
            Sort(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        }
        Console.WriteLine("Sort: {0}ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        {
            watch.Start();
            OrderBy(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        }
        Console.WriteLine("OrderBy: {0}ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        {
            watch.Start();
            OrderByWithToList(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        }
        Console.WriteLine("OrderByWithToList: {0}ms", watch.ElapsedMilliseconds);
    }

    static void Sort(List<Person> list)
    {
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    }

    static void OrderBy(List<Person> list)
    {
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    }

    static void OrderByWithToList(List<Person> list)
    {
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToList();
    }
}

2
Test kodunu şimdi LinqPad 5 (.net 5) ile çalıştırıyorum ve. İle OrderByWithToListaynı süreyi alıyor OrderBy.
güvercin

38

Bence Sortve arasındaki başka bir farkı belirtmek önemlidir OrderBy:

Person.CalculateSalary()Çok zaman alan bir yöntem olduğunu varsayalım ; muhtemelen büyük bir listeyi sıralama işleminden bile daha fazlası.

Karşılaştırmak

// Option 1
persons.Sort((p1, p2) => Compare(p1.CalculateSalary(), p2.CalculateSalary()));
// Option 2
var query = persons.OrderBy(p => p.CalculateSalary()); 

2. Seçenek yalnızca çağırır için üstün bir performansa sahip olabilir CalculateSalaryyöntemini n defa oysa Sortseçenek diyebileceğimiz CalculateSalarykadar 2 n (log n ) sıralama algoritmanın başarısına bağlı olarak, kat.


4
Bu doğrudur, ancak bu sorunun bir çözümü vardır, yani verileri bir dizide tutmak ve biri anahtar diğeri değer olmak üzere iki dizi alan Array.Sort aşırı yüklemesini kullanmak. Anahtar diziyi doldururken CalculateSalary ntimes'ı çağıracaksınız . Bu tabii ki OrderBy'yi kullanmak kadar uygun değil.
phoog

14

Kısaca :

Liste / Dizi Sıralaması ():

  • Kararsız sıralama.
  • Yerinde yapılır.
  • Introsort / Quicksort kullanın.
  • Özel karşılaştırma, bir karşılaştırıcı sağlanarak yapılır. Karşılaştırma pahalıysa, OrderBy () 'den daha yavaş olabilir (anahtarların kullanılmasına izin verir, aşağıya bakın).

OrderBy / ThenBy ():

  • Kararlı tür.
  • Yerinde değil.
  • Quicksort'u kullanın. Quicksort, istikrarlı bir tür değildir. İşin püf noktası şudur: sıralama yaparken, iki öğenin eşit anahtarı varsa, ilk sıralarını karşılaştırır (bu, sıralamadan önce saklanmıştır).
  • Öğeleri değerlerine göre sıralamak için anahtarların (lambdalar kullanılarak) kullanılmasına izin verir (örneğin:) x => x.Id. Sıralamadan önce tüm anahtarlar çıkarılır. Bu, Sort () ve özel bir karşılaştırıcı kullanmaktan daha iyi performansla sonuçlanabilir.

Kaynaklar: MDSN , referans kaynağı ve dotnet / coreclr deposu (GitHub).

Yukarıda listelenen ifadelerden bazıları, mevcut .NET çerçeve uygulamasına (4.7.2) dayanmaktadır. Gelecekte değişebilir.


0

OrderBy ve Sort yöntemleri tarafından kullanılan algoritmaların karmaşıklığını hesaplamalısınız. QuickSort, hatırladığım kadarıyla n (log n) karmaşıklığına sahiptir, burada n, dizinin uzunluğudur.

orderby's için de arama yaptım, ancak msdn kitaplığında bile herhangi bir bilgi bulamadım. tek bir özellikle ilgili aynı değerlere ve sıralamaya sahip değilseniz, Sort () yöntemini kullanmayı tercih ederim; OrderBy kullanmıyorsanız.


1
Mevcut MSDN belgelerine göre Sort, girdiye bağlı olarak 3 farklı sıralama algoritması kullanır. QuickSort bunlardan biridir. OrderBy () algoritmasıyla ilgili soru burada (Quicksort): stackoverflow.com/questions/2792074/…
Thor

-1

Sadece bu siparişin çok daha kullanışlı olduğunu eklemek istiyorum.

Neden? Çünkü bunu yapabilirim:

Dim thisAccountBalances = account.DictOfBalances.Values.ToList
thisAccountBalances.ForEach(Sub(x) x.computeBalanceOtherFactors())
thisAccountBalances=thisAccountBalances.OrderBy(Function(x) x.TotalBalance).tolist
listOfBalances.AddRange(thisAccountBalances)

Neden karmaşık karşılaştırıcı? Bir alana göre sıralayın. Burada TotalBalance'a göre sıralama yapıyorum.

Çok kolay.

Bunu nazikçe yapamam. Nedenini merak ediyorum. OrderBy ile iyi geçin.

Hız gelince her zaman O (n).


3
Soru: Cevabınızdaki O (n) Süresi (sanırım) OrderBy veya Comparer'ı mı ifade ediyor? Hızlı sıralamanın O (N) zamanına ulaşabileceğini sanmıyorum.
Kevman
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.