从java元素列表中选择java元素(无需更改)
正如在标题中,我想使用Knuth Fisher-Yates shuffle算法从列表中选择N个随机元素,但不使用列表。整理并更改列表。以下是我目前的代码:
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;
}
它使用列表。toArray()创建新数组以避免修改原始列表。然而,我现在的问题是,我的列表可能非常大,可能有100万个元素。然后列出清单。toArray()太慢了。我的n可能在100万到100万之间。当n很小时(比如说2),函数非常有效,因为它仍然需要执行列表。toArray()获取包含100万个元素的列表
有人能帮助改进上述代码,使其在处理大型列表时更高效吗。谢谢
在这里,我假设Knuth Fisher-Yates shuffle是从列表中选择n个随机元素的最佳算法。我说得对吗?如果有比Knuth Fisher-Yates shuffle更好的算法来完成这项工作,在速度和结果质量方面(保证真正的随机性),我将非常高兴
更新:
以下是我的一些测试结果:
当从1000000个元素中选择n时
当n<;1000000/4使用Daniel Lemire的位图功能,首先选择n个随机id,然后获取具有这些id的元素的最快方法:
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储层取样方法更快
所以我建立了一个函数来结合这两种方法
# 1 楼答案
总结长王的最新消息如果你想要超过250000件物品,请使用amit的答案。否则使用^{} ,如图所示
如果你想让它们随机化,我使用:
# 2 楼答案
你可能正在寻找类似Resorvoir Sampling的东西
从一个包含第一个
k
元素的初始数组开始,然后用概率递减的新元素修改它:类似java的伪代码:
这需要对数据进行单次传递,每次迭代都需要非常廉价的操作,并且空间消耗与所需的输出大小成线性关系