Java 从顺序集合中获取随机元素

Java 从顺序集合中获取随机元素,java,iterator,Java,Iterator,我与一个API交谈,该API为我提供了一个集合上的java.util.Iterator。这意味着我可以对它进行迭代,但无法直接/随机访问元素 现在我的问题是:我想从这个集合中获取一个随机元素。我该怎么做?我想我可以建立一个允许直接访问的新集合,但这不是有点消耗内存吗?我还可以对整个集合进行迭代,并对每个元素“掷骰子”,看看是否应该接受该元素并退出迭代或继续。但是我需要集合的大小,我不能从迭代器中得到它 提前感谢。迭代时,您知道迭代了多少对象,因此您知道当前元素是随机选择的元素的概率。因此,您只需

我与一个API交谈,该API为我提供了一个集合上的
java.util.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;
}
publicstatict selectRandom(最终迭代器iter,最终随机){
如果(!iter.hasNext()){
抛出新的IllegalArgumentException();
}
if(random==null){
抛出新的NullPointerException();
}
T selected=iter.next();
整数计数=1;
while(iter.hasNext()){
最终T电流=iter.next();
++计数;
if(random.nextInt(count)==0){
所选=当前;
}
}
返回选中的;
}
(堆栈溢出免责声明:未编译,当然也未测试。)

另请参见Java拼图游戏中关于集合的部分。shuffle。

唯一安全的解决方案(在没有进一步信息的情况下)是您描述的方式: 从
迭代器
创建一个
列表
,然后选择一个随机元素

如果基础集合的大小始终相同,则平均可以减少一半的工作量-只需在迭代次数随机后使用迭代器.next()之后得到的元素即可


顺便说一句:您真的在使用实现
java.util.Iterator
的集合吗?

这取决于需求,如果集合的大小不是很大,那么这就可以了,否则您应该迭代并使用您提到的骰子方法

List<Object> list = Arrays.asList(yourCollection.toArray(new Object[0]));
result = list.get(new Random().nextInt(list.size()));
List List=Arrays.asList(yourCollection.toArray(新对象[0]);
结果=list.get(new Random().nextInt(list.size());

有一种方法可以在通过集合的一次过程中完成,该方法不需要使用大量额外内存(只需集合中一个元素的大小加上一个浮点值)。在伪代码中:

  • 遍历集合
  • 对于每个项目,生成一个随机浮动
  • 如果浮点值是迄今为止看到的最低值(或最高值,无所谓),则将集合中的当前项存储在临时变量中。(还存储新的最低随机值。)
  • 一旦到达集合的末尾,temp变量中就有一个随机项
显然,这样做的缺点是每次调用时都会遍历整个集合,但是对于所面临的约束,您没有太多选择


更新:我终于想起了这类问题的名称。这称为。

如果您确实没有任何随机访问权限,并且您有一个非常大的列表,因此无法复制它,则可以执行以下操作:

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方法来排列列表。

使用此方法生成加权测试数据。虽然效率不高,但很容易

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 + "]";
    }

}
类概率集{
Set options=new HashSet();
类选项{
E对象;
双分钟;
双峰;
私有选项(E对象,双prob){
this.object=对象;
最小值=总概率;
最大值=总概率+概率;
}
@凌驾
公共字符串toString(){
返回“Option[object=“+object+”,min=“+min+”,max=“+max+””;
}
}
双总概率=0;
随机rnd=新随机();
公共无效添加(E对象,双重概率){
选项元组=新选项(对象,概率);
添加(元组);
总概率+=概率;
}
公共E getrandom元素(){
double no=rnd.nextDouble()*totalProb;
for(选项元组:选项){
if(no>=tuple.min&&no
注:概率参数将与总量相关,而不是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());
    }

}
publicstaticvoidmain(字符串[]args){
ProbabilitySet stati=新的ProbabilitySet();
统计添加(“超时”,0.2);
统计添加(“失败”,0.2);
统计添加(“成功”,1.0);
对于(int i=0;i<100;i++){
System.out.println(stati.getRandomElement());
}
}

集合通常不应该是实现
迭代器的类。您的集合是
java.util.collection
?内存消耗不应该太大。新集合只包含指向实际数据的指针,因此新集合对象的大小!=