2010-12-10 5 views
4

これら二つの質問は、IEnumerableをシャッフルするために同様のalgorthimsを与える:ここで IEnumerableをシャッフルするためのこれらの2つのアルゴリズムのパフォーマンスに違いはありますか?

  • C#: Is using Random and OrderBy a good shuffle algorithm?
  • Can you enumerate a collection in C# out of order?は2つの方法ですサイド・バイ・サイド:彼らはある

    public static IEnumerable<T> Shuffle1<T> (this IEnumerable<T> source) 
    { 
        Random random = new Random(); 
        T [] copy = source.ToArray(); 
    
        for (int i = copy.Length - 1; i >= 0; i--) { 
         int index = random.Next (i + 1); 
         yield return copy [index]; 
         copy [index] = copy [i]; 
        } 
    } 
    
    
    public static IEnumerable<T> Shuffle2<T> (this IEnumerable<T> source) 
    { 
        Random random = new Random(); 
        List<T> copy = source.ToList(); 
    
        while (copy.Count > 0) { 
         int index = random.Next (copy.Count); 
         yield return copy [index]; 
         copy.RemoveAt (index); 
        } 
    } 
    

    基本的に同一ですが、Listを使用し、1つは配列を使用します。概念的には、第2のものは私には明らかです。しかし、配列を使用することで得られる大きなパフォーマンス上の利点はありますか?たとえBig-Oの時間が同じであっても、それが数倍速ければ、それは顕著な違いを作り出すことができる。

+5

あなたは両方の方法でコードを書いています。どちらが速いかを知りたければ*コードを実行してください。なぜ多くの人々が「これらのうちのどれが速いのですか」と投稿するのは、私にとって謎です。質問。確かに、あなたの机の上に座っている間にインターネットに尋ね、他の誰かがあなたのためにそれを実行するのを待つか、推測するよりも、両方の方法でコードを実行するだけで、より速く正確になる必要があります。 –

+0

@Matthewは常にあなた自身のベンチマークを掲示し、その理由をよりよく尋ねます。 – nawfal

答えて

7

2番目のバージョンは、おそらくRemoveAtのために少し遅くなります。リストは実際に要素を追加すると拡大する配列なので、途中で挿入と削除が遅くなります(実際にはRemoveAtにO(n)の複雑さがあります)。

とにかく、プロファイラを使用して両方の方法を比較するのが最も良い方法です。

+2

+1:私は 'List .RemoveAt'がO(n)であると付け加えます。ここでnはCount - indexで、終わりよりも先頭から削除するまでに時間がかかることを意味します。インデックスの後にはすべての値を移動する必要があります。これをプロファイリングする必要はなく、2番目のものは遅くすることしかできません。最初は "Fisher-Yates Shuffle"であり、* Data Structures 101 *の知識を持っている人なら誰でも知ることができます。 – Tergiver

+2

Linqpadでタイマーカウントを実行すると、300k整数のリストを使って、最初のものは平均0.1秒かかり、2番目のものは22.5秒かかった(私は3GBのPentium Dで2GBのRAMでWindows XPを走らせている)。私はリストを初期化するタイミングを追加する必要があり、シャッフルから各要素を取り出し、ハッシュセットに配置するforeachループを使用しました。 – pstrjds

2

最初のアルゴリズムは、各反復でO(1)スワップを実行するループを持つため、O(n)です。 2番目のアルゴリズムは、各反復でO(n)RemoveAt操作を実行するので、O(n^2)です。さらに、リストのインデクサは配列のインデックスよりも遅くなります。これは、前者がメソッド呼び出しであり、後者がIL命令であるためです。

したがって、2つのうちの最初のものは、より高速になる可能性があります。それは、あなたがパフォーマンスの後にいる場合、なぜ結果を生み出すかと思いますか?すでに配列に変換されていますので、その場所をシャッフルして配列を直接返します(または、変更する人が気になる場合はReadOnlyCollection<T>で囲んでください)。

両方のメソッドには、Randomの動作が複数のスレッドで使用されている場合、定義されていないため、おそらくthread-safe random number generatorを使用する必要があるというバグがあります。

+1

すべての要素が必要ない場合は、Yieldingが良いです。最初の3つの無作為な要素が1000のコレクションから欲しいとします。これは、1000要素すべてをシャッフルして最初の3つを選択するよりもはるかに速くなります。 –

+2

@Matthew - しかし、パフォーマンスが問題であれば、2つのメソッドを作成します。完全シャッフル(シャッフル)で高速で、ランダムサンプル(サンプルまたはテイクランダム)を取るために最適化されたものです。また、シャッフルメソッドが必ずしも怠け者であるとは思わないので、おそらくもう少し自己文書化していると思います。 –

+0

FYI、それは最も確かにO(N^2)です。リストが縮小するにつれて、 'RemoveAt'は反復ごとにN/2個のアイテムの平均で動作しなければならないので、' RemoveAt'の実行時間は依然としてNに比例します。 – mquander

2

最初のものはコンパイルされませんが、あなたが列挙可能なものを正式化しようとしていることは明らかですが、その後Fisher-Yatesを実装します。それはおそらく正しいアプローチであり、前に配列をシャッフルしたことがある人にとっては不明瞭ではありません。 RemoveAtを使用して2番目のは、他のコメント者によって記載された理由で悪いです。

EDIT:あなたのトップインプリメンテーションは正しいように見えますが、これは良い方法です。

+1

コンパイルエラーを修正しました。私はリンクした2つの質問から変数名をできるだけ近く一致させるように変数名を変更しましたが、私はそれを逃しました。 –

関連する問題