A framework to build solutions to the Advent of Code competition on. It includes some basic utilities, and a test suite to verify your solutions. Example cases can also be tested.
Open advent/advent_solutions.h
and replace the macro for the solution with your solution.
Common file formats for solution texts are excluded by default via the .gitignore.
The only exception to this is advent_solutions.h
. You are STRONGLY recommended to put this onto its own commit named "DO NOT PUSH" and never push it, so you can't accidentally commit it some other way.
See the examples in advent1.h, advent1.cpp, and advent_of_code_testcases.cpp.
-
Declare the function in the approriate header (normally adventx.h, where x is the day number) and implement in the appropriate .cpp file. It must take no arguments, and return ResultType. E.g.:
// advent42.h
ResultType advent_fortytwo_testcase_a();
// advent42.cpp
ResultType advent_fortytwo_testcase_a() { return "Life The Universe and Everything"; }
-
In advent_of_code_testcases.cpp, go to
const verification_tests tests[]
and add the lineTESTCASE(your_functionName, expected_result),
at the appropriate point in the array.expected_result
should be a string. In this example it would beTESTCASE(advent_fortytwo_testcase_a,"Life The Universe and Everything"),
. Importantly: remember to add a COMMA at the end, not a semi-colon. (It's obvious, but muscle memory and habit will make you want to use the semi-colon.)
Now when you run the tests, the testcase should appear, and will report success or failure.
In main.cpp, a filter can be added to the argument of verify_all()
to only run some of hte testcases, instead of everything. For example, if your day one solution is very slow you can put an argument of "_two_"
in, and it will only run functions on the verification_tests
list which have two
in the function name (e.g. advent_two_p1()
and advent_two_p2()
as well as advent_two_p1_testcase_a()
if you added it. To run everything, leave the testcase blank (""
or std::string{}
).
Arguments on the command line will also be passed to this filter.
If you add testcases, name them advent_[day number]_[p1 or p2, depending which part]_testcase_[letter]()
in order to make the filtering easy.
For functions specifically for solving a particular solution, put them in an unnamed namespace in the right file.
If 2019's code was anything to go by, don't be afraid to add common tasks to their own headers. For an example: a generic A* search function should probably in be the utility file, and I had all the Intcode stuff in its own folder last time. Anything that used the Intcode machine had _ic
added to the function names which meant that I could edit the Intmachine and then run my testcases with _ic
as the filter to make sure I didn't break any previous compatibility. This is a great project to test your ability to reuse old code.
- Install CMake.
- Open the base folder, with
CMakeLists.txt
in it. - Open a command window on that folder.
You can then run whatever CMake command you like in there. I use cmake -S . -G "Visual Studio 17 2022"
In CMakeLists.txt
there are functions called add_day(...)
. Extra files can be added there. So for a day 1 testcase input you can add testcase_a.txt
to the advent1
folder, and then update the relevant line to read add_day(1 "testcase_a.txt")
and the file will be added to the project.
There is a secret argument you can add. If you put -DRESET_ALL_SOLUTIONS=True
onto the CMake command it will delete all solutions and reset them. I mainly use this to copy the repro from one year to the next and give myself a clean slate.
There are few general purpose headers for use with the solutions.
Very useful. Gives an AdventCheck
, AdventCheckMsg
and AdventUnreachable
message. Depending on the build mode these either throw an exception, or emit a compiler hint.
Appears to depend on a header that no longer exists in the repo. Hmm...
Put your day-to-day testcases in here.
Put your solutions here once they're known and DO NOT SUBMIT THIS FILE TO SOURCE CONTROL.
Put inputs for testcases in here.
Collects some universal types. In particular, ResultType
which is a std::variant<std::string, uint64_t, int64_t>
which can capture any puzzle output.
Helper functions for opening input files. In particular advent::open_testcase_input(1,'b')
will open advent1/testcase_b.txt
, which is really useful if you follow that naming convention.
The utils folder has many general purpose algorithms and a few containers that I've found useful over the years. These were largely created before C++17 was available to me, so some features are from more modern C++ implementations are duplicated.
Not all of these work well or perfectly, but some a very reliable.
An A* implementation that takes parameterised function objects. I've found this is always really slow, for some reason, which is surprising since I'd expect lambdas passed in to be inlined and optimised. But it still provides a reference implementation. This is basically supplanted by grid.h
.
Deprecated.
TODO: Remove this.
Modelled from std::find
but only works on sorted inputs. Several useful overloads and some range-kind-of implementations as well. This isn't used a lot, but some other utils do build on it.
TODO: Use concepts to clean this up.
Some bitwise operations. Currently only population is implemented (i.e. counting the number of 1
bits in an unsigned integer), but if others come in handy - e.g. an easy way to do a bitset - that will go in here too.
TODO: Use concepts to clean this up.
A very regularly library. Pass in strings and it will find the closing bracket. It can also do a bracket_aware_find
where it will find a substring within a string, but ignore anything in brackets. Very useful.
TODO: Remove advent_utils.h
requirement.
Surprisingly useful in lots of search puzzles. Put two maps in it and it will combine them into one map. It accepts a combine_fun
to tell it how to combine values when both maps have the same key. (E.g. take the larger/smaller/sum of the two values).
TODO: Use concepts to clean this up
Several useful variations on min
and max functions here. While the min and max with transform functions are supplanted by
std::ranges, the
Largerand
Smallerstructs are very useful and the variadic
maxfunction is really useful. For some reason the variadic
min` function isn't implemented, so I guess I never actually used that much.
TODO: Use concepts to allow overloads of variadic min
and max
taking a functor.
TODO: Implement variadic min
I don't think I've used this since the year where there were lots of cellula automata, but it's some functions for that. I probably won't try to update this until more puzzles using them shows up.
Should have a lower-case C at the start, this was one of the first things I made for this library and use extensively. It has enums for directions, and helper functions for transforming those enums (e.g. rotation, reversing) and then integer 2D coordinates which with all the natural operators overloaded. It's a must-use on any grid-based solution, and is also used with other libraries.
3D coordinates simple mathematical operations. Deprecated in favour of position3d.h
An adaptor for the 2D coords to iterate over a field defined by coordinates. For example: for(utils::coords c : utils::elem_range{ utils::coords{0,0} , utils::coords{10,10} })
will iterate over all the coordinates in that field. It can also give you row and column range.
Has utilities for turning an enum into an index, and for sorting on an enum value allowing the enum to be used as a key in maps or an ordered container.
Shortcuts the erase-remove idiom.
Convenient ways to interact with grids. This includes automatically creating them from the input, by providing a char
--> NodeType
converter, and pathfinding through them with an A* search. It's fairly new and a little wonky/buggy still, but very useful even so.
Helper functions for asking whether a container or range has duplicates.
TODO: Concepts.
TODO: Optimised path for sorted ranges.
TODO: Pick optimised path at compile time for ranges that are always sorted.
TODO: Overload for custom comparison functions.
The first never really worked, so I use the second of them... or the other way around, I can't remember right now. These adapt indexable containers by adding iterators that just index into the container. This makes building custom containers much easier. They aren't general purpose. The first one underpins the very useful int_range.h
header.
A very useful header I use in almost every solution. The int_range
thing allows for things like for(int x : utils::int_range(0,10,2))
which will iterate from 0
to 10
with a stride of 2
(essentially: 0
, 2
, 4
, 6
, 8
). utils::int_range(4,10)
will go from 4
to 10
with a stride of 1
(4
,5
,6
,7
,8
,9
). And providing only one argument will start at 0
: utils::int_range(4)
will go 0
,1
,2
,3
).
The adaptor will create a range-type function from an existing indexable container. This is useful for things that are indexable but don't already have a range associated with them.
I'm not sure what this is doing here, given std::is_sorted
exists.
Integer square roots. Guaranteed not to have any issues with floating point inaccuracy (e.g. returning a double
that's slightly below the actual result and then getting rounded down by integer truncation).
TODO: A version that returns any type of integer you want.
Really really useful. The first allows iteration over a std::istream
with each "element" being a std::string_view
of either a block (multi-line section delimited by empty lines) or a line (delimited by a '\n'). In both cases the sentintal can be customized. The line
version can take any char
as a sentinental and the block
version can take any std::string_view
as sentinental, and split elements at a line matching that. E.g. "123\n456\n---\n789\n101"
parsed with "---"
as the sentential would have elements 123\n456
and 789\n101
.
There are also ranged versions.
Does an MD5 hash of the input. This was used for one puzzle in the early days.
This can be replicated more easily with ranges now.
A new addition and still needs some fleshing out. This has components for doing modular arithmatic with integers - something that comes up surprisingly often on Advent of Code.
TODO: Concepts
TODO: Some unfinished functionality.
Some really useful still in here. get_string_elements.h
will grab particular parts of a string, based on a list of indices, and is an extension of split_string.h
's offerings in that regard. Also the remove_specific_prefix
and _suffix
functions will do error checking on a std::string_view
to make sure you're removing what you expect to remove.
Looks similar to coords3d.h
Calls push_back
on a container after checking that the container doesn't already contain it. Useful for flat set type structures. It will return an iterator to the new element or the previously exising one if one exists.
For a numeric range, check whether a value is in that range.
TODO: Concepts?
A ring buffer.
Similar to std::lock_guard
already in the standard library, but for use with shared mutexes. I rarely multithread my solutions so this doesn't see much use.
TODO: [[noexcept]]
TODO: Concept?
This is a vector implementation with a small vector optimisation. I use this a lot, and it also underpins a few other container-type things in here. Use it instead of std::vector
when you know you're expecting only a few elements.
The std::flat_*
containers introduced in C++23 will make this obsolete, but until then this is a useful one. Deferred sorting isn't the performance gain I'd hoped for in most cases, though.
Because std::span
didn't arrive until C++20.
A barebones sparse array.
A number of ways to performantly split a string up either at a given point or at the first or last delimiter.
Like the istream_line_iterator.h
but operates on a std::string_view
instead of a std::istream
.
Using the swap-remove idiom for removing things from a vector-like container where order isn't important. Probably supplanted by std::flat_set
in C++23.
I use this one a lot too. It has an is_value
check, and to_value
wraps std::from_chars
with more useful error checking.
I dont' think this has an equivalent in the standard library. There are versions where the if
is applied before AND after the transform is, and also a range-style implementation for help.
Removes trailing or leading whitespace (or both) from a std::string_view
.
- Verify XCode and other setups. (I don't have non-Windows platforms running, so feel free to add a topic telling me it works and what special steps, if any, you had to use)
- Grab puzzle inputs from the web automatically if an empty file / non-existant puzzle input is detected. (CAREFULLY!)
- Add testing to CMake setup
- Start using concepts for various bits of utils. E.g.:
binary_find
;bit_ops
;combine_maps
- Remove
advent_utils.h
dependancy from files. E.g.brackets.h