Samir Parikh / Blog


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.