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

Advent of Code - Day 05

2022/aoc/day_5.livemd

Advent of Code - Day 05

Mix.install([
  {:kino_aoc, git: "https://github.com/ljgago/kino_aoc"}
])

Advent of Code Helper

{:ok, puzzle_input} = KinoAOC.download_puzzle("2022", "5", System.fetch_env!("LB_SESSION_ID"))

Part One

-> Description Part One

Code

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

  def run(input) do
    [crates_and_ids, actions] = String.split(input, "\n\n")

    actions =
      actions
      |> String.split("\n", trim: true)
      |> Enum.map(fn action ->
        ~r"move ([0-9]+) from ([1-9]) to ([1-9])"
        |> Regex.scan(action)
        |> Enum.map(fn [_, count, from, to] ->
          {String.to_integer(count), String.to_integer(from), String.to_integer(to)}
        end)
      end)

    crates =
      crates_and_ids
      |> String.split("\n")
      # Remove the ids as I don't need it
      |> Enum.drop(-1)
      |> Enum.map(fn str ->
        str
        |> String.split("")
        # `[A] ` are 4 chars
        |> Enum.chunk_every(4, 4, [""])
        |> Enum.map(fn str ->
          str
          |> Enum.join("")
          |> String.replace(["[", "]"], "")
          |> String.trim()
        end)
        # Pad the list with the same number of
        |> then(fn list ->
          l = length(list)
          n = 10

          if l == n, do: list, else: list ++ List.duplicate("", n - l)
        end)
      end)
      |> Enum.zip()
      |> Enum.map(fn crate ->
        crate
        |> Tuple.to_list()
        |> Enum.filter(&(&1 != ""))
      end)

    Enum.reduce(actions, crates, fn [{count, from, to}], crates ->
      {move, from_crate} =
        crates
        |> Enum.at(from - 1)
        |> Enum.split(count)

      to_crate = Enum.reverse(move) ++ Enum.at(crates, to - 1)

      crates
      |> List.replace_at(from - 1, from_crate)
      |> List.replace_at(to - 1, to_crate)
    end)
    |> Enum.map(&Enum.take(&1, 1))
    |> Enum.join("")
  end
end

Tests

ExUnit.start(autorun: false)

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

  test "part one" do
    input = """
        [D]    
    [N] [C]    
    [Z] [M] [P]
    1   2   3 

    move 1 from 2 to 1
    move 3 from 1 to 3
    move 2 from 2 to 1
    move 1 from 1 to 2
    """

    result = run(input)
    assert result == "CMZ"
  end
end

ExUnit.run()

Solution

PartOne.resolve(puzzle_input)

Part Two

-> Description Part Two

Code

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

  def run(input) do
    [crates_and_ids, actions] = String.split(input, "\n\n")

    actions =
      actions
      |> String.split("\n", trim: true)
      |> Enum.map(fn action ->
        ~r"move ([0-9]+) from ([1-9]) to ([1-9])"
        |> Regex.scan(action)
        |> Enum.map(fn [_, count, from, to] ->
          {String.to_integer(count), String.to_integer(from), String.to_integer(to)}
        end)
      end)

    crates =
      crates_and_ids
      |> String.split("\n")
      # Remove the ids as I don't need it
      |> Enum.drop(-1)
      |> Enum.map(fn str ->
        str
        |> String.split("")
        # `[A] ` are 4 chars
        |> Enum.chunk_every(4, 4, [""])
        |> Enum.map(fn str ->
          str
          |> Enum.join("")
          |> String.replace(["[", "]"], "")
          |> String.trim()
        end)
        # Pad the list with the same number of
        |> then(fn list ->
          l = length(list)
          n = 10

          if l == n, do: list, else: list ++ List.duplicate("", n - l)
        end)
      end)
      |> Enum.zip()
      |> Enum.map(fn crate ->
        crate
        |> Tuple.to_list()
        |> Enum.filter(&(&1 != ""))
      end)

    Enum.reduce(actions, crates, fn [{count, from, to}], crates ->
      {move, from_crate} =
        crates
        |> Enum.at(from - 1)
        |> Enum.split(count)

      # Step 1: Enum.reverse(move)
      # Step 2: move
      to_crate = move ++ Enum.at(crates, to - 1)

      crates
      |> List.replace_at(from - 1, from_crate)
      |> List.replace_at(to - 1, to_crate)
    end)
    |> Enum.map(&Enum.take(&1, 1))
    |> Enum.join("")
  end
end

Tests

ExUnit.start(autorun: false)

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

  test "part two" do
    input = """
        [D]    
    [N] [C]    
    [Z] [M] [P]
    1   2   3 

    move 1 from 2 to 1
    move 3 from 1 to 3
    move 2 from 2 to 1
    move 1 from 1 to 2
    """

    result = run(input)
    assert result == "MCD"
  end
end

ExUnit.run()

Solution

PartTwo.resolve(puzzle_input)