custom-distribution.scm 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. ;; (From tantalum on the Guile user list.)
  2. ;; Surely not the ideal way to generate numbers with a normal distribution, but
  3. ;; there is a way to use custom probabilities from a list, which i think is nice
  4. ;; to know.
  5. ;; It works like this:
  6. ;; Have a list of probabilities. can be as long as you want. i think that is
  7. ;; called probability density.
  8. ;; ->
  9. ;; (1 0 3 1)
  10. ;; Create the cumulative sums for this list. that is, sums like this:
  11. ;; (a b c ...) -> (a (+ a b) (+ a b c) ...)
  12. ;; I think that is called cumulative distribution.
  13. ;; ->
  14. ;; (1 1 4 5)
  15. ;; Create a random number up to the largest sum.
  16. ;; ->
  17. ;; (random 5)
  18. ;; Return the first index of the list of cumulative sums that is greater than
  19. ;; the random number. given the distribution above, you would see index 2 a
  20. ;; lot, never index 1, and index 0 and 3 rarely.
  21. ;; (Implementation from tantalum on the Guile user list. Changed
  22. ;; formatting. Added comments.)
  23. (use-modules
  24. ;; Only import the procedure ~last~ from srfi-1.
  25. ((srfi srfi-1) #:select (last)))
  26. (define (cusum a . b)
  27. "calculate cumulative sums from the given numbers.
  28. (a b c ...) -> (a (+ a b) (+ a b c) ...)"
  29. (cons a
  30. (if (null? b)
  31. ;; If ~b~ is already the empty list, meaning there are no further
  32. ;; arguments given, then a is already the cumulative sum list.
  33. (list)
  34. ;; Otherwise continue building the list of cumulative sums. ~a~ is
  35. ;; already consed in this iteration. The next ~a~ will be the sum of
  36. ;; the current ~a~ and the next element. The next ~b~ will be the
  37. ;; tail of the current ~b~. This way we keep summing up in the next
  38. ;; ~a~ at each iteration.
  39. (apply cusum
  40. (+ a (car b))
  41. (cdr b)))))
  42. (define* (random-discrete-f probabilities #:optional (state *random-state*))
  43. "create a procedure, which returns a random number according to the given
  44. probabilities of numbers.
  45. (real ...) [random-state] -> procedure:{-> integer}"
  46. (let*
  47. ;; Calculate the cumulative sums of the given probabilities.
  48. ([cuprob (apply cusum probabilities)]
  49. ;; Get the last (highest) cumulative sum from the list of cumulative
  50. ;; sums. This will serve as an upper exclusive bound for the random
  51. ;; number generation.
  52. [sum (last cuprob)])
  53. ;; Return a procedure.
  54. (lambda ()
  55. ;; Generate a random positive integer, uniformly distributed, using the
  56. ;; optionally given random state, maximum excluve upper bound - 1. This
  57. ;; results in a random integer, which is smaller than the last cumulative
  58. ;; sum. This means, that there will always be a cumulative sum, which is
  59. ;; found to be greater than the random integer.
  60. (let ([deviate (random sum state)])
  61. ;; Start with index ~a~ being 0 and the current probability
  62. (let loop ([a 0] [b cuprob])
  63. ;; Check if the end of the list is reached. If the end of the list is
  64. ;; reached, return the last index of the list. In theory this case
  65. ;; should not happen.
  66. (if (null? b)
  67. a
  68. ;; Look for the cumulative sum, for which the generated random
  69. ;; number ~deviate~ is smaller than the cumulative sum. Then
  70. ;; return its index. This index is the random number generated by
  71. ;; the returned random number generating procedure.
  72. (if (< deviate (car b))
  73. a
  74. ;; Recur. Increase index by 1 and look in the tail.
  75. (loop (+ 1 a) (cdr b)))))))))
  76. ;; =====
  77. ;; USAGE
  78. ;; =====
  79. ;; Create a random procedure.
  80. (define random*
  81. (random-discrete-f (list 1 0 3 1)
  82. (random-state-from-platform)))
  83. ;; Show a random number generated by the previously created random procedure.
  84. (display (random*))