Packages

A flexible and powerful backtracking library for Elixir.

Current section

Files

Jump to
backfish lib backfish.ex
Raw

lib/backfish.ex

defmodule Backfish do
@moduledoc """
Backfish is a flexible and powerful backtracking library for Elixir, designed
to solve combinatorial and constraint satisfaction problems.
"""
@doc """
Finds all solutions to the given problem.
## Parameters
- `problem_module`: The module implementing the problem definition using
`Backfish.ProblemDefinition`.
- `opts`: Optional keyword list of options. Supports:
- `:args`: Arguments to pass to the `initial_state/1` function of the
problem module.
- `:depth_limit`: Optional limit on the depth of the search.
## Examples
iex> opts = [args: [size: 8]]
iex> Backfish.find_all_solutions(Backfish.Examples.NQueens, opts)
[solution1, solution2, ...]
"""
def find_all_solutions(problem_module, opts \\ []) do
args = Keyword.get(opts, :args, [])
depth_limit = Keyword.get(opts, :depth_limit, :infinity)
initial_state = problem_module.initial_state(args)
solve_all(problem_module, initial_state, depth_limit, 0)
end
@doc """
Finds the first valid solution to the given problem.
## Parameters
- `problem_module`: The module implementing the problem definition using
`Backfish.ProblemDefinition`.
- `opts`: Optional keyword list of options. Supports:
- `:args`: Arguments to pass to the `initial_state/1` function of the
problem module.
- `:depth_limit`: Optional limit on the depth of the search.
## Examples
iex> opts = [args: [size: 8]]
iex> {:ok, solution} = Backfish.find_first_solution(Backfish.Examples.NQueens, opts)
{:ok, solution}
"""
def find_first_solution(problem_module, opts \\ []) do
args = Keyword.get(opts, :args, [])
depth_limit = Keyword.get(opts, :depth_limit, :infinity)
initial_state = problem_module.initial_state(args)
solve_first(problem_module, initial_state, depth_limit, 0)
end
defp solve_all(_, _, depth_limit, depth) when depth >= depth_limit,
do: []
defp solve_all(problem_module, state, depth_limit, depth) do
if problem_module.is_goal?(state) do
[state]
else
problem_module.next_steps(state)
|> Enum.flat_map(fn next_state ->
solve_all(problem_module, next_state, depth_limit, depth + 1)
end)
end
end
defp solve_first(_, _, depth_limit, depth) when depth >= depth_limit,
do: :error
defp solve_first(problem_module, state, depth_limit, depth) do
if problem_module.is_goal?(state) do
{:ok, state}
else
problem_module.next_steps(state)
|> Enum.find_value(:error, fn next_state ->
case solve_first(problem_module, next_state, depth_limit, depth + 1) do
{:ok, solution} -> {:ok, solution}
:error -> nil
end
end)
end
end
end