我有一个布尔值数组,需要为 true 的值随机选择一定数量的索引。
生成索引数组的最有效方法是什么?
例如,
BitArray mask = GenerateSomeMask(length: 100000);
int[] randomIndices = RandomIndicesForTrue(mask, quantity: 10);
在这种情况下,randomIndices
的长度为10。
答案 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
实际取消您节省的空间。
转换为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之间的随机数,并在屏蔽时使用该数字。