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

Advent of Code 2024 - Day 10

elixir/2024/day_10.livemd

Advent of Code 2024 - Day 10

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

Introduction

2024 - Day 10

Puzzle

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

Parser

Code - Parser

defmodule Parser do
  def parse(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.with_index()
    |> Enum.reduce([], fn {line, y}, map ->
      line
      |> String.graphemes()
      |> Enum.with_index()
      |> Enum.reduce(map, fn {char, x}, map ->
        [{{x, y}, String.to_integer(char)} | map]
      end)
    end)
    |> Map.new()
  end
end

Tests - Parser

ExUnit.start(autorun: false)

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

  @input """
  01
  23
  """
  @expected %{{0, 0} => 0, {0, 1} => 2, {1, 0} => 1, {1, 1} => 3}

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

ExUnit.run()

Shared

defmodule Shared do
  @dir [{1, 0}, {0, -1}, {-1, 0}, {0, 1}]

  def hike(map, curr = {x, y}) do
    case Map.get(map, curr) do
      9 ->
        curr

      height ->
        @dir
        |> Enum.map(fn {dx, dy} -> {x + dx, y + dy} end)
        |> Enum.filter(fn next ->
          case Map.get(map, next) do
            nil -> false
            next_height when next_height - height != 1 -> false
            _ -> true
          end
        end)
        |> Enum.map(fn next -> hike(map, next) end)
    end
  end
end

Part One

Code - Part 1

defmodule PartOne do
  import Shared

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

  def run(input) do
    map = Parser.parse(input)

    map
    |> Enum.filter(fn {_, v} -> v == 0 end)
    |> Enum.map(fn {start, _} ->
      hike(map, start)
      |> List.flatten()
      |> Enum.uniq()
      |> Enum.count()
    end)
    |> Enum.sum()
  end
end

Tests - Part 1

ExUnit.start(autorun: false)

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

  @input """
  89010123
  78121874
  87430965
  96549874
  45678903
  32019012
  01329801
  10456732
  """
  @expected 36

  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
  import Shared

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

  def run(input) do
    map = Parser.parse(input)

    map
    |> Enum.filter(fn {_, v} -> v == 0 end)
    |> Enum.map(fn {start, _} -> hike(map, start) end)
    |> List.flatten()
    |> Enum.count()
  end
end

Tests - Part 2

ExUnit.start(autorun: false)

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

  @input """
  89010123
  78121874
  87430965
  96549874
  45678903
  32019012
  01329801
  10456732
  """
  @expected 81

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

ExUnit.run()

Solution - Part 2

PartTwo.solve(puzzle_input)