C#でジェネリックリストの順序をランダム化するための最良の方法は何ですか?宝くじタイプのアプリケーション用にそれらを描画するために、私はランダムな順序を割り当てたいリストの中に75個の数字の有限集合を持っています。
Fisher-Yatesシャッフルに基づいた拡張方法で、任意の(I)List
をシャッフルする :
private static Random rng = new Random();
public static void Shuffle<T>(this IList<T> list)
{
int n = list.Count;
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
使用法:
List<Product> products = GetProducts();
products.Shuffle();
上記のコードは、スワップ候補を選択するために、非常に批判されているSystem.Randomメソッドを使用しています。高速ですが、ランダムではありません。シャッフルの中でより高い品質のランダム性が必要な場合は、System.Security.Cryptographyの乱数ジェネレータを使用してください。
using System.Security.Cryptography;
...
public static void Shuffle<T>(this IList<T> list)
{
RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
int n = list.Count;
while (n > 1)
{
byte[] box = new byte[1];
do provider.GetBytes(box);
while (!(box[0] < n * (Byte.MaxValue / n)));
int k = (box[0] % n);
n--;
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
簡単な比較が利用可能です このブログで (WayBack Machine)。
編集:数年前にこの答えを書いて以来、多くの人々が私の比較の大きな愚かな欠陥を指摘するために私にコメントしたり書いたりしてきました。それらはもちろん正しいです。意図したとおりに使用されていれば、System.Randomに問題はありません。上記の最初の例では、Shuffleメソッド内でrng変数をインスタンス化しています。このメソッドが繰り返し呼び出される場合は問題が発生します。以下は、SOで@westonから本日受信された本当に有用なコメントに基づく、固定された完全な例です。
Program.cs:
using System;
using System.Collections.Generic;
using System.Threading;
namespace SimpleLottery
{
class Program
{
private static void Main(string[] args)
{
var numbers = new List<int>(Enumerable.Range(1, 75));
numbers.Shuffle();
Console.WriteLine("The winning numbers are: {0}", string.Join(", ", numbers.GetRange(0, 5)));
}
}
public static class ThreadSafeRandom
{
[ThreadStatic] private static Random Local;
public static Random ThisThreadsRandom
{
get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
}
}
static class MyExtensions
{
public static void Shuffle<T>(this IList<T> list)
{
int n = list.Count;
while (n > 1)
{
n--;
int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
}
アイテムを完全にランダムな順序で並べ替えるだけ(リスト内のアイテムを混在させる)だけが必要な場合は、guidでアイテムを並べ替えるシンプルで効果的なコードをお勧めします。
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
私はここでこの単純なアルゴリズムのすべての不格好なバージョンに少し驚いています。 Fisher-Yates(またはKnuth shuffle)は少しトリッキーですが非常にコンパクトです。もしあなたがウィキペディアに行くなら、あなたは逆にfor-loopを持っているこのアルゴリズムのバージョンを見るでしょう、そして多くの人が本当にそれが逆になっている理由を本当に理解していないようです。主な理由は、このバージョンのアルゴリズムは、あなたが自由に使える乱数生成器Random(n)
が次の2つの性質を持つと仮定しているからです。
しかし、.Net乱数ジェネレータは#2プロパティを満たしません。 Random.Next(n)
は、代わりに0からn-1までの数値を返します。 forループを逆に使用しようとすると、Random.Next(n+1)
を呼び出す必要があるでしょう。
しかし、.Net乱数発生器にはaからb-1までを返す別のNice関数Random.Next(a,b)
があります。これは通常、通常のforループを持つこのアルゴリズムの実装と完全に一致しています。それで、さらに面倒なことなしに、これは正しい、効率的でコンパクトな実装です。
public static void Shuffle<T>(this IList<T> list, Random rnd)
{
for(var i=0; i < list.Count - 1; i++)
list.Swap(i, rnd.Next(i, list.Count));
}
public static void Swap<T>(this IList<T> list, int i, int j)
{
var temp = list[i];
list[i] = list[j];
list[j] = temp;
}
IEnumerableの拡張メソッド
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
public static List<T> Randomize<T>(List<T> list)
{
List<T> randomizedList = new List<T>();
Random rnd = new Random();
while (list.Count > 0)
{
int index = rnd.Next(0, list.Count); //pick a random item from the master list
randomizedList.Add(list[index]); //place it at the end of the randomized list
list.RemoveAt(index);
}
return randomizedList;
}
_ edit _ RemoveAt
は私の以前のバージョンの弱点です。この解決策はそれを克服します。
public static IEnumerable<T> Shuffle<T>(
this IEnumerable<T> source,
Random generator = null)
{
if (generator == null)
{
generator = new Random();
}
var elements = source.ToArray();
for (var i = elements.Length - 1; i >= 0; i--)
{
var swapIndex = generator.Next(i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
}
}
オプションのRandom generator
に注意してください。もしRandom
の基本フレームワーク実装があなたのニーズに対してスレッドセーフでも暗号学的にも十分でないなら、あなたは実装を操作に注入することができます。
スレッドセーフで暗号学的に強力なRandom
実装のための適切な実装は、この答えで見つけることができます。
これがアイデアです。IListを(うまくいけば)効率的な方法で拡張します。
public static IEnumerable<T> Shuffle<T>(this IList<T> list) { var choices = Enumerable.Range(0, list.Count).ToList(); var rng = new Random(); for(int n = choices.Count; n > 1; n--) { int k = rng.Next(n); yield return list[choices[k]]; choices.RemoveAt(k); } yield return list[choices[0]]; }
あなたはそれをこの簡単な拡張方法を使って達成することができます
public static class IEnumerableExtensions
{
public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
{
Random r = new Random();
return target.OrderBy(x=>(r.Next()));
}
}
そして、あなたは次のことをすることによってそれを使うことができます
// use this on any collection that implements IEnumerable!
// List, Array, HashSet, Collection, etc
List<string> myList = new List<string> { "hello", "random", "world", "foo", "bar", "bat", "baz" };
foreach (string s in myList.Randomize())
{
Console.WriteLine(s);
}
アイデアは、アイテムとランダムな順序で匿名オブジェクトを取得してから、この順序と戻り値でアイテムを並べ替えることです。
var result = items.Select(x => new { value = x, order = rnd.Next() })
.OrderBy(x => x.order).Select(x => x.value).ToList()
オリジナルを変更しないことが望ましい場合は、これが私の推奨するシャッフル方法です。これは Fisher – Yatesの "裏返し"アルゴリズムの変種です これはあらゆる列挙可能なシーケンスで動作します(source
の長さは最初から知る必要はありません)。
public static IList<T> NextList<T>(this Random r, IEnumerable<T> source)
{
var list = new List<T>();
foreach (var item in source)
{
var i = r.Next(list.Count + 1);
if (i == list.Count)
{
list.Add(item);
}
else
{
var temp = list[i];
list[i] = item;
list.Add(temp);
}
}
return list;
}
このアルゴリズムは、0
からlength - 1
までの範囲を割り当て、ランダムに選択されたインデックスを最後のインデックスと交換して、すべてのインデックスが一度だけ選択されるまでインデックスをランダムに使い果たすことによっても実装できます。上記のコードはまったく同じことを実現しますが、追加の割り当ては必要ありません。これはかなりきれいです。
Random
クラスに関しては、それは汎用の数値ジェネレータです(そして私が宝くじを走っていたら私は別の何かを使うことを考えます)。デフォルトでは時間ベースのシード値にも依存します。問題を少し軽減するには、Random
クラスにRNGCryptoServiceProvider
をシードするか、これに類似した方法でRNGCryptoServiceProvider
を使用して、一様に選択されたランダムなdouble浮動小数点値を生成できますランダム性の原因の性質.
var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);
ランダムな倍精度(0から1の間のみ)を生成するのは、整数解にスケールするために使用することです。ランダムな二重x
に基づいてリストから何かを選ぶ必要があるなら、それは常に0 <= x && x < 1
になることは簡単です。
return list[(int)(x * list.Count)];
楽しい!
固定数(75)がある場合は、75個の要素を持つ配列を作成してからリストを列挙し、要素を配列内のランダムな位置に移動することができます。 Fisher-Yatesシャッフル を使用して、リスト番号から配列インデックスへのマッピングを生成できます。
私はいつも使う:
var list = new List<T> ();
fillList (list);
var randomizedList = new List<T> ();
var rnd = new Random ();
while (list.Count != 0)
{
var index = rnd.Next (0, list.Count);
randomizedList.Add (list [index]);
list.RemoveAt (index);
}
2つのLists
を使用しても構わない場合は、これがおそらく最も簡単な方法ですが、おそらく最も効率的または予測不可能な方法ではありません。
List<int> xList = new List<int>() { 1, 2, 3, 4, 5 };
List<int> deck = new List<int>();
foreach (int xInt in xList)
deck.Insert(random.Next(0, deck.Count + 1), xInt);
並べ替えによってそれを行うことができ、ランダムを使用して比較が行われます
var Rand = new Random();
var list = new List<T>();
list.Sort((a,b)=>Rand.Next(-1,2));
public Deck(IEnumerable<Card> initialCards)
{
cards = new List<Card>(initialCards);
public void Shuffle()
}
{
List<Card> NewCards = new List<Card>();
while (cards.Count > 0)
{
int CardToMove = random.Next(cards.Count);
NewCards.Add(cards[CardToMove]);
cards.RemoveAt(CardToMove);
}
cards = NewCards;
}
public IEnumerable<string> GetCardNames()
{
string[] CardNames = new string[cards.Count];
for (int i = 0; i < cards.Count; i++)
CardNames[i] = cards[i].Name;
return CardNames;
}
Deck deck1;
Deck deck2;
Random random = new Random();
public Form1()
{
InitializeComponent();
ResetDeck(1);
ResetDeck(2);
RedrawDeck(1);
RedrawDeck(2);
}
private void ResetDeck(int deckNumber)
{
if (deckNumber == 1)
{
int numberOfCards = random.Next(1, 11);
deck1 = new Deck(new Card[] { });
for (int i = 0; i < numberOfCards; i++)
deck1.Add(new Card((Suits)random.Next(4),(Values)random.Next(1, 14)));
deck1.Sort();
}
else
deck2 = new Deck();
}
private void reset1_Click(object sender, EventArgs e) {
ResetDeck(1);
RedrawDeck(1);
}
private void shuffle1_Click(object sender, EventArgs e)
{
deck1.Shuffle();
RedrawDeck(1);
}
private void moveToDeck1_Click(object sender, EventArgs e)
{
if (listBox2.SelectedIndex >= 0)
if (deck2.Count > 0) {
deck1.Add(deck2.Deal(listBox2.SelectedIndex));
}
RedrawDeck(1);
RedrawDeck(2);
}
これは、シャッフルされた値のバイト配列を返す効率的なShufflerです。必要以上にシャッフルすることはありません。以前中断したところから再開できます。私の実際の実装(図示せず)は、ユーザー指定の置換シャッフラーを可能にするMEFコンポーネントです。
public byte[] Shuffle(byte[] array, int start, int count)
{
int n = array.Length - start;
byte[] shuffled = new byte[count];
for(int i = 0; i < count; i++, start++)
{
int k = UniformRandomGenerator.Next(n--) + start;
shuffled[i] = array[k];
array[k] = array[start];
array[start] = shuffled[i];
}
return shuffled;
}
`
List<T> OriginalList = new List<T>();
List<T> TempList = new List<T>();
Random random = new Random();
int length = OriginalList.Count;
int TempIndex = 0;
while (length > 0) {
TempIndex = random.Next(0, length); // get random value between 0 and original length
TempList.Add(OriginalList[TempIndex]); // add to temp list
OriginalList.RemoveAt(TempIndex); // remove from original list
length = OriginalList.Count; // get new list <T> length.
}
OriginalList = new List<T>();
OriginalList = TempList; // copy all items from temp list to original list.
受け入れられた答え の簡単な修正は、その場で作業する代わりに新しいリストを返し、他の多くのLinqメソッドがするように、より一般的なIEnumerable<T>
を受け入れます。
private static Random rng = new Random();
/// <summary>
/// Returns a new list where the elements are randomly shuffled.
/// Based on the Fisher-Yates shuffle, which has O(n) complexity.
/// </summary>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list) {
var source = list.ToList();
int n = source.Count;
var shuffled = new List<T>(n);
shuffled.AddRange(source);
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = shuffled[k];
shuffled[k] = shuffled[n];
shuffled[n] = value;
}
return shuffled;
}
これを行うためのスレッドセーフな方法は次のとおりです。
public static class EnumerableExtension
{
private static Random globalRng = new Random();
[ThreadStatic]
private static Random _rng;
private static Random rng
{
get
{
if (_rng == null)
{
int seed;
lock (globalRng)
{
seed = globalRng.Next();
}
_rng = new Random(seed);
}
return _rng;
}
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items)
{
return items.OrderBy (i => rng.Next());
}
}