以伪随机顺序迭代N维数组的算法

时间:2015-12-06 20:56:20

标签: arrays sorting random iterator

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

是否可以实现一个迭代器来迭代像之类的元素而不先将查询表或其他数据存储在查找表中

是否可以对N> 1?

的N维数组进行此操作

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

4 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:1)

我决定解决这个问题,因为它让我生气,不记得我之前听过的解决方案的名称。不过我记得最后还记得在这篇文章的最后部分。

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

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实现中解散&#34;效果如图形宝石卷中所述。 1反过来是对2D的适应,对机制的一些优化称为&#34; LFSR&#34; (维基百科文章here,原始dissolve.c源代码here)。

答案 2 :(得分:0)

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

以下是一维数组的示例(适应多个维度应该是微不足道的):

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)来存储所有访问过的索引以避免这个问题(虽然这正是你不想使用的)

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

答案 3 :(得分:0)

是的,有可能。想象一下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个(或任何数量的维度)列表,然后只是嵌套循环它们 - 这最终将是随机的。