ソースを 1 回だけ反復し、一時的に格納するためにメモリを割り当てる必要がないメソッドを作成するには、反復したアイテムの数を数え、現在のアイテムが結果になる確率を決定します。
public T RandomChoice<T> (IEnumerable<T> source) {
Random rnd = new Random();
T result = default(T);
int cnt = 0;
foreach (T item in source) {
cnt++;
if (rnd.Next(cnt) == 0) {
result = item;
}
}
return result;
}
最初のアイテムにいるとき、それが使用される確率は 1/1 です (これまでに見た唯一のアイテムであるため)。2 番目のアイテムにいる場合、最初のアイテムを置き換える確率は 1/2 です。
dasblinkenlightが指摘したように、アイテムを選択するための単一の乱数ではなく、アイテムごとに1つの乱数を作成するため、これは当然、もう少し多くのCPUを使用します。Dan Tao が提案したように、ソースが を実装しているかどうかを確認IList<T>
し、その機能を使用してコレクションの長さを取得し、インデックスでアイテムにアクセスする実装を使用できます。
public T RandomChoice<T> (IEnumerable<T> source) {
IList<T> list = source as IList<T>;
if (list != null) {
// use list.Count and list[] to pick an item by random
} else {
// use implementation above
}
}
注:Random
インスタンスをメソッドに送信することを検討する必要があります。それ以外の場合は、シードが現在の時間から作成されるため、メソッドをあまりにも近い時間に 2 回呼び出すと、同じランダム シードが取得されます。
0 ~ 9 を含む配列から 1 つの数値を 1000000 回選択して、選択した数値の分布が歪んでいないことを示すテスト実行の結果:
0: 100278
1: 99519
2: 99994
3: 100327
4: 99571
5: 99731
6: 100031
7: 100429
8: 99482
9: 100638