;; An implementation of the Alias Method. ;; ;; NOTE: Additional comments, and an updated, more accurate version ;; can be found here: ;; ;; ;; The function MAKE-DISCRETE-RANDOM-VAR takes an array of ;; probabilities and an (optional) array of values. Produces a ;; function which returns each of the values with the specified ;; probability (or the corresponding integer no values have been ;; given). ;; ;; It needs only one call to RANDOM for every value produced. ;; ;; For the licence, see at the end of the file. (defun create-alias-method-vectors (probabilities) (let* ((N (length probabilities)) (threshold (/ 1.0d0 N)) (alternatives (make-array N :element-type 'fixnum)) (p_keep (make-array N :initial-contents (coerce probabilities 'list))) bigs lows) (loop :for i :from 0 :below N :do (if (>= threshold (aref probabilities i)) (push i lows) (push i bigs))) (loop :while lows :do (let* ((0ne (pop lows)) (Tw0 (pop bigs)) (delta (- threshold (aref p_keep 0ne)))) (if tw0 (progn (setf (aref p_keep 0ne) (/ (aref p_keep 0ne) threshold)) (setf (aref alternatives 0ne) Tw0) (decf (aref p_keep Tw0) delta) (if (>= threshold (aref p_keep Tw0)) (push Tw0 lows) (push Tw0 bigs))) (setf (aref p_keep 0ne) 1.0d0)))) ;; Numerical noise might leave some bigs dangling, with ;; | p_keep - 1/N | very small. (dolist (k bigs) (setf (aref p_keep k) 1.0d0)) (values p_keep alternatives))) (defun make-discrete-random-var (probabilities &optional values) (when (and values (/= (length values) (length probabilities))) (error "different number of values and probabilities.")) (let* ((N (float (length probabilities) 0.0d0))) (multiple-value-bind (p_keep alternatives) (create-alias-method-vectors probabilities) #'(lambda () (labels ((result (k) (if values (aref values k) k))) (multiple-value-bind (k r) (floor (random N)) (if (> r (aref p_keep k)) (result (aref alternatives k)) (result k)))))))) ;; Tests the alias method. p holds the prescribed probabilities, and ;; cnt the measured ones. (defun test-alias-method (n runs) (let ((p (make-array n :element-type 'double-float)) (cnt (make-array n :initial-element 0.0d0))) (dotimes (i n) (setf (aref p i) (random 1.0d0))) (let ((nc (loop for i from 0 below n summing (aref p i)))) (dotimes (i n) (setf (aref p i) (/ (aref p i) nc))) (let ((rp (make-discrete-random-var p))) (dotimes (i runs) (incf (aref cnt (funcall rp)))) (dotimes (i n) (setf (aref cnt i) (/ (aref cnt i) runs))))) (values p cnt))) ;;; Copyright (c) 2006, Mario S. Mommer ;;; Permission is hereby granted, free of charge, to any person ;;; obtaining a copy of this software and associated documentation ;;; files (the "Software"), to deal in the Software without ;;; restriction, including without limitation the rights to use, copy, ;;; modify, merge, publish, distribute, sublicense, and/or sell copies ;;; of the Software, and to permit persons to whom the Software is ;;; furnished to do so, subject to the following conditions: ;;; The above copyright notice and this permission notice shall be ;;; included in all copies or substantial portions of the Software. ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ;;; DEALINGS IN THE SOFTWARE.