web-dev-qa-db-ja.com

.NETで配列をランダム化する最良の方法

.NETで文字列の配列をランダム化する最良の方法は何ですか?私の配列には約500個の文字列が含まれており、同じ文字列でランダムな順序で新しいArrayを作成したいと思います。

回答にC#の例を含めてください。

121
Mats

.NET 3.5を使用している場合は、次のIEnumerableのクールさを使用できます(C#ではなくVB.NETですが、アイデアは明確なはずです...):

Random rnd=new Random();
string[] MyRandomArray = MyArray.OrderBy(x => rnd.Next()).ToArray();    

編集:OK、これは対応するVB.NETコードです:

Dim rnd As New System.Random
Dim MyRandomArray = MyArray.OrderBy(Function() rnd.Next()).ToArray()

2番目の編集、System.Randomは時間ベースのシーケンスを返すため「スレッドセーフではない」および「おもちゃのアプリにのみ適している」という発言への応答:私の例で使用されているように、Random()は完全にスレッドセーフです配列をランダム化するルーチンの再入力を許可しています。その場合、データを破損しないようにlock (MyRandomArray)のようなものが必要になり、rndを保護しますまあ。

また、エントロピーのソースとしてのSystem.Randomはそれほど強力ではないことを十分に理解する必要があります。 MSDNドキュメント に記載されているように、セキュリティ関連の操作を行う場合は、System.Security.Cryptography.RandomNumberGeneratorから派生したものを使用する必要があります。例えば:

using System.Security.Cryptography;

...

RNGCryptoServiceProvider rnd = new RNGCryptoServiceProvider();
string[] MyRandomArray = MyArray.OrderBy(x => GetNextInt32(rnd)).ToArray();

...

static int GetNextInt32(RNGCryptoServiceProvider rnd)
    {
        byte[] randomInt = new byte[4];
        rnd.GetBytes(randomInt);
        return Convert.ToInt32(randomInt[0]);
    }
154
mdb

次の実装では、 Fisher-Yatesアルゴリズム 別名Knuth Shuffleを使用します。 O(n)時間で実行され、所定の位置でシャッフルされるため、「ランダムに並べ替える」手法よりもパフォーマンスが向上しますが、コードの行は増えます。いくつかの比較パフォーマンス測定については、 here を参照してください。 System.Randomを使用しましたが、これは非暗号化の目的には適しています。*

static class RandomExtensions
{
    public static void Shuffle<T> (this Random rng, T[] array)
    {
        int n = array.Length;
        while (n > 1) 
        {
            int k = rng.Next(n--);
            T temp = array[n];
            array[n] = array[k];
            array[k] = temp;
        }
    }
}

使用法:

var array = new int[] {1, 2, 3, 4};
var rng = new Random();
rng.Shuffle(array);
rng.Shuffle(array); // different order from first call to Shuffle

*長い配列の場合、(非常に大きい)順列の数を等しくするためには、十分なエントロピーを生成するために、各スワップの多くの反復を通じて疑似乱数ジェネレーター(PRNG)を実行する必要があります。 500要素の配列の場合、可能な500のごく一部です!順列は、PRNGを使用して取得できます。それでも、Fisher-Yatesアルゴリズムは偏りがないため、シャッフルは使用するRNGと同じくらい良好です。

185
Matt Howells

シャッフルアルゴリズムを探していますか?

さて、これを行うには2つの方法があります。方法、およびそれが機能するので誰も気にしない岩として愚かな方法。

愚かな方法

  • 最初の配列の複製を作成しますが、各文字列に乱数を付ける必要があります。
  • 乱数に関して複製配列をソートします。

このアルゴリズムはうまく機能しますが、乱数ジェネレーターが2つの文字列に同じ番号をタグ付けしないようにしてください。いわゆる Birthday Paradox のため、これは予想よりも頻繁に発生します。その時間の複雑さはO(nlogn)です。

賢い方法

これを再帰アルゴリズムとして説明します。

サイズnの配列をシャッフルするには(範囲[0 ..n-1]のインデックス):

ifn= 0
  • 何もしない
ifn> 0
  • (再帰ステップ)配列の最初のn-1要素をシャッフルする
  • 範囲[0 ..n-1]で、ランダムインデックスxを選択します。
  • インデックスn-1の要素をインデックスxの要素と交換します

反復的な同等の方法は、イテレータを配列内でウォークして、ランダム要素と交換することですが、要素afterと交換できないことに注意してくださいイテレータが指すもの。これは非常によくある間違いであり、偏ったシャッフルにつながります。

時間の複雑さはO(n)です。

16
Pitarou

このアルゴリズムは単純ですが、効率的ではありません、O(N2)。すべての「順序」アルゴリズムは、通常O(N log N)です。おそらく、数十万要素以下では違いはありませんが、大きなリストの場合は違います。

var stringlist = ... // add your values to stringlist

var r = new Random();

var res = new List<string>(stringlist.Count);

while (stringlist.Count >0)
{
   var i = r.Next(stringlist.Count);
   res.Add(stringlist[i]);
   stringlist.RemoveAt(i);
}

O(N2)は微妙です: List.RemoveAt() は、最後から順番に削除しない限り、O(N)操作です。

8
Sklivvz

Matt Howellsから拡張メソッドを作成することもできます。例。

   namespace System
    {
        public static class MSSystemExtenstions
        {
            private static Random rng = new Random();
            public static void Shuffle<T>(this T[] array)
            {
                rng = new Random();
                int n = array.Length;
                while (n > 1)
                {
                    int k = rng.Next(n);
                    n--;
                    T temp = array[n];
                    array[n] = array[k];
                    array[k] = temp;
                }
            }
        }
    }

次に、次のように使用できます。

        string[] names = new string[] {
                "Aaron Moline1", 
                "Aaron Moline2", 
                "Aaron Moline3", 
                "Aaron Moline4", 
                "Aaron Moline5", 
                "Aaron Moline6", 
                "Aaron Moline7", 
                "Aaron Moline8", 
                "Aaron Moline9", 
            };
        names.Shuffle<string>();
4
Aaron

配列のランダム化は、一連の文字列を移動する必要があるため集中的に行われます。配列からランダムに読み取らないのはなぜですか?最悪の場合、getNextString()を使用してラッパークラスを作成することもできます。本当にランダム配列を作成する必要がある場合は、次のようなことができます

for i = 0 -> i= array.length * 5
   swap two strings in random places

* 5は任意です。

1
stimms

私の頭のてっぺんから考えて、あなたはこれを行うことができます:

public string[] Randomize(string[] input)
{
  List<string> inputList = input.ToList();
  string[] output = new string[input.Length];
  Random randomizer = new Random();
  int i = 0;

  while (inputList.Count > 0)
  {
    int index = r.Next(inputList.Count);
    output[i++] = inputList[index];
    inputList.RemoveAt(index);
  }

  return (output);
}
1
Tarsier

同じ長さのランダムな浮動小数点数または整数の配列を生成します。その配列をソートし、ターゲット配列で対応するスワップを実行します。

これにより、真に独立したソートが生成されます。

0
Nick
Random r = new Random();
List<string> list = new List(originalArray);
List<string> randomStrings = new List();

while(list.Count > 0)
{
int i = r.Random(list.Count);
randomStrings.Add(list[i]);
list.RemoveAt(i);
}
0
nullDev

これは、 ここに記載されている例 に基づいた完全に機能するコンソールソリューションです。

class Program
{
    static string[] words1 = new string[] { "brown", "jumped", "the", "fox", "quick" };

    static void Main()
    {
        var result = Shuffle(words1);
        foreach (var i in result)
        {
            Console.Write(i + " ");
        }
        Console.ReadKey();
    }

   static string[] Shuffle(string[] wordArray) {
        Random random = new Random();
        for (int i = wordArray.Length - 1; i > 0; i--)
        {
            int swapIndex = random.Next(i + 1);
            string temp = wordArray[i];
            wordArray[i] = wordArray[swapIndex];
            wordArray[swapIndex] = temp;
        }
        return wordArray;
    }         
}
0
usefulBee

このコードは、配列内の数値をシャッフルします。

using System;

// ...
    static void Main(string[] args)
    {
        Console.ForegroundColor = ConsoleColor.Cyan;
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        Shuffle(numbers);

        for (int i = 0; i < numbers.Length; i++)
            Console.Write(numbers[i] + (i < numbers.Length - 1 ? ", " : null));
        Console.WriteLine();

        string[] words = { "this", "is", "a", "string", "of", "words" };
        Shuffle(words);

        for (int i = 0; i < words.Length; i++)
            Console.Write(words[i] + (i < words.Length - 1 ? ", " : null));
        Console.WriteLine();

        Console.ForegroundColor = ConsoleColor.Gray;
        Console.Write("Press any key to quit . . . ");
        Console.ReadKey(true);
    }

    static void Shuffle<T>(T[] array)
    {
        Random random = new Random();

        for (int i = 0; i < array.Length; i++)
        {
            T temporary = array[i];
            int intrandom = random.Next(array.Length);
            array[i] = array[intrandom];
            array[intrandom] = temporary;
        }
    }
0
Bilal

複雑なアルゴリズムは必要ありません。

たった1つの単純な行:

Random random = new Random();
array.ToList().Sort((x, y) => random.Next(-1, 1)).ToArray();

最初にArrayを使用しない場合は、最初にListListに変換する必要があることに注意してください。

また、これは非常に大きな配列では効率的ではないことに注意してください!それ以外の場合は、クリーンでシンプルです。

0
bytecode77

この投稿には既にかなり回答があります。Fisher-YatesシャッフルのDurstenfeld実装を使用すると、高速で公平な結果が得られます。いくつかの実装も投稿されていますが、実際にはいくつかの実装が間違っています。

この手法を使用して完全シャッフルと部分シャッフルを実装する 、および(この2番目のリンクは値を追加することを望んでいます)について フォローアップ実装がバイアスされていないかどうかを確認する方法について投稿してください 。これは、シャッフルアルゴリズムを確認するために使用できます。 2番目の投稿の最後に、乱数の選択における単純な間違いの影響を確認できます。

0
Greg Beech

[OK]を、これは明らかに私の側からのバンプです(謝罪...)が、私はしばしば非常に一般的で暗号学的に強力な方法を使用します。

public static class EnumerableExtensions
{
    static readonly RNGCryptoServiceProvider RngCryptoServiceProvider = new RNGCryptoServiceProvider();
    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> enumerable)
    {
        var randomIntegerBuffer = new byte[4];
        Func<int> Rand = () =>
                             {
                                 RngCryptoServiceProvider.GetBytes(randomIntegerBuffer);
                                 return BitConverter.ToInt32(randomIntegerBuffer, 0);
                             };
        return from item in enumerable
               let rec = new {item, rnd = Rand()}
               orderby rec.rnd
               select rec.item;
    }
}

Shuffle()は任意のIEnumerableの拡張であるため、たとえば、リスト内の0から1000までの数字をランダムな順序で取得できます。

Enumerable.Range(0,1000).Shuffle().ToList()

ソート値はシーケンスの要素ごとに1回だけ生成され、記憶されるため、このメソッドはソートに関して驚くこともありません。

0
jlarsson

Jacco、カスタムIComparerであるソリューションは安全ではありません。ソートルーチンでは、比較器が適切に機能するためにいくつかの要件に準拠する必要があります。それらの最初は一貫性です。比較子が同じオブジェクトのペアで呼び出される場合、常に同じ結果を返す必要があります。 (比較も推移的でなければなりません)。

これらの要件を満たさない場合、無限ループの可能性など、ソートルーチンで問題が発生する可能性があります。

ランダムな数値を各エントリに関連付けてからその値でソートするソリューションについては、2つのエントリに同じ数値が割り当てられるたびに出力のランダム性が損なわれるため、これらは出力に固有のバイアスをもたらします。 (「安定した」ソートルーチンでは、入力の最初のいずれかが出力の最初になります。Array.Sortはたまたま安定していませんが、Quicksortアルゴリズムによって行われたパーティション分割に基づくバイアスがあります)。

必要なランダム性のレベルについて考える必要があります。決定的な攻撃者から保護するために暗号化レベルのランダム性が必要なポーカーサイトを運営している場合、曲のプレイリストをランダム化するだけの人とは非常に異なる要件があります。

曲リストシャッフルの場合、シードされたPRNG(System.Randomなど)を使用しても問題ありません。ポーカーサイトの場合、これはオプションではなく、誰もがstackoverflowであなたのためにやろうとするよりもはるかに難しい問題について考える必要があります。 (暗号化されたRNGの使用は始まりにすぎません。アルゴリズムがバイアスを導入しないこと、十分なエントロピーのソースがあること、その後のランダム性を損なうような内部状態を公開しないことを確認する必要があります)。

0
Andrew
        int[] numbers = {0,1,2,3,4,5,6,7,8,9};
        List<int> numList = new List<int>();
        numList.AddRange(numbers);

        Console.WriteLine("Original Order");
        for (int i = 0; i < numList.Count; i++)
        {
            Console.Write(String.Format("{0} ",numList[i]));
        }

        Random random = new Random();
        Console.WriteLine("\n\nRandom Order");
        for (int i = 0; i < numList.Capacity; i++)
        {
            int randomIndex = random.Next(numList.Count);
            Console.Write(String.Format("{0} ", numList[randomIndex]));
            numList.RemoveAt(randomIndex);
        }
        Console.ReadLine();
0
Nitesh Katare