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

Advent of Code 2024 - Day 19

elixir/2024/day_19.livemd

Advent of Code 2024 - Day 19

Mix.install([
  :kino_aoc,
  :memoize
])

Introduction

2024 - Day 19

Puzzle

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

Parser

Code - Parser

defmodule Parser do
  def parse(input) do
    [towel | designs] =
      input
      |> String.split("\n", trim: true)
    {String.split(towel, ", "), designs}
  end
end

Tests - Parser

ExUnit.start(autorun: false)

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

  @input """
  r, wr, b, g

  brwrr
  bggr
  """
  @expected {["r", "wr", "b", "g"], ["brwrr", "bggr"]}

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

ExUnit.run()

Shared

defmodule Shared do
  use Memoize
  
  defmemo check(designs, towel) do
    if towel == "" do
      1
    else
      next =
        designs
        |> Enum.filter(fn design -> String.starts_with?(towel, design) end)
        |> Enum.map(fn design -> String.replace_prefix(towel, design, "") end)

      case next do
        [] ->
          0

        next ->
          next
          |> Enum.reduce(0, fn towel, result ->
            result + check(designs, towel)
          end)
      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
    {designs, towels} = Parser.parse(input)

    towels
    |> Enum.count(fn towel -> check(designs, towel) > 0 end)
  end
end

Tests - Part 1

ExUnit.start(autorun: false)

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

  @input """
  r, wr, b, g, bwu, rb, gb, br

  brwrr
  bggr
  gbbr
  rrbgbr
  ubwu
  bwurrg
  brgr
  bbrgwb
  """
  @expected 6

  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
    {designs, towels} = Parser.parse(input)

    towels
    |> Enum.map(fn towel -> check(designs, towel) end)
    |> Enum.sum()
  end
end

Tests - Part 2

ExUnit.start(autorun: false)

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

  @input """
  r, wr, b, g, bwu, rb, gb, br

  brwrr
  bggr
  gbbr
  rrbgbr
  ubwu
  bwurrg
  brgr
  bbrgwb
  """
  @expected 16

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

ExUnit.run()

Solution - Part 2

PartTwo.solve(puzzle_input)