There are two approaches to solving this, checking if the number has been used already, or pre-generate the numbers randomize the order they are stored in and take them out of the list in the "shuffled order".
The first approach is better for when you do not know how many numbers you will need in total or you have a very large pool of numbers and it is unlikely that you will hit collisions from finding the same number multiple times. The disadvantage to this the more percentage of the total numbers available is used the slower the next number generation runs.
//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;
}
The second approach is better when you know exactly how many object you will need, or there is a small pool of possible choices that could be chosen.
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;
}