Skip to content

Latest commit

ย 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Labyrinthe โ€” Gรฉnรฉration multi-langage

Implรฉmentation de l'algorithme de Kruskal randomisรฉ pour gรฉnรฉrer des labyrinthes parfaits, portรฉe en Go, Zig, Rust et TypeScript (Bun).


Table des matiรจres

  1. Qu'est-ce qu'un labyrinthe parfait ?
  2. Reprรฉsentation en mรฉmoire
  3. Union-Find โ€” la structure clรฉ
  4. Algorithme de Kruskal randomisรฉ
  5. Algorithme de Prim randomisรฉ
  6. Optimisations mรฉmoire
  7. Implรฉmentation Go
  8. Implรฉmentation Zig
  9. Implรฉmentation Rust
  10. Implรฉmentation TypeScript (Bun)
  11. Benchmarks
  12. Lancer le projet

1. Qu'est-ce qu'un labyrinthe parfait ?

Un labyrinthe parfait respecte deux rรจgles :

  • Connexe : il existe un chemin entre n'importe quelle paire de cellules.
  • Sans cycle : ce chemin est unique โ€” pas de boucle possible.

Autrement dit, un labyrinthe parfait est un arbre couvrant du graphe de la grille.

โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”
โ”‚           โ”‚       โ”‚
โ”‚   โ”€โ”€โ”€โ”   โ”€โ”˜   โ”€โ”  โ”‚
โ”‚       โ”‚       โ”‚   โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”˜   โ”Œโ”€โ”€โ”€โ”˜   โ”‚
โ”‚   โ”‚               โ”‚
โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”˜

  โœ“ Parfait : une seule solution entre chaque paire de points

2. Reprรฉsentation en mรฉmoire

La grille

Une grille m ร— n a :

  • (m+1) ร— n murs horizontaux (murH) โ€” les plafonds/planchers
  • m ร— (n+1) murs verticaux (murV) โ€” les cรดtรฉs
     murH[0][0]  murH[0][1]  murH[0][2]
        โ†“           โ†“           โ†“
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”
  โ”‚     โ”‚     โ”‚     โ”‚  โ† murV[0][0..3]
  โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค
  โ”‚     โ”‚     โ”‚     โ”‚  โ† murV[1][0..3]
  โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค
  โ”‚     โ”‚     โ”‚     โ”‚  โ† murV[2][0..3]
  โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜
     murH[3][0]  ...

Convention : bit = 0 โ†’ mur fermรฉ, bit = 1 โ†’ passage ouvert

Compression en bits

Au lieu de stocker true/false par boolรฉen (1 octet), on pack 8 murs par octet :

index :  0  1  2  3  4  5  6  7    8  9 ...
         โ”Œโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ” โ”Œโ”€โ”€โ”ฌโ”€โ”€
octet 0  โ”‚0 โ”‚1 โ”‚0 โ”‚1 โ”‚1 โ”‚0 โ”‚0 โ”‚1 โ”‚ โ”‚0 โ”‚1 ...
         โ””โ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”˜ โ””โ”€โ”€โ”ดโ”€โ”€
            โ†‘ mur ouvert

ouvrirMur(arr, idx):
    arr[idx >> 3] |= 1 << (idx & 7)
    โ””โ”€โ”€ octet      โ””โ”€โ”€ bit dans l'octet

Gain : 8ร— moins de RAM โ†’ meilleure utilisation du cache CPU.


3. Union-Find โ€” la structure clรฉ

L'Union-Find (ou Disjoint Set Union) permet de savoir si deux cellules sont dรฉjร  connectรฉes en temps quasi-constant.

Principe de base

Au dรฉpart, chaque cellule est son propre groupe :

Cellules :  0    1    2    3    4
parent   : [-1] [-1] [-1] [-1] [-1]
            โ†‘ racine (valeur nรฉgative = taille du groupe)

Union par taille

Quand on fusionne les groupes de 0 et 1 :

Avant :          Aprรจs :
  0    1            0
 [-1] [-1]         [-2]
                    |
                    1  (parent[1] = 0)
                   [-1โ†’0]

Le groupe le plus petit est attachรฉ sous le plus grand โ†’ l'arbre reste plat.

Path Halving (compression de chemin)

Lors d'un find(x), on fait sauter chaque nล“ud vers son grand-parent :

Avant find(4) :      Aprรจs find(4) :

    0                    0
    |                   /|\
    1                  1  3  4
    |
    2
    |
    3
    |
    4

Rรฉsultat : les appels suivants sont quasi O(1).

Encodage avec valeurs nรฉgatives

parent[x] < 0  โ†’  x est une racine, |parent[x]| = taille du groupe
parent[x] >= 0 โ†’  x pointe vers son parent

Cela รฉvite un tableau rang[] sรฉparรฉ โ†’ moitiรฉ moins de mรฉmoire.

// Go
func find(parent []int32, x int32) int32 {
    for parent[x] >= 0 {
        if parent[parent[x]] >= 0 {
            parent[x] = parent[parent[x]] // saute vers le grand-parent
        }
        x = parent[x]
    }
    return x
}

4. Algorithme de Kruskal randomisรฉ

C'est l'adaptation de l'algorithme de Kruskal (arbre couvrant minimal) oรน les poids des arรชtes sont alรฉatoires.

ร‰tapes

1. Lister tous les murs intรฉrieurs
2. Mรฉlanger alรฉatoirement (Fisher-Yates)
3. Pour chaque mur :
   โ”œโ”€ Les deux cellules sont dans le mรชme groupe ? โ†’ ignorer (รฉvite les cycles)
   โ””โ”€ Sinon โ†’ ouvrir le mur + fusionner les groupes
4. Arrรชter quand mร—nโˆ’1 murs sont ouverts

Visualisation pas ร  pas (grille 3ร—3)

Dรฉpart : 9 cellules, toutes isolรฉes

[0][1][2]        Murs mรฉlangรฉs (exemple) :
[3][4][5]   โ†’   mur(1,2) โ†’ mur(3,4) โ†’ mur(0,1) โ†’ mur(4,5) โ†’ ...
[6][7][8]

ร‰tape 1 : ouvrir mur entre 1 et 2
โ”Œโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”    groupes : {0} {1,2} {3} {4} {5} {6} {7} {8}
โ”‚  โ”‚     โ”‚
โ”œโ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ”€โ”ค
โ”‚  โ”‚  โ”‚  โ”‚
โ”œโ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ”€โ”ค
โ”‚  โ”‚  โ”‚  โ”‚
โ””โ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”˜

ร‰tape 2 : ouvrir mur entre 3 et 4
โ”Œโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”    groupes : {0} {1,2} {3,4} {5} {6} {7} {8}
โ”‚  โ”‚     โ”‚
โ”œโ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ”€โ”ค
โ”‚     โ”‚  โ”‚
โ”œโ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ”€โ”ค
โ”‚  โ”‚  โ”‚  โ”‚
โ””โ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”˜

... aprรจs mร—nโˆ’1 = 8 ouvertures :

โ”Œโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”    Un seul groupe {0..8}
โ”‚        โ”‚    โ†’ labyrinthe parfait !
โ”œโ”€โ”€โ”  โ”Œโ”€โ”€โ”ค
โ”‚  โ”‚  โ”‚  โ”‚
โ”œโ”€โ”€โ”ดโ”€โ”€โ”ค  โ”‚
โ”‚        โ”‚
โ””โ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”˜

Encodage compact des murs

Au lieu d'un struct {i1, j1, i2, j2} (32 octets), on encode en 1 entier 32 bits :

code = (i * n + j) * 2 + direction

direction 0 โ†’ mur ร  droite  : sรฉpare (i,j) et (i, j+1)
direction 1 โ†’ mur en bas    : sรฉpare (i,j) et (i+1, j)

Dรฉcodage :
  dir  = code & 1
  cell = code >> 1
  i1   = cell / n
  j1   = cell % n
  i2   = (dir==1) ? i1+1 : i1
  j2   = (dir==0) ? j1+1 : j1

Gain : 8ร— moins de RAM pour la liste des murs.


5. Algorithme de Prim randomisรฉ

Alternative ร  Kruskal, orientรฉe frontiรจre plutรดt que liste globale.

Diffรฉrence clรฉ

Kruskal Prim
Liste initiale Tous les murs (O(mร—n)) Vide
Mรฉmoire O(mร—n) O(pรฉrimรจtre rรฉgion visitรฉe)
Croissance Globale Locale depuis une cellule

ร‰tapes

1. Marquer la cellule (0,0) comme visitรฉe
2. Ajouter ses 4 murs dans la frontiรจre
3. Tant que la frontiรจre n'est pas vide :
   โ”œโ”€ Tirer un mur au hasard (swap avec le dernier โ†’ O(1))
   โ”œโ”€ Les deux cellules visitรฉes ? โ†’ ignorer
   โ””โ”€ Sinon โ†’ ouvrir le mur, marquer la nouvelle cellule,
              ajouter ses murs ร  la frontiรจre

Visualisation

ร‰tape 1 :          ร‰tape 2 :          ร‰tape 3 :
โ”Œโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”         โ”Œโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”         โ”Œโ”€โ”€โ”ฌโ”€โ”€โ”ฌโ”€โ”€โ”
โ”‚โ–“ โ”‚  โ”‚  โ”‚         โ”‚โ–“    โ”‚  โ”‚         โ”‚โ–“    โ”‚  โ”‚
โ”œโ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ”€โ”ค    โ†’    โ”œโ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ”€โ”ค    โ†’    โ”œโ”€โ”€โ”ฌโ”€โ”€โ”ผโ”€โ”€โ”ค
โ”‚  โ”‚  โ”‚  โ”‚         โ”‚  โ”‚  โ”‚  โ”‚         โ”‚โ–“ โ”‚  โ”‚  โ”‚
โ”œโ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ”€โ”ค         โ”œโ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ”€โ”ค         โ”œโ”€โ”€โ”ผโ”€โ”€โ”ผโ”€โ”€โ”ค
โ”‚  โ”‚  โ”‚  โ”‚         โ”‚  โ”‚  โ”‚  โ”‚         โ”‚  โ”‚  โ”‚  โ”‚
โ””โ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”˜         โ””โ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”˜         โ””โ”€โ”€โ”ดโ”€โ”€โ”ดโ”€โ”€โ”˜
โ–“ = visitรฉ         frontiรจre = {โ†’, โ†“}  frontiรจre grandit

6. Optimisations mรฉmoire

Vue d'ensemble des structures de donnรฉes

                    Go (optimisรฉ)    Zig          Rust         TypeScript
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
murs (walls)        []int32          []u32        Vec<i32>     Int32Array
                    4B/mur           4B/mur       4B/mur       4B/mur

parent (UF)         []int32          []i32        Vec<i32>     โ€”
                    4B/cellule       4B/cellule   4B/cellule

murH/murV           []uint8 (bits)   []u8 (bits)  Vec<u8>      Uint8Array
                    1 bit/mur        1 bit/mur    1 bit/mur    1 bit/mur

Pour une grille 10 000 ร— 10 000

Cellules    : 100 000 000
Murs int.   : ~200 000 000

parent[]    :  100 000 000 ร— 4B  =  381 MB
murs[]      :  200 000 000 ร— 4B  =  762 MB  (temporaire)
murH+murV   :  ~200 000 000 bits =   24 MB

Total approx: ~1.2 GB RAM

7. Implรฉmentation Go

Points notables

Compression de bits :

func ouvrirMur(arr []uint8, idx int) {
    arr[idx>>3] |= 1 << uint(idx&7)
}
//      โ†‘ octet       โ†‘ bit dans l'octet

Union-Find avec valeurs nรฉgatives (pas de tableau rang sรฉparรฉ) :

func find(parent []int32, x int32) int32 {
    for parent[x] >= 0 {           // >= 0 โ†’ pas une racine
        if parent[parent[x]] >= 0 {
            parent[x] = parent[parent[x]] // path halving
        }
        x = parent[x]
    }
    return x
}

func union(parent []int32, x, y int32) bool {
    rx, ry := find(parent, x), find(parent, y)
    if rx == ry { return false }
    if parent[rx] > parent[ry] { rx, ry = ry, rx } // plus nรฉgatif = plus grand
    parent[rx] += parent[ry]  // absorber la taille
    parent[ry] = rx           // relier
    return true
}

Encodage compact :

murs = append(murs, (i*n+j)*2)    // mur ร  droite
murs = append(murs, (i*n+j)*2+1)  // mur en bas

Shuffle Fisher-Yates via math/rand/v2 :

rand.Shuffle(len(murs), func(a, b int) {
    murs[a], murs[b] = murs[b], murs[a]
})

8. Implรฉmentation Zig

Points notables

Pas de GC, allocateur explicite :

const parent = try allocator.alloc(i32, g.m * g.n);
defer allocator.free(parent);

ArrayList en mode unmanaged (0.16+) โ€” l'allocateur est passรฉ ร  chaque opรฉration :

var murs = try std.ArrayList(u32).initCapacity(allocator, 2 * g.m * g.n);
defer murs.deinit(allocator);
try murs.append(allocator, code);

Shuffle :

rng.shuffleWithIndex(u32, murs.items, usize);

Temps via std.c.clock_gettime (std.time simplifiรฉ en 0.16) :

fn nowMs() i64 {
    var ts: std.c.timespec = undefined;
    _ = std.c.clock_gettime(std.c.CLOCK.MONOTONIC, &ts);
    return @as(i64, ts.sec) * 1000 + @divTrunc(ts.nsec, 1_000_000);
}

Compilation optimisรฉe :

zig build-exe -O ReleaseFast main.zig

9. Implรฉmentation Rust

Points notables

Pas de dรฉpendance externe โ€” RNG xorshift intรฉgrรฉ :

struct Rng(u64);
impl Rng {
    fn next(&mut self) -> u64 {
        self.0 ^= self.0 << 13;
        self.0 ^= self.0 >> 7;
        self.0 ^= self.0 << 17;
        self.0
    }
}

Ownership sans GC โ€” Rust garantit l'absence de fuite mรฉmoire ร  la compilation :

let mut parent = vec![-1i32; taille]; // libรฉrรฉ automatiquement en fin de scope
let mut murs: Vec<i32> = Vec::with_capacity(2 * taille);

Compilation optimisรฉe :

rustc -O -o main_rs main.rs

L'option -O active les optimisations LLVM (รฉquivalent ร  -O2).


10. Implรฉmentation TypeScript (Bun)

Points notables

Typed Arrays = pas de GC sur les donnรฉes critiques :

const parent = new Int32Array(taille).fill(-1);  // mรฉmoire contiguรซ, pas de GC
const murs   = new Int32Array(2 * taille);        // idem
const murH   = new Uint8Array(...);               // bits compressรฉs

Les TypedArray sont allouรฉs en dehors du tas JS โ†’ le GC ne les scanne pas โ†’ performance proche du natif.

Shuffle Fisher-Yates inline :

function shuffle(arr: Int32Array): void {
    for (let i = arr.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        const t = arr[i]; arr[i] = arr[j]; arr[j] = t;
    }
}

Timing avec performance.now() :

const debut = performance.now();
// ...
const duree = performance.now() - debut; // en millisecondes (flottant)

Exรฉcution :

bun run main.ts

Bun compile TypeScript ร  la volรฉe sans tsc โ€” zรฉro configuration.


11. Benchmarks

Grille 10 000 ร— 10 000 (100 millions de cellules) sur Apple Silicon M-series.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Langage              โ”‚  Temps   โ”‚ Notes                            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๐Ÿฅ‡ Rust              โ”‚   9.63s  โ”‚ LLVM -O2, RNG xorshift           โ”‚
โ”‚ ๐Ÿฅˆ Zig               โ”‚  12.04s  โ”‚ ReleaseFast, backend LLVM        โ”‚
โ”‚ ๐Ÿฅ‰ Bun (TypeScript)  โ”‚  14.71s  โ”‚ JSC JIT + TypedArray             โ”‚
โ”‚ 4  Go                โ”‚  16.75s  โ”‚ math/rand/v2, GC minimal         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Pourquoi Rust gagne ?

  • Mรชme backend LLVM que Zig, mais auto-vectorisation plus agressive sur ce workload.
  • Ownership garantit qu'il n'y a pas d'aliasing โ†’ le compilateur peut optimiser plus librement.

Pourquoi Bun bat Go ?

  • Les TypedArray JS sont hors du tas GC โ†’ aucune pause de collecte.
  • Le JIT de JavaScriptCore (moteur de Bun) reconnaรฎt les patterns sur Int32Array et gรฉnรจre du code natif efficace.

12. Lancer le projet

Prรฉrequis

Outil Installation
Go https://go.dev/dl
Zig brew install zig
Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Bun brew install bun

Commandes

make all    # lance les 4 langages
make go     # Go seulement
make zig    # Zig seulement
make rust   # Rust seulement
make ts     # Bun/TypeScript seulement
make clean  # supprime les binaires compilรฉs

Changer la taille de la grille

Modifier la constante en haut de chaque fichier :

Fichier Constante
main.go lignes = 10000 / colonnes = 10000
main.zig lignes: usize = 10000 / colonnes: usize = 10000
main.rs LIGNES: usize = 10_000 / COLONNES: usize = 10_000
main.ts LIGNES = 10_000 / COLONNES = 10_000

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages