diff --git a/day5.livemd b/day5.livemd new file mode 100644 index 0000000..3fc7d1e --- /dev/null +++ b/day5.livemd @@ -0,0 +1,235 @@ +# 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() +```