web-dev-qa-db-ja.com

C#で整数のリストをランダムに「ソート」(シャッフル)する最も効率的な方法

可能な限り最も効率的な方法で整数のリスト(0-1999)をランダムに「ソート」する必要があります。何か案は?

現在、私は次のようなことをしています:

bool[] bIndexSet = new bool[iItemCount];

for (int iCurIndex = 0; iCurIndex < iItemCount; iCurIndex++)
{
    int iSwapIndex = random.Next(iItemCount);
    if (!bIndexSet[iSwapIndex] && iSwapIndex != iCurIndex)
    {
        int iTemp = values[iSwapIndex];
        values[iSwapIndex] = values[iCurIndex];
        values[iCurIndex] = values[iSwapIndex];
        bIndexSet[iCurIndex] = true;
        bIndexSet[iSwapIndex] = true;
    }
}
51
Carl

優れた線形時間シャッフルアルゴリズムは Fisher-Yatesシャッフル です。

提案されたアルゴリズムで見つかる問題の1つは、シャッフルの終わり近くで、ループがまだスワップされていないランダムに選択された要素を探すのに多くの時間を費やすことです。スワップする最後の要素に到達すると、これには時間がかかります。

また、ソートする要素の数が奇数の場合、アルゴリズムは終了しないようです。

53
Greg Hewgill
static Random random = new Random();

public static IEnumerable<T> RandomPermutation<T>(IEnumerable<T> sequence)
{
    T[] retArray = sequence.ToArray();


    for (int i = 0; i < retArray.Length - 1; i += 1)
    {
        int swapIndex = random.Next(i, retArray.Length);
        if (swapIndex != i) {
            T temp = retArray[i];
            retArray[i] = retArray[swapIndex];
            retArray[swapIndex] = temp;
        }
    }

    return retArray;
}

iEnumerableを実装するリストまたは他のオブジェクトを処理するように変更

30
ICR

これから拡張メソッドを作成して、IListコレクションのRandom列挙子を取得できます。

class Program
{
    static void Main(string[] args)
    {
        IList<int> l = new List<int>();
        l.Add(7);
        l.Add(11);
        l.Add(13);
        l.Add(17);

        foreach (var i in l.AsRandom())
            Console.WriteLine(i);

        Console.ReadLine();
    }
}


public static class MyExtensions
{
    public static IEnumerable<T> AsRandom<T>(this IList<T> list)
    {
        int[] indexes = Enumerable.Range(0, list.Count).ToArray();
        Random generator = new Random();

        for (int i = 0; i < list.Count; ++i )
        {
            int position = generator.Next(i, list.Count);

            yield return list[indexes[position]];

            indexes[position] = indexes[i];
        }
    }
}   

これは、ランダムに列挙したいリストのインデックスで逆Fisher-Yatesシャッフルを使用します。サイズはわずかですが(4 * list.Countバイトを割り当てます)、O(n)で実行されます。

18
foson

グレッグが指摘したように、 Fisher-Yates shuffle が最良のアプローチです。ウィキペディアのアルゴリズムの実装は次のとおりです。

public static void shuffle (int[] array)
{
   Random rng = new Random();   // i.e., Java.util.Random.
   int n = array.length;        // The number of items left to shuffle (loop invariant).
   while (n > 1)
   {
      int k = rng.nextInt(n);  // 0 <= k < n.
      n--;                     // n is now the last pertinent index;
      int temp = array[n];     // swap array[n] with array[k] (does nothing if k == n).
      array[n] = array[k];
      array[k] = temp;
   }
}

上記の実装は、十分にランダムで公平な結果を提供するRandom.nextInt(int)に依存しています

5
Micah

効率係数はわかりませんが、ArrayListを使用することに反対しない場合は、次のようなものを使用しました。

private ArrayList ShuffleArrayList(ArrayList source)
{
    ArrayList sortedList = new ArrayList();
    Random generator = new Random();

    while (source.Count > 0)
    {
        int position = generator.Next(source.Count);
        sortedList.Add(source[position]);
        source.RemoveAt(position);
    }

    return sortedList;
}

これを使用すると、中間スワッピングについて心配する必要はありません。

4
Joseph Ferris

効率を向上させるために、スワップされたことを示すブール値ではなく、スワップされた値/インデックスのセットを保持できます。残りのプールからランダム化されたスワップインデックスを選択します。プールが0の場合、または最初のリストで作成した場合、完了です。ランダムスワップインデックス値を選択しようとする可能性はありません。

スワップを行うときは、プールからそれらを削除するだけです。

あなたが見ているデータのサイズについては、大したことではありません。

2
Tim
itemList.OrderBy(x=>Guid.NewGuid()).Take(amount).ToList()
2
Dmitry

ICRの答えは非常に高速ですが、結果の配列は正常に配布されません。正規分布が必要な場合は、次のコードを使用します。

    public static IEnumerable<T> RandomPermutation<T>(this IEnumerable<T> sequence, int start,int end)
    {
        T[] array = sequence as T[] ?? sequence.ToArray();

        var result = new T[array.Length];

        for (int i = 0; i < start; i++)
        {
            result[i] = array[i];
        }
        for (int i = end; i < array.Length; i++)
        {
            result[i] = array[i];
        }

        var sortArray=new List<KeyValuePair<double,T>>(array.Length-start-(array.Length-end));
        lock (random)
        {
            for (int i = start; i < end; i++)
            {
                sortArray.Add(new KeyValuePair<double, T>(random.NextDouble(), array[i]));
            }
        }

        sortArray.Sort((i,j)=>i.Key.CompareTo(j.Key));

        for (int i = start; i < end; i++)
        {
            result[i] = sortArray[i - start].Value;
        }

        return result;
    }

私のテストでは、このアルゴリズムはICRが提供するものよりも6倍遅いことに注意してください。ただし、これは正規の結果分布を得るために思いつく唯一の方法です。

1
Arsen Zahray

どうですか:

System.Array.Sort(arrayinstance, RandomizerMethod);
...
//any evoluated random class could do it !
private static readonly System.Random Randomizer = new System.Random();

private static int RandomizerMethod<T>(T x, T y)
    where T : IComparable<T>
{
    if (x.CompareTo(y) == 0)
        return 0;

    return Randomizer.Next().CompareTo(Randomizer.Next());
}

出来上がり!

0
que dal

このようなものはありませんか?

var list = new[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
var random = new Random();
list.Sort((a,b)=>random.Next(-1,1));
0
Dan

ミカの答えでは、最後の2行を入れ替える必要があると思います。したがって、コードは次のようになります

 public static void shuffle(int[] array) {
        Random rng = new Random();   // i.e., Java.util.Random.
        int n = array.Length;        // The number of items left to shuffle (loop invariant).
        while (n > 1) {
            int k = rng.Next(n);  // 0 <= k < n.
            n--;                     // n is now the last pertinent index;
            int temp = array[n];     // swap array[n] with array[k] (does nothing if k == n).
            array[n] = array[k];
            array[k] = temp;

        }
    }
0
nKnight