用 1M 个条目有效地屏蔽了 30% 的阵列

efficiently mask-out exactly 30% of array with 1M entries

本文关键字:阵列 屏蔽 有效地 1M      更新时间:2023-10-16

我的问题标题与此链接类似,但是没有回答我的期望。

我有一个整数数组(1 000 000 个条目),需要屏蔽30% 的元素。 我的方法是循环访问元素并为每个元素掷骰子。以不间断的方式执行此操作有利于缓存一致性。

一旦我注意到恰好有30万个元素确实被屏蔽了,我就需要停下来。但是,我可能会到达数组的末尾,并且只有 200 000 个元素被屏蔽,迫使我第二次循环,甚至可能是第三次,依此类推。


确保我不必第二次循环并且不偏向于选择某些元素的最有效方法是什么?

编辑:

//I need to preserve the order of elements.
//For instance, I might have:
[12, 14, 1, 24, 5, 8]
//Masking away 30% might give me:
[0,  14, 1, 24, 0, 8]

屏蔽的结果必须是原始数组,某些元素设置为零

只需进行 fisher-yates shuffle,但仅在 300000 次迭代时停止。最后 300000 个元素将是随机选择的元素。

std::size_t size = 1000000;
for(std::size_t i = 0; i < 300000; ++i)
{
std::size_t r = std::rand() % size;
std::swap(array[r], array[size-1]);
--size;
}

为了简洁起见,我正在使用std::rand。显然你想使用更好的东西。


另一种方式是这样的:

for(std::size_t i = 0; i < 300000;)
{
std::size_t r = rand() % 1000000;
if(array[r] != 0)
{
array[r] = 0;
++i;
}
}

它没有偏见,不会对元素进行重新排序,但不如费舍尔耶茨,尤其是对于高百分比。

当我看到一个庞大的列表时,我的脑海中总是首先想到分而治之。

我不会在这里写出一个完整的算法,只是一个骨架。 你似乎有足够的线索来采取体面的想法并带着它运行。 我想我只需要为你指出正确的方向。 话虽如此...

我们需要一个 RNG,它可以返回一个适当分布的值,以显示有多少掩码值可能低于列表中的给定切割点。 我将使用列表的中间点进行上述切割。 一些统计学家可能会为您设置正确的 RNG 函数。 (有人吗? 我不想假设它只是均匀随机的[0..mask_count),但它可能是。

鉴于此,您可以执行以下操作:

// the magic RNG your stats homework will provide
int random_split_sub_count_lo( int count, int sub_count, int split_point );
void mask_random_sublist( int *list, int list_count, int sub_count )
{
if (list_count > SOME_SMALL_THRESHOLD)
{
int list_count_lo = list_count / 2;               // arbitrary
int list_count_hi = list_count - list_count_lo;
int sub_count_lo = random_split_sub_count_lo( list_count, mask_count, list_count_lo );
int sub_count_hi = list_count - sub_count_lo;
mask( list,                list_count_lo, sub_count_lo );
mask( list + sub_count_lo, list_count_hi, sub_count_hi );
}
else
{
// insert here some simple/obvious/naive implementation that
// would be ludicrous to use on a massive list due to complexity,
// but which works great on very small lists.  I'm assuming you 
// can do this part yourself.
}
}

假设你能找到比我更了解统计分布的人,为你提供拆分子列表计数所需的随机发生器的线索,这应该给你 O(n) 性能,其中"n"是屏蔽条目的数量。 此外,由于递归设置为以不断升序的索引顺序遍历实际的物理数组,因此缓存使用率应尽可能最佳。


警告:由于列表的离散性质与 30% 的分数相比,可能存在轻微的分布问题,因为您递归和向下递归到较小的列表大小。 在实践中,我怀疑这可能无关紧要,但是无论这个解决方案是针对什么人的,在显微镜下观察时,都可能不满意随机分布是真正均匀的。 YMMV,我猜。

这里有一个建议。一百万比特只有128K,这不是一个繁重的数量。

因此,创建一个所有项目初始化为零的位数组。然后随机选择其中的 300,000 个(当然要考虑重复项),并将这些位标记为一个。

然后,您可以运行位数组,并且任何设置为一个(或零,如果你的掩码想法意味着你想要处理其他700,000),对原始数组中的相应条目执行任何您想要的操作。


如果您想确保在随机选择它们时没有重复的可能性,只需使用 Fisher-Yates 洗牌来权衡空间以换时间。

构造所有索引的集合,对于要删除的 700,000 个索引中的每一个(如果如前所述,掩码意味着您要处理其他索引,则为 300,000 个),请选择:

  • 从剩余的集合中随机选择一个。
  • 将最后一个元素复制到所选元素上。
  • 减小设置大小。

这将为您提供一个随机的索引子集,您可以使用它们来处理主数组中的整数。

你想要水库采样。 示例代码由维基百科提供:

(*
S has items to sample, R will contain the result
*)
ReservoirSample(S[1..n], R[1..k])
// fill the reservoir array
for i = 1 to k
R[i] := S[i]
// replace elements with gradually decreasing probability
for i = k+1 to n
j := random(1, i)   // important: inclusive range
if j <= k
R[j] := S[i]