我使用一个API与集合交互,它返回一个java.util.Iterator
,这意味着我可以遍历它,但无法直接/随机访问元素。
现在我的问题是:我想从集合中获取一个随机元素。我该怎么办?我猜我可以构建一个允许直接访问的新集合,但那不会占用一些内存吗?我也可以遍历整个集合,并为每个元素“roll a dice”,以查看是否应该取该元素并退出迭代或继续。但是我需要知道集合的大小,而我无法从Iterator获得这个信息。
预先感谢。
有一种方法可以在集合中一次遍历就完成,而且不会使用过多的额外内存(只需要一个元素的大小和一个浮点数)。伪代码如下:
显然,这种方法的缺点是每次调用都需要遍历整个集合,但是在面对您所面临的限制时,您没有太多选择。
更新:我终于想起来这类问题的名称了。这被称为蓄水池抽样算法。
在迭代时,您知道迭代了多少个对象,因此您知道当前元素是随机选择的概率。因此,您只需要保持计数和当前随机选择的项。
public static <T> T selectRandom(final Iterator<T> iter, final Random random) {
if (!iter.hasNext()) {
throw new IllegalArgumentException();
}
if (random == null) {
throw new NullPointerException();
}
T selected = iter.next();
int count = 1;
while (iter.hasNext()) {
final T current = iter.next();
++count;
if (random.nextInt(count) == 0) {
selected = current;
}
}
return selected;
}
(Stack Overflow免责声明:未编译,当然也未经测试。)
请参阅Java Puzzlers中关于Collections.shuffle
的部分。
random.nextInt(count) == 0
的概率都会越来越低。 - Denis Tulskiy这取决于需求,如果集合的大小不是很大,那么这样做就可以了,否则您应该迭代并使用您提到的骰子方法。
List<Object> list = Arrays.asList(yourCollection.toArray(new Object[0]));
result = list.get(new Random().nextInt(list.size()));
用这个来生成加权测试数据。虽然不是很高效,但很容易使用。
class ProbabilitySet<E> {
Set<Option<E>> options = new HashSet<Option<E>>();
class Option<E> {
E object;
double min;
double max;
private Option(E object, double prob) {
this.object = object;
min = totalProb;
max = totalProb + prob;
}
@Override
public String toString() {
return "Option [object=" + object + ", min=" + min + ", max=" + max + "]";
}
}
double totalProb = 0;
Random rnd = new Random();
public void add(E object, double probability){
Option<E> tuple = new Option<E>(object, probability);
options.add(tuple);
totalProb += probability;
}
public E getRandomElement(){
double no = rnd.nextDouble() * totalProb;
for (Option<E> tuple : options) {
if (no >= tuple.min && no < tuple.max){
return tuple.object;
}
}
return null; // if this happens sumfink is wrong.
}
@Override
public String toString() {
return "ProbabilitySet [options=" + options + ", totalProb=" + totalProb + "]";
}
}
注意:概率参数将与总数相关,而不是1.0。
用法:
public static void main(String[] args) {
ProbabilitySet<String> stati = new ProbabilitySet<String>();
stati.add("TIMEOUT", 0.2);
stati.add("FAILED", 0.2);
stati.add("SUCCESSFUL", 1.0);
for (int i = 0; i < 100; i++) {
System.out.println(stati.getRandomElement());
}
}
int n = 2
iterator i = ...
Random rand = new Random();
Object candidate = i.next();
while (i.hasNext()) {
if (rand.nextInt(n)) {
candidate = i.next();
} else {
i.next();
}
n++;
}
return candidate;
这将保留列表中的一个随机元素,但需要遍历整个列表。如果您想要一个真正均匀分布的值,那么您别无选择。
或者,如果项目数量很少,或者如果您想要一个未知大小的列表的随机排列(换句话说,您想以随机顺序访问列表的所有元素),那么我建议将所有引用复制到新列表中(这不会占用大量内存,除非您有数百万个项目,因为您只存储引用)。然后使用随机整数使用get方法或使用标准java.util.Collections shuffle方法对列表进行排列。
Iterator
接口的类。 - thejhjava.util.Collection
吗? - thejhnull
,这可能会使得集合的ArrayList
相对较大。 - Tom Hawtin - tackline