C# 有效提取大型可枚举数据的小随机子集的最佳方法是什么?

C# 有效提取大型可枚举数据的小随机子集的最佳方法是什么?,c#,.net,sorting,random,C#,.net,Sorting,Random,以随机顺序从IEnumerable中获取n项的最佳方法是什么 我正在编写一个存储API,需要提供一组小的随机项,这些随机项有时来自大量的项枚举。底层可枚举项有时是一个数组,有时是所述数组的惰性计算过滤器 由于我只是从枚举中按比例获取少量项,因此最好在枚举中使用某种重复的随机索引并每次重复检查,而不是使用现有算法对整个列表进行随机排序并获取顶部x,对吗 有更好的主意吗?在另一个答案中,我提供了一种从序列返回的方法,只需一次传递 我怀疑这可以很容易地调整,以使用循环缓冲区并选择给定大小的随机序列,但

以随机顺序从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所说,您还必须确保不会两次获得相同的元素。我认为这很简单。由于只选择了一小部分项目,您可以测试您是否已经看到该特定的随机数,然后选择另一个(或者只需从该位置选择第一个未选择的项目)。