# Advent of Code 2022 - Day 5

Today’s challenge for me was about using reduce:

``````defmodule Prob5 do
def move([[h1 | rest] = _list1, list2]) do
[rest, [h1 | list2]]
end

def move_n([l1, l2], n) do
Enum.reduce(1..n, [l1, l2], fn _, acc -> move(acc) end)
end

def single_instruction(input, [n, from_ind, to_ind], move_function) do
from_ind = from_ind - 1
to_ind = to_ind - 1
[from, to] = move_function.([Enum.at(input, from_ind), Enum.at(input, to_ind)], n)
input
|> List.replace_at(from_ind, from)
|> List.replace_at(to_ind, to)
end

def parse_line(line) do
[[n], [from_ind], [to_ind]] = Regex.scan(~r/\d+/, line)
Enum.map([n, from_ind, to_ind], &String.to_integer/1)
end

def move_n_part2([l1, l2], n) do
to_move = Enum.take(l1, n)
[Enum.drop(l1, n), to_move ++ l2]
end
end
``````

I manually parsed the first part of the input into lists, like thus:
test_input = [[:n, :z], [:d, :c, :m], [:p]]

And then parsed the 2nd part of the input into simple lists, and finally ran it through reduce:

``````Enum.reduce(
instructions,
input,
fn instruction, input -> Prob5.single_instruction(input, instruction, &Prob5.move_n/2) end)
``````

… using the 2 move functions - one for each part.

Let me know how this can be improved!

Thanks

I spent more time than I would have only if I hadn’t updated the “initial state” instead of “accumulator” inside that reducer. It was silly of me and thanks to `dbg`, copying the example data, and 10 minutes of exploration, I got back on track. Cute of me to think I could get away with a `_` in lieu of `acc` lol.

1 Like

Biggest challenge for me was just parsing the input, which is pretty typical for me.
I probably went overboard spinning up a GenServer but it’s what I always think of when there’s some “moves” or “ops” that have to be tracked.

``````defmodule Day5 do
defmodule Input do
def sample_data() do
"""
[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
"""
end

ReqAOC.fetch!({2022, 05, System.fetch_env!("AOC2022Session")})
end

def parse(input) do
[stacks, moves] = input |> String.split("\n\n", trim: true) |> IO.inspect()

stacks =
stacks
|> String.split("\n", trim: true)
|> Enum.drop(-1)
|> Enum.flat_map(fn line ->
line |> String.graphemes() |> Enum.drop(1) |> Enum.take_every(4) |> Enum.with_index(1)
end)
|> Enum.group_by(&elem(&1, 1))
|> Enum.map(fn {column, vals} ->
{column, vals |> Enum.map(&elem(&1, 0)) |> Enum.reject(&Kernel.==(&1, " "))}
end)
|> Map.new()

moves =
moves
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
line
|> String.split(["move ", " from ", " to "], trim: true)
|> Enum.map(&String.to_integer(&1))
end)

{stacks, moves} |> IO.inspect()
end
end

defmodule Stacks do
use GenServer

@impl true
def init(stacks) do
{:ok, stacks}
end

def handle_cast({:move_9000, n, from, to}, stacks) do
new_stacks =
1..n
|> Enum.reduce(stacks, fn _, new_stacks ->
[crate | stack] = Map.get(new_stacks, from)
dest = Map.get(new_stacks, to)
Map.put(new_stacks, from, stack) |> Map.put(to, [crate | dest]) |> IO.inspect()
end)

end

def handle_cast({:move_9001, n, from, to}, stacks) do
{moving, staying} = Map.get(stacks, from) |> Enum.split(n)

new_stacks =
Map.put(stacks, from, staying)
|> Map.update!(to, fn dest -> moving ++ dest end)

end

def handle_call(:crates_on_top, _from, stacks) do
tops =
stacks
|> Enum.map(fn
{_col, [hd | rest]} -> hd
{_col, []} -> :empty
end)
|> Enum.reject(fn crate -> crate == :empty end)

end
end

defmodule Part1 do
def solve({stacks, moves}) do

moves
|> Enum.each(fn [n, from, to] -> GenServer.cast(pid, {:move_9000, n, from, to}) end)

GenServer.call(pid, :crates_on_top) |> IO.inspect()
GenServer.stop(pid)
end
end

defmodule Part2 do
def solve({stacks, moves}) do

moves
|> Enum.each(fn [n, from, to] -> GenServer.cast(pid, {:move_9001, n, from, to}) end)

GenServer.call(pid, :crates_on_top) |> Enum.join("") |> IO.inspect()
GenServer.stop(pid)
end
end
end
``````
1 Like

As always, I am here to advertise `Access`.

``````# moving crates (part I)
Enum.reduce(0..count - 1, acc, fn _, acc ->
{v, acc} = get_and_update_in(acc, [Access.at(from - 1)], fn [h|t] -> {h, t} end)
update_in(acc, [Access.at(to - 1)], fn t -> [v|t] end)
end)
``````
``````# moving crates (part II)
{v, acc} = get_and_update_in(acc, [Access.at(from - 1)], fn l -> Enum.split(l, count) end)
update_in(acc, [Access.at(to - 1)], fn t -> v ++ t end)
``````
7 Likes

Thanks. Learnt something new!

`Access` is the extremely powerful and most underrated Elixir feature.

5 Likes

Finite number of moves is a synonym of `reduce/3` through

3 Likes

Sure. Wasn’t sure what part 2 was going to be though, so it felt most flexible to do it with a GenServer.

1 Like

I think the use of a module and named functions greatly improves readability. But I wanted to be as close as possible to one-liners.

part1
``````start = System.monotonic_time(:microsecond)

stacks = File.stream!("input.txt")
|> Stream.take(8)
|> Stream.map(fn line ->
# pos = idx / 4 + 1 and idx = (pos - 1) * 4
# Get the positions of the stacks going from 1 to 9. "[D]                     [N] [F]    " -> [1, 7, 8]
Regex.scan(~r/\[\w\]/, line, return: :index) |> Enum.map(fn [{start, _end}] -> div(start,4)+1 end)
# Get the letters on a line with their respective positions. [1, 7, 8] -> [{8, "F"}, {7, "N"}, {1, "D"}]
|> Enum.reduce([], fn pos, acc -> crate = String.slice(line, (pos-1)*4+1, 1) ; [{pos, crate} | acc] end)
end)
# reverse the list to pile up the crates in the right order starting with the crates at the bottom
|> Enum.reverse()
# populate the map representing the stacks starting with an empty map
|> Enum.reduce(%{}, fn lst, m ->
Enum.reduce(lst, m, fn {pos, crate}, m -> update_in(m[pos], fn nil -> [crate] ; lst -> [crate | lst] end) end)
end)
# |> IO.inspect(label: "init stacks")

File.stream!("input.txt")
|> Stream.drop(10)
|> Stream.map(&String.trim_trailing/1)
|> Stream.map(fn line ->
Regex.run(~r/^move (\d+) from (\d) to (\d)\$/, line, capture: :all_but_first)
|> Enum.map(&String.to_integer/1)
end)
|> Enum.reduce(stacks, fn [n,src,dst], stacks -> Enum.reduce(1..n, stacks, fn _i, stacks ->
[crate | crates] = stacks[src]
stacks
|> put_in([src], crates) # remove the crate from the source stack
|> update_in([dst], fn nil -> [crate] ; lst -> [crate | lst] end) # put the crate in the destination stack
end)
end)
# |> IO.inspect(label: "final stacks")
# transform the map of stacks into a list of stacks in the right order
|> Enum.sort_by(fn {pos, _lst} -> pos end)
# keep only the top crate
|> Enum.map(fn {_pos, [crate | _]} -> crate end)
|> Enum.join("")
|> tap(fn crates -> IO.puts "Crates on top of each stack: #{crates}" end)

elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Job done in #{elapsed} µs"
``````

Part 2 is very similar to part 1. The way of moving multiple crates is simplified since a `reduce` disappears.

part2
``````start = System.monotonic_time(:microsecond)

stacks = File.stream!("input.txt")
|> Stream.take(8)
|> Stream.map(fn line ->
# pos = idx / 4 + 1 and idx = (pos - 1) * 4
# Get the positions of the stacks going from 1 to 9. "[D]                     [N] [F]    " -> [1, 7, 8]
Regex.scan(~r/\[\w\]/, line, return: :index) |> Enum.map(fn [{start, _end}] -> div(start,4)+1 end)
# Get the letters on a line with their respective positions. [1, 7, 8] -> [{8, "F"}, {7, "N"}, {1, "D"}]
|> Enum.reduce([], fn pos, acc -> crate = String.slice(line, (pos-1)*4+1, 1) ; [{pos, crate} | acc] end)
end)
# reverse the list to pile up the crates in the right order starting with the crates at the bottom
|> Enum.reverse()
# populate the map representing the stacks starting with an empty map
|> Enum.reduce(%{}, fn lst, m ->
Enum.reduce(lst, m, fn {pos, crate}, m -> update_in(m[pos], fn nil -> [crate] ; lst -> [crate | lst] end) end)
end)
# |> IO.inspect(label: "init stacks")

File.stream!("input.txt")
|> Stream.drop(10)
|> Stream.map(&String.trim_trailing/1)
|> Stream.map(fn line ->
Regex.run(~r/^move (\d+) from (\d) to (\d)\$/, line, capture: :all_but_first)
|> Enum.map(&String.to_integer/1)
end)
|> Enum.reduce(stacks, fn [n,src,dst], stacks ->
crates = Enum.take(stacks[src], n)
stacks
|> update_in([src], fn lst -> Enum.drop(lst, n) end) # remove the crates from the source stack
|> update_in([dst], fn nil -> crates ; lst -> crates ++ lst end) # put the crates in the destination stack
end)
# |> IO.inspect(label: "final stacks")
# transform the map of stacks into a list of stacks in the right order
|> Enum.sort_by(fn {pos, _lst} -> pos end)
# keep only the top crate
|> Enum.map(fn {_pos, [crate | _]} -> crate end)
|> Enum.join("")
|> tap(fn crates -> IO.puts "Crates on top of each stack: #{crates}" end)

elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Job done in #{elapsed} µs"
``````
1 Like

I put each stack of crates in a map, indexed by the stacks index so I could access them without traversing the list of stacks all the time

Thanks for pointing me to the `Access` module, there are indeed some neat stuff in there that I didn’t know about.

I implemented my own `Stack` module that just wraps `List` with some (hopefully) more stack-ish names. I haven’t decided if this was overkill or not, but I thought it was okay in the end. My solution is just converting the move instructions to stack operations (peek, drop, and push). I didn’t see a cleaner way of doing a pop and not peek and drop, This one might be one I revisit with fresh eyes and consider a major refactor if I think of a cleaner way after reviewing solutions here tomorrow. advent-of-code/advent_of_code_2022.livemd at main · bmitc/advent-of-code · GitHub

Also, I hand-coded the initial stacks because I didn’t want to waste time decoding such terrible input.

(Gotta scroll to see the bulk of the solution.)

``````
defmodule Day5 do
@moduledoc """
Solutions for Day 5
"""

alias Stack

@typedoc """
Represents a crate where the name of the crate is an integer index
"""
@type crate() :: non_neg_integer()

@typedoc """
Represents a single stack of crates
"""
@type stack() :: Stack.t(crate())

@type stack_name() :: non_neg_integer()

@typedoc """
Represents an instruction that says which crate to move from what stack
to what stack
"""
@type move_instruction() :: %{
crate: crate(),
from: stack_name(),
to: stack_name()
}

@spec initial_stacks() :: [Stack.t()]
def initial_stacks() do
[
["F", "T", "N", "Z", "M", "G", "H", "J"],
["J", "W", "V"],
["H", "T", "B", "J", "L", "V", "G"],
["L", "V", "D", "C", "N", "J", "P", "B"],
["G", "R", "P", "M", "S", "W", "F"],
["M", "V", "N", "B", "F", "C", "H", "G"],
["R", "M", "G", "H", "D"],
["D", "Z", "V", "M", "N", "H"],
["H", "F", "N", "G"]
]
|> Enum.map(&Stack.new/1)
end

@doc """
Parse an assignment pair string into a tuple of section assignment ranges

## Examples:
iex> Day4.parse_assignment_pair("2-4,6-8")
{2..4, 6..8}
"""
@spec parse_move_instruction(String.t()) :: move_instruction()
def parse_move_instruction(string) do
["move", number, "from", from, "to", to] = String.split(string, " ", trim: true)

%{
number_of_crates: String.to_integer(number),
from: String.to_integer(from) - 1,
to: String.to_integer(to) - 1
}
end

@doc """
List of all move instructions
"""
@spec move_instructions() :: [move_instruction()]
def move_instructions() do
|> Enum.map(&parse_move_instruction/1)
end

@doc """
Handle a move instruction for the case where crates are moved one at a time
"""
@spec handle_move_instruction([stack()], move_instruction()) :: [stack()]
def handle_move_instruction(stacks, move_instruction) do
# Pop (really peek and drop) and push crates
1..move_instruction.number_of_crates
|> Enum.reduce(stacks, fn _, stacks ->
# Get the crate that will be moved by peeking it from the "from" stack
crate_to_move =
stacks
|> Enum.at(move_instruction.from)
|> Stack.peek()

# Drop the crate from the "from" stack and push it to the "to" stack
stacks
|> List.update_at(move_instruction.from, &Stack.drop/1)
|> List.update_at(move_instruction.to, &Stack.push(&1, crate_to_move))
end)
end

@doc """
Handle a move instruction for the case where multiple crates are moved at once
"""
@spec handle_move_instruction_in_order([stack()], move_instruction()) :: [stack()]
def handle_move_instruction_in_order(stacks, move_instruction) do
# Pop (really peek and drop) and push crates
crates_to_move =
stacks
|> Enum.at(move_instruction.from)
|> Stack.peek(move_instruction.number_of_crates)

stacks
|> List.update_at(
move_instruction.from,
&Stack.drop(&1, move_instruction.number_of_crates)
)
|> List.update_at(move_instruction.to, &Stack.push_as_one(&1, crates_to_move))
end

def part_one() do
move_instructions()
|> Enum.reduce(initial_stacks(), fn move_instruction, stacks ->
handle_move_instruction(stacks, move_instruction)
end)
# Peek the top of all the final stacks
|> Enum.map(&Stack.peek/1)
|> Enum.join()
end

def part_two() do
move_instructions()
|> Enum.reduce(initial_stacks(), fn move_instruction, stacks ->
handle_move_instruction_in_order(stacks, move_instruction)
end)
# Peek the top of all the final stacks
|> Enum.map(&Stack.peek/1)
|> Enum.join()
end
end
``````
1 Like

Similar approach to others. Think I just organized parsing/solving a bit different.
I end up committing regex for text splitting, but commented non-regex options (e.g. patterned matched the moves originally)

I wondered the same thing about simplifying peek/drop/push.

I ended up doing something like this...
``````crates = # current state: %{column_idx => [crate]}
[count, from, to] = # parsed: "move <count> from <from> to <to>"
{to_move, crates} = Map.get_and_update(crates, from, &Enum.split(&1, count))
# Reverse is for part 1 only, remove for part 2
Map.update(crates, to, [], &(Enum.reverse(to_move) ++ &1))
``````

Lenses in general aren’t really popular outside of the “hard core FP”.

My solution:

1 Like

I didn’t enjoy the parsing process. Here’s my code for it:

``````  def read_input(fname) do
[crates, moves] =
|> String.split("\n\n", trim: true)

{to_map(crates), to_cmdlist(moves)}
end

# input -> list of rows
# each row:
# - split into list of single-character strings
# - drop the [ and ] characters
# - filter out "empty" crates
# - add index to turn it into {crate, column} tuples
# use flatmap to turn it into a map
# - the key is the column (1, 2, ...)
# - value is a list of crates in that column
defp to_map(crates) do
String.split(crates, "\n", trim: true)
|> Enum.flat_map(fn row ->
String.graphemes(row)
|> Enum.drop_every(2)
|> Enum.with_index()
|> Enum.filter(fn {crate, _} -> crate != " " end)
end)
|> Enum.reverse()
|> Enum.reduce(%{}, fn {crate, col}, cratesmap ->
Map.update(cratesmap, div(col, 2) + 1, [crate], fn existing -> [crate | existing] end)
end)
end

defp to_cmdlist(moves) do
moves
|> String.split("\n", trim: true)
|> Enum.map(fn movestr ->
[_, count, _, from, _, to] = String.split(movestr, " ", trim: true)
{String.to_integer(count), String.to_integer(from), String.to_integer(to)}
end)
end

``````

Moving the crates was more work than necessary:

``````  def part1({crates, commands}) do
Enum.reduce(commands, crates, fn {count, from, to}, crates ->
{take, remain} = Enum.split(crates[from], count)

Map.put(crates, from, remain)
|> Map.put(to, Enum.reverse(take) ++ crates[to])
end)
|> top_crates()
end

def part2({crates, commands}) do
Enum.reduce(commands, crates, fn {count, from, to}, crates ->
{take, remain} = Enum.split(crates[from], count)

Map.put(crates, from, remain)
|> Map.put(to, take ++ crates[to])
end)
|> top_crates()
end

defp top_crates(crates) do
Map.keys(crates)
|> Enum.sort()
|> Enum.map(fn col -> hd(crates[col]) end)
|> Enum.join("")
end
``````
1 Like

I spent much more time parsing the input than moving the crates

``````defmodule Day05 do
def part1(input_path) do
{stacks, instructions} = parse_input(input_path)

instructions
|> Enum.reduce(stacks, fn instruction, stacks ->
move1(stacks, instruction.from, instruction.to, instruction.amount)
end)
|> Enum.sort()
|> Enum.map(&elem(&1, 1))
|> Enum.map(&hd/1)
end

def part2(input_path) do
{stacks, instructions} = parse_input(input_path)

instructions
|> Enum.reduce(stacks, fn instruction, stacks ->
move2(stacks, instruction.from, instruction.to, instruction.amount)
end)
|> Enum.sort()
|> Enum.map(&elem(&1, 1))
|> Enum.map(&hd/1)
end

defp parse_input(input_path) do
[stacks_part, instructions_part] =
input_path
|> String.split("\n\n", parts: 2, trim: true)

{build_stacks(stacks_part), build_instructions(instructions_part)}
end

defp build_stacks(stacks_part) do
stacks_part
|> String.split("\n", trim: true)
|> Enum.reverse()
|> tl()
|> Enum.map(&to_layer(&1, []))
|> Enum.reduce(%{}, &push_layer/2)
end

defp to_layer("", acc), do: Enum.reverse(acc)
defp to_layer("    " <> rest, acc), do: to_layer(rest, [?_ | acc])
defp to_layer("   " <> rest, acc), do: to_layer(rest, [?_ | acc])
defp to_layer(<<?[, char, ?], ?\s, rest::binary>>, acc), do: to_layer(rest, [char | acc])
defp to_layer(<<?[, char, ?], rest::binary>>, acc), do: to_layer(rest, [char | acc])

defp push_layer(layer, stacks) do
layer
|> Enum.with_index(1)
|> Enum.reduce(stacks, fn
{?_, _}, stacks -> stacks
{char, i}, stacks -> Map.update(stacks, i, [char], &[char | &1])
end)
end

defp build_instructions(instructions_part) do
~r/\d+/m
|> Regex.scan(instructions_part)
|> List.flatten()
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(3)
|> Enum.map(fn [amount, from, to] ->
%{amount: amount, from: from, to: to}
end)
end

defp move1(stacks, from, to, amount) do
{from_stack, to_stack} = for _ <- 1..amount, reduce: {stacks[from], stacks[to]} do
{[h | t], to_stack} -> {t, [h | to_stack]}
end

%{stacks | from => from_stack, to => to_stack}
end

defp move2(stacks, from, to, amount) do
{to_move, from_stack} = Enum.split(stacks[from], amount)
to_stack = to_move ++ stacks[to]

%{stacks | from => from_stack, to => to_stack}
end
end
``````

I certainly overengieneered this, as I created a struct for holding the state and having an API to call, transforming from and to text (for debugging). But that also meant part 2 was actually just taking away from the existing solution

Solution
``````defmodule Day5 do
defmodule State do
defstruct stacks: %{}

@col_size 3

def from_text(text) do
lines = String.split(text, "\n")
{labels_line, stack_lines} = List.pop_at(lines, -1)
labels = parse_columns(" ", labels_line)
stack_columns = Enum.map(stack_lines, &parse_columns([" ", "[", "]"], &1))

stacks =
stack_columns
|> Enum.reverse()
|> Enum.reduce(Enum.map(labels, &{&1, []}), fn line, stack ->
Enum.zip(stack, line)
|> Enum.map(fn
{col, ""} -> col
{{label, stack}, cargo} -> {label, [cargo | stack]}
end)
end)

%__MODULE__{stacks: Map.new(stacks)}
end

defp parse_columns(trim, <<text::binary-size(@col_size)>>) do
[trim(text, trim)]
end

defp parse_columns(trim, <<text::binary-size(@col_size), " ", rest::binary>>) do
[trim(text, trim) | parse_columns(trim, rest)]
end

defp trim(string, to_trim) do
to_trim
|> List.wrap()
|> Enum.reduce(string, &String.trim(&2, &1))
end

def move(%__MODULE__{stacks: stacks}, from, to, num) do
{to_move, stacks} =
Map.get_and_update!(stacks, from, fn list ->
Enum.split(list, num)
end)

to_put = Enum.reverse(to_move)

stacks = Map.update!(stacks, to, fn current -> to_put ++ current end)
%__MODULE__{stacks: stacks}
end

def move_alternate(%__MODULE__{stacks: stacks}, from, to, num) do
{to_move, stacks} =
Map.get_and_update!(stacks, from, fn list ->
Enum.split(list, num)
end)

stacks = Map.update!(stacks, to, fn current -> to_move ++ current end)
%__MODULE__{stacks: stacks}
end

def id(%__MODULE__{stacks: stacks}) do
stacks
|> Map.values()
|> Enum.map_join("", &List.first/1)
end

defimpl Inspect do
def inspect(%_{stacks: stacks}, _) do
stack_lists = Map.values(stacks)
height = stack_lists |> Enum.map(&length/1) |> Enum.max()
indexes = -height..-1//1

lines =
Enum.map_join(indexes, "\n", fn index ->
Enum.map_join(stack_lists, " ", fn stack ->
case Enum.at(stack, index) do
nil -> "   "
cargo -> "[#{cargo}]"
end
end)
end)

labels = stacks |> Map.keys() |> Enum.map_join(" ", fn label -> " #{label} " end)

text = lines <> "\n" <> labels

"""
Day5.State.from_text(\"\"\"
#{text}
\"\"\")
"""
end
end
end

def parse(text) do
[starting_stacks, procedure] = String.split(text, "\n\n", parts: 2)
state = State.from_text(starting_stacks)

operations =
~r/^move (\d+) from (.) to (.)\$/m
|> Regex.scan(procedure)
|> Enum.map(fn [_, num, from, to] ->
%{num: String.to_integer(num), from: from, to: to}
end)

{state, operations}
end

def run_operations(text) do
{state, operations} = parse(text)

operations
|> Enum.reduce(state, fn operation, state ->
State.move(state, operation.from, operation.to, operation.num)
end)
|> State.id()
end

def run_operations_alternate(text) do
{state, operations} = parse(text)

operations
|> Enum.reduce(state, fn operation, state ->
State.move_alternate(state, operation.from, operation.to, operation.num)
end)
|> State.id()
end
end
``````
1 Like

Getting harder. I’m using Advent of Code to get more familiar with Elixir and functional language and until now it was very pleasant thanks to Elixir amazing stdlib.

But I’m hitting the conceptual wall today. An operation thas is trivial in many languages (pop a few item from a container, put them in another) became incredibly difficult with Elixir and its immutable structure. I spent hours trying to figure out how to do that, only to end up with a bunch of hard to understand Enum.reduce.

Day5
``````the_crates = [
["P", "Z", "M", "T", "R", "C", "N"],
["Z", "B", "S", "T", "N", "D"],
["G", "T", "C", "F", "R", "Q", "H", "M"],
["Z", "R", "G"],
["H", "R", "N", "Z"],
["D", "L", "Z", "P", "W", "S", "H", "F"],
["M", "G", "C", "R", "Z", "D", "W"],
["Q", "Z", "W", "H", "L", "F", "J", "S"],
["N", "W", "P", "Q", "S"]
]

defmodule Day5 do
def move_crates(crates, quantity, from, to) do
from = from - 1
to = to - 1
{crates_to_move, remaining} = Enum.at(crates, from)  |> Enum.split(quantity)
crates
|> Enum.with_index
|> Enum.reduce([], fn {stack, index}, acc ->
case index do
^from -> acc ++ [remaining]
# part 1
^to -> acc ++ [Enum.concat(Enum.reverse(crates_to_move), stack)]
# part 2
#^to -> acc ++ [Enum.concat(crates_to_move, stack)]
_ -> acc ++ [stack]
end
end)
end
end

File.stream!("input05")
|> Stream.map(fn line ->
String.split(line, ["move","from"," to"], trim: true)
|> Enum.map(&String.trim/1)
|> Enum.map(&String.to_integer/1)
end)
|> Enum.reduce(the_crates, fn [quantity, from, to], acc ->
Day5.move_crates(acc, quantity, from, to)
end)
|> Enum.map(&hd/1)
|> Enum.join("")
|> IO.puts
``````
1 Like

One more linear binary scanning day

``````defmodule AOC do

import :erlang, only: [setelement: 3, element: 2]

def run(input) do
{towers, commands_str} = traverse_towers(input)
IO.inspect(towers)
towers = traverse_commands(commands_str, towers)
end

defp index_for(?\s, index), do: index + 1
defp index_for(?\n, index), do: 1

def traverse_towers(string, index \\ 1, towers \\ {}) do
case string do
<<"\nmove ", tail :: binary>> ->
{towers, tail}

<<"   ", delimiter, tail :: binary>> ->
towers =
with towers when tuple_size(towers) < index <- towers do
Tuple.append(towers, [])
end
traverse_towers(tail, index_for(delimiter, index), towers)

<<?[, char, ?], delimiter, tail :: binary>> ->
towers =
case towers do
towers when tuple_size(towers) < index ->
Tuple.append(towers, [char])

_ ->
tower = element(index, towers)
setelement(index, towers, [char | tower])
end

traverse_towers(tail, index_for(delimiter, index), towers)

<<" ", index_char, " ", _, tail :: binary>> when index_char in ?0..?9 ->
tower = element(index, towers)
tower = :lists.reverse(tower)
towers = setelement(index, towers, tower)
traverse_towers(tail, index + 1, towers)
end
end

defp traverse_commands(string, towers, number \\ 0, acc \\ [])
defp traverse_commands("", towers, 0, [to, from, how_much]) do
move(towers, how_much, from, to)
end
defp traverse_commands(string, towers, 0, [to, from, how_much] = acc) do
towers = move(towers, how_much, from, to)
traverse_commands(string, towers, 0, [])
end
defp traverse_commands(string, towers, number, acc) do
case string do
<<"\nmove ", tail :: binary>> ->
traverse_commands(tail, towers, 0, [number | acc])

<<" from ", tail :: binary>> ->
traverse_commands(tail, towers, 0, [number | acc])

<<" to ", tail :: binary>> ->
traverse_commands(tail, towers, 0, [number | acc])

"\n" ->
traverse_commands("", towers, 0, [number | acc])

<<char, tail :: binary>> ->
# IO.inspect <<char>>, label: :char
traverse_commands(tail, towers, number * 10 + char - ?0, acc)

"" ->
traverse_commands("", towers, 0, [number | acc])
end
end

defp move(towers, how_much, from_index, to_index) do
to = element(to_index, towers)
from = element(from_index, towers)
{moving, from} = :lists..split(how_much, from)
to = moving ++ to

towers = setelement(to_index, towers, to)
setelement(from_index, towers, from)
end

end

And it is also extremely unobvious and unsupported feature. Performance, structures, lenses as lists, introspection, `Access.key` etc.