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

Advent of Code 2024 - Day 02

elixir/2024/day_02.livemd

Advent of Code 2024 - Day 02

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

Introduction

2024 - Day 02

Puzzle

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

Parser

Code - Parser

defmodule Parser do
  @reg ~r/\s+/

  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(fn s ->
      s
      |> String.split(@reg)
      |> 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

  @input """
  7 6 4 2 1
  1 2 7 8 9
  """
  @expected [[7, 6, 4, 2, 1], [1, 2, 7, 8, 9]]

  test "parse test" do
    actual = parse(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Shared

defmodule Shared do
  def is_safe(report) do
    report
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.reduce_while(nil, fn [a, b], prev_level ->
      level = a - b

      cond do
        abs(level) < 1 or abs(level) > 3 -> {:halt, false}
        prev_level != nil and sign(prev_level) != sign(level) -> {:halt, false}
        true -> {:cont, level}
      end
    end)
  end

  defp sign(x) when x > 0, do: "+"
  defp sign(x) when x < 0, do: "-"
end

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) do
    input
    |> Parser.parse()
    |> Enum.count(&amp;Shared.is_safe/1)
  end
end

Tests - Part 1

ExUnit.start(autorun: false)

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

  @input """
  7 6 4 2 1
  1 2 7 8 9
  9 7 6 2 1
  1 3 2 4 5
  8 6 4 4 1
  1 3 6 7 9
  """
  @expected 2

  test "part one" do
    actual = run(@input)
    assert actual == @expected
  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) do
    input
    |> Parser.parse()
    |> Enum.count(&amp;is_safe/1)
  end

  defp is_safe(report) do
    Enum.count(report)..0
    |> Enum.any?(fn i ->
      report
      |> List.delete_at(i)
      |> Shared.is_safe()
    end)
  end
end

Tests - Part 2

ExUnit.start(autorun: false)

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

  @input """
  7 6 4 2 1
  1 2 7 8 9
  9 7 6 2 1
  1 3 2 4 5
  8 6 4 4 1
  1 3 6 7 9
  """
  @expected 4

  test "part two" do
    actual = run(@input)
    assert actual == @expected
  end
end

ExUnit.run()

Solution - Part 2

PartTwo.solve(puzzle_input)