【问题标题】:Selecting rows from IEnumerable based on a percentage根据百分比从 IEnumerable 中选择行
【发布时间】:2012-05-08 19:47:55
【问题描述】:

我目前有一个每 5 分钟运行一次的 Windows 服务。该代码从数据库中选择行进行处理。有一个上限(允许选择的最大行数),因此选择的行数可以是 0-100 之间的任意值。

我希望根据随机百分比选择对这些行进行一些处理。

  • 任务 1 25%
  • 任务 2 50%
  • 任务 3 100%

为简单起见,假设服务选择了 100 行,那么随机选择的 25 行将运行任务 1,随机选择的 50 行将运行任务 2,并且所有行都将运行任务 3。

我目前的代码如下所示:

var rows = repository.GetRows(100);

foreach(var row in rows)
{
    task1.Run(row);
    task2.Run(row);
    task3.Run(row);
}

这将在所有行上运行所有三个任务。我将如何只选择分配给每个任务的百分比?

【问题讨论】:

  • 您的意思是根据 precemtage 从行中选择随机行

标签: c#


【解决方案1】:

可能有点土气……

var rows = repository.GetRows(100);

rows.OrderBy(Guid.NewGuid()).Take(25).ToList().ForEach(m => task1.Run(m));
rows.OrderBy(Guid.NewGuid()).Take(50).ToList().ForEach(m => task2.Run(m));
rows.ToList().ForEach(m => task3.Run(m));

【讨论】:

  • NewGuid() 是...可以作为随机数据的来源,并且对于您的日常洗牌来说“足够随机”,但正如 cmets 中提到的另一个答案,GUID 本身应该是唯一的,不一定是随机的(例如,等价物在 T-SQL 中不起作用,它使用不同的 GUID 算法)。
【解决方案2】:

您可以定义一个 Shuffle() 扩展方法来执行 Fisher-Yates-Durstenfeld shuffle(它以线性时间执行,而不是 OrderBy 的 NlogN 时间):

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> input)
{
    var buffer = input.ToArray();
    //Math.Random is OK for "everyday" randomness;
    //you should use RNGCryptoServiceProvider if you need 
    //cryptographically-strong randomness
    var rng = new Math.Random();

    //as the loop proceeds, the element to output will be randomly chosen
    //from the elements at index i or above, which will then be swapped with i;
    //the yield return gives us each shuffled value as it is chosen, and
    //allows the shuffling to happen "lazily".
    for (int i = 0; i < buffer.Length; i++)
    {
        int j = rng.Next(i, buffer.Length);
        yield return buffer[j];
        //if we cared about the elements in the buffer this would be a swap,
        //but we don't, so...    
        buffer[j] = buffer[i];
    }
}

//simple extension method to provide List.ForEach()-like functionality
//on any collection or IEnumerable.
public static void ForEach(this IEnumerable<T> collection, Action<T> action)
{
    foreach(var element in collection) action(element);
}

//Usage - pretty much the same as Raphael's, 
//but now you don't have to convert to a List to use ForEach:
rows.Shuffle().Take(25).ForEach(m => task1.Run(m));
rows.Shuffle().Take(50).ForEach(m => task2.Run(m));
rows.ForEach(m => task3.Run(m));

【讨论】:

  • 在第二次调用中,rows.Shuffle 中缺少 ()。 ;)
【解决方案3】:

您可以通过以下方式获得随机子集合:

task1.Run(rows);
task2.Run(rows.OrderBy(x => Guid.NewGuid()).Take(25));
task2.Run(rows.OrderBy(x => Guid.NewGuid()).Take(50))

【讨论】:

  • 哇,我需要进一步研究一下,我不知道你可以像这样随机获取子集合,
  • @PaulPhillips 没有,但会由他们订购。
  • @scottm 这怎么可能?我的直觉是,您在这里将 GUID 视为随机数。如果它们不是随机的,则无法获得真正的随机排序。我有疏忽吗? (这主要是学术性的,因为不清楚这个问题需要多强的“随机性”,但我很好奇)
  • @PaulPhillips,我将给定的一组 GUID 的排序顺序视为随机的,而不是 GUID 本身。对于大多数情况,这是“足够随机的”。
  • @scottm:从技术上讲,您是对的,但通常不鼓励使用 NewGuid() 作为随机数据的来源。 GUID 值保证是唯一的,而不是随机的,而且只有通过 PRNG 生成 V4 GUID 的 95% 的数据(NewGuid() 生成)才能使这项工作有效。如果 GUID 改为 V1 GUID,则值会按预期呈上升趋势,并且随机播放根本不起作用。
【解决方案4】:

对于这种情况,您可以使用 Knuth 的随机抽样方法(从 n 中选择 m 个项目):

var rows = repository.GetRows(100);
int[] maxTake = new[] {25,50,100};
int remaining = rows.Length;
Random rand = new Random();

for (int i = 0; i < rows.Length; i++)
{
    var num = rand.Next() % remaining;
    if (num < maxTake[0])
    {
        task1.Run(rows[i]);
        maxTake[0]--;
    }
    if (num < maxTake[1])
    {
        task2.Run(rows[i]);
        maxTake[1]--;
    }
    if (num < maxTake[2])
    {
        task3.Run(rows[i]);
        maxTake[2]--;
    }
    remaining--;
}

【讨论】:

  • 这看起来有偏见;传递给 task1 的所有元素也传递给 task2。大多数其他答案是在选择每个样本集之前对集合进行洗牌,因此不能保证属于“task1 组”的元素也属于“task2 组”。
  • 随机选择也可能不自然地偏向集合的后半部分,或者基于当前顺序的均匀分布。每个被选中的元素都会减少紧随其后的元素被选中的机会,而每个被选中的元素都会增加下一个被选中的机会。这将大致每第 N 个选择一次,或者达到必须使用所有剩余元素才能填充配额的状态。可能两者兼而有之。
【解决方案5】:

获得 25 个随机唯一数字

 Random rand=new Random()

 int[] task1nums = new int[25];
 for (int i=0;i<25;i++);
 {
    int r=rand.Next(100);

    while (task1nums.Contains(r))
    {
        r=rand.Next(100);
    }
    task1nums[i]=r;
}

获得 50 个随机唯一数字

 int[] task2nums = new int[50];
 for (int i=0;i<50;i++);
 {
    int r=rand.Next(100);

    while (task2nums.Contains(r))
    {
        r=rand.Next(100);
    }
    task2nums[i]=r;
}

所以现在你有 25 个随机数和 50 个随机数

var rows = repository.GetRows(100);
int counter=0
foreach(var row in rows)
{
    if (task1nums.Contains(counter))
    task1.Run(row);
    if (task2nums.Contains(counter))
    task2.Run(row);


    task3.Run(row);

    counter++;
} 

【讨论】:

  • 是的,我确实将其展开以使其更易于阅读。它可以大大简化。
  • 显然我的答案没有上面的那么好。虽然我相信它会起作用,但它太麻烦了。
  • N 平方复杂度;您必须每行遍历一次随机数集合(绑定到输入的大小)。就像你说的那样,它会起作用,但大多数其他答案都更优雅。
【解决方案6】:

您可以使用Random 实例为每一行生成一个随机值(介于 0.0 和 1.0 之间)。

大约 25% 的行的生成值小于 0.25;大约 50% 的行的生成值会小于 0.5。

var rows = repository.GetRows(100);

Random random = new Random();

task1.Run(rows.Where(_ => random.NextDouble() <= 0.25));
task2.Run(rows.Where(_ => random.NextDouble() <= 0.5));
task3.Run(row);

如果您想保证准确地获得行集合的 25% 和 50%(向下舍入),请使用:

Random random = new Random();

var rows = repository.GetRows(100);
var rowsRandomized = rows.OrderBy(_ => random.NextDouble());

task2.Run(rowsRandomized.Take((int)(0.25 * rows.Count())));
task2.Run(rowsRandomized.Take((int)(0.5 * rows.Count())));
task3.Run(rowsRandomized);

【讨论】:

  • 嗯,rowsRandomized 将是一个IEnumerable&lt;double&gt;;您将丢失所有原始行数据。此外,将浮点数转换为整数将始终向下舍入而不是最接近的整数,这将低估当总数不被 4 整除时应采用的行数。
  • 第一点你是对的;现在更正了。至于四舍五入,我已经在回答中提到了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-10
  • 2017-03-22
  • 2016-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多