C#の並べ替えとOrderByの比較


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

22
私は答えがどれもこれに言及していないとは信じられませんが、最大の違いはこれです:OrderByは配列またはリストのソートされたコピーを作成しますが、Sortは実際に所定の位置にソートします。
PRMan 2018

2
タイトルが比較を言うように、要素が他の不安定なアルゴに切り替わる場合、最大16要素の挿入ソートが使用されるため、OrderByは安定しており、ソートは最大16要素まで安定していることを追加したいと思います。同じキーを持つ要素の。
Eklavyaa

@PRManいいえ、OrderByは遅延列挙型を作成します。返された列挙型に対してToListなどのメソッドを呼び出した場合にのみ、ソートされたコピーが取得されます。
スチュワート

1
@ Stewart、System.Core / System / Linq / Enumerable.csのBufferにあるArray.CopyまたはCollection.Copy into TElement []をコピーと見なしていませんか?また、IEnumerableでToListを呼び出すと、メモリに一時的に一度に3つのコピーを保持できます。これは、私の指摘の一部であった非常に大きな配列の問題です。また、同じ並べ替え順序が複数回必要な場合は、永続的なため、Listを繰り返し並べ替えるよりも、その場で並べ替えを1回呼び出すほうがはるかに効率的です。
PRMan

1
@PRManああ、あなたはソートされたコピーが内部で構築されることを意味しました。OrderByはコピーを作成しないので、それでも不正確です。私が見ることができることから、コレクションを実際にループし始めると、これはGetEnumeratorメソッドによって行われます。コードをステップ実行してみたところ、LINQ式から変数を設定するコードがほぼ瞬時に実行されることがわかりましたが、foreachループに入ると、ソートに時間がかかります。もう少し時間があったら、裏でそれがどのように機能するかを理解するために少し費やす必要があると思います。
スチュワート

回答:


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アイテム)を数回並べ替えたりするのとはかなり異なると思います。どちらも非常に重要です。実際には、リストのミディアムサイズ(ミディアムとは?...今のところ1000アイテムとしましょう)が最も興味深いものです。私見、3つのアイテムを含むリストの並べ替えはあまり意味がありません。
Stefan Steinegger、2009

25
「高速」と「著しく高速」には違いがあることに注意してください。最後の例では、違いは約1/4秒でした。ユーザーは気づくでしょうか?ユーザーが結果を9秒近く待つことは受け入れられませんか?両方の質問に対する答えが「いいえ」の場合、パフォーマンスの観点からどちらを選択するかは問題ではありません。
Eric Lippert、

12
また、ここでのテストはストップウォッチを開始する前にリストをソートするため、ソートされた入力に直面したときの2つのアルゴリズムの比較方法を比較しています。これは、ソートされていない入力での相対的なパフォーマンスとはかなり異なる場合があります。
phoog

3
LINQインプレースList<T>.Sort実装と比較して追加のメモリを費やす必要があるという事実を考えると、これらの結果はIMHOにとってかなり驚くべきものです。新しい.NETバージョンでこれが改善されたかどうかはわかりませんが、私のマシン(i7第3世代64ビット.NET 4.5リリース)では、すべてのケースでSortパフォーマンスが優れOrderByています。さらに、OrderedEnumerable<T>ソースコードを見ると、3つの追加の配列(最初にa Buffer<T>、次に投影されたキーの配列、次にインデックスの配列)が作成され、最後にQuicksortを呼び出してインデックスの配列を並べ替えているようです。
Groo

2
...そして、このすべての後にToArray、結果の配列を作成する呼び出しがあります。メモリ操作と配列のインデックス付けは信じられないほど高速な操作ですが、これらの結果の背後にあるロジックはまだ見つかりません。
Groo

121

いいえ、同じアルゴリズムではありません。手始めに、LINQ OrderBy安定版として文書化されNameています(つまり、2つの項目が同じである場合、それらは元の順序で表示されます)。

また、クエリをバッファリングするか、数回反復するかによっても異なります(LINQ-to-Objectsは、結果をバッファリングしない限り、ごとに並べ替えられますforeach)。

OrderBy問合せ、私はまた、使用するために誘惑されるだろう。

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

(のため{yourchoice}の一つCurrentCultureOrdinalまたはInvariantCulture)。

List<T>.Sort

このメソッドは、QuickSortアルゴリズムを使用するArray.Sortを使用します。この実装は不安定なソートを実行します。つまり、2つの要素が等しい場合、それらの順序は保持されない可能性があります。対照的に、安定したソートでは、等しい要素の順序が保持されます。

Enumerable.OrderBy

このメソッドは安定したソートを実行します。つまり、2つの要素のキーが等しい場合、要素の順序は保持されます。対照的に、不安定なソートでは、同じキーを持つ要素の順序は保持されません。ソート; つまり、2つの要素が等しい場合、それらの順序は保持されない可能性があります。対照的に、安定したソートでは、等しい要素の順序が保持されます。


5
.NET ReflectorまたはILSpyを使用Enumerable.OrderByして内部実装をクラックオープンしてドリルダウンすると、OrderBy並べ替えアルゴリズムがQuickSortの変種であり、安定した並べ替えを行うことがわかります。(参照System.Linq.EnumerableSorter<TElement>。)したがって、Array.SortEnumerable.OrderByの両方が期待できるO(NログN)実行時間、Nは、コレクション内の要素の数です。
John Beyer 2013年

@Marc 2つの要素が等しく、それらの順序が保持されなかった場合、どのような違いがあるのか​​は、完全にはわかりません。これは、プリミティブデータ型の問題のようには見えません。しかし、参照タイプであっても、なぜソートする場合、Marc Gravellという名前の人がMarc Gravellという名前の別の人の前に現れることが重要なのでしょうか(たとえば:))?私はあなたの答え/知識を問うのではなく、このシナリオの適用を探しています。
ムクス、2014年

4
@Mukusは、会社のアドレス帳を名前で(または実際に生年月日で)並べ替えると想像します。必然的に重複が発生します。問題は、結局のところ、彼らに何が起こるのかということです。サブオーダーは定義されていますか?
Marc Gravell

55

ダリン・ディミトロフの答えは、すでにソートされた入力に直面したときOrderByよりもわずかに速いことを示してList.Sortいます。私は彼のコードを変更して、ソートされていないデータを繰り返しソートし、OrderByほとんどの場合少し遅くなるようにしました。

さらに、OrderByテストはToArrayLinq列挙子の列挙を強制するために使用しますが、これは明らかに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
これは当てはまりますが、その問題の解決策があります。つまり、データを配列に保持し、キーの1つと値のもう1つの2つの配列を取るArray.Sortオーバーロードを使用します。キー配列を埋めるには、CalculateSalary n回呼び出します。これは、明らかにOrderByを使用する場合ほど便利ではありません。
phoog 2015年

14

一言で言えば :

リスト/配列Sort():

  • 不安定なソート。
  • インプレースで行われます。
  • Introsort / Quicksortを使用します。
  • カスタム比較は、比較子を提供することによって行われます。比較にコストがかかる場合は、OrderBy()(キーを使用できるようにする、以下を参照)よりも遅くなる可能性があります。

OrderBy / ThenBy():

  • 安定したソート。
  • インプレースではありません。
  • Quicksortを使用します。クイックソートは安定したソートではありません。トリックは次のとおりです。並べ替えるときに、2つの要素のキーが等しい場合、最初の順序(並べ替えの前に格納されている)を比較します。
  • キーを使用して(ラムダを使用)、値に基づいて要素を並べ替えることができます(例::) x => x.Id。ソートする前に、すべてのキーが最初に抽出されます。これにより、Sort()およびカスタム比較子を使用するよりもパフォーマンスが向上する可能性があります。

ソース: MDSN参照ソースdotnet / coreclrリポジトリ(GitHub)。

上記のステートメントの一部は、現在の.NETフレームワーク実装(4.7.2)に基づいています。将来変更される可能性があります。


0

OrderByおよびSortメソッドで使用されるアルゴリズムの複雑さを計算する必要があります。私が覚えているように、QuickSortの複雑さはn(log n)です。ここで、nは配列の長さです。

私もorderbyを検索しましたが、msdnライブラリでも情報を見つけることができませんでした。同じ値がなく、1つのプロパティのみに関連する並べ替えがない場合は、Sort()メソッドを使用することをお勧めします。そうでない場合は、OrderByを使用します。


1
現在のMSDNドキュメントによると、Sortは入力に基づいて3つの異なるソートアルゴリズムを使用しています。その中にはQuickSortがあります。[並べ替え()アルゴリズムの質問は(クイックソート)はこちら:stackoverflow.com/questions/2792074/...
トール

-1

私はそのorderbyを追加したいだけです。

どうして?私はこれを行うことができるので:

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)時間を達成できるとは思いません。
ケブマン2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.