Advent of Code 2022: Day 4
This task is interesting as we introduce string-scanning, one of the unique elements of Icon/Unicon.
Specifically, we have to decompose strings such as: "1-2,3-4", identifying the four numbers "1", "2", "3" and "4".
String scanning works by moving through a scanned string, returning substrings
which match given sets of characters. The tab
procedure is often used to make
this movement: it can move "until" a character in a given set is found, or
"while" the current character is in the given set.
Consider the following, which attempts to find the four numbers for our two ranges in a string:
line ? { # <1> tab(upto(&digits)) # <2> start_1 := tab(many(&digits)) # <3> tab(upto(&digits)) end_1 := tab(many(&digits)) tab(upto(&digits)) start_2 := tab(many(&digits)) tab(upto(&digits)) end_2 := tab(many(&digits)) }
-
Starts scanning the string in
line
. - Moves "until" it reaches a digit.
- Moves "while" it finds a digit, returning the sequence of found digits.
Another option for tab
is to "match" a given sequence: tab(match(","))
will
only move forward if the next character is a comma. As this is a frequently
used operation, there is a shorthand form: =(",")
. We can use this to make
our line scanner recognise the grammar of the task input: the range limits are
separated by hyphens, and the two ranges by a comma.
line ? { start_1 := tab(many(&digits)) =("-") end_1 := tab(many(&digits)) =(",") start_2 := tab(many(&digits)) =("-") end_2 := tab(many(&digits)) }
To make the components easy to handle, we use a record to store each set of pairs in four separate fields:
record assignment_pair(start_1, end_1, start_2, end_2)
The two tasks merely ask us to count whether the two ranges overlap or not, so
we define two procedures, one to test if one range contains the other and
another to test if they overlap. A simple loop counts those assignment_pair
instances which match the given condition.
Final Program
procedure main(inputs) local filename if *inputs = 1 then { filename := inputs[1] # Part 1 solution write("Part 1 count is: ", count_if(filename, contains)) # <1> # Part 2 solution write("Part 2 score is: ", count_if(filename, overlap)) } else { write("Provide a filename of data") } end record assignment_pair(start_1, end_1, start_2, end_2) procedure line2assignment(line) local start_1, end_1, start_2, end_2 line ? { start_1 := tab(many(&digits)) =("-") end_1 := tab(many(&digits)) =(",") start_2 := tab(many(&digits)) =("-") end_2 := tab(many(&digits)) return assignment_pair(start_1, end_1, start_2, end_2) } stop("Invalid line representation") end procedure contains(assignment) return assignment.start_1 <= assignment.start_2 <= assignment.end_2 <= assignment.end_1 | assignment.start_2 <= assignment.start_1 <= assignment.end_1 <= assignment.end_2 end procedure overlap(assignment) return not (assignment.end_1 < assignment.start_2 | assignment.end_2 < assignment.start_1) end procedure count_if(filename, condition) local count, file, assignment count := 0 file := open(filename, "r") | stop ("Cannot open ", filename) every assignment := line2assignment(!file) do { # <2> if condition(assignment) then # <3> count +:= 1 } close(file) return count end
- The two tasks are similar, differing just on the condition with which to count.
- Loop through the file, parsing each line into an assignment_pair instance.
- Count 1 if the given condition applies.