236 lines
4.5 KiB
Text
236 lines
4.5 KiB
Text
|
# 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(<<?\n, _::binary>>, 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()
|
||
|
```
|