Generating shuffled range using a PRNG rather than shuffling

前端 未结 5 1975
孤城傲影
孤城傲影 2020-12-01 04:55

Is there any known algorithm that can generate a shuffled range [0..n) in linear time and constant space (when output produced iteratively), given an arbitrary seed value?

相关标签:
5条回答
  • 2020-12-01 05:17

    Based on Jason's answer, I've made a simple straightforward implementation in C#. Find the next largest power of two greater than N. This makes it trivial to generate a and c, since c needs to be relatively prime (meaning it can't be divisible by 2, aka odd), and (a-1) needs to be divisible by 2, and (a-1) needs to be divisible by 4. Statistically, it should take 1-2 congruences to generate the next number (since 2N >= M >= N).

    class Program
    {
        IEnumerable<int> GenerateSequence(int N)
        {
            Random r = new Random();
            int M = NextLargestPowerOfTwo(N);
            int c = r.Next(M / 2) * 2 + 1; // make c any odd number between 0 and M
            int a = r.Next(M / 4) * 4 + 1; // M = 2^m, so make (a-1) divisible by all prime factors, and 4
    
            int start = r.Next(M);
            int x = start;
            do
            {
                x = (a * x + c) % M;
                if (x < N)
                    yield return x;
            } while (x != start);
        }
    
        int NextLargestPowerOfTwo(int n)
        {
            n |= (n >> 1);
            n |= (n >> 2);
            n |= (n >> 4);
            n |= (n >> 8);
            n |= (n >> 16);
            return (n + 1);
        }
    
        static void Main(string[] args)
        {
            Program p = new Program();
            foreach (int n in p.GenerateSequence(1000))
            {
                Console.WriteLine(n);
            }
    
            Console.ReadKey();
        }
    }
    
    0 讨论(0)
  • 2020-12-01 05:17

    Here is a Python implementation of the Linear Congruential Generator from FryGuy's answer. Because I needed to write it anyway and thought it might be useful for others.

    import random
    import math
    
    def lcg(start, stop):
        N = stop - start
    
        # M is the next largest power of 2
        M = int(math.pow(2, math.ceil(math.log(N+1, 2))))
    
        # c is any odd number between 0 and M
        c = random.randint(0, M/2 - 1) * 2 + 1
    
        # M=2^m, so make (a-1) divisible by all prime factors and 4
        a = random.randint(0, M/4 - 1) * 4 + 1
    
        first = random.randint(0, M - 1)
        x = first
        while True:
            x = (a * x + c) % M
            if x < N:
                yield start + x
            if x == first:
                break
    
    if __name__ == "__main__":
        for x in lcg(100, 200):
            print x,
    
    0 讨论(0)
  • 2020-12-01 05:18

    Sounds like you want an algorithm which is guaranteed to produce a cycle from 0 to n-1 without any repeats. There are almost certainly a whole bunch of these depending on your requirements; group theory would be the most helpful branch of mathematics if you want to delve into the theory behind it.

    If you want fast and don't care about predictability/security/statistical patterns, an LCG is probably the simplest approach. The wikipedia page you linked to contains this (fairly simple) set of requirements:

    The period of a general LCG is at most m, and for some choices of a much less than that. The LCG will have a full period if and only if:

    1. c and m are relatively prime,
    2. a - 1 is divisible by all prime factors of m
    3. a - 1 is a multiple of 4 if m is a multiple of 4

    Alternatively, you could choose a period N >= n, where N is the smallest value that has convenient numerical properties, and just discard any values produced between n and N-1. For example, the lowest N = 2k - 1 >= n would let you use linear feedback shift registers (LFSR). Or find your favorite cryptographic algorithm (RSA, AES, DES, whatever) and given a particular key, figure out the space N of numbers it permutes, and for each step apply encryption once.

    If n is small but you want the security to be high, that's probably the trickiest case, as any sequence S is likely to have a period N much higher than n, but is also nontrivial to derive a nonrepeating sequence of numbers with a shorter period than N. (e.g. if you could take the output of S mod n and guarantee nonrepeating sequence of numbers, that would give information about S that an attacker might use)

    0 讨论(0)
  • 2020-12-01 05:24

    See my article on secure permutations with block ciphers for one way to do it.

    0 讨论(0)
  • 2020-12-01 05:28

    Look into Linear Feedback Shift Registers, they can be used for exactly this. The short way of explaining them is that you start with a seed and then iterate using the formula

    x = (x << 1) | f(x)
    

    where f(x) can only return 0 or 1.

    If you choose a good function f, x will cycle through all values between 1 and 2^n-1 (where n is some number), in a good, pseudo-random way. Example functions can be found here, e.g. for 63 values you can use

    f(x) = ((x >> 6) & 1) ^ ((x >> 5) & 1)
    
    0 讨论(0)
提交回复
热议问题