Day 9: Mirage Maintenance – Advent of Code 2023
input =
File.stream!("/Users/pw/src/weiland/adventofcode/2023/input/09.txt")
|> Stream.map(&String.trim/1)
test_input = "0 3 6 9 12 15
1 3 6 10 15 21
10 13 16 21 30 45" |> String.split("\n")
Parsing
parse_input = fn report ->
report
|> Stream.map(fn line ->
String.split(line, " ", trim: true) |> Enum.map(&String.to_integer/1)
end)
end
parse_input.(test_input) |> Enum.to_list()
Part One
defmodule PartOne do
def next_line(history) do
history
|> Stream.with_index(1)
|> Enum.reduce([], fn {elem, next_index}, acc ->
next = Enum.at(history, next_index)
if next == nil, do: Enum.reverse(acc), else: [next - elem | acc]
end)
end
def walk_line_down(line, acc) do
if Enum.all?(line, &(&1 == 0)),
do: [line | acc],
else: walk_line_down(next_line(line), [line | acc])
end
def extrapolate_line(line, prev) do
last = Enum.at(line, -1)
if prev == nil, do: last, else: prev + last
end
def extrapolate(tree) do
Enum.reverse(tree) |> Enum.reduce(nil, &extrapolate_line/2)
end
end
part_one = fn input ->
input
|> parse_input.()
|> Stream.map(&PartOne.walk_line_down(&1, []))
|> Stream.map(&PartOne.extrapolate(&1))
|> Enum.sum()
end
part_one.(test_input) == 114 &&
part_one.(input)
Part Two
defmodule PartTwo do
def reduce_tree([first | _rest], nil), do: first
def reduce_tree([first | _rest], prev), do: first - prev
def extrapolate_begin(tree) do
Enum.reduce(tree, nil, &reduce_tree/2)
end
end
part_two = fn input ->
input
|> parse_input.()
|> Stream.map(&PartOne.walk_line_down(&1, []))
|> Stream.map(&PartTwo.extrapolate_begin(&1))
|> Enum.sum()
end
part_two.(test_input) &&
part_two.(input)
Part 1 & 2 without recursion
solve = fn input, dir ->
input
|> parse_input.()
|> Enum.reduce(0, fn line, sum ->
sum +
Enum.reduce_while(line, [line, []], fn _curr, [next, acc] ->
next_acc = [Enum.at(next, dir) | acc]
if Enum.group_by(next, & &1) |> Enum.count() == 1 do
{:halt, Enum.reduce(next_acc, if(dir == 0, do: &-/2, else: &+/2))}
else
{:cont,
[Enum.chunk_every(next, 2, 1, :discard) |> Enum.map(fn [a, b] -> b - a end), next_acc]}
end
end)
end)
end
solve.(test_input, -1) == 114 &&
solve.(input, -1) |> IO.inspect(label: "Part one")
solve.(test_input, 0) == 2 &&
solve.(input, 0) |> IO.inspect(label: "Part two")