A slightly less wasteful in memory than
>>2 suggested is to just
sum up the precentages (or in my case, I prefer to think of it in a more general case: weights), then pick a random number between 0 and the total, and select the item that matches when the weights are added up to the selected random number. It will have the same effect, but not be limited to normalized 1.0 (or 100% total) probabilies, instead you can just use whatever numbers as weights. Here's some sample code that I wrote a while ago for doing this:
(defun weighted-random-pick (groups)
(let* ((total-weight (reduce #'+ (mapcar #'second groups)))
(choice (random total-weight))
(current 0) (last-total 0))
(loop for (name weight) in groups do
(setq last-total current)
(incf current weight)
(when (<= last-total choice current) (return name)))))
And a test:
; seen as precentages
CL-USER> (loop repeat 10 collect (weighted-random-pick '((Macaroni 30) (Pizza 50) (/prog/-flavored-aisu-cream 20))))
(PIZZA MACARONI MACARONI /PROG/-FLAVORED-AISU-CREAM MACARONI MACARONI PIZZA
MACARONI PIZZA PIZZA)
; seen as weights (in this case, having it choose something based on personal interest in doing that activity at that moment)
CL-USER> (weighted-random-pick '((study 60) (code 50) (watch-anime 40) (play-games 15.5)))
STUDY
CL-USER> (weighted-random-pick '((study 60) (code 50) (watch-anime 40) (play-games 15.5)))
CODE