【问题标题】:Random and complete iteration that is scalable?可扩展的随机和完整迭代?
【发布时间】:2012-01-27 15:49:23
【问题描述】:

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

  • [[a, b, c, d, e],
  • [f, g, h],
  • [i, j, k, l]]

所以外部列表的大小为 3,内部列表的大小为 5、3 和 4。

我需要从任何这些内部列表中获取一个随机元素,假设每个元素都有同样随机的机会。所以我可以写一个算法:

  • 0totalListsSize (5 + 3 + 4) = 12之间生成一个随机数,例如randomIndex 7
  • 遍历所有列表并减去它们的大小,如果它大于它们的大小,例如randomIndex 7 - firstListSize 5 = newRandomIndex 2
  • 返回下一个列表中的元素randomIndex 2 in secondList = element g

问题是顺序选择必须是完整且可耗尽的:在上面示例中的 12 次顺序选择之后,我必须选择每个元素一次。

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

  • 不首先初始化所有列表并随机化加入的列表
  • 如果持有已选择索引的布尔数组,则无需遍历该布尔数组来转换生成的randomIndex

【问题讨论】:

    标签: java algorithm collections random


    【解决方案1】:

    您为什么不生成所有可能索引的排列(换句话说,您将序列 [0,12) 打乱)。然后你就知道你会以随机顺序准确地击中所有元素。

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

    【讨论】:

    • 二分查找是一个非常好的主意。我仍然需要初始化一个 totalListsSize 大小的列表并随机播放它,但我认为没有任何办法。
    • 当然你可以在这里用速度换取内存。您期望有多少列表和项目?
    • 外部列表的大小约为 3 到 10。内部列表实际上是迭代器,可以轻松生成多个 1000 000 项。但是很常见的是,在随机选择了一个1000 项目后,算法就完成了。另一方面,它也可以选择所有1000 000 项目:没有办法预先知道。
    • 那么,我给你一个额外的加速:不要在一开始就生成排列。相反,使用哈希表来跟踪已使用的索引。但是在可能选择了 5000 个项目之后,您可能还需要更多。所以只有这样你才能生成(剩余索引的)整个排列。这种概率算法为您节省了以高概率生成排列的框架。
    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

      是否可以在“弹出”它们时从列表中删除元素?

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

      【讨论】:

        【解决方案4】:

        我建议如下:

        • 存储整数列表mark,它会记住每个列表中的选定元素
        • 然后,确定哪个元素对应于您的randomIndex

          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();
          }
          

        该解决方案的复杂性在于 O(numberOfLists + maximumListSize) 的实现,用于实现在恒定时间内提供元素访问的标记列表(例如 ArrayList)。请注意,它不是两个术语的乘积,因为只迭代了一个列表。

        【讨论】:

          【解决方案5】:

          使用以下类:

          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;
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2015-12-05
            • 2013-10-26
            • 1970-01-01
            • 1970-01-01
            • 2022-11-14
            • 2012-03-08
            • 2015-08-02
            • 2018-12-23
            • 2012-03-04
            相关资源
            最近更新 更多