;; i freely admit that i don't holistically grok this; i just transcribed the algorithm into something slightly more clojurish

"
Algorithm: Vose's Alias Method
Initialization:
Create arrays Alias and Prob, each of size n.
Create two worklists, Small and Large.

Multiply each probability by n.

For each scaled probability pi:
If pi<1, add i to Small.
Otherwise (pi≥1), add i to Large.

While Small and Large are not empty: (Large might be emptied first)
Remove the first element from Small; call it l.
Remove the first element from Large; call it g.
Set Prob[l]=pl.
Set Alias[l]=g.
Set pg:=(pg+pl)−1. (This is a more numerically stable option.)
If pg<1, add g to Small.
Otherwise (pg≥1), add g to Large.

While Large is not empty:
Remove the first element from Large; call it g.
Set Prob[g]=1.

While Small is not empty: This is only possible due to numerical instability.
Remove the first element from Small; call it l.
Set Prob[l]=1.

Generation:
Generate a fair die roll from an n-sided die; call the side i.
Flip a biased coin that comes up heads with probability Prob[i].
If the coin comes up \"heads,\" return i.
Otherwise, return Alias[i]."

(defn vose [weights]
  (let [ct (count weights)
        weighted
        (into {}
              (for [[v p] weights]
                [v (* p ct)]))
        [small large]
        (reduce
         (fn [[small large] [i pi]]
           (if (< pi 1)
             [(conj small i) large]
             [small (conj large i)]))
         [[] []]
         weighted)
        [small large prob alias]
        (loop [[l & msmall :as small] small
               [g & mlarge :as large] large
               prob nil
               alias nil
               weighted weighted]
          (if (and l g)
            (let [w2 (update weighted g (partial + (weighted l) -1))]
              (recur
               (cond->> msmall (< (w2 g) 1) (cons g))
               (cond->> mlarge (>= (w2 g) 1) (cons g))
               (assoc prob l (weighted l))
               (assoc alias l g)
               w2))
            [small large prob alias]))
        prob
        (merge prob
               (into {}
                     (map (juxt identity (constantly 1)) (concat large small))))]
    {:n (count weights)
     :opts (vec (map first weights))
     :prob prob
     :alias alias}))

"
Generation:
Generate a fair die roll from an n-sided die; call the side i.
Flip a biased coin that comes up heads with probability Prob[i].
If the coin comes up \"heads,\" return i.
Otherwise, return Alias[i].
"
(defn generate [{:keys [opts n prob alias]}]
  (let [i (rand-int n)
        base (nth opts i)
        pi (get prob base)
        t? (< (rand) pi)]
    (if t? base (get alias base))))