C++: Advent of Code Introduction

Rud Merriam
One Stop C++

--

This is the introduction to a series using the Advent of Code (AOC) 2023 by Eric Wastl to explore Functional Programming (FP) in C++ using the Ranges library. In December of 2022, Šimon Tóth each day discussed his AOC solutions in an article on Medium. I was impressed by his solving them and writing about them each day. He suggested waiting to read the discussion until attempting a solution on one’s own. I did wait. After working through it for a few days, I decided to look at his code.

It was filled with FP based on the C++20/23 Ranges library. I was gobsmacked and inspired to learn about Ranges. That led to my talk, A Journey into Ranges, Views, Pipelines, and Currying, at CppNorth in July. The presentation is a light introduction, which might be interesting if you haven’t yet worked with the Ranges library.

I’m not satisfied with my understanding of the Ranges library and, to some extent, FP. This year, I will use the AOC to understand both interests further.

The AOC

The AOC is a story that runs through the 25 days of Advent. Each day, a challenge is presented based on the story. The challenge occurs in two parts. The first outlines the problem, and only after it is solved can you move to the second. The second is a variation of the first, basically a typical management response of “Oh, we forgot to mention this requirement.” Sometimes, a modification of the first part is possible. Often, nearly a complete rework is required.

The problems require calculating a numeric sum. Each part provides a few lines of validation input for testing and the expected value. The problem data is much larger, possibly 100s of lines. Each part has validation data, but the problem data is the same.

The correct result for part one is needed before moving on to part two. If the day is open, you can move to another day without finishing the previous day. For example, on the 5th, you can access any earlier days but nothing after.

Project Organization

Feel free to examine my GitLab repository for my code. Day01, Day02, etc, contain each day’s code. The Day00 code is a skeleton for the data and code for the two parts. I can copy those files into the new day’s directory to get started. They aren’t large, but any reduction in effort helps.

I start with the files part_1.cpp and part2_cpp, but if I experiment with other versions, I append the letters a,b,c… For example, on Day 01, there are part_1a through part1d.

AdventCpp.h

The regular pattern of the challenge of validating, testing against a known result, and running against the final data allows a framework for automating the process. The header file AdventCpp.h provides this framework.

// AdventCpp.h
#include <cstdint>
#include <functional>

extern std::string_view test_data1;
extern std::string_view test_data2;
extern std::string_view aoc_data;

namespace rng = std::ranges;
namespace vws = std::views;

using execFunc = std::function<uint64_t(std::string_view)>;

auto advent_cpp =
[ ](auto const& test_data, auto const& data_file,
execFunc& exec_func, uint64_t const expected_value) {

std::cout << '\n';

auto res {exec_func(test_data)};

if (res == expected_value) {
std::cout << "\nValidation successful with value " << res << '\n'
<< "Final result: " << exec_func(data_file) << '\n';
} else {
std::cout << "\nValidation failed\nExpected " << expected_value
<< " but calculated " << res << '\n';;
}
};

The header files are those needed for the framework lambda advent_cpp. The using statements provide short versions of the namespaces for ranges and views.

The three extern statements provide access to the two sets of validation data and the more extensive testing data.

The declaration of execFunc defines the prototype of the function to execute the code. The function must return a uint64_t and accept a std::string_view as an argument. This argument is one of the data files.

The function is passed to the advent_cpp, along with the test data, the problem data, and the value expected from the test data.

The lambda executes the passed-in function with the test data. The result is checked against the expected value. The expected and calculated values are reported if the test result is not as expected. If correct, the function processes the problem data with the result reported on the console. This is entered into a field on the AOC site to see if it is correct.

Main.cpp

Here is main, whose only purpose is to call the framework. The expected value is for day one, part one.

int main() {
advent_cpp(test_data1, aoc_data, execute, 142);
return 0;
}

Line.h

The header file Line.h defines a simple structure that can read or write a line of data. It is helpful with AOC because reading lines of data for processing is sometimes the best approach. It isn’t fancy. It has just a string member to hold the line of data and the input and output operators.

// line.h
struct Line {
std::string mLine;

operator std::string() const {
return mLine;
}
friend std::ostream& operator<<(std::ostream& os, Line const& l) {
os << l.mLine;
return os;
}
friend std::istream& operator>>(std::istream& is, Line& l) {
std::getline(is, l.mLine);
return is;
}
};

Data Files

The data for the day is in a file data.cpp. As shown below, there are three data sets as raw strings in a std::string_view. Access is provided through the extern declarations for each set of data.

Here’s a range containing data as a raw string, as indicated by the R preceding the string:

std::string_view test_data1 { //
R"(1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet)" //
};

Note that as a raw string, the end-of-line characters in the code file are included in the string. The three sets of data are:

  • test_data1 — contains the day’s test data for part one
  • test_data2 — contains the day’s test data for part two
  • aoc_data — contains the day’s problem data for part two

Wrap Up

This series explores functional programming and the ranges library, especially pipelines. But there will be times when a structured programming or procedural approach is valid or will be one of the alternatives tried to see how they compare. Comparing functional and structured programming techniques will be interesting. Structured programming (SP) is programming using functions. The methods from SP, like functional decomposition, should be applicable. Let’s see how much I remember.

I won’t complete this series during the Advent and will always be behind by several days. I’ll repeat Toth’s suggestion that you attempt a solution before reading my articles.

I won’t discuss every alternative I try. Some will be ugly, but I leave them in the repository as examples from which to learn. They are experiments, and that’s why I do this: to keep experimenting to understand more. That’s why it’s a Journey into Functional Programming with the Ranges Library.

Epilogue

Here is the rest of the series:

C++: Advent of Code — Day 1, Part 1

C++: Advent of Code — Day 1, Part 2

C++: Advent of Code — Day 2, Part 1 & 2

C++: Advent of Code — Day 3

Please clap or comment if you liked the article. This tells me if I should write more like this. I am in the Membership > program, so I do get some funds from these articles when you read and clap.

Note: I’ve started a new publication strictly for C++. If you are writing C++ articles on Medium, please consider > publishing them on One Stop C++. Also follow the publication to keep up with C++ > articles. No hassle, no editing, just publishing.

--

--

Rud Merriam
One Stop C++

I am a retired software engineer with decades of experience with embedded systems and have used C++ since the early 90s.