# AOC 2022 - Day 5 ```elixir Mix.install([ {:req, "~> 0.3.3"}, {:nimble_parsec, "~> 1.2.3"} ]) ``` ## Puzzle description [Day 5: Supply Stacks](https://adventofcode.com/2022/day/5). ## Input ```elixir defmodule Load do def input do aoc_session = System.fetch_env!("LB_AOC_SESSION") input_url = "https://adventofcode.com/2022/day/5/input" Req.get!(input_url, headers: [cookie: "session=#{aoc_session}"]).body end end ``` ## Solution ```elixir defmodule Parser do import NimbleParsec crate = ignore(string("[")) |> utf8_char([?A..?Z]) |> ignore(string("]")) empty_slot = string(" ") |> replace(:empty) slot = choice([crate, empty_slot]) row = repeat_while( concat( slot, optional(ignore(string(" "))) ), :not_newline ) |> ignore(string("\n")) |> tag(:row) rows = repeat_while(row, :not_index_row) |> tag(:rows) indices = repeat_while( ignore(string(" ")) |> integer(min: 1) |> ignore(string(" ")) |> optional(ignore(string(" "))), :not_newline ) |> ignore(string("\n")) |> tag(:indices) instruction = ignore(string("move ")) |> unwrap_and_tag(integer(min: 1), :amount) |> ignore(string(" from ")) |> unwrap_and_tag(integer(min: 1), :from) |> ignore(string(" to ")) |> unwrap_and_tag(integer(min: 1), :to) |> ignore(optional(string("\n"))) |> tag(:instruction) instructions = instruction |> repeat() |> tag(:instructions) |> eos() defparsec( :crates, rows |> concat(indices) |> concat(ignore(string("\n"))) |> concat(instructions) ) defp not_newline(<>, context, _, _), do: {:halt, context} defp not_newline(_, context, _, _), do: {:cont, context} defp not_index_row(<<" 1", _::binary>>, context, _, _), do: {:halt, context} defp not_index_row(_, context, _, _), do: {:cont, context} end # Parser.rows(~s([S] [C] # [P] [M] [Z] # 1 2 3)) |> IO.inspect() # Parser.slot(" ") |> IO.inspect() # Parser.slot("brt") |> IO.inspect() ``` ```elixir ``` ```elixir defmodule Part1 do def run(input) do {:ok, data, _, _, _, _} = input |> Parser.crates() stacks = data |> Keyword.get(:rows) |> Keyword.values() |> List.zip() |> Enum.map(&Tuple.to_list/1) |> Enum.map( &Enum.reject(&1, fn :empty -> true _ -> false end) ) instructions = data |> Keyword.get(:instructions) |> Keyword.values() instructions |> Enum.reduce(stacks, fn [amount: amount, from: from, to: to], acc -> from = from - 1 to = to - 1 {transfer, from_stack} = Enum.at(acc, from) |> Enum.split(amount) to_stack = transfer |> Enum.reverse() |> Enum.concat(Enum.at(acc, to)) List.replace_at(acc, from, from_stack) |> List.replace_at(to, to_stack) end) |> Enum.map(&Enum.take(&1, 1)) |> Enum.join() end end defmodule Part2 do def run(input) do {:ok, data, _, _, _, _} = input |> Parser.crates() stacks = data |> Keyword.get(:rows) |> Keyword.values() |> List.zip() |> Enum.map(&Tuple.to_list/1) |> Enum.map( &Enum.reject(&1, fn :empty -> true _ -> false end) ) instructions = data |> Keyword.get(:instructions) |> Keyword.values() instructions |> Enum.reduce(stacks, fn [amount: amount, from: from, to: to], acc -> from = from - 1 to = to - 1 {transfer, from_stack} = Enum.at(acc, from) |> Enum.split(amount) to_stack = transfer |> Enum.concat(Enum.at(acc, to)) List.replace_at(acc, from, from_stack) |> List.replace_at(to, to_stack) end) |> Enum.map(&Enum.take(&1, 1)) |> Enum.join() end end ExUnit.start(autorun: false) defmodule Test do use ExUnit.Case, async: true @example_input ~s( [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) @input Load.input() test "it loads the input" do assert String.length(@input) > 0 end test "part 1 example" do assert Part1.run(@example_input) === "CMZ" end test "part 1" do assert Part1.run(@input) === "GFTNRBZPF" end test "part 2 example" do assert Part2.run(@example_input) === "MCD" end test "part 2" do assert Part2.run(@input) === "VRQWPDSGP" end end ExUnit.run() ```