以下代码部分按我希望的那样工作
for (int i = 1; i <= practicehistory.TotalNumQuestions; i++)
{
query = from a in db.Questions
where a.CategoryId == practicehistory.CategoryId
orderby a.QuestionId
select a;
randomNumber = random.Next(1, count + 1);
int qNum = query.Skip(randomNumber - 1).First().QuestionId;
asked.QuestionId = qNum;
asked.OrderAsked = i;
db.AskedHistories.Add(asked);
db.SaveChanges();
}
但是,我偶尔会遇到随机数与之前for循环运行中的随机数相同的情况。我想知道是否有人有一个优雅的解决方案,我怎么能确保只生成一个比以前没有生成过的随机数?我正在考虑填充一个数组并检查它,但这似乎相当多余!
答案 0 :(得分:7)
这样做的一种方法是生成可能值的数组(例如,整数1到N的数组),然后shuffle它们,然后根据需要迭代尽可能多的结果混洗值。如果您对“通过示例”进行编码感兴趣,快速Google搜索会产生Fisher-Yates shuffle的几个C#实现。
答案 1 :(得分:0)
有两种方法可以解决这个问题,检查数字是否已被使用,或预先生成数字随机化它们存储的顺序,并以“混洗顺序”将它们从列表中取出。
第一种方法更好,因为当您不知道总共需要多少数字或者您拥有非常大的数字池时,您不可能多次查找相同数字的碰撞。这样做的缺点是可用总数的百分比越多,下一个数字生成运行的速度越慢。
//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;
}
当您确切知道需要多少个对象,或者可以选择一小部分可能的选择时,第二种方法会更好。
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;
}