Day 5 - Supply Stacks
Mix.install([{:kino, "~> 0.8.0"}, {:kino_bumblebee, "~> 0.1.0"}, {:exla, "~> 0.4.1"}],
config: [nx: [default_backend: EXLA.Backend]]
)
:ok
Part 1
input = Kino.Input.textarea("Please insert your input")
[crates_, movements_] =
input
|> Kino.Input.read()
|> String.split("\n\n", trim: true)
[" [Q] [P] [P]\n [G] [V] [S] [Z] [F]\n [W] [V] [F] [Z] [W] [Q]\n [V] [T] [N] [J] [W] [B] [W]\n [Z] [L] [V] [B] [C] [R] [N] [M]\n[C] [W] [R] [H] [H] [P] [T] [M] [B]\n[Q] [Q] [M] [Z] [Z] [N] [G] [G] [J]\n[B] [R] [B] [C] [D] [H] [D] [C] [N]\n 1 2 3 4 5 6 7 8 9 ",
"move 3 from 6 to 2\nmove 5 from 6 to 7\nmove 6 from 2 to 5\nmove 1 from 9 to 7\nmove 1 from 1 to 9\nmove 1 from 5 to 3\nmove 1 from 2 to 5\nmove 3 from 4 to 5\nmove 10 from 7 to 3\nmove 1 from 4 to 9\nmove 6 from 8 to 7\nmove 4 from 7 to 8\nmove 1 from 7 to 3\nmove 1 from 1 to 2\nmove 1 from 2 to 8\nmove 1 from 9 to 1\nmove 3 from 9 to 4\nmove 4 from 8 to 3\nmove 4 from 7 to 1\nmove 4 from 4 to 6\nmove 2 from 8 to 7\nmove 9 from 3 to 8\nmove 2 from 7 to 4\nmove 3 from 4 to 9\nmove 4 from 1 to 9\nmove 4 from 3 to 9\nmove 2 from 1 to 4\nmove 1 from 4 to 6\nmove 3 from 3 to 2\nmove 1 from 2 to 8\nmove 1 from 2 to 7\nmove 3 from 6 to 2\nmove 2 from 6 to 7\nmove 4 from 2 to 3\nmove 3 from 7 to 9\nmove 2 from 5 to 6\nmove 15 from 9 to 4\nmove 4 from 9 to 2\nmove 12 from 5 to 4\nmove 9 from 8 to 5\nmove 25 from 4 to 7\nmove 1 from 4 to 7\nmove 1 from 4 to 8\nmove 2 from 2 to 5\nmove 1 from 4 to 2\nmove 23 from 7 to 6\nmove 2 from 5 to 2\nmove 22 from 6 to 8\nmove 4 from 5 to 9\nmove 1 from 7 to 9\nmove 2 from 6 to 4\nmove 2 from 4 to 7\nmove 25 from 8 to 3\nmove 1 from 2 to 1\nmove 3 from 2 to 3\nmove 1 from 6 to 8\nmove 1 from 1 to 8\nmove 1 from 2 to 8\nmove 1 from 8 to 1\nmove 4 from 5 to 7\nmove 1 from 8 to 4\nmove 5 from 9 to 8\nmove 5 from 8 to 9\nmove 1 from 8 to 5\nmove 3 from 5 to 4\nmove 3 from 9 to 1\nmove 30 from 3 to 4\nmove 3 from 1 to 4\nmove 2 from 9 to 5\nmove 4 from 7 to 9\nmove 16 from 4 to 8\nmove 6 from 3 to 9\nmove 3 from 7 to 3\nmove 19 from 4 to 7\nmove 8 from 9 to 4\nmove 1 from 1 to 9\nmove 13 from 7 to 9\nmove 3 from 7 to 8\nmove 3 from 5 to 9\nmove 4 from 8 to 3\nmove 2 from 7 to 3\nmove 14 from 9 to 4\nmove 10 from 3 to 1\nmove 12 from 4 to 8\nmove 6 from 1 to 9\nmove 1 from 1 to 2\nmove 1 from 7 to 1\nmove 6 from 9 to 3\nmove 17 from 8 to 6\nmove 10 from 8 to 5\nmove 1 from 7 to 8\nmove 1 from 9 to 5\nmove 2 from 3 to 1\nmove 4 from 5 to 9\nmove 1 from 8 to 7\nmove 6 from 9 to 7\nmove 4 from 4 to 2\nmove 3 from 4 to 6\nmove 4 from 5 to 9\nmove 4 from 9 to 3\nmove 1 from 2 to 4\nmove 4 from 4 to 7\nmove 3 from 5 to 3\nmove 1 from 4 to 5\nmove 5 from 1 to 2\nmove 1 from 1 to 9\nmove 7 from 2 to 7\nmove 1 from 5 to 7\nmove 8 from 3 to 5\nmove 20 from 6 to 7\nmove 9 from 7 to 9\nmove 2 from 2 to 9\nmove 2 from 3 to 1\nmove 2 from 1 to 3\nmove 2 from 3 to 4\nmove 2 from 4 to 6\nmove 1 from 3 to 9\nmove 1 from 4 to 9\nmove 1 from 6 to 9\nmove 2 from 5 to 8\nmove 2 from 8 to 5\nmove 1 from 6 to 7\nmove 2 from 5 to 8\nmove 6 from 9 to 5\nmove 2 from 8 to 6\nmove 11 from 9 to 2\nmove 1 from 6 to 5\nmove 11 from 2 to 5\nmove 1 from 6 to 4\nmove 7 from 5 to 9\nmove 7 from 9 to 1\nmove 1 from 4 to 9\nmove 28 from 7 to 5\nmove 1 from 7 to 5\nmove 5 from 5 to 9\nmove 5 from 9 to 3\nmove 6 from 1 to 8\nmove 1 from 1 to 7\nmove 5 from 3 to 2\nmove 1 from 7 to 8\nmove 7 from 8 to 1\nmove 1 from 9 to 4\nmove 2 from 2 to 5\nmove 22 from 5 to 3\nmove 1 from 7 to 8\nmove 1 from 4 to 7\nmove 1 from 8 to 9\nmove 1 from 9 to 4\nmove 14 from 5 to 7\nmove 5 from 5 to 9\nmove 19 from 3 to 4\nmove 1 from 2 to 9\nmove 2 from 2 to 5\nmove 1 from 5 to 1\nmove 6 from 1 to 7\nmove 2 from 7 to 6\nmove 1 from 1 to 9\nmove 2 from 5 to 8\nmove 8 from 4 to 5\nmove 3 from 4 to 7\nmove 3 from 3 to 5\nmove 2 from 8 to 9\nmove 16 from 7 to 5\nmove 9 from 4 to 6\nmove 22 from 5 to 3\nmove 1 from 5 to 8\nmove 1 from 8 to 7\nmove 10 from 3 to 4\nmove 1 from 5 to 4\nmove 10 from 4 to 5\nmove 8 from 5 to 2\nmove 5 from 2 to 7\nmove 5 from 7 to 1\nmove 4 from 7 to 6\nmove 3 from 9 to 7\nmove 2 from 2 to 3\nmove 3 from 5 to 1\nmove 6 from 9 to 7\nmove 5 from 7 to 8\nmove 6 from 1 to 5\nmove 6 from 3 to 4\nmove 4 from 4 to 2\nmove 1 from 4 to 6\nmove 5 from 8 to 7\nmove 3 from 2 to 3\nmove 1 from 1 to 4\nmove 1 from 1 to 9\nmove 2 from 2 to 1\nmove 2 from 4 to 3\nmove 4 from 3 to 7\nmove 3 from 7 to 3\nmove 13 from 6 to 1\nmove 1 from 9 to 2\nmove 6 from 3 to 5\nmove 8 from 1 to 4\nmove 1 from 2 to 7\nmove 9 from 4 to 9\nmove 7 from 5 to 1\nmove 2 from 5 to 6\nmove 1 from 1 to 4\nmove 1 from 4 to 3\nmove 2 from 1 to 2\nmove 5 from 3 to 6\nmove 2 from 6 to 1\nmove 13 from 7 to 6\nmove 2 from 3 to 4\nmove 2 from 2 to 9\nmove 2 from 7 to 8\nmove 6 from 9 to 2\nmove 1 from 9 to 3\nmove 1 from 5 to 2\nmove 7 from 1 to 2\nmove 1 from 6 to 7\nmove 1 from 4 to 8\nm" <> ...]
An alternative to build a data structure that represents our stacks of crates would be to create a list of lists. Each list represents a stack. So for the sample input of
[D]
[N] [C]
[Z] [M] [P]
Our resulting data structure would be
[["N", "Z"], ["D", "C", "M"], ["P"]]
What first comes to mind is that if I can transpose the input, it will get easier to work because it would look like this:
[[nil, "N", "Z"], ["D", "C", "M"], [nil, nil, "P"]]
If we consider nil
as an “empty” slot in the stack… all that’s left is to weed it to get to the resulting data structure.
defmodule StackParser do
@moduledoc """
This module parses a single raw line into a list of crates or nils to represent "empty" slots
## Examples
iex> StackParser.parse("[Z] [M] [P]")
["Z", "M", "P"]
iex> StackParser.parse("[N] [C] ")
["N", "C", nil]
iex> StackParser.parse(" [D] ")
[nil, "D", nil]
"""
def parse(ln), do: parse(ln, [])
defp parse("", stack), do: stack
defp parse(" " <> rest, stack), do: parse(rest, stack ++ [nil])
defp parse(" " <> rest, stack), do: parse(rest, stack ++ [nil])
defp parse(" [" <> <> <> "]" <> rest, stack),
do: parse(rest, stack ++ [crate])
defp parse("[" <> <> <> "]" <> rest, stack),
do: parse(rest, stack ++ [crate])
end
3 doctests, 0 failures
{:module, StackParser, <<70, 79, 82, 49, 0, 0, 9, ...>>, {:parse, 2}}
defmodule Transp do
# copied from https://elixirforum.com/t/transpose-a-list-of-lists-using-list-comprehension/17638
def transpose([[] | _]), do: []
def transpose(m) do
[Enum.map(m, &hd/1) | transpose(Enum.map(m, &tl/1))]
end
end
{:module, Transp, <<70, 79, 82, 49, 0, 0, 6, ...>>, {:transpose, 1}}
crates =
crates_
|> String.split("\n")
|> Enum.drop(-1)
|> Enum.map(&StackParser.parse/1)
|> Transp.transpose()
# The following simply removes the nils
|> Enum.map(fn stack -> Enum.filter(stack, & &1) end)
[
["C", "Q", "B"],
["Z", "W", "Q", "R"],
["V", "L", "R", "M", "B"],
["W", "T", "V", "H", "Z", "C"],
["G", "V", "N", "B", "H", "Z", "D"],
["Q", "V", "F", "J", "C", "P", "N", "H"],
["S", "Z", "W", "R", "T", "G", "D"],
["P", "Z", "W", "B", "N", "M", "G", "C"],
["P", "F", "Q", "W", "M", "B", "J", "N"]
]
movements =
movements_
|> String.split("\n", trim: true)
["move 3 from 6 to 2", "move 5 from 6 to 7", "move 6 from 2 to 5", "move 1 from 9 to 7",
"move 1 from 1 to 9", "move 1 from 5 to 3", "move 1 from 2 to 5", "move 3 from 4 to 5",
"move 10 from 7 to 3", "move 1 from 4 to 9", "move 6 from 8 to 7", "move 4 from 7 to 8",
"move 1 from 7 to 3", "move 1 from 1 to 2", "move 1 from 2 to 8", "move 1 from 9 to 1",
"move 3 from 9 to 4", "move 4 from 8 to 3", "move 4 from 7 to 1", "move 4 from 4 to 6",
"move 2 from 8 to 7", "move 9 from 3 to 8", "move 2 from 7 to 4", "move 3 from 4 to 9",
"move 4 from 1 to 9", "move 4 from 3 to 9", "move 2 from 1 to 4", "move 1 from 4 to 6",
"move 3 from 3 to 2", "move 1 from 2 to 8", "move 1 from 2 to 7", "move 3 from 6 to 2",
"move 2 from 6 to 7", "move 4 from 2 to 3", "move 3 from 7 to 9", "move 2 from 5 to 6",
"move 15 from 9 to 4", "move 4 from 9 to 2", "move 12 from 5 to 4", "move 9 from 8 to 5",
"move 25 from 4 to 7", "move 1 from 4 to 7", "move 1 from 4 to 8", "move 2 from 2 to 5",
"move 1 from 4 to 2", "move 23 from 7 to 6", "move 2 from 5 to 2", "move 22 from 6 to 8",
"move 4 from 5 to 9", "move 1 from 7 to 9", ...]
defmodule CrateMover9000 do
@moduledoc """
This module implenents the crane that moves crates around
## Examples
iex> CrateMover9000.move_crates("move 1 from 2 to 1", [["N", "Z"], ["D", "C", "M"], ["P"]])
[["D", "N", "Z"], ["C", "M"], ["P"]]
iex> CrateMover9000.move_crates("move 3 from 1 to 3", [["D", "N", "Z"], ["C", "M"], ["P"]])
[[], ["C", "M"], ["Z", "N", "D", "P"]]
iex> CrateMover9000.move_crates("move 2 from 2 to 1", [[], ["C", "M"], ["Z", "N", "D", "P"]])
[["M", "C"], [], ["Z", "N", "D", "P"]]
iex> CrateMover9000.move_crates("move 1 from 1 to 2", [["M", "C"], [], ["Z", "N", "D", "P"]])
[["C"], ["M"], ["Z", "N", "D", "P"]]
"""
def move_crates(
"move " <>
<> <>
" from " <> <> <> " to " <> <>,
crates
) do
_move(String.to_integer(qty), String.to_integer(src) - 1, String.to_integer(dst) - 1, crates)
end
# this is an ugly hack because sometimes qty has 2 digits (10, 11, ...)
# and I don't know how to pattern match on variable length
def move_crates(
"move " <>
<> <>
" from " <> <> <> " to " <> <>,
crates
) do
_move(String.to_integer(qty), String.to_integer(src) - 1, String.to_integer(dst) - 1, crates)
end
defp _move(1, src, dst, crates) do
[crate | rest] = Enum.at(crates, src)
crates
|> Enum.with_index()
|> Enum.map(fn
{_, ^src} -> rest
{stack, ^dst} -> [crate] ++ stack
{stack, _} -> stack
end)
end
defp _move(qty, src, dst, crates) do
moved_crates = _move(1, src, dst, crates)
_move(qty - 1, src, dst, moved_crates)
end
end
4 doctests, 0 failures
{:module, CrateMover9000, <<70, 79, 82, 49, 0, 0, 12, ...>>, {:_move, 4}}
resulting_stacks =
movements
|> Enum.reduce(crates, fn cmd, moved_crates ->
CrateMover9000.move_crates(cmd, moved_crates)
end)
[
["B"],
["W", "F", "T", "T", "Z", "Z", "B", "C", "S", "V", "F"],
["N", "W", "H", "C", "H", "H", "N", "V", "G", "N", "Z", "G", "B", "J"],
["C"],
["Q"],
["R", "V", "Q", "W", "N", "L", "W", "P", "M", "C", "B", "V"],
["M", "R", "P", "W"],
["D", "Z", "Z", "P"],
["B", "R", "Q", "M", "J", "Q", "G", "D"]
]
resulting_stacks
|> Enum.map(&hd/1)
|> Enum.join()
"BWNCQRMDB"
Part 2
defmodule CrateMover9001 do
@moduledoc """
This module implenents the crane that moves crates around
## Examples
iex> CrateMover9001.move_crates("move 1 from 2 to 1", [["N", "Z"], ["D", "C", "M"], ["P"]])
[["D", "N", "Z"], ["C", "M"], ["P"]]
iex> CrateMover9001.move_crates("move 3 from 1 to 3", [["D", "N", "Z"], ["C", "M"], ["P"]])
[[], ["C", "M"], ["D", "N", "Z", "P"]]
iex> CrateMover9001.move_crates("move 2 from 2 to 1", [[], ["C", "M"], ["D", "N", "Z", "P"]])
[["C", "M"], [], ["D", "N", "Z", "P"]]
iex> CrateMover9001.move_crates("move 1 from 1 to 2", [["C", "M"], [], ["D", "N", "Z", "P"]])
[["M"], ["C"], ["D", "N", "Z", "P"]]
"""
def move_crates(
"move " <>
<> <>
" from " <> <> <> " to " <> <>,
crates
) do
_move(String.to_integer(qty), String.to_integer(src) - 1, String.to_integer(dst) - 1, crates)
end
# this is an ugly hack because sometimes qty has 2 digits (10, 11, ...)
# and I don't know how to pattern match on variable length
def move_crates(
"move " <>
<> <>
" from " <> <> <> " to " <> <>,
crates
) do
_move(String.to_integer(qty), String.to_integer(src) - 1, String.to_integer(dst) - 1, crates)
end
defp _move(qty, src, dst, crates) do
src_stack =
crates
|> Enum.at(src)
picked =
src_stack
|> Enum.take(qty)
remaining =
src_stack
|> Enum.take(-(length(src_stack) - qty))
crates
|> Enum.with_index()
|> Enum.map(fn
{_, ^src} -> remaining
{stack, ^dst} -> picked ++ stack
{stack, _} -> stack
end)
end
end
4 doctests, 0 failures
{:module, CrateMover9001, <<70, 79, 82, 49, 0, 0, 12, ...>>, {:_move, 4}}
real_resulting_stacks =
movements
|> Enum.reduce(crates, fn cmd, moved_crates ->
CrateMover9001.move_crates(cmd, moved_crates)
end)
[
["N"],
["H", "R", "B", "P", "H", "W", "V", "V", "F", "C", "V"],
["W", "Z", "C", "R", "Z", "D", "G", "B", "B", "P", "N", "J", "W", "M"],
["Z"],
["C"],
["B", "T", "Z", "M", "R", "T", "Q", "Q", "P", "L", "G", "M"],
["N", "G", "W", "W"],
["B", "S", "J", "C"],
["F", "Z", "D", "Q", "Q", "V", "H", "N"]
]
real_resulting_stacks
|> Enum.map(&hd/1)
|> Enum.join()
"NHWZCBNBF"