You want a random number from a given range. For example, you wish to randomly select one element from an array, simulate rolling a die in a game of chance, or generate a random password.
Use
(random-integer n) function from
SRFI27?. It returns a random
exact integer
in the range of 0 to n-1.
> (require (lib "27.ss" "srfi"))
> (random-integer 172)
58
This code generates and prints a random integer between 25 and 75, inclusive:
> (let ((rand (+ (random-integer 51) 25)))
(for-each display (list "Random = " rand "\n")))
Random = 40
In the example, we want to limit the range to 25 to 75, so we add 25 to the result of the
random function. This means we really want a random number between 0 and 50, so we give the
random function the argument 51 (since the generated number is always from 0 to n-1).
The canonical application for this kind of number generation is the random selection of an element from a vector:
> (define myvec #(#\A #\B #\C #\D #\1 #\2 #\3))
> (define elt
(vector-ref myvec (random-integer 7))
> elt
#\B
Another common example is generating a random password. For this example, we will make use of
SRFI 14 (Character Set Library) and
SRFI 42 (List Comprehensions):
(require (lib "14.ss" "srfi")
(lib "27.ss" "srfi")
(lib "42.ss" "srfi"))
(define (generate-8-password)
(string-append-ec (:range i 0 8)
(let* ((bigcs (substring (char-set->string char-set:ascii) 6 95))
(rand (random-integer (string-length bigcs))))
(substring bigcs rand (+ rand 1)))))
> (generate-8-password)
"n9R*g(la"
NumberRecipeBigRandomNumber
I would compute
bigcs only once:
(define generate-8-password
(let* ((bigcs (substring (char-set->string char-set:ascii) 6 95))
(bigcs-length (string-length bigcs)))
(lambda ()
(string-append-ec (:range i 0 8)
(let ((rand (random-integer bigcs-length)))
(substring bigcs rand (+ rand 1)))))))
Two other implementations that don't rely on
SRFIs but are much more verbose (and assume
integer->char is equivalent to
ascii->char):
(define generate-8-password
(let* ((password-length 8)
(min-ascii 33)
(max-ascii 95)
(random-argument (+ 1 (- max-ascii min-ascii))))
(lambda ()
(apply string
(let loop ((i password-length))
(if (zero? i)
'()
(cons (integer->char (+ (random-integer random-argument)
min-ascii))
(loop (- i 1)))))))))
The second is for implementations with fast
string-set!.
(define generate-8-password
(let* ((password-length 8) (min-ascii 33)
(max-ascii 95)
(random-argument (+ 1 (- max-ascii min-ascii))))
(lambda ()
(let ((password (make-string password-length)))
(let loop ((i (- password-length 1)))
(string-set! password
i
(integer->char (+ (random-integer random-argument)
min-ascii)))
(if (zero? i)
password
(loop (- i 1))))))))
If I had first-year CS students, I would make them do it the verbose way. Although list comprehensions are probably more appealing to most Cookbook users, and perhaps are easier for the compiler to optimize in some cases.
--
NeilVanDyke - 11 Jun 2004