web-dev-qa-db-fra.com

Mélanger tableau en C

Je cherche une fonction dans ANSI C qui permettrait de randomiser un tableau comme le fait la fonction shuffle() de PHP. Existe-t-il une telle fonction ou dois-je l'écrire moi-même? Et si je dois l'écrire moi-même, quelle est la meilleure façon/la plus performante de le faire? 

Mes idées jusqu'ici:

  • Parcourez le tableau 100 fois, par exemple, et échangez un index aléatoire avec un autre index aléatoire.
  • Créez un nouveau tableau et remplissez-le avec les index aléatoires du premier en vérifiant chaque fois si l'indice est déjà pris (performance = 0 complexité = sérieux)
35
Asmodiel

Collé de Asmodiel 's link to Écrits de Ben Pfaff , pour la persistance:

#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: Et voici une version générique qui fonctionne pour tout type (int, struct, ...) à memcpy. Avec un exemple de programme à exécuter, il nécessite des VLA, tous les compilateurs ne le supportant pas. Par conséquent, vous voudrez peut-être le changer en malloc (qui fonctionnera mal) ou en un tampon statique suffisamment grand pour contenir tout type de message:

#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;
}
36
John Leehey

Le code suivant garantit que le tableau sera brassé en fonction d'une graine aléatoire tirée de l'heure usec. Cela met également en œuvre le Fisher – Yates shuffle correctement. J'ai testé la sortie de cette fonction et elle a l'air bien (même si un élément de tableau est le premier élément après la lecture aléatoire.

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

Il n'y a pas de fonction dans le standard C pour randomiser un tableau.

  • Regardez Knuth - il a des algorithmes pour le travail.
  • Ou regardez Bentley - Perles de programmation ou Plus de perles de programmation.
  • Ou regardez dans presque tous les livres d'algorithmes.

Assurer un mélange équitable (où chaque permutation de l'ordre d'origine est également probable) est simple, mais pas trivial.

6
Jonathan Leffler

Voici une solution qui utilise memcpy au lieu d’affecter, de sorte que vous pouvez l’utiliser pour un tableau sur des données arbitraires. Vous avez besoin de deux fois la mémoire de la matrice d'origine et le coût est linéaire 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++] ) );
}
4
Hyperboreus

Je vais simplement faire écho à la réponse de Neil Butterworth et signaler quelques problèmes avec votre première idée:

Vous avez suggéré, 

Parcourez le tableau 100 fois, par exemple, et échangez un index aléatoire avec un autre index aléatoire.

Rendez ceci rigoureux. Je supposerai l'existence de randn(int n), un wrapper autour d'un RNG, produisant des nombres également distribués dans [0, n -1], et 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;
}

qui permutent a[i] et a[j]. Maintenant, implémentons votre 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
}

Notez que ce n'est pas mieux que cette version plus simple (mais toujours fausse):

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

Eh bien, qu'est-ce qui ne va pas? Considérez le nombre de permutations que ces fonctions vous donnent: Avec n (ou 2 × n pour silly_shuffle) dans les sélections aléatoires dans [0, n -1], le code en sélectionnera «assez» un de n ² (ou 2 × n ²) manières de mélanger le jeu. Le problème est qu'il y a n! = n × (n -1) × × 2 × 1 arrangements possibles du tableau, et ni n ² ni 2 × n ² n'est un multiple de n !, prouvant que certaines permutations sont plus probables que d’autres.

Le shuffle de Fisher-Yates est en fait équivalent à votre seconde suggestion, mais avec quelques optimisations qui changent (performance = 0, complexité = grave) à (performance = très bien, complexité = assez simple). (En fait, je ne suis pas sûr qu’une version correcte plus rapide ou plus simple existe.)

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: Voir aussi ce post sur Coding Horror .

3
J. C. Salomon

Je ne l'ai pas vu parmi les réponses, alors je propose cette solution si elle peut aider quelqu'un

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
Antonin GAVREL