【问题标题】:Randomly select a specific quantity of indices from an array?从数组中随机选择特定数量的索引?
【发布时间】:2014-04-28 01:52:01
【问题描述】:

我有一个布尔值数组,需要为 true 的值随机选择特定数量的索引。

生成索引数组最有效的方法是什么?

例如,

BitArray mask = GenerateSomeMask(length: 100000);
int[] randomIndices = RandomIndicesForTrue(mask, quantity: 10);

在这种情况下,randomIndices 的长度为 10。

【问题讨论】:

    标签: c# .net algorithm random


    【解决方案1】:

    有一种更快的方法可以做到这一点,只需扫描一次列表。

    当您不知道文件中有多少行并且文件太大而无法放入内存时,请考虑从文本文件中随机选择一行。显而易见的解决方案是读取文件一次以计算行数,在 0 到 Count-1 的范围内选择一个随机数,然后再次读取文件直到所选的行号。这可行,但需要您读取文件两次。

    更快的解决方案是读取第一行并将其保存为选定的行。您将选定的行替换为概率为 1/2 的下一行。当你阅读第三行时,你用概率1/3替换,等等。当你阅读整个文件时,你随机选择了一行,每一行被选中的概率相等。代码如下所示:

    string selectedLine = null;
    int numLines = 0;
    Random rnd = new Random();
    foreach (var line in File.ReadLines(filename))
    {
        ++numLines;
        double prob = 1.0/numLines;
        if (rnd.Next() >= prob)
            selectedLine = line;
    }
    

    现在,如果您想选择 2 行怎么办?您选择前两个。然后,在读取每一行时,它将替换两行之一的概率为 2/n,其中 n 是已读取的行数。如果您确定需要替换一行,则随机选择要替换的行。您可以按照相同的基本思想随机选择任意数量的行。例如:

    string[] selectedLines = new int[M];
    int numLines = 0;
    Random rnd = new Random();
    foreach (var line in File.ReadLines(filename))
    {
        ++numLines;
        if (numLines <= M)
        {
            selectedLines[numLines-1] = line;
        }
        else
        {
            double prob = (double)M/numLines;
            if (rnd.Next() >= prob)
            {
                int ix = rnd.Next(M);
                selectedLines[ix] = line;
            }
        }
    }
    

    你可以很容易地将它应用到你的BitArray:

    int[] selected = new int[quantity];
    int num = 0;  // number of True items seen
    Random rnd = new Random();
    for (int i = 0; i < items.Length; ++i)
    {
        if (items[i])
        {
            ++num;
            if (num <= quantity)
            {
                selected[num-1] = i;
            }
            else
            {
                double prob = (double)quantity/num;
                if (rnd.Next() > prob)
                {
                    int ix = rnd.Next(quantity);
                    selected[ix] = i;
                }
            }
        }
    }
    

    最后需要一些特殊情况代码来处理数组中没有 quantity 设置位的情况,但任何解决方案都需要它。

    这会在BitArray 上进行一次传递,它使用的唯一额外内存是用于选定索引的列表。如果它没有明显快于 LINQ 版本,我会感到惊讶。

    请注意,我使用概率计算来说明数学。您可以将第一个示例中的内部循环代码更改为:

    if (rnd.Next(numLines+1) == numLines)
    {
        selectedLine = line;
    }
    ++numLines;
    

    您可以对其他示例进行类似的更改。这与概率计算的作用相同,并且应该执行得更快一些,因为它消除了每个项目的浮点除法。

    【讨论】:

      【解决方案2】:

      您可以使用两种方法:确定性和非确定性。第一个涉及找到集合中所有符合条件的元素,然后随机选择N;第二个涉及随机进入集合,直到找到N 符合条件的项目。

      由于您的集合的大小在 100K 时是不可忽略的,并且您只想从中挑选一些,乍一看,应该考虑不确定性,因为它可以在实践中产生非常好的结果。 然而,由于无法保证 N 个 true 值甚至存在于集合中,因此不确定性可能会使您的程序陷入无限循环(灾难性较小,它可能只需要 a很长的时间 产生结果)。

      因此,我建议采用确定性方法,即使您要通过资源使用来支付所需的保证。特别是,该操作将涉及辅助集合的就地排序;这实际上会抵消您使用BitArray 节省的空间。

      抛开理论,让我们开始工作吧。处理此问题的标准方法是:

      1. 将所有符合条件的索引过滤到辅助集合中。
      2. 使用Fisher-Yates 随机打乱集合(有一个方便的implementation on StackOverflow)。
      3. 选择洗牌集合的前 N ​​个项目。如果少于 N,则您的输入无法满足您的要求。

      翻译成LINQ:

      var results = mask
          .Select((i, f) => Tuple.Create)  // project into index/bool pairs
          .Where(t => t.Item2)             // keep only those where bool == true
          .Select(t => t.Item1)            // extract indices
          .ToList()                        // prerequisite for next step
          .Shuffle()                       // Fisher-Yates
          .Take(quantity)                  // pick N
          .ToArray();                      // into an int[]
      
      if (results.Length < quantity)
      {
          // not enough true values in input
      }
      

      【讨论】:

      • 感谢您的详细回答 - 这很有意义:)
      • 您没有考虑另一种方法。看我的回答。
      • @JimMischel:我过去曾遇到过这种方法,但显然没有引起足够的注意,因为在这种情况下它肯定是你能做的最好的。你有我的支持和感谢提醒。
      【解决方案3】:

      如果您有 10 个索引可供选择,您可以生成一个从 0 到 2^10 - 1 的随机数,并将其用作掩码。

      【讨论】:

      • 我应该澄清一下,mask 包含 100,000 个元素,我只对 true 的 10 个元素的索引感兴趣。
      • @LeaHayes:其中有多少可以选择?你总是只想要10个吗?在这些情况下,首选答案涉及过滤、使用 Fisher-Yates 对结果进行洗牌并选择前 10 个——但对 100K 项目进行过滤/洗牌只是为了随机选择 10 个听起来不太好。
      • @Jon:特定掩码中所需索引的数量是用户可配置的,范围在 1 到 361 之间。
      • @LeaHayes:掩码中true 值的预期/最小数量?这必然会涉及权衡,因此您需要选择正确的。
      • @Jon:掩码本质上是用户绘制的位图,可以包含任何值true 或false。
      猜你喜欢
      • 2014-04-21
      • 1970-01-01
      • 1970-01-01
      • 2020-10-27
      • 1970-01-01
      • 2019-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多