【发布时间】:2014-05-18 08:29:22
【问题描述】:
如标题所示,我想使用 Knuth-Fisher-Yates 洗牌算法从列表中选择 N 个随机元素,但不使用 List.toArray 并更改列表。这是我当前的代码:
public List<E> getNElements(List<E> list, Integer n) {
List<E> rtn = null;
if (list != null && n != null && n > 0) {
int lSize = list.size();
if (lSize > n) {
rtn = new ArrayList<E>(n);
E[] es = (E[]) list.toArray();
//Knuth-Fisher-Yates shuffle algorithm
for (int i = es.length - 1; i > es.length - n - 1; i--) {
int iRand = rand.nextInt(i + 1);
E eRand = es[iRand];
es[iRand] = es[i];
//This is not necessary here as we do not really need the final shuffle result.
//es[i] = eRand;
rtn.add(eRand);
}
} else if (lSize == n) {
rtn = new ArrayList<E>(n);
rtn.addAll(list);
} else {
log("list.size < nSub! ", lSize, n);
}
}
return rtn;
}
它使用 list.toArray() 来创建一个新数组以避免修改原始列表。但是,我现在的问题是我的列表可能非常大,可能有 100 万个元素。那么 list.toArray() 太慢了。我的 n 范围可以从 1 到 100 万。当 n 很小(比如 2)时,该函数的效率非常低,因为它仍然需要为 100 万个元素的列表执行 list.toArray()。
有人可以帮助改进上述代码,使其在处理大型列表时更加高效。谢谢。
在这里,我假设 Knuth-Fisher-Yates shuffle 是从列表中选择 n 个随机元素的最佳算法。我对吗?如果有其他算法比 Knuth-Fisher-Yates shuffle 在速度和结果质量(保证真正的随机性)方面完成这项工作,我将非常高兴。
更新:
这是我的一些测试结果:
当从 1000000 个元素中选择 n 时。
当 n
public List<E> getNElementsBitSet(List<E> list, int n) {
List<E> rtn = new ArrayList<E>(n);
int[] ids = genNBitSet(n, 0, list.size());
for (int i = 0; i < ids.length; i++) {
rtn.add(list.get(ids[i]));
}
return rtn;
}
genNBitSet 使用来自https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2013/08/14/java/UniformDistinct.java 的代码 generateUniformBitmap
当 n>1000000/4 时,Reservoir Sampling 方法更快。
所以我构建了一个函数来结合这两种方法。
【问题讨论】: