C # เรียงลำดับและเรียงลำดับโดยเปรียบเทียบ


105

ฉันสามารถจัดเรียงรายการโดยใช้ Sort หรือ OrderBy อันไหนเร็วกว่ากัน? ทั้งสองทำงานบนอัลกอริทึมเดียวกันหรือไม่

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

24
ฉันไม่อยากจะเชื่อเลยว่าไม่มีคำตอบใดที่กล่าวถึงสิ่งนี้ แต่ความแตกต่างที่ใหญ่ที่สุดคือ OrderBy สร้างสำเนา Array หรือ List ที่เรียงลำดับในขณะที่ Sort เรียงลำดับตามความเป็นจริง
PRMan

2
ในฐานะที่เป็นชื่อพูดการเปรียบเทียบฉันต้องการเพิ่มว่า OrderBy มีความเสถียรและการเรียงลำดับมีความเสถียรไม่เกิน 16 องค์ประกอบเนื่องจากใช้การเรียงลำดับการแทรกไม่เกิน 16 องค์ประกอบหากองค์ประกอบมีมากกว่านั้นจะเปลี่ยนไปใช้ algos อื่นที่ไม่เสถียรแก้ไข: เสถียรหมายถึงการรักษาลำดับสัมพัทธ์ ขององค์ประกอบที่มีคีย์เดียวกัน
Eklavyaa

@PRMan Nope, OrderBy สร้างความขี้เกียจแจกแจง เฉพาะในกรณีที่คุณเรียกใช้เมธอดเช่น ToList บนตัวนับที่ส่งคืนคุณจะได้รับสำเนาที่เรียงลำดับ
Stewart

1
@Stewart คุณไม่ถือว่า Array.Copy หรือ Collection.Copy เป็น TElement [] ใน Buffer ใน System.Core / System / Linq / Enumerable.cs เป็นสำเนาหรือไม่? และถ้าคุณเรียก ToList บน IEnumerable คุณอาจมีสำเนา 3 ชุดในหน่วยความจำพร้อมกันชั่วขณะ นี่เป็นปัญหาสำหรับอาร์เรย์ขนาดใหญ่มากซึ่งเป็นส่วนหนึ่งของประเด็นของฉัน นอกจากนี้หากคุณต้องการคำสั่งเรียงเดียวกันมากกว่าหนึ่งครั้งการเรียก Sort in-place เพียงครั้งเดียวจะมีประสิทธิภาพมากกว่าการจัดเรียงรายการซ้ำ ๆ เนื่องจากความคงทน
PRMan

1
@PRMan โอ้คุณหมายถึงสำเนาที่เรียงไว้ถูกสร้างขึ้นภายใน ยังคงไม่ถูกต้องเนื่องจาก OrderBy ไม่ได้สร้างสำเนา - จากสิ่งที่ฉันเห็นสิ่งนี้ทำได้โดยเมธอด GetEnumerator เมื่อคุณเริ่มวนซ้ำคอลเลกชันจริงๆ ฉันเพิ่งลองก้าวผ่านรหัสของฉันและพบว่ารหัสที่เติมตัวแปรจากนิพจน์ LINQ ทำงานเกือบจะในทันที แต่เมื่อคุณเข้าสู่ลูป foreach จะใช้เวลาในการเรียงลำดับ ฉันเดาว่าเมื่อฉันมีเวลาเพิ่มขึ้นอีกสักหน่อยฉันควรใช้ความพยายามเพื่อหาวิธีการทำงานเบื้องหลัง
Stewart

คำตอบ:


90

ทำไมไม่วัดมัน:

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

บนคอมพิวเตอร์ของฉันเมื่อคอมไพล์ในโหมดเผยแพร่โปรแกรมนี้จะพิมพ์:

Sort: 1162ms
OrderBy: 1269ms

อัพเดท:

ตามที่แนะนำโดย @Stefan นี่คือผลลัพธ์ของการจัดเรียงรายการใหญ่ครั้งน้อยลง:

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

พิมพ์:

Sort: 8965ms
OrderBy: 8460ms

ในสถานการณ์นี้ดูเหมือนว่า OrderBy จะทำงานได้ดีกว่า


UPDATE2:

และใช้ชื่อสุ่ม:

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

ที่ไหน:

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

ผลตอบแทน:

Sort: 8968ms
OrderBy: 8728ms

ยังคง OrderBy เร็วกว่า


2
ฉันคิดว่าการจัดเรียงรายการขนาดเล็กมาก (3 รายการ) 1000000 ครั้งแตกต่างกันมากหรือโดยการจัดเรียงรายการที่มีขนาดใหญ่มาก (1000000 รายการ) เพียงไม่กี่ครั้ง ทั้งสองมีความเกี่ยวข้องกันมาก ในทางปฏิบัติรายการขนาดกลาง (กลางคืออะไร ... สมมติว่าตอนนี้ 1,000 รายการ) น่าสนใจที่สุด IMHO การจัดเรียงรายการที่มี 3 รายการไม่ได้มีความหมายมากนัก
Stefan Steinegger

25
โปรดทราบว่ามีความแตกต่างระหว่าง "เร็วกว่า" และ "เร็วกว่าอย่างเห็นได้ชัด" ในตัวอย่างสุดท้ายของคุณความแตกต่างอยู่ที่ประมาณหนึ่งในสี่ของวินาที ผู้ใช้จะสังเกตเห็นหรือไม่? เป็นที่ยอมรับไม่ได้หรือไม่ที่ผู้ใช้ต้องรอเกือบเก้าวินาทีเพื่อรับผลลัพธ์ หากคำตอบของทั้งสองคำถามคือ "ไม่" ก็ไม่สำคัญว่าคุณจะเลือกข้อใดจากมุมมองด้านประสิทธิภาพ
Eric Lippert

12
โปรดทราบด้วยว่าการทดสอบที่นี่จะจัดเรียงรายการก่อนที่จะเริ่มนาฬิกาจับเวลาดังนั้นเราจึงเปรียบเทียบว่าอัลกอริทึมทั้งสองเปรียบเทียบกันอย่างไรเมื่อเผชิญกับอินพุตที่เรียงลำดับ สิ่งนี้อาจแตกต่างจากประสิทธิภาพสัมพัทธ์กับอินพุตที่ไม่ได้เรียงลำดับ
ผี

3
ผลการเหล่านี้จะสวยน่าแปลกใจ IMHO พิจารณาความจริงที่ว่าLINQมีการใช้จ่ายหน่วยความจำเพิ่มเติมเมื่อเทียบกับในสถานที่List<T>.Sortดำเนินงาน ฉันไม่แน่ใจว่าพวกเขาปรับปรุงสิ่งนี้ใน. NET เวอร์ชันใหม่กว่าหรือไม่ แต่ในเครื่องของฉัน (i7 3rd gen 64-bit .NET 4.5 release) มีSortประสิทธิภาพดีกว่าOrderByในทุกกรณี นอกจากนี้เมื่อดูที่OrderedEnumerable<T>ซอร์สโค้ดดูเหมือนว่ามันจะสร้างอาร์เรย์เพิ่มเติมสามอาร์เรย์ (อันดับแรก a Buffer<T>จากนั้นอาร์เรย์ของคีย์ที่คาดการณ์ไว้จากนั้นอาร์เรย์ของดัชนี) ก่อนที่จะเรียก Quicksort เพื่อจัดเรียงอาร์เรย์ของดัชนีให้เข้าที่
Groo

2
... และหลังจากนั้นก็มีการToArrayเรียกที่สร้างอาร์เรย์ผลลัพธ์ การทำงานของหน่วยความจำและการจัดทำดัชนีอาร์เรย์เป็นการดำเนินการที่รวดเร็วอย่างไม่น่าเชื่อ แต่ฉันยังไม่พบตรรกะที่อยู่เบื้องหลังผลลัพธ์เหล่านี้
Groo

121

ไม่มันไม่ใช่อัลกอริทึมเดียวกัน สำหรับการเริ่มต้น LINQ ได้OrderByรับการจัดทำเป็นเอกสารว่ามีความเสถียร (กล่าวคือหากสองรายการมีเหมือนกันรายการNameเหล่านั้นจะปรากฏตามลำดับเดิม)

นอกจากนี้ยังขึ้นอยู่กับว่าคุณบัฟเฟอร์แบบสอบถามเทียบกับการทำซ้ำหลาย ๆ ครั้ง (LINQ-to-Objects เว้นแต่คุณจะบัฟเฟอร์ผลลัพธ์จะเรียงลำดับใหม่ต่อforeach)

สำหรับOrderByคำถามนี้ฉันอยากจะใช้:

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

(สำหรับ{yourchoice}หนึ่งCurrentCulture, OrdinalหรือInvariantCulture)

List<T>.Sort

วิธีนี้ใช้ Array.Sort ซึ่งใช้อัลกอริทึม QuickSort การใช้งานนี้ดำเนินการจัดเรียงที่ไม่เสถียร นั่นคือถ้าสององค์ประกอบเท่ากันลำดับของมันอาจไม่ได้รับการรักษาไว้ ในทางตรงกันข้ามการจัดเรียงแบบคงที่จะรักษาลำดับขององค์ประกอบที่เท่ากัน

Enumerable.OrderBy

วิธีนี้ดำเนินการจัดเรียงที่มั่นคง นั่นคือถ้าคีย์ของสององค์ประกอบเท่ากันลำดับขององค์ประกอบจะถูกรักษาไว้ ในทางตรงกันข้ามการจัดเรียงที่ไม่เสถียรจะไม่รักษาลำดับขององค์ประกอบที่มีคีย์เดียวกัน เรียง; นั่นคือถ้าสององค์ประกอบเท่ากันลำดับของมันอาจไม่ได้รับการรักษาไว้ ในทางตรงกันข้ามการจัดเรียงแบบคงที่จะรักษาลำดับขององค์ประกอบที่เท่ากัน


5
หากคุณใช้. NET Reflector หรือ ILSpy เพื่อถอดรหัสเปิดEnumerable.OrderByและเจาะลึกลงไปในการใช้งานภายในคุณจะเห็นว่าอัลกอริทึมการเรียงลำดับ OrderBy เป็นตัวแปรของ QuickSort ที่จัดเรียงได้อย่างเสถียร (ดูSystem.Linq.EnumerableSorter<TElement>.) ดังนั้นArray.SortและEnumerable.OrderByคาดว่าทั้งสองจะมีเวลาดำเนินการO (N log N)โดยที่Nคือจำนวนองค์ประกอบในคอลเล็กชัน
John Beyer

@ มาร์คฉันไม่ค่อยทำตามความแตกต่างจะเป็นอย่างไรถ้าสององค์ประกอบเท่ากันและลำดับของพวกเขาไม่ได้รับการรักษาไว้ สิ่งนี้ดูเหมือนจะไม่เป็นปัญหาสำหรับประเภทข้อมูลดั้งเดิมอย่างแน่นอน แต่ถึงจะเป็นประเภทอ้างอิงทำไมมันถึงสำคัญถ้าฉันจะจัดเรียงคนที่มีชื่อ Marc Gravell ก็ปรากฏตัวต่อหน้าคนอื่นที่มีชื่อ Marc Gravell (เช่น :)) ฉันไม่ได้ถามคำตอบ / ความรู้ของคุณ แต่กำลังมองหาการประยุกต์ใช้สถานการณ์นี้
Mukus

5
@ มูกัสลองนึกภาพคุณจัดเรียงสมุดที่อยู่ของ บริษัท ตามชื่อ (หรือตามวันเดือนปีเกิด) - จะมีการซ้ำกันอย่างหลีกเลี่ยงไม่ได้ คำถามคือท้ายที่สุดแล้วจะเกิดอะไรขึ้นสำหรับพวกเขา? มีการกำหนดคำสั่งย่อยหรือไม่?
Marc Gravell

55

คำตอบของ Darin Dimitrov แสดงให้เห็นว่าOrderByเร็วกว่าเล็กน้อยList.Sortเมื่อต้องเผชิญกับข้อมูลที่จัดเรียงไว้แล้ว ฉันแก้ไขโค้ดของเขาเพื่อให้มันเรียงลำดับข้อมูลที่ไม่ได้เรียงลำดับซ้ำ ๆ และOrderByในกรณีส่วนใหญ่ช้ากว่าเล็กน้อย

นอกจากนี้การOrderByทดสอบToArrayยังใช้เพื่อบังคับให้มีการแจงนับของ Linq enumerator แต่จะส่งกลับ type ( Person[]) ซึ่งแตกต่างจากประเภทอินพุต ( List<Person>) อย่างชัดเจน ฉันจึงทำการทดสอบอีกครั้งโดยใช้ToListมากกว่าToArrayและได้รับความแตกต่างที่ยิ่งใหญ่กว่า:

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

รหัส:

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
ฉันรันโค้ดทดสอบใน LinqPad 5 (.net 5) แล้วและOrderByWithToListใช้เวลาเดียวกันกับOrderBy.
dovid

38

ฉันคิดว่าสิ่งสำคัญคือต้องสังเกตความแตกต่างระหว่างSortและOrderBy :

สมมติว่ามีไฟล์ Person.CalculateSalary()วิธีการหนึ่งซึ่งใช้เวลามาก อาจมากกว่าการดำเนินการจัดเรียงรายการขนาดใหญ่

เปรียบเทียบ

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

ตัวเลือกที่ 2อาจมีประสิทธิภาพที่เหนือกว่าเนื่องจากเรียกเฉพาะCalculateSalaryเมธอดnครั้งในขณะที่SortตัวเลือกอาจเรียกCalculateSalaryได้ถึง2 n log ( n )ครั้งขึ้นอยู่กับความสำเร็จของอัลกอริทึมการเรียงลำดับ


4
นี่เป็นความจริงแม้ว่าจะมีวิธีแก้ปัญหานั้นกล่าวคือเพื่อเก็บข้อมูลไว้ในอาร์เรย์และใช้ Array จัดเรียงข้อมูลเกินพิกัดที่ใช้อาร์เรย์สองอาร์เรย์หนึ่งในคีย์และค่าอื่น ๆ ในการกรอกคีย์อาร์เรย์คุณจะเรียกใช้ CalculateSalary ntimes เห็นได้ชัดว่าไม่สะดวกเท่าการใช้ OrderBy
phoog

14

โดยสังเขป :

เรียงรายการ / อาร์เรย์ ():

  • การจัดเรียงที่ไม่เสถียร
  • ทำในสถานที่
  • ใช้ Introsort / Quicksort
  • การเปรียบเทียบแบบกำหนดเองทำได้โดยการให้ตัวเปรียบเทียบ หากการเปรียบเทียบมีราคาแพงอาจช้ากว่า OrderBy () (ซึ่งอนุญาตให้ใช้คีย์ดูด้านล่าง)

OrderBy / ThenBy ():

  • การจัดเรียงที่มั่นคง
  • ไม่อยู่ในสถานที่
  • ใช้ Quicksort Quicksort ไม่ใช่การจัดเรียงที่มั่นคง นี่คือเคล็ดลับ: เมื่อจัดเรียงหากองค์ประกอบสองรายการมีคีย์เท่ากันจะเปรียบเทียบลำดับเริ่มต้น (ซึ่งเก็บไว้ก่อนการเรียงลำดับ)
  • จะช่วยให้การใช้ปุ่ม (ใช้ lambdas) องค์ประกอบเรียงลำดับค่าของพวกเขา (เช่นx => x.Id) คีย์ทั้งหมดจะถูกแยกออกก่อนการเรียงลำดับ ซึ่งอาจส่งผลให้ประสิทธิภาพดีกว่าการใช้ Sort () และตัวเปรียบเทียบแบบกำหนดเอง

แหล่งที่มา: MDSN , แหล่งอ้างอิงและdotnet / coreclrพื้นที่เก็บข้อมูล (GitHub)

ข้อความบางส่วนที่ระบุไว้ข้างต้นเป็นไปตามการใช้งาน. NET framework ปัจจุบัน (4.7.2) อาจมีการเปลี่ยนแปลงในอนาคต


0

คุณควรคำนวณความซับซ้อนของอัลกอริทึมที่ใช้โดยวิธีการ OrderBy และ Sort QuickSort มีความซับซ้อนของ n (log n) เท่าที่ฉันจำได้โดยที่ n คือความยาวของอาร์เรย์

ฉันค้นหา orderby ด้วย แต่ฉันไม่พบข้อมูลใด ๆ แม้แต่ในไลบรารี msdn หากคุณไม่มีค่าเดียวกันและการเรียงลำดับที่เกี่ยวข้องกับคุณสมบัติเพียงรายการเดียวฉันต้องการใช้เมธอด Sort () ถ้าไม่ใช้ OrderBy


1
ตามเอกสาร MSDN ปัจจุบัน Sort ใช้อัลกอริธึมการเรียงลำดับที่แตกต่างกัน 3 แบบตามอินพุต ซึ่ง ได้แก่ QuickSort คำถามเกี่ยวกับอัลกอริทึม OrderBy () อยู่ที่นี่ (Quicksort): stackoverflow.com/questions/2792074/…
ธ .

-1

ฉันแค่ต้องการเพิ่มคำสั่งนั้นเป็นวิธีที่มีประโยชน์มากขึ้น

ทำไม? เพราะฉันทำได้:

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

ทำไมตัวเปรียบเทียบที่ซับซ้อน? เพียงจัดเรียงตามเขตข้อมูล ฉันกำลังเรียงลำดับตาม TotalBalance

ง่ายมาก.

ฉันทำแบบนั้นไม่ได้ ฉันสงสัยว่าทำไม. ทำได้ดีกับ orderBy

สำหรับความเร็วก็คือ O (n) เสมอ


3
คำถาม: O (n) เวลา (ฉันถือว่า) ในคำตอบของคุณหมายถึง OrderBy หรือ Comparer? ฉันไม่คิดว่าการเรียงลำดับอย่างรวดเร็วจะทำให้ได้เวลา O (N)
Kevman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.