diff --git a/2025/aoc b/2025/aoc index 97b0f58..3f42aa9 100755 Binary files a/2025/aoc and b/2025/aoc differ diff --git a/2025/aoc.cpp b/2025/aoc.cpp index 663d537..340dfe7 100644 --- a/2025/aoc.cpp +++ b/2025/aoc.cpp @@ -1,8 +1,11 @@ + #include #include #include #include #include +#include +#include #include "aoc.hpp" #include "day1.hpp" @@ -12,7 +15,7 @@ void GenerateDay(int day, std::filesystem::path base) { - std::cout << "Ganerating template for day " << day << std::endl; + std::cout << "Generating template for day " << day << std::endl; std::ostringstream className; className << "Day" << std::setw(2) << std::setfill('0') << day; // Day01 std::string classStr = className.str(); @@ -71,6 +74,7 @@ int main(int argc, char** argv) // Very shit command-line parsing :tm: int run_day = 0; + int num_runs = 1; std::filesystem::path base = "./"; for (int i = 1; i < argc; ++i) @@ -90,6 +94,20 @@ int main(int argc, char** argv) std::cout << "Selected day to run: " << run_day << "\n"; } + // Runs flag + if (arg == "-r" || arg == "--runs") + { + if (i + 1 >= argc) + { + std::cerr << "Error: -r requires a number of runs\n"; + return 1; + } + ++i; + num_runs = std::atoi(argv[i]); + if (num_runs < 1) num_runs = 1; + std::cout << "Number of runs for averaging: " << num_runs << "\n"; + } + // Path flag if (arg == "-p" || arg == "--path") { @@ -120,23 +138,15 @@ int main(int argc, char** argv) { std::cout << "\nUsage:\n" << " -d [day] Run a specific day\n" + << " -r [runs] Number of runs to average timings (default: 1)\n" << " -p [path] Set a base path for input\n" - << " -g [day] Generate a new day! (Stil need to add to compiler)\n" + << " -g [day] Generate a new day! (Still need to add to compiler)\n" << " help Show this help message\n" << " (no args) Run all days\n"; return 0; } - - // Unknown argument - if (arg != "-d" && arg != "-p" && arg != "--path" && - arg != "help" && arg != "--help" && arg != "-h" && - arg != "-g" && arg != "--generate") - { - std::cerr << "Unknown argument: " << arg << "\n" - << "Use 'help' for usage information.\n"; - return 1; - } } + // // Run days if (run_day == 0) @@ -153,40 +163,74 @@ int main(int argc, char** argv) File file1{path}; uint64_t partOne = day->PartOne(file); - int partTwo = day->PartTwo(file1); + uint64_t partTwo = day->PartTwo(file1); std::cout << "Part 1: " << partOne << "\n"; std::cout << "Part 2: " << partTwo << "\n"; } - } else + } + else { for (auto& [num, day] : GetRegisteredDays(run_day)) { - std::cout << "Running only Day " << num << ":\n"; + std::cout << "Running Day " << num; + if (num_runs > 1) { + std::cout << " (averaging over " << num_runs << " runs)"; + } + std::cout << ":\n"; std::string filename = std::to_string(num) + ".txt"; std::filesystem::path path = base / filename; std::cout << "Reading " << path << "..." << std::endl; - File file{path}; - File file1{path}; - auto start = std::chrono::high_resolution_clock::now(); + // Storage for timing results + std::vector part1_times; + std::vector part2_times; + std::vector total_times; - uint64_t partOne = day->PartOne(file); + part1_times.reserve(num_runs); + part2_times.reserve(num_runs); + total_times.reserve(num_runs); - auto endpart1 = std::chrono::high_resolution_clock::now(); - auto startpart2 = std::chrono::high_resolution_clock::now(); + uint64_t partOne = 0; + uint64_t partTwo = 0; - uint64_t partTwo = day->PartTwo(file1); + // Run multiple times + for (int run = 0; run < num_runs; ++run) + { + File file{path}; + File file1{path}; - auto end = std::chrono::high_resolution_clock::now(); + auto start = std::chrono::high_resolution_clock::now(); - std::cout << "Part 1: " << partOne << " - took " << std::chrono::duration(endpart1 - start).count() << "ms" << std::endl; - std::cout << "Part 2: " << partTwo << " - took " << std::chrono::duration(end - startpart2).count() << "ms" << std::endl; - std::cout << "Day " << run_day << " ran in " << std::chrono::duration(end - start).count() << "ms" << std::endl; + partOne = day->PartOne(file); + + auto endpart1 = std::chrono::high_resolution_clock::now(); + auto startpart2 = std::chrono::high_resolution_clock::now(); + + partTwo = day->PartTwo(file1); + + auto end = std::chrono::high_resolution_clock::now(); + + part1_times.push_back(std::chrono::duration(endpart1 - start).count()); + part2_times.push_back(std::chrono::duration(end - startpart2).count()); + total_times.push_back(std::chrono::duration(end - start).count()); + } + + // Calculate averages + double p1_avg = std::accumulate(part1_times.begin(), part1_times.end(), 0.0) / num_runs; + double p2_avg = std::accumulate(part2_times.begin(), part2_times.end(), 0.0) / num_runs; + double total_avg = std::accumulate(total_times.begin(), total_times.end(), 0.0) / num_runs; + + // Print results + std::cout << "Part 1: " << partOne << " - took " + << std::fixed << std::setprecision(3) << p1_avg << "ms" << std::endl; + std::cout << "Part 2: " << partTwo << " - took " + << std::fixed << std::setprecision(3) << p2_avg << "ms" << std::endl; + std::cout << "Day " << run_day << " ran in " + << std::fixed << std::setprecision(3) << total_avg << "ms" << std::endl; } } - } diff --git a/2025/aoc.hpp b/2025/aoc.hpp index b64a599..7a2ee58 100644 --- a/2025/aoc.hpp +++ b/2025/aoc.hpp @@ -9,79 +9,103 @@ template class Grid { - public: - Grid() = default; - Grid(size_t h, size_t w) : data(h, std::vector(w)) {} - Grid(const std::vector>& d) : data(d) {} +public: + Grid() = default; - size_t Height() const { return data.size(); } - size_t Width() const { return data.empty() ? 0 : data[0].size(); } + Grid(size_t h, size_t w) : height(h), width(w), data(h * w) {} - const T& At(size_t r, size_t c) const { return data[r][c]; } + Grid(const std::vector d) + : height(d.size()) + , width(d.empty() ? 0 : d[0].size()) + , data(d) {} - const T* SafeAt(size_t r, size_t c) const + size_t Height() const { return height; } + size_t Width() const { return width; } + + const T& At(size_t r, size_t c) const + { + return data[r * width + c]; + } + + const T* SafeAt(size_t r, size_t c) const + { + if (r >= height || c >= width) return nullptr; + return &data[r * width + c]; + } + + void ReplaceAt(size_t r, size_t c, const T& value) + { + data[r * width + c] = value; + } + + size_t Neighbours4(size_t r, size_t c, const T* out[4], bool wrap = false) const + { + size_t count = 0; + + if (wrap) { - if (r >= Height() || c >= Width()) return nullptr; - return &data[r][c]; + out[count++] = &data[((r - 1 + height) % height) * width + c]; + out[count++] = &data[((r + 1) % height) * width + c]; + out[count++] = &data[r * width + ((c - 1 + width) % width)]; + out[count++] = &data[r * width + ((c + 1) % width)]; + } else + { + if (r > 0) + out[count++] = &data[(r - 1) * width + c]; + if (r + 1 < height) + out[count++] = &data[(r + 1) * width + c]; + if (c > 0) + out[count++] = &data[r * width + (c - 1)]; + if (c + 1 < width) + out[count++] = &data[r * width + (c + 1)]; } - void ReplaceAt(size_t r, size_t c, const T& value) - { - data[r][c] = value; - } - // - // 4-neighbours (up, down, left, right) - std::vector Neighbours4(size_t r, size_t c, bool wrap = false) const - { - std::vector out; + return count; + } - auto H = Height(); - auto W = Width(); + size_t Neighbours8(size_t r, size_t c, const T* out[8], bool wrap = false) const + { + size_t count = 0; - auto idx = [&](size_t x, size_t limit) -> size_t { - return wrap ? ((x + limit) % limit) : x; - }; + if (wrap) { + for (int dr = -1; dr <= 1; ++dr) + { + for (int dc = -1; dc <= 1; ++dc) + { + if (dr == 0 && dc == 0) continue; - if (r > 0 || wrap) out.push_back(&data[idx(r - 1, H)][c]); - if (r + 1 < H || wrap) out.push_back(&data[idx(r + 1, H)][c]); - if (c > 0 || wrap) out.push_back(&data[r][idx(c - 1, W)]); - if (c + 1 < W || wrap) out.push_back(&data[r][idx(c + 1, W)]); - - return out; - } - - // 8-neighbours (diagonals) - std::vector Neighbours8(size_t r, size_t c, bool wrap = false) const - { - std::vector out; - auto H = Height(); - auto W = Width(); - - auto idx = [&](size_t x, size_t limit) -> size_t { - return wrap ? ((x + limit) % limit) : x; - }; - - for (int dr = -1; dr <= 1; dr++) { - for (int dc = -1; dc <= 1; dc++) { + size_t rr = (r + dr + height) % height; + size_t cc = (c + dc + width) % width; + out[count++] = &data[rr * width + cc]; + } + } + } else { + for (int dr = -1; dr <= 1; ++dr) + { + for (int dc = -1; dc <= 1; ++dc) + { if (dr == 0 && dc == 0) continue; size_t rr = r + dr; size_t cc = c + dc; - if (!wrap) { - if (rr >= H || cc >= W) continue; - } + if (rr >= height || cc >= width) continue; - out.push_back(&data[idx(rr, H)][idx(cc, W)]); + out[count++] = &data[rr * width + cc]; } } - - return out; } - private: - std::vector> data; + + return count; + } + +private: + size_t height = 0; + size_t width = 0; + std::vector data; }; + struct FileFragment { int Line, Col; @@ -238,28 +262,32 @@ class File template Grid AsGrid() const { - std::vector> d; - d.reserve(_lines.size()); + const size_t height = _lines.size(); + if (height == 0) return Grid(0, 0); - for (auto& line : _lines) + const size_t width = _lines[0].size(); + + Grid grid(height, width); + + for (size_t r = 0; r < height; ++r) { - std::vector row; - row.reserve(line.size()); - - for (char ch : line) + const auto& line = _lines[r]; + for (size_t c = 0; c < width; ++c) { + char ch = line[c]; + if constexpr (std::is_constructible_v) { - row.emplace_back(T(ch)); + grid.ReplaceAt(r, c, T(ch)); } else if constexpr (std::is_constructible_v) { - row.emplace_back(T(std::string_view(&ch, 1))); + grid.ReplaceAt(r, c, T(std::string_view(&ch, 1))); } else if constexpr (std::is_arithmetic_v) { if (std::isdigit(ch)) - row.emplace_back(T(ch - '0')); + grid.ReplaceAt(r, c, T(ch - '0')); else throw std::runtime_error("Grid: cannot convert character to numeric T"); } @@ -268,11 +296,9 @@ class File static_assert(sizeof(T) == 0, "Grid: T must be constructible from char or string_view"); } } - - d.push_back(std::move(row)); } - return Grid(d); + return grid; } diff --git a/2025/day4.hpp b/2025/day4.hpp index cfeb7b2..42d352a 100644 --- a/2025/day4.hpp +++ b/2025/day4.hpp @@ -1,3 +1,4 @@ + #pragma once #include "aoc.hpp" @@ -13,85 +14,121 @@ public: const auto grid = f.AsGrid(); int res = 0; + const size_t height = grid.Height(); + const size_t width = grid.Width(); + const char* neighbours[8]; - for (int i = 0; i < grid.Width(); i++) + for (size_t i = 0; i < height; i++) { - for (int j = 0; j < grid.Width(); j++) + for (size_t j = 0; j < width; j++) { if (grid.At(i, j) != '@') continue; - auto neighbors = grid.Neighbours8(i, j); + size_t count = grid.Neighbours8(i, j, neighbours); + + // Unroll, unroll unroll int rollsOfPaper = 0; - for (auto* n : neighbors) - { - if (*n == '@') - { - rollsOfPaper++; - if (rollsOfPaper > 4) - { - break; - } - } - } + rollsOfPaper += (count > 0 && *neighbours[0] == '@'); + rollsOfPaper += (count > 1 && *neighbours[1] == '@'); + rollsOfPaper += (count > 2 && *neighbours[2] == '@'); + rollsOfPaper += (count > 3 && *neighbours[3] == '@'); + rollsOfPaper += (count > 4 && *neighbours[4] == '@'); + rollsOfPaper += (count > 5 && *neighbours[5] == '@'); + rollsOfPaper += (count > 6 && *neighbours[6] == '@'); + rollsOfPaper += (count > 7 && *neighbours[7] == '@'); - if (rollsOfPaper < 4) - { - res++; - } + if (rollsOfPaper < 4) res++; } } return res; } - int MovePossibleRolls(Grid* grid) + uint64_t PartTwo(File& f) override { - int removed = 0; - for (int i = 0; i < grid->Width(); i++) - { - for (int j = 0; j < grid->Width(); j++) - { - if (grid->At(i, j) != '@') continue; - auto neighbors = grid->Neighbours8(i, j); + const auto input = f.AsGrid(); - int rollsOfPaper = 0; - for (auto* n : neighbors) + const size_t width = input.Width(); + const size_t height = input.Height(); + const char* neighbours[8]; + + std::vector counts(width * height); + + for (size_t i = 0; i < height; i++) + { + for (size_t j = 0; j < width; j++) + { + const size_t idx = i * width + j; + + if (input.At(i, j) == '.') { - if (*n == '@') + counts[idx] = 255; + continue; + } + + size_t count = input.Neighbours8(i, j, neighbours); + + uint8_t roll_count = 0; + for (size_t k = 0; k < count; k++) + { + roll_count += (*neighbours[k] != '.'); + } + + counts[idx] = roll_count; + } + } + + int64_t removed = 0; + bool updated = true; + + Grid grid(height, width); + for (size_t i = 0; i < height; i++) + { + for (size_t j = 0; j < width; j++) + { + grid.ReplaceAt(i, j, counts[i * width + j]); + } + } + + while (updated) + { + updated = false; + + for (size_t i = 0; i < height; i++) + { + for (size_t j = 0; j < width; j++) + { + uint8_t val = grid.At(i, j); + if (val >= 4) continue; + + updated = true; + grid.ReplaceAt(i, j, 255); + removed++; + + size_t count = grid.Neighbours8(i, j, (const uint8_t**)neighbours); + for (size_t k = 0; k < count; k++) { - rollsOfPaper++; - if (rollsOfPaper > 4) + size_t ni, nj; + // AAAAA + const uint8_t* base = &grid.At(0, 0); + size_t offset = (const uint8_t*)neighbours[k] - base; + ni = offset / width; + nj = offset % width; + + uint8_t nval = grid.At(ni, nj); + if (nval < 255 && nval > 0) { - break; + grid.ReplaceAt(ni, nj, nval - 1); } } } - - if (rollsOfPaper < 4) - { - grid->ReplaceAt(i, j, '.'); - removed++; - } } } + return removed; } - uint64_t PartTwo(File& f) override - { - auto grid = f.AsGrid(); - - int removedRolls = 0; - - int removedLastTime = 0; - do - { - removedLastTime = MovePossibleRolls(&grid); - removedRolls += removedLastTime; - } while (removedLastTime != 0); - - return removedRolls; - } }; ADD_AOC_DAY(Day04); +