我需要能够从包含m
元素的地图中删除n
元素,只能访问迭代器。我可以简单地迭代字典一次,并以概率m/n
删除所有元素,但这可能会驱逐多于或少于m
个项目(尽管预期项目已删除是正确的m
)。
int m = 10;
int n = map.size();
Iterator<K> keys = map.keySet().iterator();
while (keys.hasNext()) {
keys.next();
if (random.nextDouble() < m / (double) n) {
keys.remove();
}
}
我一直在考虑的解决方案是在m
元素被驱逐后简单地停止驱逐元素,并且在迭代结束时,如果evicted < m
元素被驱逐,则驱逐剩余的m - evicted
元素。第二次迭代中的{1}}个元素。我担心第二次传球在概率上是不正确的。
int m = 10;
int n = size();
int evicted = 0;
outer: while (evicted < m) {
Iterator<K> keys = keySet().iterator();
while (keys.hasNext()) {
keys.next();
if (random.nextDouble() < m / (double) n) {
keys.remove();
if (++evicted == m) {
break outer;
}
}
}
或者,我可以保留一个键列表并用一次迭代对列表进行存储 - 并删除m
键列表中的所有键,但是有一些内存开销我宁愿不被迫使用。此外,使用迭代器删除比通过键删除元素更快(它需要找到密钥存储在其中的存储桶,然后在列表中的位置)。是否有另一种在线算法我只能访问迭代器(不创建单独的列表)?
编辑:对于有兴趣的人,我发现了一篇详细说明如何按顺序生成随机发布的论文,以便不需要单独的排序步骤。代码是这样的(截断为整数时可能包含重复项):
int curmax = 1.0;
int[] indices = new int[m];
for (int i = indices.length; i >= 0; i--) {
curmax = curmax * Math.pow(random.nextDouble(), 1 / (double) (i+1));
indices[i] = (int) curmax;
}
答案 0 :(得分:3)
为什么不先驱逐第一个 M 元素然后停止迭代器?迭代器中是否有一些反映会对被驱逐元素产生不必要的偏差?
如果是的话,你的双程方式将 在统计上完美无瑕。如果第一遍通过提前终止,因为在到达迭代结束之前到达了 M ,则后来的元素从未考虑过驱逐。
如果你在没有驱逐 M 元素的情况下到达迭代的末尾,迭代的第一个元素将“冒险”驱逐两次,而在迭代结束时的那些将冒险驱逐一次
如果你事先知道 N ,你可以在0到N之间建立一个 M 随机,非重复数字的列表。迭代一次,保持计数在哪里你在迭代中。如果迭代编号在您的“驱逐列表”中,则逐出该元素。
遵循该方法,您必须暂时为 M 索引位置(可能是整数)分配内存,以用于迭代过程。
答案 1 :(得分:1)
执行此操作的正确方法是以概率m / n删除每个元素,但根据结果重新规范概率(如果我们删除元素,则减少m,并且当前概率需要按元素数量缩放)可供选择)。我的java有点生疏,我无法访问编译器,所以请原谅我,如果这不起作用(但你应该能够解决它而不会有太多的麻烦我希望):
int seen = 0
Iterator<K> keys = map.keySet().iterator();
while (keys.hasNext()) {
if (m==0)
break;
keys.next();
prob = m / (double)(n-seen) //renormalise the prob so that the total available is 1 across all remaining instances
if (random.nextDouble() < prob) {
keys.remove();
m--;
}
seen++;
}
我希望这里的逻辑是清楚的 - 这是如何从概率为1 / n的集合中对一个元素进行采样的概括,其中一旦你拒绝了一个元素,你就可以忽略它并考虑所有元素的分布剩下的元素。这应该确保您准确地返回具有正确概率的m个元素。
修改强>:
修正了一些拼写错误并删除了冗余变量。