That’s a fun little problem. Here’s my take, from playing around in iex
iex(109)> items = [apples: 10, oranges: 30, bananas: 60]
[apples: 10, oranges: 30, bananas: 60]
iex(110)> accumulated_weights = Enum.scan(items, fn {k, w}, {_, w_sum} -> {k, w + w_sum} end)
[apples: 10, oranges: 40, bananas: 100]
iex(111)> {_, max} = List.last(accumulated_weights)
{:bananas, 100}
iex(112)> random_value = Enum.random(1..max)
17
iex(113)> sample = Enum.reduce_while(accumulated_weights, random_value, fn {k, w}, r -> if r <= w, do: {:halt, k}, else: {:cont, r} end)
:oranges
I believe this is the first time I’ve used Enum.scan. It lets us get that accumulated_weights format, which is handy for reuse if we will be getting more random samples from the same list of inputs.
We get our max by pattern matching on the last item of accumulated_weights
Enum.reduce_while then halts iteration when it has found the proper match. It might make some static type people twitch because when continuing the random_value is kept as the accumulator, but when halting, the keyword becomes the accumulator that is returned from the overall iteration.






















