.NETで文字列の配列をランダム化する最良の方法は何ですか?私の配列には約500個の文字列が含まれていArray
ますが、同じ文字列でランダムな順序で新しい文字列を作成したいと思います。
回答にC#の例を含めてください。
myArray.Shuffled().ToArray()
(またはmyArray.Shuffle()
、現在の配列を変更したい場合)
.NETで文字列の配列をランダム化する最良の方法は何ですか?私の配列には約500個の文字列が含まれていArray
ますが、同じ文字列でランダムな順序で新しい文字列を作成したいと思います。
回答にC#の例を含めてください。
myArray.Shuffled().ToArray()
(またはmyArray.Shuffle()
、現在の配列を変更したい場合)
回答:
.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]);
}
OrderBy
ソートキーを内部的にキャッシュしない限り、これには順序付き比較の推移的なプロパティに違反するという問題もあります。OrderBy
正しい結果を生成したデバッグモードの検証がある場合、理論的には例外がスローされる可能性があります。
次の実装では、Fisher-Yatesアルゴリズム、別名Knuth Shuffleを使用しています。コードの行数は増えますが、O(n)時間で実行され、所定の位置でシャッフルされるため、「ランダムで並べ替え」手法よりもパフォーマンスが向上します。いくつかの比較パフォーマンス測定については、こちらをご覧ください。私は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と同じくらい優れています。
array.Shuffle(new Random());
..のようにした方がいいのではないでしょうか。
new Random()
、現在のシステム時刻に基づいたシード値で初期化され、〜16msごとにのみ更新されるためです。
あなたはシャッフルアルゴリズムを探していますよね?
わかりました、これを行うには2つの方法があります。方法、そしてそれは機能するので、ロックとしてのダムですが、気にしないでください。
- 最初の配列の複製を作成しますが、各文字列には乱数をタグ付けする必要があります。
- 乱数に関して重複した配列をソートします。
このアルゴリズムはうまく機能しますが、乱数ジェネレーターが2つの文字列に同じ番号のタグを付ける可能性が低いことを確認してください。いわゆるバースデーパラドックスが原因で、これは予想よりも頻繁に発生します。その時間の複雑さはO(n log n)です。
これを再帰的アルゴリズムとして説明します。
サイズnの配列をシャッフルするには(範囲[0 .. n -1]のインデックス):
n = 0の 場合n > 0の 場合
- 何もしない
- (再帰的ステップ)配列の最初のn -1要素をシャッフルします
- 範囲[0 .. n -1]のランダムなインデックスxを選択します
- インデックスn -1の要素をインデックスxの要素と交換する
同等の反復処理とは、反復子を配列内でウォークしながらランダムな要素と入れ替えることですが、反復子が指す要素の後に要素を入れ替えることはできません。これは非常に一般的な間違いであり、シャッフルの偏りにつながります。
時間の複雑さはO(n)です。
このアルゴリズムは単純ですが効率的ではありません、O(N 2)。すべての「order by」アルゴリズムは通常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(N 2)である理由は微妙です。最後から順番に削除しない限り、List.RemoveAt()はO(N)操作です。
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>();
一連の文字列をシフトする必要があるため、配列のランダム化は集中的に行われます。配列からランダムに読み取るだけではどうですか。最悪の場合、getNextString()を使用してラッパークラスを作成することもできます。ランダム配列を作成する必要がある場合は、次のようにすることができます
for i = 0 -> i= array.length * 5
swap two strings in random places
* 5は任意です。
私の頭のてっぺんを考えて、あなたはこれを行うことができます:
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);
}
Jacco、あなたのソリューションはカスタムIComparerが安全ではありません。ソートルーチンは、正常に機能するために、コンパレータがいくつかの要件に準拠する必要があります。それらの最初に一貫性があります。同じオブジェクトのペアで比較演算子が呼び出された場合、常に同じ結果を返す必要があります。(比較は推移的でなければなりません)。
これらの要件を満たしていない場合、無限ループの可能性を含め、並べ替えルーチンで問題が発生する可能性があります。
ランダムな数値を各エントリに関連付けてからその値で並べ替えるソリューションに関しては、2つのエントリに同じ数値が割り当てられると出力のランダム性が損なわれるため、これらは出力に固有のバイアスをもたらします。(「安定した」ソートルーチンでは、入力の最初の方が出力の最初になります。Array.Sortはたまたま安定していませんが、クイックソートアルゴリズムによって実行されたパーティション分割に基づくバイアスがあります)。
必要なランダム性のレベルについて考えてみる必要があります。決定的な攻撃者から保護するために暗号レベルのランダム性が必要なポーカーサイトを運営している場合、曲のプレイリストをランダム化したいだけの人とは非常に異なる要件があります。
曲リストのシャッフルでは、シードされたPRNG(System.Randomなど)を使用しても問題はありません。ポーカーサイトの場合、これはオプションではなく、stackoverflowで誰もがあなたのためにやろうとするよりもずっと難しい問題について考える必要があります。(暗号化RNGの使用は始まりにすぎません。アルゴリズムがバイアスを導入しないこと、エントロピーの十分なソースがあること、およびその後のランダム性を損なう可能性のある内部状態を公開しないことを確認する必要があります)。
この投稿はすでにかなりよく回答されています-高速で公平な結果を得るには、フィッシャーイェーツのシャッフルのDurstenfeld実装を使用してください。いくつかの実装が投稿されていますが、実際には正しくないものもあります。
私はしばらく前に、この手法を使用して完全シャッフルと部分シャッフルを実装することについていくつか投稿しました(この2番目のリンクは、私が価値を追加したいと思っている場所です)、実装が公平であるかどうかを確認する方法についてのフォローアップ投稿、これは、任意のシャッフルアルゴリズムをチェックするために使用できます。2番目の投稿の最後で、乱数選択の単純な間違いの影響を確認できます。
わかりました、これは明らかに私の側からのバンプです(謝罪...)が、私はしばしば非常に一般的で暗号学的に強力な方法を使用します。
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回だけ生成および記憶されるため、この方法でもソートに関しては驚くことはありません。
これは、ここで提供される例に基づく完全に機能するコンソールソリューションです。
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;
}
}
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();
OLINQを使用する簡単な方法を次に示します。
// Input array
List<String> lst = new List<string>();
for (int i = 0; i < 500; i += 1) lst.Add(i.ToString());
// Output array
List<String> lstRandom = new List<string>();
// Randomize
Random rnd = new Random();
lstRandom.AddRange(from s in lst orderby rnd.Next(100) select s);
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;
}
sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();