C# 获取随机数的更快方法

C# 获取随机数的更快方法,c#,C#,我有个问题 我需要随机数,但除了我已经有的数 我的代码: List<int> current_numbers = repository.GetCurrentNumbers(); Random rnd = new Random(42); var new_values = Enumerable.Range(10000000,99999999) .Except(current_numbers) .OrderBy(o=> rnd.Next())

我有个问题

我需要随机数,但除了我已经有的数

我的代码:

List<int> current_numbers = repository.GetCurrentNumbers();            
Random rnd = new Random(42);
var new_values = Enumerable.Range(10000000,99999999)
    .Except(current_numbers)
    .OrderBy(o=> rnd.Next())
    .Take(amount)
    .ToList();
但是这个代码非常慢

当我尝试使用select代替OrderBy时,我得到了重复的结果。 就我而言,它必须没有副本

HashSet<int> current_numbers = new HashSet<int>(repository.GetCurrentNumbers());
HashSet<int> newValues = new HashSet<int>();
while (newValues.Count < amount)
{
    var next = rnd.Next(10000000,99999999);
    if (!current_numbers.Contains(next))
        newValues.Add(next);
}
更新: 使用OrderBy-我的内存有问题:

范围必须是1M-99M 谢谢。

使用哈希集而不是列表,然后使用Contains进行测试-如果您仔细看一下,您会注意到Except将把那些现有的数字构建到一个集合中

由于OrderBy尝试对整个集合进行排序,因此使用OrderBy(而不是使用常规循环并生成随机数)将失去延迟执行枚举的好处

var random = new Random();  // Default constructor or you'll get the same sequence because of a constant seed
var result = new HashSet<int>();
var currentNumbers = new HashSet<int>(repository.GetCurrentNumbers());

while(result.Count < amount)
{
    var next = random.Next(1000000,99000000);
    if(currentNumbers.Contains(next)) continue;

    result.Add(next);
}
或者写你自己的发电机

public static IEnumerable<int> GenerateRandom()
{
    var random = new Random();
    while(true) { yield return random.Next(1000000,99000000); }
}
// Later...
var newValues = MyClass.GenerateRandom()
    .Where(next => !currentNumbers.Contains(next))
    .Distinct()
    .Take(amount)
    .ToList();

由于您需要的数字范围如此之大,因此可能需要使用哈希集来消除重复

HashSet<int> current_numbers = new HashSet<int>(repository.GetCurrentNumbers());
HashSet<int> newValues = new HashSet<int>();
while (newValues.Count < amount)
{
    var next = rnd.Next(10000000,99999999);
    if (!current_numbers.Contains(next))
        newValues.Add(next);
}

将当前\u号码转换为哈希集将加快此过程,因为如果当前\u号码未存储为哈希集,则调用Contains将花费很长时间。

为了避免创建如此庞大的号码列表,您可以跟踪您拥有的号码,并随机选择下一个号码的来源。首先,您需要一个已使用号码的有序列表。然后向其添加上下限。然后跟踪上下界的索引。然后迭代,直到有足够的数量,每次都在下界索引和上界索引之间随机选取一个索引。检查该索引处的数字与下一个索引处的数字之间的差值是否为1,如果是,则增加索引,直到它不为1或达到上限。如果达到上限,则只需向下走上限,然后重试。当您在已用编号中找到一个缺口时,随机选择缺口中的一个编号,并将其添加到您的退货列表和位于适当索引处的已用编号列表中。然后,如果需要,确保向上移动下限索引

var used = repository.GetCurrentNumbers().OrderBy(x => x).ToList();
used.InsertAt(0, 999999) // This is the lower bounds;
used.Add(100000000); // This is the upper bounds;
var random = new Random();

var values = new List<int>();
int lowerIndex = 0; 
int upperIndex = used.Length - 1;
while(values.Count < amount) {
    int ind = random.Next(lowerIndex, upperIndex); 
    while(ind < upperIndex && used[ind+1] - used[ind] == 1) ind++;
    if(ind == upperIndex){
        while(used[upperIndex] - used[upperIndex-1] == 1) upperIndex--;
        continue;
    }

    int val = random.Next(used[ind]+1, used[ind+1]);
    values.Add(val);
    used.InsertAt(ind+1, val);

    while(used[lowerIndx+1] - used[lowerIndex] == 1) lowerIndex++;
}

如果数量不是一个很大的数字,并且您的总体范围很大,并且初始使用的数字也很稀疏,则此方法效果最好。

列表中需要多少数字?42?我不知道C,但典型的方法是将可用的数字放入一个列表中,然后要么洗牌列表并弹出第一个元素,要么从中随机抽取。如果您使用的是支持O1访问的结构,那么后者应该是O1访问,因此它应该是快速的。没有副本是什么意思?计算机可以存储有限的值,因此无法确保真正的唯一性。有一些近似值,比如GUID。我没有可用的数值。它的所有数字都在这个范围内。我需要数一数。在随机-只要把42。随机可以是空的constructior@hatchet更具体地说,这可能是一个例外。可枚举。范围本身不会使用太多内存,因为。但是,Except会创建一个底层哈希集,将所有元素添加到该哈希集中,因此那里的内存使用量将激增。当然,这只是因为OP需要1亿个无重复的整数。这将导致一组新结果和当前结果的组合,而不是新结果的单独集合