Shuffle array in C

前端 未结 7 1766
执念已碎
执念已碎 2020-11-22 03:15

I\'m looking for a function in ANSI C that would randomize an array just like PHP\'s shuffle() does. Is there such a function or do I have to write it on my own

相关标签:
7条回答
  • 2020-11-22 03:30

    Pasted from Asmodiel's link to Ben Pfaff's Writings, for persistence:

    #include <stdlib.h>
    
    /* Arrange the N elements of ARRAY in random order.
       Only effective if N is much smaller than RAND_MAX;
       if this may not be the case, use a better random
       number generator. */
    void shuffle(int *array, size_t n)
    {
        if (n > 1) 
        {
            size_t i;
            for (i = 0; i < n - 1; i++) 
            {
              size_t j = i + rand() / (RAND_MAX / (n - i) + 1);
              int t = array[j];
              array[j] = array[i];
              array[i] = t;
            }
        }
    }
    

    EDIT: And here's a generic version that works for any type (int, struct, ...) through memcpy. With an example program to run, it requires VLAs, not every compiler supports this so you might want to change that to malloc (which will perform badly) or a static buffer large enough to accommodate any type you throw at it:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    
    /* compile and run with
     * cc shuffle.c -o shuffle && ./shuffle */
    
    #define NELEMS(x)  (sizeof(x) / sizeof(x[0]))
    
    /* arrange the N elements of ARRAY in random order.
     * Only effective if N is much smaller than RAND_MAX;
     * if this may not be the case, use a better random
     * number generator. */
    static void shuffle(void *array, size_t n, size_t size) {
        char tmp[size];
        char *arr = array;
        size_t stride = size * sizeof(char);
    
        if (n > 1) {
            size_t i;
            for (i = 0; i < n - 1; ++i) {
                size_t rnd = (size_t) rand();
                size_t j = i + rnd / (RAND_MAX / (n - i) + 1);
    
                memcpy(tmp, arr + j * stride, size);
                memcpy(arr + j * stride, arr + i * stride, size);
                memcpy(arr + i * stride, tmp, size);
            }
        }
    }
    
    #define print_type(count, stmt) \
        do { \
        printf("["); \
        for (size_t i = 0; i < (count); ++i) { \
            stmt; \
        } \
        printf("]\n"); \
        } while (0)
    
    struct cmplex {
        int foo;
        double bar;
    };
    
    int main() {
        srand(time(NULL));
    
        int intarr[] = { 1, -5, 7, 3, 20, 2 };
    
        print_type(NELEMS(intarr), printf("%d,", intarr[i]));
        shuffle(intarr, NELEMS(intarr), sizeof(intarr[0]));
        print_type(NELEMS(intarr), printf("%d,", intarr[i]));
    
        struct cmplex cmparr[] = {
            { 1, 3.14 },
            { 5, 7.12 },
            { 9, 8.94 },
            { 20, 1.84 }
        };
    
        print_type(NELEMS(intarr), printf("{%d %f},", cmparr[i].foo, cmparr[i].bar));
        shuffle(cmparr, NELEMS(cmparr), sizeof(cmparr[0]));
        print_type(NELEMS(intarr), printf("{%d %f},", cmparr[i].foo, cmparr[i].bar));
    
        return 0;
    }
    
    0 讨论(0)
  • 2020-11-22 03:42

    I didn't see it among answers so I propose this solution if it can help anybody:

    static inline void shuffle(size_t n, int arr[])
    {
        size_t      rng;
        size_t      i;
        int         tmp[n];
        int         tmp2[n];
    
       memcpy(tmp, arr, sizeof(int) * n);
        bzero(tmp2, sizeof(int) * n);
        srand(time(NULL));
        i = 0;
        while (i < n)
        {
            rng = rand() % (n - i);
            while (tmp2[rng] == 1)
                ++rng;
            tmp2[rng] = 1;
            arr[i] = tmp[rng];
            ++i;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 03:44

    The following code ensures that the array will be shuffled based on a random seed taken from the usec time. Also this implements the Fisher–Yates shuffle properly. I've tested the output of this function and it looks good (even expectation of any array element being the first element after shuffle. Also even expectation for being the last).

    void shuffle(int *array, size_t n) {    
        struct timeval tv;
        gettimeofday(&tv, NULL);
        int usec = tv.tv_usec;
        srand48(usec);
    
    
        if (n > 1) {
            size_t i;
            for (i = n - 1; i > 0; i--) {
                size_t j = (unsigned int) (drand48()*(i+1));
                int t = array[j];
                array[j] = array[i];
                array[i] = t;
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 03:46

    I’ll just echo Neil Butterworth’s answer, and point out some trouble with your first idea:

    You suggested,

    Iterate through the array for, say, 100 times and exchange a random index with another random index

    Make this rigorous. I'll assume the existence of randn(int n), a wrapper around some RNG, producing numbers evenly distributed in [0, n-1], and swap(int a[], size_t i, size_t j),

    swap(int a[], size_t i, size_t j) {
      int temp = a[i]; a[i] = a[j]; a[j] = temp;
    }
    

    which swaps a[i] and a[j]. Now let’s implement your suggestion:

    void silly_shuffle(size_t n, int a[n]) {
        for (size_t i = 0; i < n; i++)
            swap(a, randn(n), randn(n)); // swap two random elements
    }
    

    Notice that this is not any better than this simpler (but still wrong) version:

    void bad_shuffle(size_t n, int a[n]) {
        for (size_t i = 0; i < n; i++)
            swap(a, i, randn(n));
    }
    

    Well, what’s wrong? Consider how many permutations these functions give you: With n (or 2×n for silly_shuffle) random selections in [0, n-1], the code will “fairly” select one of n² (or 2×n²) ways to shuffle the deck. The trouble is that there are n! = n×(n-1)×⋯×2×1 possible arrangements of the array, and neither n² nor 2×n² is a multiple of n!, proving that some permutations are more likely than others.

    The Fisher-Yates shuffle is actually equivalent to your second suggestion, only with some optimizations that change (performance = 0, complexity = serious) to (performance = very good, complexity = pretty simple). (Actually, I’m not sure that a faster or simpler correct version exists.)

    void fisher_yates_shuffle(size_t n, int a[n]) {
        for (size_t i = 0; i < n; i++)
            swap(a, i, i+randn(n-1-i)); // swap element with random later element
    }
    

    ETA: See also this post on Coding Horror.

    0 讨论(0)
  • 2020-11-22 03:48

    There isn't a function in the C standard to randomize an array.

    • Look at Knuth - he has algorithms for the job.
    • Or look at Bentley - Programming Pearls or More Programming Pearls.
    • Or look in almost any algorithms book.

    Ensuring a fair shuffle (where every permutation of the original order is equally likely) is simple, but not trivial.

    0 讨论(0)
  • 2020-11-22 03:48

    Here a solution that uses memcpy instead of assignment, so you can use it for array over arbitrary data. You need twice the memory of original array and the cost is linear O(n):

    void main ()
    {
        int elesize = sizeof (int);
        int i;
        int r;
        int src [20];
        int tgt [20];
    
        for (i = 0; i < 20; src [i] = i++);
    
        srand ( (unsigned int) time (0) );
    
        for (i = 20; i > 0; i --)
        {
            r = rand () % i;
            memcpy (&tgt [20 - i], &src [r], elesize);
            memcpy (&src [r], &src [i - 1], elesize);
        }
        for (i = 0; i < 20; printf ("%d ", tgt [i++] ) );
    }
    
    0 讨论(0)
提交回复
热议问题