C# 有效提取大型可枚举数据的小随机子集的最佳方法是什么?
以随机顺序从IEnumerable中获取n项的最佳方法是什么 我正在编写一个存储API,需要提供一组小的随机项,这些随机项有时来自大量的项枚举。底层可枚举项有时是一个数组,有时是所述数组的惰性计算过滤器 由于我只是从枚举中按比例获取少量项,因此最好在枚举中使用某种重复的随机索引并每次重复检查,而不是使用现有算法对整个列表进行随机排序并获取顶部x,对吗C# 有效提取大型可枚举数据的小随机子集的最佳方法是什么?,c#,.net,sorting,random,C#,.net,Sorting,Random,以随机顺序从IEnumerable中获取n项的最佳方法是什么 我正在编写一个存储API,需要提供一组小的随机项,这些随机项有时来自大量的项枚举。底层可枚举项有时是一个数组,有时是所述数组的惰性计算过滤器 由于我只是从枚举中按比例获取少量项,因此最好在枚举中使用某种重复的随机索引并每次重复检查,而不是使用现有算法对整个列表进行随机排序并获取顶部x,对吗 有更好的主意吗?在另一个答案中,我提供了一种从序列返回的方法,只需一次传递 我怀疑这可以很容易地调整,以使用循环缓冲区并选择给定大小的随机序列,但
有更好的主意吗?在另一个答案中,我提供了一种从序列返回的方法,只需一次传递 我怀疑这可以很容易地调整,以使用循环缓冲区并选择给定大小的随机序列,但您必须相当小心地平衡概率。如果使用,则可以对列表的一部分进行随机洗牌。因此,不必为了得到n个随机项而对整个列表进行排序。我不知道这是否能在您的约束范围内有效地完成,因为在应用算法之前,您仍然需要将正在获取的内容转换为列表
本质上,策略是抓取一个随机项,将其与列表中的第一项交换。下次您需要随机元素时,请跳过第一个。如果您事先知道项目的数量,那么计算该范围内的n个随机数,然后获取具有这些索引的随机数是相当简单的。这里有另一个想法:
using System;
using System.Collections.Generic;
using System.Linq;
namespace RandomElements
{
class Program
{
static IEnumerable<int> GetRandomElements(IEnumerable<int> source, int count)
{
var random = new Random();
var length = source.Count();
var enumerator = source.GetEnumerator();
if (length < count)
{
throw new InvalidOperationException("Seriously?");
}
while (count > 0)
{
const int bias = 5;
var next = random.Next((length / bias) - count - bias) + 1; // To make sure we don't starve.
length -= next;
while (next > 0)
{
if (!enumerator.MoveNext())
{
throw new InvalidOperationException("What, we starved out?");
}
--next;
}
yield return enumerator.Current;
--count;
}
}
static void Main(string[] args)
{
var sequence = Enumerable.Range(1, 100);
var random = GetRandomElements(sequence, 10);
random.ToList().ForEach(Console.WriteLine);
}
}
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
名称空间随机化元素
{
班级计划
{
静态IEnumerable GetRandomElements(IEnumerable源,int计数)
{
var random=新的random();
var length=source.Count();
var枚举器=source.GetEnumerator();
如果(长度<计数)
{
抛出新的InvalidOperationException(“严重?”);
}
而(计数>0)
{
常数int偏差=5;
var next=random.next((长度/偏差)-count-bias)+1;//以确保我们不会饿死。
长度-=下一个;
while(下一步>0)
{
如果(!enumerator.MoveNext())
{
抛出新的残疾手术例外(“什么,我们饿死了?”);
}
--其次;
}
产生返回枚举数。当前;
--计数;
}
}
静态void Main(字符串[]参数)
{
变量序列=可枚举范围(1100);
var random=GetRandomElements(序列,10);
random.ToList().ForEach(Console.WriteLine);
}
}
}
它只需要遍历枚举一次(如果传入ICollection,则需要知道长度)。如果遍历枚举或复制所有元素或其他开销很大,这可能会很有用
我不是统计学家、数学家或魔术师,所以不要反对我,但我发现,没有第22行引入的“偏见”,我觉得它有点想从序列的后端选取更多。也许有人可以进一步调整概率?如果枚举真的很昂贵,您可以让它更偏向前面
欢迎评论。正如OP所说,您还必须确保不会两次获得相同的元素。我认为这很简单。由于只选择了一小部分项目,您可以测试您是否已经看到该特定的随机数,然后选择另一个(或者只需从该位置选择第一个未选择的项目)。