从数组中随机选择特定数量的索引?

时间:2014-03-20 22:07:47

标签: c# .net algorithm random

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

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

例如,

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

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

3 个答案:

答案 0 :(得分:3)

有一种更快的方法,只需要对列表进行一次扫描。

当您不知道文件中有多少行,并且文件太大而无法放入内存时,请考虑从文本文件中随机选取一行。显而易见的解决方案是读取文件一次以计算行数,选择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;

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

答案 1 :(得分:2)

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

由于你的收藏品的大小在100K时是不可忽略的,而你只想从中挑选一些,乍看之下应该考虑非确定性的声音,因为它可以在实践中给出非常好的结果。 然而,因为无法保证集合中甚至存在N true值,所以非确定性可能会使您的程序进入无限循环(灾难性更小,它可能只需要很长一段时间来产生结果。)

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

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

  1. 将所有符合条件的索引过滤为辅助馆藏。
  2. 使用Fisher-Yates随机播放该集合(方便的implementation on StackOverflow)。
  3. 选择洗牌集合的N个第一项。如果小于N则输入不能满足您的要求。
  4. 转换为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
    }
    

答案 2 :(得分:0)

如果您有10个索引可供选择,您可以生成0到2 ^ 10 - 1之间的随机数,并在屏蔽时使用该数字。