【问题标题】:Algorithm to iterate N-dimensional array in pseudo random order以伪随机顺序迭代 N 维数组的算法
【发布时间】:2016-03-11 09:51:12
【问题描述】:

我有一个数组,我想以随机顺序迭代。也就是说,我希望我的迭代以看似随机的顺序只访问每个元素一次。

是否有可能实现一个迭代器来迭代像这样的元素而不首先将订单或其他数据存储在查找表中

是否可以对 N>1 的 N 维数组执行此操作?

更新:一些答案提到如何通过存储索引来做到这一点。这个问题的一个重点是如何在不存储索引或其他数据的情况下做到这一点。

【问题讨论】:

  • 你的问题就是你的问题! “迭代”的定义: 将一组指令重复指定次数或直到达到特定结果的过程。 -> 你原来的问题说得很清楚“我想以随机方式迭代”。所以,我们迭代的次数是随机的。但是,由于这是随机的,您需要记住访问过的地方,或者您的迭代不再是迭代,而只是随机访问。是的,随机访问任意地点多次都没有问题。但是如果你迭代,你需要记住你访问过的地方。类似于访问国家
  • 我不敢苟同,因为我知道这个问题有一个纯数学的解决方案,不需要存储多个种子数。我只是不记得它的名称或实现的细节。因为我希望这对其他人有用,所以我决定在 S.O. 上提问
  • 请看我更新的答案。而且,再次,如果您真的迭代或随机访问 - 您不需要保存访问过的地方。如果你随机迭代 - 你会这样做!
  • “我知道有办法”。好的,给我们解决方案。我们会等的。 :o) 好吧,我知道如何编程。数学……这是给爱因斯坦的。顺便提一句。如果您查看我的解决方案,您并没有真正写入任何表格。你所做的一切 - 你有一个控制列表,你可以从中删除你已经访问过的项目。
  • @T.S.我终于想起来了:-D 请在下面查看我的答案。

标签: arrays sorting random iterator


【解决方案1】:

您可以在列表中收集所有可能的索引,然后删除要访问的随机索引。我知道这有点像查找表,但我没有看到除此之外的任何其他选项。

这里是一个一维数组的例子(适应多维应该是微不足道的):

class RandomIterator<T> {
    T[] array;
    List<Integer> remainingIndeces;

    public RandomIterator(T[] array) {
        this.array = array;
        this.remainingIndeces = new ArrayList<>();
        for(int i = 0;i<array.length;++i)
            remainingIndeces.add(i);
    }

    public T next() {
        return array[remainingIndeces.remove((int)(Math.random()*remainingIndeces.size()))];
    }

    public boolean hasNext() {
        return !remainingIndeces.isEmpty();
    }
}

附带说明:如果此代码与性能相关,则此方法的性能会更差,因为如果您使用由数组支持的列表(链表无济于事),从列表中随机删除会触发副本或者,因为索引访问是 O(n))。我建议使用一个查找结构(例如 Java 中的 HashSet)来存储所有访问过的索引来规避这个问题(尽管这正是你不想使用的)

编辑:另一种方法是复制所述数组并使用库函数对其进行洗牌,然后以线性顺序遍历它。如果您的数组不是那么大,这似乎是最具可读性和性能的选项。

【讨论】:

  • 嗨!我的问题有点含糊。我现在已经更新了。我真的不想存储任何数据来解决这个问题。
【解决方案2】:

是的,这是可能的。想象一下 3D 数组(您可能不会使用除此之外的任何东西)。这就像一个立方体,所有 3 条线连接的地方都是一个单元格。您可以使用字典枚举单元格 1 到 N,您可以在循环中执行此初始化,并创建用于随机绘制的单元格列表

初始化

totalCells = ... (xMax * yMax * zMax)
index = 0
For (x = 0; x < xMax ; x++)
{
    For (y = 0; y < yMax ; y++)
    {
        For (z = 0; z < zMax ; z++)
        {         
            dict.Add(i, new Cell(x, y, z))
            lst.Add(i)
            i++
        }
    }
}

现在,你所要做的就是随机迭代

Do While (lst.Count > 0)
{
    indexToVisit = rand.Next(0, lst.Count - 1)
    currentCell = dict[lst[indexToVisit]]
    lst.Remove(indexToVisit)
    // Do something with current cell here
    . . . .  . . 
}

这是伪代码,因为您没有提及您使用的语言

另一种方法是随机化 3 个(或任何数量的维度)列表,然后通过它们嵌套循环 - 这最终将是随机的。

【讨论】:

    【解决方案3】:

    您需要创建一个伪随机数生成器,生成从 0 到 X-1 的值,并在重复循环之前进行 X 次迭代,其中 X 是所有维度大小的乘积。我不知道是否有通用的解决方案来做到这一点。一种随机数生成器的 Wiki 文章:

    http://en.wikipedia.org/wiki/Linear_congruential_generator

    【讨论】:

      【解决方案4】:

      我决定解决这个问题,因为不记得我以前听过的解决方案的名称,这让我非常恼火。不过最后我确实记得,更多内容在这篇文章的底部。

      我的解决方案取决于一些巧妙计算的数字的数学属性

      range = array size
      prime = closestPrimeAfter(range)
      root = closestPrimitiveRootTo(range/2)
      state = root
      

      通过这种设置,我们可以重复计算以下内容,它会以看似随机的顺序对数组的所有元素进行一次精确的迭代,之后它将再次以相同的顺序循环遍历数组。

      state = (state * root) % prime
      

      我在 Java 中实现并测试了它,所以我决定将我的代码粘贴到这里以供将来参考。

      import java.math.BigInteger;
      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.Random;
      
      public class PseudoRandomSequence {
      
          private long            state;
          private final long  range;
          private final long  root;
          private final long  prime;
          //Debugging counter
          private int             dropped = 0;
      
          public PseudoRandomSequence(int r) {
              range = r;
              prime = closestPrimeAfter(range);
              root = modPow(generator(prime), closestPrimeTo(prime / 2), prime);
              reset();
              System.out.println("-- r:" + range);
              System.out.println("   p:" + prime);
              System.out.println("   k:" + root);
              System.out.println("   s:" + state);
          }
      
          // https://en.wikipedia.org/wiki/Primitive_root_modulo_n
          private static long modPow(long base, long exp, long mod) {
              return BigInteger.valueOf(base).modPow(BigInteger.valueOf(exp), BigInteger.valueOf(mod)).intValue();
          }
      
          //http://e-maxx-eng.github.io/algebra/primitive-root.html
          private static long generator(long p) {
              ArrayList<Long> fact = new ArrayList<Long>();
              long phi = p - 1, n = phi;
              for (long i = 2; i * i <= n; ++i) {
                  if (n % i == 0) {
                      fact.add(i);
                      while (n % i == 0) {
                          n /= i;
                      }
                  }
              }
              if (n > 1) fact.add(n);
              for (long res = 2; res <= p; ++res) {
                  boolean ok = true;
                  for (long i = 0; i < fact.size() && ok; ++i) {
                      ok &= modPow(res, phi / fact.get((int) i), p) != 1;
                  }
                  if (ok) {
                      return res;
                  }
              }
              return -1;
          }
      
          public long get() {
              return state - 1;
          }
      
          public void advance() {
              //This loop simply skips all results that overshoot the range, which should never happen if range is a prime number.
              dropped--;
              do {
                  state = (state * root) % prime;
                  dropped++;
              } while (state > range);
          }
      
          public void reset() {
              state = root;
              dropped = 0;
          }
      
          private static boolean isPrime(long num) {
              if (num == 2) return true;
              if (num % 2 == 0) return false;
              for (int i = 3; i * i <= num; i += 2) {
                  if (num % i == 0) return false;
              }
              return true;
          }
      
          private static long closestPrimeAfter(long n) {
              long up;
              for (up = n + 1; !isPrime(up); ++up)
                  ;
              return up;
          }
      
          private static long closestPrimeBefore(long n) {
              long dn;
              for (dn = n - 1; !isPrime(dn); --dn)
                  ;
              return dn;
          }
      
          private static long closestPrimeTo(long n) {
              final long dn = closestPrimeBefore(n);
              final long up = closestPrimeAfter(n);
              return (n - dn) > (up - n) ? up : dn;
          }
      
          private static boolean test(int r, int loops) {
              final int array[] = new int[r];
              Arrays.fill(array, 0);
              System.out.println("TESTING: array size: " + r + ", loops: " + loops + "\n");
              PseudoRandomSequence prs = new PseudoRandomSequence(r);
              final long ct = loops * r;
              //Iterate the array 'loops' times, incrementing the value for each cell for every visit. 
              for (int i = 0; i < ct; ++i) {
                  prs.advance();
                  final long index = prs.get();
                  array[(int) index]++;
              }
              //Verify that each cell was visited exactly 'loops' times, confirming the validity of the sequence
              for (int i = 0; i < r; ++i) {
                  final int c = array[i];
                  if (loops != c) {
                      System.err.println("ERROR: array element @" + i + " was " + c + " instead of " + loops + " as expected\n");
                      return false;
                  }
              }
              //TODO: Verify the "randomness" of the sequence
              System.out.println("OK:  Sequence checked out with " + prs.dropped + " drops (" + prs.dropped / loops + " per loop vs. diff " + (prs.prime - r) + ") \n");
              return true;
          }
      
          //Run lots of random tests
          public static void main(String[] args) {
              Random r = new Random();
              r.setSeed(1337);
              for (int i = 0; i < 100; ++i) {
                  PseudoRandomSequence.test(r.nextInt(1000000) + 1, r.nextInt(9) + 1);
              }
          }
      
      }
      

      如顶部所述,在我花了大半夜的时间实际获得结果后大约 10 分钟,我确实记得我在哪里读到了有关原始方法的信息。它是在 2D 图形“溶解”效果的小型 C 实现中,如 Graphics Gems vol 中所述。 1 这反过来又是对 2D 的适应,对称为“LFSR”的机制进行了一些优化(维基百科文章 here,原始溶解.c 源代码 here)。

      【讨论】:

      • 我需要几天时间来消化。希望我有时间。但我会检查
      猜你喜欢
      • 2013-09-30
      • 1970-01-01
      • 2013-12-10
      • 1970-01-01
      • 2013-04-03
      • 2012-09-30
      • 2010-10-18
      • 1970-01-01
      • 2011-11-20
      相关资源
      最近更新 更多