Originally published on 16 November 2019
Earlier this year, I was belatedly working through one of the programming challenges from the 2018 edition of the Advent of Code. This particular problem requires you to parse a set of lines in the format:
#99 @ 652,39: 24x23
#100 @ 61,13: 15x24
#101 @ 31,646: 16x28
I wanted to store each number (or match) as an element in an array so that I could refer to them by index. For example, for the first line:
m = [99, 652, 39, 24, 23]
assert(m[0] == 99);
assert(m[1] == 652);
// ...
assert(m[4] == 23);
After reading Dmitry Olshansky's article on std.regex
and the std.regex
documentation, I came up with the following attempt:
import std.stdio;
import std.regex;
void main() {
auto line = "#99 @ 652,39: 24x23";
auto pattern = regex(r"\d+");
auto m = matchAll(line, pattern);
writeln(m);
}
which results in
[["99"], ["652"], ["39"], ["24"], ["23"]]
The issue is that m
is not an iterable array as changing writeln(m)
to writeln(m[0])
yields
Error: no [] operator overload for type RegexMatch!string
Changing the line to writeln(m.front[0])
does return 99
but m.front
doesn't allow you to access other elements. For example, trying m.front[1]
will return
requested submatch number 1 is out of range
----------------
??:? _d_assert_msg [0x4dc27a]
??:? inout pure nothrow @trusted inout(immutable(char)[]) std.regex.Captures!(immutable(char)[]).Captures.opIndex!().opIndex(ulong) [0x4d8d57]
??:? Dmain [0x49ffc8]
I also tried something like
foreach (m; matchAll(line, pattern))
writeln(m.hit);
which was close but again, did not result in an array.
I ultimately posted this question to the D Programming Language Discussion Forum and got some great feedback on how to use the map function. I slightly modified a response to my question to come up with this:
auto allMatches = matchAll(line, pattern)
.map!(a => to!int(a.hit))
.array;
which ultimately solved my problem. It still uses the matchAll
function as before but it now passes the result onto the map
function which, along with to!int()
, converts the results into integers and creates an iterable array using .array
. I was pretty happy at this point that I got a working solution when another forum user came forward with an even more elegant option: the std.file
function slurp:
auto matches = slurp!(int, int, int, int, int)(file, pattern);
slurp
returns a tuple for each pattern it matches in the input file. You can iterate through them as follows:
foreach (tuple; matches) {
writeln(tuple); // or tuple[0], tuple[1], ...
}
My personal take is that slurp
isn't as powerful or flexible as matchAll
but it is certainly more readable and easier to understand. In the end, slurp
was the function I used to complete the problem.