The two tasks in this problem again differ only in a small element, how
multiple crates are moved from one column to another. This difference is
captured in a procedure, passed to a main control procedure, process_crates
:
-
make_move_singly
moves each crate, one at a time, for part 1 -
make_move_multiple
moves the crates as a block, for part 2
The manipulation of the crates requires some further operations on the list data structure, treating each list as a single- or double-ended queue, where appropriate:
-
pull
/put
- add or remove items from the end of a list -
push
/pop
- add or remove items from the front of a list
The more complex part of the problem is to decode the input file format, which falls into two parts:
- the definition and layout of the crates on columns.
- the definition of the moves.
For the first part, we rely on the crates all being of the form "[N]", where N is a single-letter name for the crate. A new string-scanning construct is find
:
-
find("[", line)
returns the position of the character "[" in the line, or fails. -
find
is a generator - used as the expression for anevery ... do
loop, it will generate the position of every "[" character in the line.
The positions returned by find
are used to determine the columns for each crate
from the input.
For the second part, the lines follow a standard format, starting with "move", and are processed like the ranges from Day 4.
The following line is interesting, in how it extracts the top crate from each column, concatenating them all into a string:
every result ||:= pull(!crates) | " "
-
The
||:=
operator concatenates the string on the right to the variable on the left. -
The
|
operator evaluates its left-hand expression, returning its value if it succeeds, or the value of its right-hand expression if it does not. In this case, it works like a default value for empty columns: columns with no crates fail onpull
, and so the default value is a space.
procedure main(inputs) local filename if *inputs = 1 then { filename := inputs[1] # Part 1 solution write("Part 1 top crates are: ", process_crates(filename, make_move_singly)) # Part 2 solution write("Part 2 top crates are: ", process_crates(filename, make_move_multiple)) } else { write("Provide a filename of data") } end procedure read_crates(file) # <1> local column, crate, crates, line, posn crates := [] every line := !file do { if find("[", line) then { # <2> every posn := find("[", line) do { # <3> column := (posn-1)/4 + 1 # <4> crate := line[posn+1] # <5> # put crate on column while *crates < column do { # <6> put(crates, []) } push(crates[column], crate) # <7> } } else { # <8> return crates } } stop ("Invalid file format") end procedure make_move_singly(crates, move_count, move_from, move_to) every 1 to move_count do { put(crates[move_to], pull(crates[move_from])) } end procedure make_move_multiple(crates, move_count, move_from, move_to) local hook hook := [] every 1 to move_count do { push(hook, pull(crates[move_from])) } every put(crates[move_to], !hook) end procedure process_crates(filename, make_move_proc) local file, line local crates, move_count, move_from, move_to local result file := open(filename) | stop("Could not open file") crates := read_crates(file) # <9> # process moves every line := !file do { # <10> line ? if =("move ") then { move_count := tab(many(&digits)) =(" from ") move_from := tab(many(&digits)) =(" to ") move_to := tab(many(&digits)) make_move_proc(crates, move_count, move_from, move_to) # <11> } } close(file) result := "" every result ||:= pull(!crates) | " " # <12> return result end
- Given a file, returns a list of columns, with the names of all the crates.
- Checks if the line is part of the crate-definition.
- If it is, then loop through the position of all crates.
- Some mathematics is used to find the column number ...
- ... and the crate name.
- Make sure there are enough crate definitions in the crates list.
- Add the crate to the appropriate column.
- When the line is not a crate-definition, return the crates list.
- First, read in the crate definitions ...
- ... then process each remaining line, looking for "move" definitions.
- Process each move in turn.
- Extract the top crate name of each column for the final result, or empty space for an empty column.