Weight-based random sampling

So

defmodule Demo do
  def weight_based_choice(items) do
    value =
      items
      |> Enum.reduce(0, &acc_weight/2)
      |> :rand.uniform()

    select_item(items, value)
  end

  # base cases
  defp select_item([{name, _}], _),
    do: name

  defp select_item([{name, weight} | _], index) when weight >= index,
    do: name

  # recursive case
  defp select_item([{_, weight} | tail], index),
    do: select_item(tail, index - weight)

  defp acc_weight({_, weight}, total),
    do: total + weight

end

items = [apples: 10, oranges: 30, bananas: 60]

items
|> Demo.weight_based_choice()
|> inspect()
|> IO.puts()

or

defmodule Demo do
  def weight_based_choice(items) do
    marker =
      items
      |> Enum.reduce(0, &acc_weight/2)
      |> :rand.uniform()

    Enum.reduce_while(items, marker, &item_below_marker/2)
  end

  defp item_below_marker({_name, weight}, acc) when weight < acc,
    do: {:cont, acc - weight}

  defp item_below_marker({name, _weight}, _acc),
   do: {:halt, name}

  defp acc_weight({_, weight}, total),
    do: total + weight

end

items = [apples: 10, oranges: 30, bananas: 60]

items
|> Demo.weight_based_choice()
|> inspect()
|> IO.puts()

While also making use of Enum.reduce/3

People tend to have familiarity with reduce from other languages - when learning I feel that resorting to Enum immediately is like heading straight for the power-tools even for the smallest job. To a certain degree choosing reduce when a simple recursive function will do feels a bit like “premature pessimization” especially as recursion is how iteration is accomplished in Elixir/Erlang - but I know I’m in the minority there.