In this task, we must read a text file consisting of groups of numbers: each number is on its own line, and groups separated with a blank line.
In Icon, an open file can be read using a standard every ... do
idiom:
file := open(filename) | stop("Cannot open ", filename) # <1> every line := !file do { # <2> # process the line } close(file) # <3>
-
Opens the given filename for reading. If the call to
open
fails, then the program is stopped with the given error message. - This loop iterates through every line in the file.
- Finally, the file must be closed.
The numbers in each group must be totalled. An array is used to store the totals for each group, creating a new group for each blank line and adding the number into the last group if the line contains a number.
groups := [0] # <1> file := open(filename) | stop("Cannot open ", filename) every line := !file do { if line == "" then put(groups, 0) # <2> else groups[*groups] +:= line # <3> } close(file)
- Each group is a number, stored in a list, starting with one entry for the first group.
- When the line is empty, extend the list to start a new group.
- When the line is not empty, assume it contains a number to add to the last element in the list.
Notice that:
- strings are coerced to numbers when necessary: the line is a string, but 0 + "1" is treated as numerical addition.
- lists are indexed from 1, so the last element of a list has index equal to its length.
Find the maximum element in the list. This can be done using iteration and comparison:
max_element := 0 every item := !groups do { if item > max_element then max_element := item }
Unicon has an operator max
, and generators can be embedded in expressions, so
an equivalent version is:
max_element := 0 every max_element := max(max_element, !groups)
Even more concisely:
every (max_element := 0) := max(max_element, !groups)
Find the top-three items in the list, and return their sum. A simple but costly
solution uses sort
:
top_3 := sort(groups)[-3:0] # <1> sum_top_3 := 0 every sum_top_3 +:= !top_3 # <2>
- After sorting, take the last three items in the list: 0 is the index of the end of the list, and -3 means three items back from the end.
- Adds up all the numbers, in a similar style to above.
A more efficient way to find the top three scans the list once, retaining the top three items. The following solution assumes the numbers in the list are positive values, as in the problem definition.
top_3 := [0, 0, 0] # <1> every item := !groups do { # <2> if item > top_3[1] then # <3> top_3 := [item, top_3[1], top_3[2]] # <4> else if item > top_3[2] then top_3 := [top_3[1], item, top_3[2]] else if item > top_3[3] then top_3 := [top_3[1], top_3[2], item] }
- Starts with the top three assumed to be 0.
- Works through each item in turn.
- The if conditions check if the item falls in the first, second or third position of the top three ...
- ... and is put into place, accordingly.
procedure main(inputs) local filename if *inputs = 1 then { filename := inputs[1] # Part 1 solution write("Part 1: ", part_1(filename)) # Part 2 solution write("Part 2: ", part_2(filename)) } else { write("Provide a filename of data") } end procedure part_1(filename) local groups, max_element groups := read_data(filename) every item := !groups do { if item > max_element then max_element := item } return max_element end procedure part_2(filename) local groups, item, top_3 groups := read_data(filename) top_3 := [0, 0, 0] every item := !groups do { if item > top_3[1] then top_3 := [item, top_3[1], top_3[2]] else if item > top_3[2] then top_3 := [top_3[1], item, top_3[2]] else if item > top_3[3] then top_3 := [top_3[1], top_3[2], item] } return top_3[1] + top_3[2] + top_3[3] end procedure read_data(filename) local file, groups, line file := open(filename) | stop("Cannot open ", filename) groups := [0] every line := !file do { if line == "" then put(groups, 0) else groups[*groups] +:= line } close(file) return groups end
Icon supports one aspect of higher-order functions, in that procedures can be
passed as arguments to other procedures. This allows us to write more
functional-style code. For instance, the solution above uses three every
expressions to collect information generated from one source. We can capture
this pattern in a functional way, by writing a fold
(or reduce
) procedure:
procedure fold(generator, base, fn) local result result := base every result := fn(result, !generator) return result end
In this way, a list can be summed using:
fold(lst, 0, "+") # infix operators are given as strings
or the maximum item found using (define max
if not using Unicon):
fold(lst, 0, max)
Here is the complete program rewritten to use fold
:
procedure main(inputs) local file, filename, groups if *inputs = 1 then { filename := inputs[1] file := open(filename) | stop("Cannot open ", filename) groups := fold(file, [0], process_line) close(file) # Part 1 solution write("Part 1: ", fold(groups, 0, max)) # Part 2 solution write("Part 2: ", fold(fold(groups, [0, 0, 0], top_three), 0, "+")) } else { write("Provide a filename of data") } end procedure top_three(top_3, item) if item > top_3[1] then return [item, top_3[1], top_3[2]] else if item > top_3[2] then return [top_3[1], item, top_3[2]] else if item > top_3[3] then return [top_3[1], top_3[2], item] else return top_3 end procedure process_line(groups, line) if line == "" then put(groups, 0) else groups[*groups] +:= line end procedure fold(generator, base, fn) local result result := base every result := fn(result, !generator) return result end # if not using Unicon, define `max` procedure max(a, b) if a > b then return a else return b end