これを解決するには 2 つの方法があります。番号が既に使用されているかどうかを確認するか、番号を事前に生成して、格納されている順序をランダム化し、「シャッフルされた順序」でリストから取り出します。
最初のアプローチは、合計で必要な数がわからない場合、または非常に大きな数のプールがあり、同じ数を複数回見つけることで衝突が発生する可能性が低い場合に適しています。この欠点は、使用可能な合計数のパーセンテージが多く使用されるほど、次の数生成の実行が遅くなります。
//This function will run slower and slower until all numbers have been used and then it will throw a InvalidOperationExecption.
//You could get a InvalidOperationExecption early if you reuse the ISet for multiple ranges.
public static int NextUnused(this Rand rand, int minValue, int maxValue, ISet<int> usedNumbers)
{
if(usedNumbers.Count >= maxValue - minValue)
throw new InvalidOperationExecption("All possible numbers have been used");
int number;
do
{
number = rand.Next(minValue, maxValue);
} while(!usedNumbers.Add(number)) //if we have seen the number before it will return false and try again.
return number;
}
2 番目のアプローチは、必要なオブジェクトの数が正確にわかっている場合、または選択できる選択肢が少ない場合に適しています。
public class RandomRange
{
public RandomRange(int start, int count) : this(start, count, new Rand())
{
}
public RandomRange(int start, int count, Rand randSource)
{
var numberList = new List<int>(Enumerable.Range(start, count);
Shuffle(numberList);
_numbers = new Queue<int>(numberList);
}
//Will throw a InvalidOperationExecption when you run out of numbers.
public int GetNextNumber()
{
return _numbers.Dequeue();
}
private static void Shuffle(List<int> list)
{
throw new NotImplementedException("An exercise for the reader");
}
private Queue<int> _numbers;
}