lib/solver/search/first_fail.ex

defmodule CPSolver.Search.Strategy.FirstFail do
  alias CPSolver.Search.Strategy
  @behaviour Strategy
  alias CPSolver.DefaultDomain, as: Domain
  alias CPSolver.Search.DomainPartition

  @impl true
  def select_variable(variables) do
    initial_acc = {nil, :infinity}

    {min_var, _size} =
      choice =
      Enum.reduce_while(variables, initial_acc, fn var, {_v, v_size} = acc ->
        case Domain.size(var.domain) do
          ## Skip fixed vars
          1 -> {:cont, acc}
          s when s < v_size -> {:cont, {var, s}}
          _s -> {:cont, acc}
        end
      end)

    cond do
      choice == initial_acc ->
        {:error, Strategy.all_vars_fixed_exception()}

      true ->
        {:ok, min_var}
    end
  end

  @impl true
  def partition(domain) do
    ## Choice of value doesn't matter for first_fail
    DomainPartition.by_min(domain)
  end
end