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

Advent of Code - Day 9

2023_day9.livemd

Advent of Code - Day 9

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

Introduction

–> Content

Puzzle

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2023", "9", System.fetch_env!("LB_AOC_SESSION"))

Parser

Code - Parser

defmodule Parser do
  def parse(input) do
    String.split(input, "\n", trim: true)
    |> Enum.map(fn string ->
      String.split(string, " ", trim: true)
      |> Enum.map(&String.to_integer(&1))
    end)
  end
end

Tests - Parser

ExUnit.start(autorun: false)

defmodule ParserTest do
  use ExUnit.Case, async: true
  import Parser

  @input1 """
  0 3 6 9 12 15
  1 3 6 10 15 21
  10 13 16 21 30 45
  """

  @expected1 [[0, 3, 6, 9, 12, 15], [1, 3, 6, 10, 15, 21], [10, 13, 16, 21, 30, 45]]

  describe "parse test" do
    test "input 1" do
      actual = parse(@input1)
      assert actual == @expected1
    end
  end
end

ExUnit.run()

Part One

Code - Part 1

defmodule PartOne do
  def solve(input) do
    IO.puts("--- Part One ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input_string) do
    histories = Parser.parse(input_string)

    Enum.map(histories, fn history ->
      report = report_for_history(history)
      report_revised = report_with_extrapolation(report)
      extrapolated_value_in_report(report_revised)
    end)
    |> Enum.sum()
  end

  def report_for_history(history) do
    deltas = Enum.zip_with([history, tl(history)], fn [first, second] -> second - first end)

    if Enum.all?(deltas, &(&1 == 0)) do
      [history] ++ [deltas]
    else
      [history] ++ report_for_history(deltas)
    end
  end

  def report_with_extrapolation(report) do
    # IO.puts("")
    # IO.puts("")
    report = List.update_at(report, Enum.count(report) - 1, fn zeros -> zeros ++ [0] end)

    {new_report, _} =
      Enum.reduce(
        report |> Enum.reverse() |> tl(),
        {[List.last(report)], 0},
        fn sequence, {new_report, value_below} ->
          # IO.puts("")
          # IO.inspect(new_report, label: "new_report")
          # IO.inspect(value_below, label: "value_below")

          value_left = List.last(sequence)
          extrapolation = value_left + value_below
          # IO.inspect(value_left, label: "value_left")
          # IO.inspect(extrapolation, label: "extrapolation")
          {[sequence ++ [extrapolation]] ++ new_report, extrapolation}
        end
      )

    new_report
  end

  def extrapolated_value_in_report(report) do
    report |> hd() |> List.last()
  end
end

Tests - Part 1

ExUnit.start(autorun: false)

defmodule PartOneTest do
  use ExUnit.Case, async: true
  import PartOne

  @input1 """
  0 3 6 9 12 15
  1 3 6 10 15 21
  10 13 16 21 30 45
  """
  @expected1 114

  test "simple example 1" do
    actual = run(@input1)
    assert actual == @expected1
  end

  describe "report_for_history/1" do
    test "example 1" do
      actual = report_for_history([0, 3, 6, 9, 12, 15])
      assert actual == [[0, 3, 6, 9, 12, 15], [3, 3, 3, 3, 3], [0, 0, 0, 0]]
    end

    test "example 2" do
      actual = report_for_history([1, 3, 6, 10, 15, 21])
      assert actual == [[1, 3, 6, 10, 15, 21], [2, 3, 4, 5, 6], [1, 1, 1, 1], [0, 0, 0]]
    end

    test "example 3" do
      actual = report_for_history([10, 13, 16, 21, 30, 45])

      assert actual == [
               [10, 13, 16, 21, 30, 45],
               [3, 3, 5, 9, 15],
               [0, 2, 4, 6],
               [2, 2, 2],
               [0, 0]
             ]
    end
  end

  describe "report_with_extrapolation/1" do
    test "example 1" do
      actual = report_with_extrapolation([[0, 3, 6, 9, 12, 15], [3, 3, 3, 3, 3], [0, 0, 0, 0]])
      assert actual == [[0, 3, 6, 9, 12, 15, 18], [3, 3, 3, 3, 3, 3], [0, 0, 0, 0, 0]]
    end

    test "example 2" do
      actual =
        report_with_extrapolation([
          [1, 3, 6, 10, 15, 21],
          [2, 3, 4, 5, 6],
          [1, 1, 1, 1],
          [0, 0, 0]
        ])

      assert actual == [
               [1, 3, 6, 10, 15, 21, 28],
               [2, 3, 4, 5, 6, 7],
               [1, 1, 1, 1, 1],
               [0, 0, 0, 0]
             ]
    end

    test "example 3" do
      actual =
        report_with_extrapolation([
          [10, 13, 16, 21, 30, 45],
          [3, 3, 5, 9, 15],
          [0, 2, 4, 6],
          [2, 2, 2],
          [0, 0]
        ])

      assert actual == [
               [10, 13, 16, 21, 30, 45, 68],
               [3, 3, 5, 9, 15, 23],
               [0, 2, 4, 6, 8],
               [2, 2, 2, 2],
               [0, 0, 0]
             ]
    end
  end
end

ExUnit.run()

Solution - Part 1

PartOne.solve(puzzle_input)

Part Two

Code - Part 2

defmodule PartTwo do
  def solve(input) do
    IO.puts("--- Part Two ---")
    IO.puts("Result: #{run(input)}")
  end

  def run(input_string) do
    histories = Parser.parse(input_string)

    Enum.map(histories, fn history ->
      report = PartOne.report_for_history(history)
      report_revised = report_with_extrapolation(report)
      extrapolated_value_in_report(report_revised)
    end)
    |> Enum.sum()
  end

  def report_with_extrapolation(report) do
    # IO.puts("")
    # IO.puts("")
    report = List.update_at(report, Enum.count(report) - 1, fn zeros -> [0] ++ zeros end)

    {new_report, _} =
      Enum.reduce(
        report |> Enum.reverse() |> tl(),
        {[List.last(report)], 0},
        fn sequence, {new_report, value_below} ->
          # IO.puts("")
          # IO.inspect(new_report, label: "new_report")
          # IO.inspect(value_below, label: "value_below")

          value_right = hd(sequence)
          extrapolation = value_right - value_below
          # IO.inspect(value_left, label: "value_left")
          # IO.inspect(extrapolation, label: "extrapolation")
          {[[extrapolation] ++ sequence] ++ new_report, extrapolation}
        end
      )

    new_report
  end

  def extrapolated_value_in_report(report) do
    report |> hd() |> hd()
  end
end

Tests - Part 2

ExUnit.start(autorun: false)

defmodule PartTwoTest do
  use ExUnit.Case, async: true
  import PartTwo

  @input1 """
  0 3 6 9 12 15
  1 3 6 10 15 21
  10 13 16 21 30 45
  """
  @expected1 2

  test "simple example 1" do
    actual = run(@input1)
    assert actual == @expected1
  end

  describe "report_with_extrapolation/1" do
    test "example 3" do
      actual =
        report_with_extrapolation([
          [10, 13, 16, 21, 30, 45],
          [3, 3, 5, 9, 15],
          [0, 2, 4, 6],
          [2, 2, 2],
          [0, 0]
        ])

      assert actual == [
               [5, 10, 13, 16, 21, 30, 45],
               [5, 3, 3, 5, 9, 15],
               [-2, 0, 2, 4, 6],
               [2, 2, 2, 2],
               [0, 0, 0]
             ]
    end
  end
end

ExUnit.run()

Solution - Part 2

PartTwo.solve(puzzle_input)