123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- ;; (From tantalum on the Guile user list.)
- ;; Surely not the ideal way to generate numbers with a normal distribution, but
- ;; there is a way to use custom probabilities from a list, which i think is nice
- ;; to know.
- ;; It works like this:
- ;; Have a list of probabilities. can be as long as you want. i think that is
- ;; called probability density.
- ;; ->
- ;; (1 0 3 1)
- ;; Create the cumulative sums for this list. that is, sums like this:
- ;; (a b c ...) -> (a (+ a b) (+ a b c) ...)
- ;; I think that is called cumulative distribution.
- ;; ->
- ;; (1 1 4 5)
- ;; Create a random number up to the largest sum.
- ;; ->
- ;; (random 5)
- ;; Return the first index of the list of cumulative sums that is greater than
- ;; the random number. given the distribution above, you would see index 2 a
- ;; lot, never index 1, and index 0 and 3 rarely.
- ;; (Implementation from tantalum on the Guile user list. Changed
- ;; formatting. Added comments.)
- (use-modules
- ;; Only import the procedure ~last~ from srfi-1.
- ((srfi srfi-1) #:select (last)))
- (define (cusum a . b)
- "calculate cumulative sums from the given numbers.
- (a b c ...) -> (a (+ a b) (+ a b c) ...)"
- (cons a
- (if (null? b)
- ;; If ~b~ is already the empty list, meaning there are no further
- ;; arguments given, then a is already the cumulative sum list.
- (list)
- ;; Otherwise continue building the list of cumulative sums. ~a~ is
- ;; already consed in this iteration. The next ~a~ will be the sum of
- ;; the current ~a~ and the next element. The next ~b~ will be the
- ;; tail of the current ~b~. This way we keep summing up in the next
- ;; ~a~ at each iteration.
- (apply cusum
- (+ a (car b))
- (cdr b)))))
- (define* (random-discrete-f probabilities #:optional (state *random-state*))
- "create a procedure, which returns a random number according to the given
- probabilities of numbers.
- (real ...) [random-state] -> procedure:{-> integer}"
- (let*
- ;; Calculate the cumulative sums of the given probabilities.
- ([cuprob (apply cusum probabilities)]
- ;; Get the last (highest) cumulative sum from the list of cumulative
- ;; sums. This will serve as an upper exclusive bound for the random
- ;; number generation.
- [sum (last cuprob)])
- ;; Return a procedure.
- (lambda ()
- ;; Generate a random positive integer, uniformly distributed, using the
- ;; optionally given random state, maximum excluve upper bound - 1. This
- ;; results in a random integer, which is smaller than the last cumulative
- ;; sum. This means, that there will always be a cumulative sum, which is
- ;; found to be greater than the random integer.
- (let ([deviate (random sum state)])
- ;; Start with index ~a~ being 0 and the current probability
- (let loop ([a 0] [b cuprob])
- ;; Check if the end of the list is reached. If the end of the list is
- ;; reached, return the last index of the list. In theory this case
- ;; should not happen.
- (if (null? b)
- a
- ;; Look for the cumulative sum, for which the generated random
- ;; number ~deviate~ is smaller than the cumulative sum. Then
- ;; return its index. This index is the random number generated by
- ;; the returned random number generating procedure.
- (if (< deviate (car b))
- a
- ;; Recur. Increase index by 1 and look in the tail.
- (loop (+ 1 a) (cdr b)))))))))
- ;; =====
- ;; USAGE
- ;; =====
- ;; Create a random procedure.
- (define random*
- (random-discrete-f (list 1 0 3 1)
- (random-state-from-platform)))
- ;; Show a random number generated by the previously created random procedure.
- (display (random*))
|