Java 可伸缩的随机完整迭代?

Java 可伸缩的随机完整迭代?,java,algorithm,collections,random,Java,Algorithm,Collections,Random,假设我有一个列表,如下所示: [a,b,c,d,e] [f,g,h] [i,j,k,l]] 因此,外部列表的大小为3,内部列表的大小为5、3和4 我需要得到这些内部列表中任意一个的随机元素,给每个元素一个相同的随机机会。所以我可以写一个算法: 生成一个介于0和Totalistssize(5+3+4)=12之间的随机数,例如randomIndex 7 迭代所有列表,如果其大小大于其大小,则减去其大小,例如randomIndex 7-firstListSize 5=newRandomIndex

假设我有一个列表,如下所示:

  • [a,b,c,d,e]
  • [f,g,h]
  • [i,j,k,l]]
因此,外部列表的大小为3,内部列表的大小为5、3和4

我需要得到这些内部列表中任意一个的随机元素,给每个元素一个相同的随机机会。所以我可以写一个算法:

  • 生成一个介于
    0
    Totalistssize(5+3+4)=12之间的随机数,例如
    randomIndex 7
  • 迭代所有列表,如果其大小大于其大小,则减去其大小,例如
    randomIndex 7-firstListSize 5=newRandomIndex 2
  • 返回下一个列表中的元素,
    第二个列表中的随机索引2=元素g
问题是顺序选择必须是完整且可耗尽的:在上面的示例中进行了12次顺序选择之后,我必须选择每个元素一次。

有没有一种可以扩展的方法

  • 不首先初始化所有列表并随机化联接的列表
  • 如果持有一个已选择索引的布尔数组,而不必迭代该布尔数组来转换生成的
    randomIndex

好吧,您可以创建一组可能的索引,随机选择其中一个,删除所选索引并访问相应的对象

或者,正如您所说,您可以创建一个关联列表并从中选择,删除任何选定的元素

这两种方法都需要一些初始化,但无论如何,您必须做一些簿记


另一种方法可能是将选定的索引存储在一个集合中,在创建一个新的随机索引后,您可以检查新索引是否已经在“已使用”集合中。然而,如果您想在整个池中选择一个较高的百分比,这种方法将变得越来越慢,因为您会更频繁地使用已使用的索引。如果只从一个大列表中选择几个,这种方法可能会更好,因为它不需要太多的初始化和内存。

为什么不生成所有可能索引的排列(换言之,您洗牌序列[0,12])。然后您就知道,您将以随机顺序恰好命中所有元素一次


为了高效查找,您可以保留数组的运行总长度。在您的示例中:0、5、8、12。这样,您可以通过“总索引”进行二进制搜索以查找任何数组。

在“弹出”元素时,是否可以从列表中删除这些元素


如果是这样的话,您可以简单地这样做:当您选择一个元素时,只需从列表中删除它,然后在计算下一个索引之前从总大小中减去一个,然后根据需要重复执行。

我建议如下:

  • 存储整数列表
    标记
    ,以记住每个列表中的选定元素
  • 然后,要确定哪个元素对应于您的
    随机索引
    ,请执行以下操作:

    List<List<Integer>> mark    = // ... one mark list for each array
    E[][] lists = // ... the lists you want to select random elements from
    
    void selectAllElementsOnce( int totalElementCount ){
        Random r = new Random();
        for(int selected = 0; selected < totalElementCount; selected++){
            E element = this.elementForRandomIndex(r.nextInt(totalElementCount - selected));
            // do something with this element
        }
    }
    
    E elementForRandomIndex( int randomIndex ) {
        for(int i = 0; i < lists.length; i++ ) {
            if(randomIndex < lists[i].length - mark.get( i ).size()) {
                int j = 0;
                while(mark.get( i ).size() > j && mark.get( i ).get( j ) <= randomIndex) {
                    randomIndex++ ;
                    j++ ;
                }
                mark.get( i ).add( j, randomIndex );
                return lists[i][randomIndex];
            } else {
                randomIndex -= lists[i].length - mark.get( i ).size();
            }
        }
        throw new IndexOutOfBoundsException();
    }
    
    List mark=/…每个数组一个标记列表
    E[][]列出=/…要从中选择随机元素的列表
    void selectAllegementSonce(int totalElementCount){
    随机r=新随机();
    对于(int selected=0;selected而(mark.get(i).size()>j&&mark.get(i).get(j)则使用以下类:

    import java.util.Enumeration;
    import java.util.Random;
    
    public class RandomPermuteIterator implements Enumeration<Long> {
        int c = 1013904223, a = 1664525;
        long seed, N, m, next;
        boolean hasNext = true;
    
        public RandomPermuteIterator(long N) throws Exception {
            if (N <= 0 || N > Math.pow(2, 62)) throw new Exception("Unsupported size: " + N);
            this.N = N;
            m = (long) Math.pow(2, Math.ceil(Math.log(N) / Math.log(2)));
            next = seed = new Random().nextInt((int) Math.min(N, Integer.MAX_VALUE));
        }
    
        public static void main(String[] args) throws Exception {
            RandomPermuteIterator r = new RandomPermuteIterator(100);
            while (r.hasMoreElements()) System.out.print(r.nextElement() + " ");
        }
    
        @Override
        public boolean hasMoreElements() {
            return hasNext;
        }
    
        @Override
        public Long nextElement() {
            next = (a * next + c) % m;
            while (next >= N) next = (a * next + c) % m;
            if (next == seed) hasNext = false;
            return  next;
        }
    }
    
    import java.util.Enumeration;
    导入java.util.Random;
    公共类RandomPermuteIterator实现枚举{
    INTC=1013904223,a=1664525;
    长种子,N,m,次之;
    布尔hasNext=true;
    公共随机PermuteIterator(长N)引发异常{
    if(N Math.pow(2,62))抛出新异常(“不支持的大小:+N”);
    这个,N=N;
    m=(long)Math.pow(2,Math.ceil(Math.log(N)/Math.log(2));
    next=seed=new Random().nextInt((int)Math.min(N,Integer.MAX_VALUE));
    }
    公共静态void main(字符串[]args)引发异常{
    随机置换器r=新的随机置换器(100);
    while(r.hasMoreElements())System.out.print(r.nextElement()+);
    }
    @凌驾
    公共布尔值hasMoreElements(){
    返回hasNext;
    }
    @凌驾
    公共长期nextElement(){
    下一步=(a*下一步+c)%m;
    而(next>=N)next=(a*next+c)%m;
    如果(next==seed)hasNext=false;
    下一步返回;
    }
    }
    
    二进制搜索是一个非常好的主意。我仍然需要初始化一个大小为
    totalistsize
    的列表并将其洗牌,但我不认为有任何方法可以解决这个问题。当然,你可以在这里用速度换取内存。你希望有多少列表和项目?外部列表的大小约为3到10。内部列表实际上是一个可以轻松生成超过
    1000
    个项目的RATOR。但很常见的是,在随机选择
    1000个
    个项目后,算法就会完成。另一方面,它也可以选择所有
    1000
    个项目:无法预先知道。然后,我为您提供了一个额外的加速:不生成在开始时进行排列。相反,使用哈希表跟踪已使用的索引。但是在选择了5000个项目之后,您可能需要更多的项目。因此,只有这样,您才能生成(剩余索引的)整个排列.这种概率算法节省了生成高概率排列的时间。