defmodule Utils do
def parse_integer_list(string, split, trim \\ true) do
string
|> String.split(split, trim: trim)
|> Enum.map(& String.to_integer(&1))
end
end
defmodule Day07 do
defmodule Equation do
@derive Inspect
defstruct [:result, :terms]
def from_string(string) do
string
|> Utils.parse_integer_list([":", " "])
|> then(& %Equation{result: hd(&1), terms: tl(&1)})
end
def lr_solution?(equation, ops) do
lr_solution_rev?(equation.result, Enum.reverse(equation.terms), ops)
end
defp lr_solution_rev?(result, terms, ops) do
cond do
terms == [] and result == 0 ->
true
terms == [] or result == 0 ->
false
true ->
ops
|> Enum.map(& apply_op_rev(result, hd(terms), &1))
|> Enum.filter(&Kernel.is_integer/1)
|> Enum.any?(& lr_solution_rev?(&1, tl(terms), ops))
end
end
defp apply_op_rev(l, r, :add), do: l >= r and l-r
defp apply_op_rev(l, r, :mul), do: rem(l, r) == 0 and div(l, r)
defp apply_op_rev(l, r, :concat), do: uncat(l, r)
defp uncat(l, 0), do: l
defp uncat(0, _), do: false
defp uncat(l, r) when rem(l, 10) == rem(r, 10), do: uncat(div(l, 10), div(r, 10))
defp uncat(_, _), do: false
end
def part1(data) do
data
|> String.split("\n", trim: true)
|> Enum.map(& Equation.from_string(&1))
|> Enum.filter(& Equation.lr_solution?(&1, [:mul, :add]))
|> Enum.map(& &1.result)
|> Enum.sum()
end
def part2(data) do
data
|> String.split("\n", trim: true)
|> Enum.map(& Equation.from_string(&1))
|> Enum.filter(& Equation.lr_solution?(&1, [:mul, :concat, :add]))
|> Enum.map(& &1.result)
|> Enum.sum()
end
end
data = IO.read(:stdio, :eof)
{time1 , ans1} = :timer.tc(fn -> Day07.part1(data) end)
IO.puts("Time : #{time1 / 1000000}")
IO.puts("Answer: #{ans1}")
{time2 , ans2} = :timer.tc(fn -> Day07.part2(data) end)
IO.puts("Time : #{time2 / 1000000}")
IO.puts("Answer: #{ans2}")