From: Pat Thoyts Date: Wed, 11 Dec 2024 15:00:57 +0000 (+0000) Subject: day10: implementation using C++ X-Git-Url: https://privyetmir.co.uk/gitweb?a=commitdiff_plain;h=387c0824378c95cfec7b24474fe89d7a004fe044;p=aoc2024.git day10: implementation using C++ --- diff --git a/.gitignore b/.gitignore index fec9490..ddf6be4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ target/ data/ __pycache__/ .pytest_cache/ +.vs/ +.vscode/ +build/ +out/ \ No newline at end of file diff --git a/day10/CMakeLists.txt b/day10/CMakeLists.txt new file mode 100644 index 0000000..fc8b5c0 --- /dev/null +++ b/day10/CMakeLists.txt @@ -0,0 +1,13 @@ +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) diff --git a/day10/main.cpp b/day10/main.cpp new file mode 100644 index 0000000..b267609 --- /dev/null +++ b/day10/main.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +#include +#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 diff --git a/day10/problem.cpp b/day10/problem.cpp new file mode 100644 index 0000000..82a4215 --- /dev/null +++ b/day10/problem.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include +#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& 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 Problem::run(const Options& options) +{ + int part1 = 0; + int part2 = 0; + for (const auto& head : trailheads()) + { + std::set 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 }; +} diff --git a/day10/problem.h b/day10/problem.h new file mode 100644 index 0000000..c6258c7 --- /dev/null +++ b/day10/problem.h @@ -0,0 +1,33 @@ +#include +#include + +struct Coord +{ + int row; + int col; + bool operator<(const Coord& rhs) const; +}; + +class Problem +{ + using data_type = std::vector; + using size_type = data_type::size_type; + using heads_type = std::vector; + 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 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& ends) const; +}; diff --git a/day10/utils.cpp b/day10/utils.cpp new file mode 100644 index 0000000..b64c131 --- /dev/null +++ b/day10/utils.cpp @@ -0,0 +1,71 @@ +#include "utils.h" + +#include +#include +#include + +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 split(const std::string_view& s, const std::string_view& delim) +{ + std::vector 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> load_buffer(const std::string_view& filename) +{ + std::ifstream infile(filename.data(), std::ios_base::binary); + + auto buffer = std::vector{ + std::istreambuf_iterator(infile), + std::istreambuf_iterator() + }; + + auto mark = std::find_if(buffer.begin(), buffer.end(), + [](char c) { return std::isspace(c); }); + const auto cols = static_cast(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 }; +} diff --git a/day10/utils.h b/day10/utils.h new file mode 100644 index 0000000..489de24 --- /dev/null +++ b/day10/utils.h @@ -0,0 +1,35 @@ +#ifndef _utils_h_INCLUDE +#define _utils_h_INCLUDE + +#include +#include +#include +#include + +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 split(const std::string_view& s, const std::string_view& delim); + +// Load a file of chars to a vector (removes all whitespace) +std::tuple> load_buffer(const std::string_view& filename); + +#endif