Une façon peut-être slighly artificiel ce (pseudo-code):
- Construire le masque de bits que vous décrivez sur la base desquels les voisins sont ouverts.
Utilisez ce masque de bits comme l'index dans un tableau de:
struct RandomData
{
size_t num_directions;
struct { signed int dx, dy; } deltas[4];
} random_data[16];
où num_directions est le nombre de voisins ouverts, et deltas[]
vous indique comment arriver à chaque voisin.
Cela a beaucoup de données fastidieuses, mais cela supprime le bouclage et le branchement.
MISE À JOUR: Bon, pour une raison ou pour une autre, j'ai eu des problèmes pour laisser partir cette idée. Je blâme un certain nombre d'endoctrinement au sujet de la «programmation pilotée par les données» au travail, puisque ce problème très simple m'a fait «prendre» un peu mieux l'idée de la gestion des données. Ce qui est toujours agréable.
Quoi qu'il en soit, voici une implémentation complète, travail testé et de la fonction aléatoire pas à pas en utilisant les idées ci-dessus:
/* Directions are ordered from north and going clockwise, and assigned to bits:
*
* 3 2 1 0
* WEST | SOUTH | EAST | NORTH
* 8 4 2 1
*/
static void random_walk(unsigned int *px, unsigned int *py, unsigned max_x, unsigned int max_y)
{
const unsigned int x = *px, y = *py;
const unsigned int dirs = ((x > 0) << 3) | ((y < max_y) << 2) |
((x < max_x) << 1) | (y > 0);
static const struct
{
size_t num_dirs;
struct { int dx, dy; } deltas[4];
} step_info[] = {
#define STEP_NORTH { 0, -1 }
#define STEP_EAST { 1, 0 }
#define STEP_SOUTH { 0, 1 }
#define STEP_WEST { -1, 0 }
{ 0 },
{ 1, { STEP_NORTH } },
{ 1, { STEP_EAST } },
{ 2, { STEP_NORTH, STEP_EAST } },
{ 1, { STEP_SOUTH } },
{ 2, { STEP_NORTH, STEP_SOUTH } },
{ 2, { STEP_EAST, STEP_SOUTH } },
{ 3, { STEP_NORTH, STEP_EAST, STEP_SOUTH } },
{ 1, { STEP_WEST } },
{ 2, { STEP_NORTH, STEP_WEST } },
{ 2, { STEP_EAST, STEP_WEST } },
{ 3, { STEP_NORTH, STEP_EAST, STEP_WEST } },
{ 2, { STEP_SOUTH, STEP_WEST } },
{ 3, { STEP_NORTH, STEP_SOUTH, STEP_WEST } },
{ 3, { STEP_EAST, STEP_SOUTH, STEP_WEST } },
{ 4, { STEP_NORTH, STEP_EAST, STEP_SOUTH, STEP_WEST } }
};
const unsigned int step = rand() % step_info[dirs].num_dirs;
*px = x + step_info[dirs].deltas[step].dx;
*py = y + step_info[dirs].deltas[step].dy;
}
int main(void)
{
unsigned int w = 16, h = 16, x = w/2, y = h/2, i;
struct timeval t1, t2;
double seconds;
srand(time(NULL));
gettimeofday(&t1, NULL);
for(i = 0; i < 100000000; i++)
{
random_walk(&x, &y, w - 1, h - 1);
}
gettimeofday(&t2, NULL);
seconds = (t2.tv_sec - t1.tv_sec) + 1e-6 * (t2.tv_usec - t1.tv_usec);
printf("Took %u steps, final position is (%u,%u) after %.2g seconds => %.1f Msteps/second\n", i, x, y, seconds, (i/1e6)/seconds);
return EXIT_SUCCESS;
}
Quelques explications pourraient être dans l'ordre, ce qui précède est assez opaque jusqu'à ce que vous « get » il , Je suppose:
- L'interface à la fonction elle-même devrait être clair. Notez que la largeur et la hauteur de la grille sont représentées par "
max_x
" et "max_y
", pour économiser sur certaines soustractions constantes lorsque vous vérifiez si la position actuelle est sur la bordure ou non.
- La variable
dirs
est définie sur un masque de bits des directions «ouvertes» à parcourir. Pour une grille vide, il s'agit toujours de 0x0f sauf si vous êtes sur une bordure. Cela pourrait être fait pour gérer les murs en testant la carte, bien sûr. Le tableau step_info
recueille des informations sur les étapes disponibles pour chacune des 16 combinaisons possibles de directions ouvertes. Lorsque vous lisez les initialisations (une par ligne) de chaque struct
, pensez à l'index de cette structure en binaire et convertissez-le en bits dans dirs
.
- La macro
STEP_NORTH
(et les amis) réduit la saisie et permet de mieux comprendre ce qui se passe.
- J'aime comment la «viande» de
random_walk()
est juste quatre expressions presque claires, il est rafraîchissant de ne pas voir un seul if
là-bas.
Lors de la compilation avec gcc (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 sur mon 2,4 GHz système x86_64, en utilisant le niveau d'optimisation -O3, la performance semble être un peu moins de 36 millions de mesures par seconde. En lisant l'assemblage, la logique de base est sans branches. Bien sûr, il y a un appel à rand()
, je n'avais pas envie d'aller jusqu'au bout et de mettre en place un générateur de nombres aléatoires local pour le mettre en ligne.
NOTE: Cela ne résout pas la question exacte posée, mais je pensais que la technique valait la peine d'être développée.
Merci pour une réponse rapide :) –
techniquement la méthode de direction aléatoire pourrait prendre un nombre illimité d'essais avec un seul direction valide .... vous pourriez être très malchanceux! – mikera
Oui, je suis d'accord qu'il peut prendre un nombre illimité d'essais (il peut aussi longtemps qu'il y a des directions invalides), mais le nombre moyen est de quatre et il est peu probable que le nombre d'essais serait extrêmement élevé. –