#lang racket/base

; Various functions working with randomness
(require
  racket/list
  math/distributions
  racket/flonum)

(provide
  random:boolean random:normal
  random:from-vector random:from-list random:from-hash
  random:subvector random:sublist random:subhash
  hash-key* hash-ref*
  random:hash-combine
  )

; Choose a random boolean
(define (random:boolean)
  (eq? 0 (random 2)))

; Choose a number using the normal distribution
; It is possible to indicate a mean and a standard-deviation
(define (random:normal [mean 0.] [dev 1.])
  (flvector-ref (flnormal-sample mean dev 1) 0))

; Choose a random element from a vector
(define (random:from-vector vec)
  (if (< 0 (vector-length vec))
      (vector-ref vec (random (vector-length vec)))
      (error "Cannot choose an element from an empty vector.")))

; Choose a random element from a list
(define (random:from-list lst)
  (if (not (null? lst))
      (car (shuffle lst))
      (error "Cannot choose an element from an empty list.")))

; Choose a random element from a hash
; return a pair (key . value)
(define (random:from-hash hsh)
  (if (not (hash-empty? hsh))
      (random:from-list (hash->list hsh))
      (error "Cannot choose an element from an empty hash.")))

; Create a list of at most n elements choosen at random from a list
(define (random:sublist lst n)
  ; Take at most n element from a list
  (define (take-at-most in n [out (list)])
    (if (or (null? in) (eq? n 0))
        (reverse out)
        (take-at-most (cdr in) (- n 1) (cons (car in) out))))
  (take-at-most (shuffle lst) n))

; Create a vector of at most n elements choosen at random from a vector
(define (random:subvector vec n)
  (list->vector (random:sublist (vector->list vec) n)))

; Create a hash of at most n elements choosen at random from a hash
(define (random:subhash hsh n)
  (let ((res (make-hash)))
    (for-each
      (lambda (c)
        (hash-set! res (car c) (cdr c)))
      (random:sublist (hash->list hsh) n))
    res))

; Return the key if it is present in the hash or return a random key if it is absent
; Return #f if the hash is empty
(define (hash-key* hsh key)
  (cond
    ( (hash-empty? hsh)
      #f)
    ( (hash-has-key? hsh key)
      key)
    ( #t
      (car (random:from-hash hsh)))))

; Return the value associated to a key in the hash if the key is present
; or return a random value from the hash if absent
; Return #f if the hash is empty
(define (hash-ref* hsh key)
  (cond
    ( (hash-empty? hsh)
      #f)
    ( (hash-has-key? hsh key)
      (hash-ref hsh key))
    ( #t
      (cdr (random:from-hash hsh)))))

(define (random:hash-combine . hsh-lst)
  (define tmp (make-hash))
  (define ret (make-hash))
  (for-each
    (lambda (h)
      (hash-for-each
        h
        (lambda (k v)
          (if (hash-has-key? tmp k)
              (hash-set! tmp k (cons v (hash-ref tmp k)))
              (hash-set! tmp k (list v))))))
    hsh-lst)
  (hash-for-each
    tmp
    (lambda (k v)
      (hash-set! ret k (random:from-list v))))
  ret)
