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

Advent of Code 2022 - Day 3

aoc2022-03.livemd

Advent of Code 2022 - Day 3

Mix.install([
  {:req_aoc, github: "mcrumm/req_aoc", ref: "28e3813"}
])

Puzzle description

Day 3: Rucksack Reorganization.

Note: I grew tired of downloading my user input, so I figured out how to download the input from the Advent of Code site. You need to get your session cookie on adventofcode.com. Use DevTools or similar and inspect the Request Headers to find your session id and then add it to Livebook secrets as AOC_SESSION.

Input

defmodule Input do
  def load! do
    ReqAOC.fetch!({2022, 03, System.fetch_env!("LB_AOC_SESSION")}, max_retries: 0)
  end

  @doc """
  Splits a string by newlines.
  """
  def to_lines(input) when is_binary(input) do
    input
    |> String.trim("\n")
    |> String.split("\n")
  end
end

Solution

defmodule Part1 do
  # preflight:
  ## split string into lines (without break characters)
  def run(input) do
    input
    |> Enum.map(fn line ->
      # split the rucksack into two equal compartments
      {head, tail} = String.split_at(line, div(String.length(line), 2))

      head
      |> String.codepoints()
      |> Enum.reduce_while(:ok, fn code, _ ->
        case :binary.match(tail, code) do
          {_, 1} -> {:halt, code |> priority()}
          :nomatch -> {:cont, :ok}
        end
      end)
    end)
    |> Enum.sum()
  end

  lower = Enum.into(?a..?z, [])
  upper = Enum.into(?A..?Z, [])
  alphabet = lower ++ upper

  def priority(x) when is_binary(x) do
    x |> :binary.first() |> priority()
  end

  for {c, k} <- Enum.with_index(alphabet, 1) do
    def priority(unquote(c)), do: unquote(k)
  end
end

defmodule Part2 do
  # preflight:
  ## split string into lines (without break characters)
  def run(input) do
    # chunk lines into groups of three
    # find codepoint intersecting all three lines
    # convert to char value
    # sum
    input
    |> Enum.chunk_every(3)
    |> Enum.map(fn [a, b, c] ->
      # convert a to codepoints
      # look for match in b
      a
      |> String.codepoints()
      |> Enum.reduce_while(nil, fn code, acc ->
        # find match in b
        case :binary.match(b, code) do
          {_, _} ->
            # find match in c
            case :binary.match(c, code) do
              {_, _} ->
                # success! return the priority value
                {:halt, Part1.priority(code)}

              :nomatch ->
                # continue to next character
                {:cont, acc}
            end

          :nomatch ->
            # continue to next character
            {:cont, acc}
        end
      end)
    end)
    |> Enum.sum()
  end
end

ExUnit.start(autorun: false)

defmodule Test do
  use ExUnit.Case, async: true
  @example_input ~s(
vJrwpWtwJgWrhcsFMMfFFhFp
jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
PmmdzqPrVvPwwTWBwg
wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
ttgJtRGJQctTZtZT
CrZsJsPPZsGzwwsLwLmpwMDw
)

  @input Input.load!()

  test "priority" do
    for {l, k} <- Enum.with_index(?a..?z, 1) do
      assert Part1.priority(l) == k
    end

    for {u, k} <- Enum.with_index(?A..?Z, 27) do
      assert Part1.priority(u) == k
    end
  end

  test "part 1" do
    assert @example_input |> Input.to_lines() |> Part1.run() === 157

    @input |> Input.to_lines() |> Part1.run() |> dbg()
  end

  test "part 2" do
    assert @example_input |> Input.to_lines() |> Part2.run() === 70

    @input |> Input.to_lines() |> Part2.run() |> dbg()
  end
end

ExUnit.configure(trace: true)
ExUnit.run()