day10: implementation using C++
authorPat Thoyts <pat.thoyts@gmail.com>
Wed, 11 Dec 2024 15:00:57 +0000 (15:00 +0000)
committerPat Thoyts <pat.thoyts@gmail.com>
Wed, 11 Dec 2024 20:02:42 +0000 (20:02 +0000)
.gitignore
day10/CMakeLists.txt [new file with mode: 0644]
day10/main.cpp [new file with mode: 0644]
day10/problem.cpp [new file with mode: 0644]
day10/problem.h [new file with mode: 0644]
day10/utils.cpp [new file with mode: 0644]
day10/utils.h [new file with mode: 0644]

index fec949088bdbd73607ab81198d38714b579dc8ef..ddf6be44d6e1f35041906a4fb9739b1a0938017b 100644 (file)
@@ -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 (file)
index 0000000..fc8b5c0
--- /dev/null
@@ -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 (file)
index 0000000..b267609
--- /dev/null
@@ -0,0 +1,29 @@
+#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
diff --git a/day10/problem.cpp b/day10/problem.cpp
new file mode 100644 (file)
index 0000000..82a4215
--- /dev/null
@@ -0,0 +1,81 @@
+#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 };
+}
diff --git a/day10/problem.h b/day10/problem.h
new file mode 100644 (file)
index 0000000..c6258c7
--- /dev/null
@@ -0,0 +1,33 @@
+#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;
+};
diff --git a/day10/utils.cpp b/day10/utils.cpp
new file mode 100644 (file)
index 0000000..b64c131
--- /dev/null
@@ -0,0 +1,71 @@
+#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 };
+}
diff --git a/day10/utils.h b/day10/utils.h
new file mode 100644 (file)
index 0000000..489de24
--- /dev/null
@@ -0,0 +1,35 @@
+#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