Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Day 9: Mirage Maintenance – Advent of Code 2023

2023/09.livemd

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")