data/
__pycache__/
.pytest_cache/
+.vs/
+.vscode/
+build/
+out/
\ No newline at end of file
--- /dev/null
+cmake_minimum_required(VERSION 3.24)
+
+project(aoc2024-day14
+ VERSION 1.0
+ DESCRIPTION "Advent of Code 2024 - day 14"
+ LANGUAGES CXX
+)
+
+set (TARGET ${PROJECT_NAME})
+set (SOURCES main.cpp problem.cpp problem.h utils.cpp utils.h)
+
+add_executable(${TARGET} ${SOURCES})
+target_compile_features(${TARGET} PRIVATE cxx_std_20)
--- /dev/null
+#include <iostream>
+#include <fstream>
+#include <string_view>
+#include <vector>
+#include <set>
+#include <filesystem>
+#include "utils.h"
+#include "problem.h"
+
+int main(int argc, char *argv[])
+{
+ try
+ {
+ const auto options = argparse(argc, argv);
+ Problem problem = Problem::make_problem(options);
+ const auto [ part1, part2] = problem.run(options);
+ std::cout << "part1: " << part1 << std::endl;
+ std::cout << "part2: " << part2 << std::endl;
+ }
+ catch (const usage_exception& ex)
+ {
+ std::filesystem::path program(argv[0]);
+ std::cerr << "error: " << ex.what() << std::endl;
+ std::cerr << "usage: " << program.filename() << " ?-d? filename" << std::endl;
+ return 1;
+ }
+
+ return 0;
+}
\ No newline at end of file
--- /dev/null
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <string_view>
+#include <vector>
+#include <set>
+#include "problem.h"
+#include "utils.h"
+
+bool Coord::operator<(const Coord& rhs) const
+{
+ return (row * 1000 + col) < (rhs.row * 1000 + rhs.col);
+}
+
+Problem Problem::make_problem(const Options& options)
+{
+ auto [cols, data] = load_buffer(options.filename);
+ auto rows = data.size() / cols;
+ return Problem(rows, cols, data);
+}
+
+Problem::heads_type Problem::trailheads() const
+{
+ heads_type heads;
+ for (int row = 0; row < _rows; ++row)
+ {
+ for (int col = 0; col < _cols; ++col)
+ {
+ if (_data[row * _cols + col] == '0')
+ heads.emplace_back(Coord{ row, col });
+ }
+ }
+ return heads;
+}
+
+int Problem::score(const Coord& head, std::set<Coord>& ends) const
+{
+ const auto left = Coord(0, -1);
+ const auto up = Coord(-1, 0);
+ const auto right = Coord(0, 1);
+ const auto down = Coord(1, 0);
+
+ auto val = _data[head.row * _cols + head.col];
+ if (val == '9')
+ {
+ ends.emplace(head);
+ return 1;
+ }
+
+ int result = 0;
+ char nextval = val + 1;
+ for (const auto dir : { left, up, right, down })
+ {
+ Coord pos = Coord(head.row + dir.row, head.col + dir.col);
+ if (pos.row >= 0 && pos.row < _rows && pos.col >= 0 && pos.col < _cols)
+ {
+ if (_data[pos.row * _cols + pos.col] == nextval)
+ {
+ result += score(pos, ends);
+ }
+ }
+ }
+ return result;
+}
+
+std::tuple<int, int> Problem::run(const Options& options)
+{
+ int part1 = 0;
+ int part2 = 0;
+ for (const auto& head : trailheads())
+ {
+ std::set<Coord> ends;
+ const auto val2 = score(head, ends);
+ int val1 = ends.size();
+ if (options.debug)
+ std::cout << "(" << head.row << "," << head.col << ") " << val1 << " : " << val2 << std::endl;
+ part1 += val1;
+ part2 += val2;
+ }
+ return { part1, part2 };
+}
--- /dev/null
+#include <vector>
+#include <set>
+
+struct Coord
+{
+ int row;
+ int col;
+ bool operator<(const Coord& rhs) const;
+};
+
+class Problem
+{
+ using data_type = std::vector<char>;
+ using size_type = data_type::size_type;
+ using heads_type = std::vector<Coord>;
+ data_type _data;
+ size_type _rows;
+ size_type _cols;
+
+public:
+ explicit Problem(size_type rows, size_type cols, data_type data) : _rows(rows), _cols(cols), _data(data) {}
+ static Problem make_problem(const Options& options);
+ std::tuple<int, int> run(const Options& options);
+
+ size_type rows() const { return _rows; }
+ size_type cols() const { return _cols; }
+
+ data_type::const_iterator begin() const { return _data.begin(); }
+ data_type::const_iterator end() const { return _data.end(); }
+
+ heads_type trailheads() const;
+ int score(const Coord& heads, std::set<Coord>& ends) const;
+};
--- /dev/null
+#include "utils.h"
+
+#include <iostream>
+#include <fstream>
+#include <cctype>
+
+Options argparse(int argc, char *argv[])
+{
+ Options options = { "", false, false };
+ int opt = 1;
+ for (; opt < argc; ++opt)
+ {
+ std::string_view arg(argv[opt]);
+ if (!(arg[0] == '-' && arg.length() > 1))
+ break;
+ if (arg[1] == 'd')
+ options.debug = true;
+ else if (arg[1] == '2')
+ options.part2 = true;
+ else
+ throw usage_exception("unrecognised option");
+ }
+ if ((argc - opt) != 1) {
+ throw usage_exception("filename required");
+ }
+ options.filename = argv[opt];
+ return options;
+}
+
+
+std::vector<std::string_view> split(const std::string_view& s, const std::string_view& delim)
+{
+ std::vector<std::string_view> result;
+ std::string_view::size_type begin = 0, pos = 0;
+ while ((pos = s.find_first_of(delim, begin)) != std::string_view::npos)
+ {
+ result.emplace_back(s.substr(begin, pos - begin));
+ begin = pos + 1;
+ }
+ if (pos != s.length())
+ result.emplace_back(s.substr(begin, s.length() - begin));
+ return result;
+}
+
+std::tuple<uint32_t, std::vector<char>> load_buffer(const std::string_view& filename)
+{
+ std::ifstream infile(filename.data(), std::ios_base::binary);
+
+ auto buffer = std::vector<char>{
+ std::istreambuf_iterator<char>(infile),
+ std::istreambuf_iterator<char>()
+ };
+
+ auto mark = std::find_if(buffer.begin(), buffer.end(),
+ [](char c) { return std::isspace(c); });
+ const auto cols = static_cast<uint32_t>(mark - buffer.begin());
+
+ // remove line endings
+ auto last = std::remove(buffer.begin(), buffer.end(), '\n');
+ buffer.erase(last, buffer.end());
+ last = std::remove(buffer.begin(), buffer.end(), '\r');
+ buffer.erase(last, buffer.end());
+
+ // remove any spaces
+ last = std::remove_if(buffer.begin(), buffer.end(),
+ [](char c) { return std::isspace(c); }
+ );
+ buffer.erase(last, buffer.end());
+
+ return { cols, buffer };
+}
--- /dev/null
+#ifndef _utils_h_INCLUDE
+#define _utils_h_INCLUDE
+
+#include <vector>
+#include <string_view>
+#include <functional>
+#include <exception>
+
+class usage_exception : public std::exception
+{
+public:
+ explicit usage_exception() : std::exception() {}
+ explicit usage_exception(const char* const message) : std::exception(message) {}
+};
+
+struct Options
+{
+ std::string_view filename;
+ bool debug;
+ bool part2;
+};
+
+// parse standard options
+Options argparse(int argc, char* argv[]);
+
+// Split a string on any of the characters in 'delim'.
+// Characters before the first delimiter and after the last are included in the first
+// and last elements of the result.
+// Returns a vector of string views.
+std::vector<std::string_view> split(const std::string_view& s, const std::string_view& delim);
+
+// Load a file of chars to a vector (removes all whitespace)
+std::tuple<uint32_t, std::vector<char>> load_buffer(const std::string_view& filename);
+
+#endif