lib/examples/xkcd_np.ex

defmodule CPSolver.Examples.XKCD.NP do
  @doc """
  <a href="https://xkcd.com/287/">xkcd-np</a>.
  """
  alias CPSolver.IntVariable, as: Variable
  alias CPSolver.Model

  import CPSolver.Variable.View.Factory
  import CPSolver.Constraint.Factory

  def model() do
    appetizers = [
      {:mixed_fruit, 215},
      {:french_fries, 275},
      {:side_salad, 335},
      {:hot_wings, 355},
      {:mozarella_sticks, 420},
      {:sampler_plate, 580}
    ]

    total = 1505

    quantities =
      Enum.map(appetizers, fn {name, price} ->
        mul(Variable.new(0..div(total, price), name: name), price)
      end)

    sum_constraint = sum(quantities, total)

    Model.new(quantities, [sum_constraint], extra: %{appetizers: appetizers, total: total})
  end

  def check_solution(solution, %{extra: %{appetizers: appetizers, total: total}} = _model) do
    appetizers
    |> Enum.zip(solution |> Enum.take(length(appetizers)))
    |> Enum.reduce(0, fn {{_name, price}, quantity}, acc -> acc + price * quantity end)
    |> then(fn sum -> sum == total end)
  end

  def solve() do
    model = model()
    num_appetizers = length(model.extra.appetizers)
    {:ok, res} = CPSolver.solve(model)

    Enum.map_join(res.solutions, "\n OR \n", fn sol ->
      sol
      |> Enum.zip(res.variables)
      |> Enum.take(num_appetizers)
      |> Enum.reject(fn {q, _name} -> q == 0 end)
      |> Enum.map_join(", ", fn {q, name} -> "#{name} : #{q}" end)
    end)
    |> IO.puts()
  end
end