Sha256: 4a2e110dcce2f69c822526595e1b312420973a32b2c73827399e13875a3454c9

Contents?: true

Size: 1.8 KB

Versions: 66

Compression:

Stored size: 1.8 KB

Contents

defmodule Alphametics do
  @type puzzle :: binary
  @type solution :: %{required(?A..?Z) => 0..9}

  @doc """
  Takes an alphametics puzzle and returns a solution where every letter
  replaced by its number will make a valid equation. Returns `nil` when
  there is no valid solution to the given puzzle.

  ## Examples

      iex> Alphametics.solve("I + BB == ILL")
      %{?I => 1, ?B => 9, ?L => 0}

      iex> Alphametics.solve("A == B")
      nil
  """
  @spec solve(puzzle) :: solution | nil
  def solve(puzzle) do
    [sum | addends] = words = parse(puzzle)
    letters = words |> List.flatten() |> Enum.uniq()
    first_letters = words |> Enum.map(&hd/1) |> Enum.uniq()
    problem = %{addends: addends, sum: sum, first_letters: first_letters}

    solve(letters, Enum.to_list(0..9), %{}, problem)
  end

  defp solve([], _numbers, solution, %{addends: addends, sum: sum}) do
    if sum(addends, solution) == to_number(sum, solution) do
      solution
    end
  end

  defp solve([letter | letters], numbers, solution, problem) do
    Enum.find_value(numbers, fn number ->
      leading_zero? = number == 0 && letter in problem.first_letters

      unless leading_zero? do
        numbers = List.delete(numbers, number)
        solution = put_in(solution[letter], number)
        solve(letters, numbers, solution, problem)
      end
    end)
  end

  defp sum(addends, solution) do
    Enum.reduce(addends, 0, fn addend, sum ->
      sum + to_number(addend, solution)
    end)
  end

  defp to_number(letters, numbers) do
    letters
    |> Enum.map(& numbers[&1])
    |> Integer.undigits()
  end

  defp parse(puzzle) do
    [left, right] = String.split(puzzle, " == ")

    addends =
      left
      |> String.split(" + ")
      |> Enum.map(&String.to_charlist/1)

    sum = String.to_charlist(right)

    [sum | addends]
  end
end

Version data entries

66 entries across 66 versions & 1 rubygems

Version Path
trackler-2.2.1.180 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.179 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.178 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.177 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.176 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.175 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.174 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.173 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.172 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.171 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.170 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.169 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.167 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.166 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.165 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.164 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.163 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.162 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.161 tracks/elixir/exercises/alphametics/example.exs
trackler-2.2.1.160 tracks/elixir/exercises/alphametics/example.exs