language-Modula2-0.1: examples/Modula-2_Libraries/andrea-m2/lib/generic/shufflefa.mod
IMPLEMENTATION MODULE ShuffleFast;
(* See the definition module for details *)
(* V3.0, John Andrea, Jun.1793 - determine bitmap size at runtime *)
(* V2.0, John Andrea, Apr.3/92 - use bitmaps to further reduce memory size *)
(* This code may be freely used and distributed, it may not be sold *)
FROM SYSTEM IMPORT TSIZE, ADDRESS;
FROM Storage IMPORT ALLOCATE;
FROM Randomly IMPORT Choose_1_To_N;
TYPE
Deck = POINTER TO RECORD
length :CARDINAL; (* number of numbers *)
n_left :CARDINAL; (* number of numbers not yet picked *)
start :ADDRESS; (* first element in memory *)
size :CARDINAL; (* actual size of the bitmap in bytes *)
sets :CARDINAL; (* number of bitsets *)
END;
VAR
adr :POINTER TO BITSET;
i, k :CARDINAL;
empty, full :BITSET;
bset_bits, bset_bytes :CARDINAL;
(* ------------------------------------------- *)
PROCEDURE Create( VAR d :Deck; n :CARDINAL );
VAR n_bits, in_last :CARDINAL;
BEGIN
(* protect users from themselves *)
IF n = 0 THEN n := 1 END;
NEW( d );
d^.length := n;
n_bits := n;
in_last := n_bits MOD bset_bits;
IF in_last = 0 THEN
in_last := bset_bits; (* last one is filed *)
ELSE
(* must be a multiple of bits in a bitset *)
n_bits := n_bits + bset_bits - in_last;
END;
d^.sets := n_bits DIV bset_bits; (* convert to bitsets *)
d^.size := d^.sets * bset_bytes; (* convert to bytes *)
ALLOCATE( d^.start, d^.size );
Reset( d );
END Create;
(* ------------------------------------------- *)
PROCEDURE Reset( d :Deck );
BEGIN
d^.n_left := d^.length;
k := 0;
FOR i := 1 TO d^.length DO
adr := d^.start + k;
adr^ := full;
k := k + bset_bytes;
END;
END Reset;
(* ------------------------------------------- *)
PROCEDURE Next( d :Deck ) :CARDINAL;
VAR n, count, which_bit :CARDINAL;
BEGIN
IF d^.n_left = 0 THEN Reset( d ); END;
n := Choose_1_To_N( d^.n_left );
(* find the n'th available item in the list *)
(* the index of that item will the random number to return *)
which_bit := bset_bits; (* force a new value the very first time *)
i := 0;
k := 0;
count := 0;
REPEAT
which_bit := which_bit + 1;
IF which_bit >= bset_bits THEN
which_bit := 0;
adr := d^.start + k; (* get a whole new bitset *)
k := k + bset_bytes;
(* but skip any bitset which has already been completeley chosen *)
WHILE adr^ = empty DO
i := i + bset_bits; (* jump the index for all of them *)
adr := d^.start + k;
k := k + bset_bytes;
END;
END;
i := i + 1;
IF which_bit IN adr^ THEN count := count + 1; END;
UNTIL count = n;
EXCL( adr^, which_bit ); (* make this number un-available *)
d^.n_left := d^.n_left - 1; (* now the list is smaller by one *)
RETURN i;
END Next;
BEGIN
bset_bytes := TSIZE( BITSET );
bset_bits := bset_bytes * 8;
empty := {};
full := {};
FOR i := 0 TO bset_bits - 1 DO
INCL( full, i );
END;
END ShuffleFast.