Advent of Code is an annual set of Christmas-themed computer programming challenges that follow an Advent calendar.
Who wants to join this year?
I am solving the puzzles in Fortran because I want to evaluate them for my Fortran course in February. On the last day of my last class, I let the students solve any puzzle from AoC23 to test their Fortran skills. In the entire two weeks of the course, they had never worked as hard or as ambitiously as they did on that day.
I removed some inactive members from the leaderboard, but feel free to (re-)join:
Go to Leaderboard - Advent of Code 2024 and enter 1510956-4380811b.
I always like doing these. I also don’t mind reinventing the wheel so every year I get to write new file reading, string parsing, and sorting utilities!
I think it is a good way to test new fortran compilers, like flang-new,lfortran.
Because the solution usually contains intrinsic procedures, file I/O and some fortran magic tricks.
There is no single best answer here. Some problems can be tackled line by line, in which case list-directed reads into a character variable or integer array can work well. Other times it will feel more natural to parse one character at a time. Yet other problems can be tackled in a very “matrix-y” way by reading the whole data into a 2D array.
If I had to recommend one most flexible way, I would say to work line-by-line. Read each line into a “big enough” character variable, parse it in whatever way makes sense for your solution approach, and stop when you hit the end of file (by checking the iostat of each read statement).
I have a read_line function that I use that returns an allocatable character string. That, with a simple string class and various routines like split, string_to_int, etc. are usually all you need for these, without having to mess around with any format statements, or assume “big enough” character strings.
As stated by others above, it depends on the problem. Some of them it’s best to read the line as text because you’re going to be searching for some pattern, perhaps across multiple lines, and other times you are dealing with more simple “there will be N integers, do something…”
I generally use fpm and general-purpose-fortran.
Some of the most common I/O related procedures that usually reduce the I/O to a few lines are
M_io:
fileread read (ie. slurp) a file into a string array
filebyte read (ie. slurp) a file into a character array
get_next_char read from a file one character at a time
getline read a line from specified LUN into allocatable string
read_line read a line from specified LUN into allocatable string cleaning up input line
read_table read a numeric table from a file
M_strings:
CASE
lower changes a string to lowercase over specified range
upper changes a string to uppercase
COMPARE
glob compare given string for match to a pattern which may contain globbing wildcard characters
EDITING
join append CHARACTER variable array into a single CHARACTER variable with specified separator
replace function replaces one substring for another in string
substitute subroutine globally substitutes one substring for another in string
transliterate replace characters from old set with new set
TOKENS
delim parse a string and store tokens into an array
sep function to parse string into an array using specified delimiters
slice parse string into an array using specified delimiters
split parse string into an array using specified delimiters
split2020 parse a string into tokens using proposed f2023 method
use M_strings, only: split=>split2020
use M_strings, only : tokenize=>split2020
TYPE
getvals read arbitrary number of REAL values from a character variable up to size of VALUES() array
s2vs given a string representing numbers return a numeric array
string_to_values read a string representing numbers into a numeric array
WHITESPACE
compact converts contiguous whitespace to a single character (or nothing)
Note that a copy of split2020 by @milancurcic is included, so if you do not have the standard
split and tokenize you can use the USE statements shown to try them out as well. If you just
want these functions and not the entire GPF add these lines to the fpm manifest file fpm.toml:
the source contains complete man-pages as comments as well as the documentation described in the repositories. The various regular expression modules available via fpm can be very useful as well.
@jacobwilliams your read_line function looks like a great candidate for stdlib or a standalone fpm package. @urbanjost is your read_line from M_io equivalent? If so, then maybe M_io is such an fpm package.
read_line from @jacobwilliams and getline from M_io are basically equivalent except a slight difference in that if an error occurs in getline the I/O message is returned as the value. the read_line in M_io is used in some specific codes that allow a backslash on the end to continue a long line, expands tabs, and removes DOS and GNU/Linux line terminators, etc. The General Purpose Fortran collection and the AOC module appear to have a good number of similiar functions, indicating such functionality might be good candidates for a shared library such as stdlib. Some is in stdlib already. But none of the GPF has a graphic as great as the one I see on the AOC site.
agreed that @jacobwilliamsread_line is very good, but could someone verify that it works with both gfortran and ifort (or latest ifx)? I more or less copied it at work today and ifort was consistently getting stuck at the end of file. Without looking too much into it, seemed that the version of gfortran I had (4.something ancient on RHEL8) would stop reading at end of file for advance='no' and report iostat_eof but would change to iostat=5001 if another read statement was encountered. The old version of ifort I had would just stay stuck at the iostat_eof forever and never report a bad read. Perhaps user error, but I didn’t see how a caller of read_line was supposed to know that a file was done other than getting a status_ok = .false..