सी # सॉर्ट और ऑर्डर की तुलना


105

मैं सॉर्ट या ऑर्डरबाय का उपयोग करके एक सूची को सॉर्ट कर सकता हूं। कौन सा तेज है? क्या दोनों एक ही एल्गोरिदम पर काम कर रहे हैं?

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
मैं विश्वास नहीं कर सकता कि उत्तर में से कोई भी इसका उल्लेख नहीं करता है, लेकिन सबसे बड़ा अंतर यह है: ऑर्डरबाय अर्रे या लिस्ट की एक हल की हुई प्रतिलिपि बनाता है, जबकि सॉर्ट वास्तव में जगह में छाँटता है।
PRMan

2
जैसा कि शीर्षक कहता है कि तुलना, मैं जोड़ना चाहूंगा कि ऑर्डरबाय स्थिर है और 16 तत्वों तक की तरह स्थिर है, क्योंकि 16 तत्वों के सम्मिलन प्रकार का उपयोग किया जाता है यदि तत्व इससे अधिक हैं, तो यह अन्य अस्थिर एल्गोस पर स्विच करता है संपादित करें: स्थिर का मतलब सापेक्ष क्रम बनाए रखना है एक ही कुंजी वाले तत्वों की।
एकलव्य

@ मैनमैन नोप, ऑर्डरबाय एक आलसी असंख्य बनाता है। केवल तभी जब आप किसी विधि को कहते हैं जैसे कि लौटाए जाने योग्य पर टोललिस्ट आपको एक हल की हुई कॉपी मिलती है।
स्टीवर्ट

1
@Stewart, आप Array.Copy या Collection.Copy को टीलेमेंट में नहीं मानते [] System.Core / System / Linq / Enumerable.cs में एक कॉपी होने के लिए बफर में? और यदि आप IEnumerable पर ToList को कॉल करते हैं, तो आप एक ही बार में मेमोरी में 3 प्रतियां रख सकते हैं। यह बहुत बड़ी सरणियों के लिए एक समस्या है, जो मेरी बात का हिस्सा थी। इसके अलावा, यदि आपको एक ही क्रमबद्ध क्रम में एक से अधिक बार की आवश्यकता है, तो एक बार क्रमबद्ध होने के बजाय सूची को बार-बार छांटने की तुलना में अधिक कुशल है।
PRMan

1
@PRMan ओह, आपका मतलब है कि एक हल की गई कॉपी आंतरिक रूप से बनाई गई है। फिर भी यह गलत है, क्योंकि ऑर्डरबाय कॉपी नहीं बनाता है - जो मैं देख सकता हूं, यह गेटइन्यूमर विधि द्वारा किया जाता है जब आप वास्तव में संग्रह के माध्यम से लूप करना शुरू करते हैं। मैंने बस अपने कोड के माध्यम से कदम रखने की कोशिश की, और पाया कि एक कोड जो LINQ अभिव्यक्ति से एक चर को पॉप्युलेट करता है, लगभग तुरंत चलता है, लेकिन जब आप फॉरेस्ट लूप में जाते हैं, तो यह इसे छांटने में समय बिताता है। मुझे लगता है कि जब मेरे पास थोड़ा और समय होता है तो मुझे यह जानने की कोशिश करनी चाहिए कि यह पर्दे के पीछे कैसे काम करता है।
स्टीवर्ट

जवाबों:


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

इस परिदृश्य में ऐसा लगता है कि ऑर्डरबाय बेहतर प्रदर्शन करता है।


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

फिर भी आर्डरबी तेज है


2
मुझे लगता है, यह बहुत छोटी सूची (3 आइटम) को 1000000 बार सॉर्ट करने के लिए बहुत अलग है, या बहुत बड़ी सूची (1000000 आइटम) को केवल कुछ समय में सॉर्ट करके। दोनों बहुत प्रासंगिक है। व्यवहार में, सूची का मध्यम आकार (मध्यम क्या है? ... अब के लिए 1000 आइटम कहते हैं) सबसे दिलचस्प है। IMHO, 3 आइटमों के साथ सूचियों को क्रमबद्ध करना बहुत सार्थक नहीं है।
स्टेफन स्टाइनगर 12

25
ध्यान दें कि "तेज" और "स्पष्ट रूप से तेज" के बीच अंतर है। आपके अंतिम उदाहरण में अंतर एक सेकंड के एक चौथाई के बारे में था। क्या उपयोगकर्ता नोटिस करने जा रहा है? क्या परिणाम के लिए लगभग नौ सेकंड इंतजार करना उपयोगकर्ता के लिए अस्वीकार्य है? यदि दोनों प्रश्नों के उत्तर "नहीं" हैं, तो यह वास्तव में कोई फर्क नहीं पड़ता कि आप किसी प्रदर्शन के नजरिए से किसे चुनते हैं।
एरिक लिपर्ट

12
यह भी ध्यान दें कि स्टॉपवॉच शुरू करने से पहले यहां परीक्षण सूची को सॉर्ट करता है, इसलिए हम तुलना कर रहे हैं कि दो एल्गोरिदम की तुलना कैसे की जाती है जब छंटे हुए इनपुट का सामना करना पड़ता है। यह बिना इनपुट के उनके सापेक्ष प्रदर्शन से काफी भिन्न हो सकता है।
फोग

3
इन परिणामों को बहुत आश्चर्यजनक आश्चर्य है, इस तथ्य को देखते हुए कि LINQइन-प्लेस List<T>.Sortकार्यान्वयन की तुलना में अतिरिक्त मेमोरी खर्च करना पड़ता है । मुझे यकीन नहीं है कि उन्होंने इसे नए .NET संस्करणों में सुधार किया है, लेकिन मेरी मशीन पर (i7 3rd gen 64-bit .NET 4.5 रिलीज़) सभी मामलों में Sortबेहतर प्रदर्शन OrderByकरते हैं। इसके अलावा, OrderedEnumerable<T>स्रोत कोड को देखकर , ऐसा लगता है कि यह तीन अतिरिक्त सरणियों को बनाता है (पहले एक Buffer<T>, फिर अनुमानित कुंजियों की एक सरणी, फिर सूचकांकों की एक सरणी) अंत में क्विकॉर्ट को कॉल करने के लिए क्विकॉर्ट कॉल करने से पहले।
ग्रू

2
... और फिर इस सब के बाद, ToArrayकॉल है जो परिणामी सरणी बनाता है। स्मृति संचालन और सरणी अनुक्रमण अविश्वसनीय रूप से तेज़ संचालन हैं, लेकिन मैं अभी भी इन परिणामों के पीछे तर्क नहीं खोज सकता।
ग्रू

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 रिफ्लेक्टर या ILSpy का उपयोग खुले में दरार करने Enumerable.OrderByऔर इसके आंतरिक कार्यान्वयन में ड्रिल करने के लिए करते हैं, तो आप देख सकते हैं कि ऑर्डरबाय सॉर्टिंग एल्गोरिथ्म QuickSort का एक प्रकार है जो एक स्थिर सॉर्ट करता है। (देखें System.Linq.EnumerableSorter<TElement>।) इस प्रकार, Array.Sortऔर Enumerable.OrderByदोनों से ओ (एन लॉग एन) निष्पादन समय की उम्मीद की जा सकती है , जहां एन संग्रह में तत्वों की संख्या है।
जॉन बेयर

@Marc मैं इस बात का पालन नहीं करता कि दो तत्व समान थे और उनके आदेश को संरक्षित नहीं किया गया तो क्या अंतर होगा। यह निश्चित रूप से आदिम डेटा प्रकारों के लिए एक समस्या की तरह नहीं दिखता है। लेकिन एक संदर्भ प्रकार के लिए भी, यह क्यों मायने रखता है, अगर मैं सॉर्ट करना चाहता था, तो मार्क ग्रेवेल नाम वाला व्यक्ति मार्क ग्रेवेल (उदाहरण के लिए :)) के साथ किसी अन्य व्यक्ति के सामने आया था। मैं आपके उत्तर / ज्ञान पर सवाल नहीं उठा रहा हूं, बल्कि इस परिदृश्य के आवेदन की तलाश कर रहा हूं।
मुकुस

4
@ मूकस आपको लगता है कि नाम से (या वास्तव में जन्म की तारीख तक) एक कंपनी के पते की किताब को छाँटते हैं - अनिवार्य रूप से डुप्लिकेट होने जा रहे हैं। प्रश्न अंततः है: उनके लिए क्या होता है? क्या उप-आदेश को परिभाषित किया गया है?
मार्क Gravell

55

डारिन दिमित्रोव का जवाब दिखाता है कि पहले से छांटे गए इनपुट के साथ सामना करने की OrderByतुलना में थोड़ा तेज है List.Sort। मैंने उसके कोड को संशोधित किया ताकि यह बार-बार अनसोल्ड डेटा को सॉर्ट करे और OrderByज्यादातर मामलों में थोड़ा धीमा हो।

इसके अलावा, OrderByपरीक्षण ToArrayलाइनक एन्यूमरेटर की गणना को मजबूर करने के लिए उपयोग करता है, लेकिन यह स्पष्ट रूप से एक प्रकार ( 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विकल्प क्रमबद्ध एल्गोरिथ्म की सफलता के आधार पर 2 n लॉग ( n ) बार कॉल CalculateSalaryकर सकता है ।


4
यह सच है, हालांकि उस समस्या का एक समाधान है, अर्थात्, डेटा को एक सरणी में रखना और Array.Sort अधिभार का उपयोग करना जो दो सरणियों, चाबियों में से एक और मूल्यों के दूसरे को लेता है। कुंजी ऐरे को भरने में, आप कैलकुलेटरी nकाल की गणना करेंगे । यह स्पष्ट रूप से ऑर्डरबी का उपयोग करने के रूप में सुविधाजनक नहीं है।
2

14

संक्षेप में :

सूची / क्रमबद्ध करें ():

  • अस्थिर प्रकार।
  • जगह-जगह किया गया।
  • Introsort / Quicksort का उपयोग करें।
  • कस्टम तुलना एक तुलना प्रदान करके किया जाता है। यदि तुलना महंगी है, तो यह ऑर्डरबी () की तुलना में धीमा हो सकता है (जो कुंजी का उपयोग करने की अनुमति देता है, नीचे देखें)।

आर्डरबाय / तब ():

  • स्थिर प्रकार।
  • जगह में नहीं।
  • क्विकॉर्ट का उपयोग करें। क्विकसॉर्ट एक स्थिर प्रकार नहीं है। यहाँ यह चाल है: जब छँटाई होती है, अगर दो तत्वों की समान कुंजी होती है, तो यह उनके प्रारंभिक क्रम की तुलना करता है (जो छँटाई से पहले संग्रहीत किया गया है)।
  • उनके मूल्यों पर तत्वों को सॉर्ट करने के लिए कुंजियों (लैम्ब्डा का उपयोग करके) का उपयोग करने की अनुमति देता है (जैसे:) x => x.Id। सभी कुंजियों को छँटाई से पहले निकाला जाता है। यह क्रमबद्ध () और एक कस्टम तुलना का उपयोग करने से बेहतर प्रदर्शन हो सकता है।

स्रोत: MDSN , संदर्भ स्रोत और dotnet / coreclr रिपॉजिटरी (GitHub)।

ऊपर सूचीबद्ध कुछ कथन वर्तमान .NET फ्रेमवर्क कार्यान्वयन (4.7.2) पर आधारित हैं। यह भविष्य में बदल सकता है।


0

आपको ऑर्डरबी और सॉर्ट के तरीकों द्वारा उपयोग किए जाने वाले एल्गोरिदम की जटिलता की गणना करनी चाहिए। QuickSort में n (लॉग एन) की एक जटिलता है जैसा कि मुझे याद है, जहां n सरणी की लंबाई है।

मैंने ऑर्डरबाय के लिए भी खोज की है, लेकिन मुझे msdn लाइब्रेरी में भी कोई जानकारी नहीं मिली। यदि आपके पास केवल एक ही संपत्ति से संबंधित समान मूल्य और छंटनी नहीं है, तो मैं Sort () विधि का उपयोग करना पसंद करता हूं; अगर ऑर्डरबी का उपयोग नहीं करते हैं।


1
वर्तमान MSDN प्रलेखन के अनुसार सॉर्ट इनपुट के आधार पर 3 अलग-अलग सॉर्टिंग एल्गोरिदम का उपयोग करता है। जिनमें क्विकॉर्ट है। 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 के आधार पर छँटाई कर रहा हूँ।

बहुत आसान।

मैं ऐसा नहीं कर सकता। मुझे आश्चर्य है क्योंकि। आदेश के साथ ठीक करो।

गति के रूप में यह हमेशा O (n) है।


3
प्रश्न: आपके उत्तर में O (n) समय (मुझे लगता है) ऑर्डरबी या तुलनाकर्ता को संदर्भित करता है? मुझे नहीं लगता कि त्वरित प्रकार ओ (एन) समय को प्राप्त कर सकता है।
केवमैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.