diff --git a/README.md b/README.md index 273800f..0d79ded 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,12 @@ The algorithm recursively tries the different operators from left to right until For any pair of antennas of the same frequency, the algorithm adds the difference of their positions to one of these positions and subtracts it from the other to find the antinodes. For part 2, this process is repeated for each of the two directions until the found antinodes leave the defined area. Counting duplicate antinodes is prevented by keeping track of their locations in a two-dimensional array covering the area. +### Day 9: Disk Fragmenter + +:mag_right: Puzzle: , :white_check_mark: Solver: [`DiskFragmenter.cpp`](src/DiskFragmenter.cpp) + +The input string is processed simultaneously from both ends to calculate the checksums of files or file blocks without actually moving them. The algorithm does not have to keep track of positions for specific files or file blocks. For part two, a list of empty spaces is maintained, but only up to the point where the front and back processing meets. An array of indices per possible file size, where each index tracks the first empty space that could hold a file of that size and advances as needed, is also kept to speed up the search in the list of empty spaces. + ### Day 10: Hoof It :mag_right: Puzzle: , :white_check_mark: Solver: [`HoofIt.cpp`](src/HoofIt.cpp) diff --git a/include/aoc/DiskFragmenter.hpp b/include/aoc/DiskFragmenter.hpp index 64d538f..19ae2fd 100644 --- a/include/aoc/DiskFragmenter.hpp +++ b/include/aoc/DiskFragmenter.hpp @@ -15,6 +15,10 @@ #pragma once +#include +#include + +#include #include class DiskFragmenter @@ -26,6 +30,16 @@ class DiskFragmenter virtual void processDataLine(const std::string& line) override; virtual void finish() override; private: - int getDigit(const std::string& line, const size_t index) const; + typedef std::vector Intervals; + typedef std::array DigitIndexArray; + void moveFileBlocks(const std::string& line); + void moveWholeFiles(const std::string& line); + void AddNewEmptySpaces(Intervals& emptySpaces, const std::string& line, size_t& front, const size_t back, + const int nBackBlocks, size_t& position); + void UpdateEmptySpaceIndices(const Intervals& emptySpaces, DigitIndexArray& emptySpaceIndices); + void moveBackFileForward(size_t& back, size_t& backIdNumber, const int nBackBlocks, Interval& emptySpace); + void keepBackFile(const std::string& line, size_t& back, size_t& backIdNumber, const int nBackBlocks, + size_t& position); + unsigned int getDigit(const std::string& line, const size_t index) const; long long int calcChecksumPart(const size_t idNumber, const int nBlocks, size_t& position) const; }; diff --git a/include/aoc/Interval.hpp b/include/aoc/Interval.hpp new file mode 100644 index 0000000..4d08f64 --- /dev/null +++ b/include/aoc/Interval.hpp @@ -0,0 +1,27 @@ +// Solutions to the Advent Of Code 2024. +// Copyright (C) 2025 Stefan Müller +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later +// version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +#pragma once + +class Interval +{ +public: + Interval(const size_t start, const size_t length) + : start{ start }, length{length} + { + } + size_t start; + size_t length; +}; diff --git a/src/DiskFragmenter.cpp b/src/DiskFragmenter.cpp index 793a326..9a3e2e3 100644 --- a/src/DiskFragmenter.cpp +++ b/src/DiskFragmenter.cpp @@ -15,6 +15,8 @@ #include +#include + const std::string DiskFragmenter::getPuzzleName() const { return "Day 9: Disk Fragmenter"; @@ -27,36 +29,40 @@ const std::string DiskFragmenter::getInputFileName() const void DiskFragmenter::processDataLine(const std::string& line) { - //size_t maxIdNumber{ line.size() / 2 }; + moveFileBlocks(line); + moveWholeFiles(line); +} + +void DiskFragmenter::moveFileBlocks(const std::string& line) +{ // Index of the first unprocessed digit in the disk map. - size_t front{ 0 }; + size_t front{ 1 }; // ID number of the file 'front' refers to (when 'isFile == true'). Equivalent to 'front / 2', but calculated // incrementally. - size_t frontIdNumber{ 0 }; + size_t frontIdNumber{ 1 }; // Index of the last unprocessed digit in the disk map. size_t back{ line.size() - 1 }; - // Number of remaining fragmented blocks in the file that 'back' refers to (when 'isFile == true'). - int nRemainingBackBlocks{ getDigit(line, back) }; - // ID number of the file 'back' refers to (when 'isFile == true'). Equivalent to 'back / 2', but calculated - // incrementally. + // Number of remaining fragmented blocks in the file that 'back' refers to. + unsigned int nRemainingBackBlocks{ getDigit(line, back) }; + // ID number of the file 'back' refers to. Equivalent to 'back / 2', but calculated incrementally. size_t backIdNumber{ line.size() / 2 }; // Current block position. - size_t position{ 0 }; - - bool isFile{ true }; + size_t position{ getDigit(line, 0) }; + // If 'true', 'front' refers to a file in the digit map, otherwise it refers to an empty space. + bool isFile{ false }; while (front < back) { if (isFile) { // Adds the checksum for the file at 'front'. - int nFileBlocks = getDigit(line, front); + unsigned int nFileBlocks = getDigit(line, front); part1 += calcChecksumPart(frontIdNumber, nFileBlocks, position); frontIdNumber++; } else { - int nFreeBlocks = getDigit(line, front); + unsigned int nFreeBlocks = getDigit(line, front); while (nFreeBlocks > 0) { if (nFreeBlocks >= nRemainingBackBlocks) @@ -87,11 +93,105 @@ void DiskFragmenter::processDataLine(const std::string& line) part1 += calcChecksumPart(backIdNumber, nRemainingBackBlocks, position); } +void DiskFragmenter::moveWholeFiles(const std::string& line) +{ + // Index of the first unprocessed empty space in the disk map. + size_t front{ 1 }; + // Index of the last unprocessed file in the disk map. + size_t back{ line.size() - 1 }; + // ID number of the file 'back' refers to. Equivalent to 'back / 2', but calculated incrementally. + size_t backIdNumber{ line.size() / 2 }; + // Current block position. + size_t position{ getDigit(line, 0) }; + + // Contains empty spaces as Interval. + Intervals emptySpaces{}; + emptySpaces.reserve(backIdNumber / 2); + + // Contains indices of the next empty space with at least as many blocks as their index. 'emptySpaceIndices[0]' is + // not used, but included for convenience of accessing the other values by index. + DigitIndexArray emptySpaceIndices; + emptySpaceIndices.fill(0); + + while (back > 0) + { + int nBackBlocks = getDigit(line, back); + if (emptySpaceIndices[nBackBlocks] < emptySpaces.size() && + emptySpaces[emptySpaceIndices[nBackBlocks]].start < position) + { + if (front >= back) + { + position -= getDigit(line, back) + getDigit(line, back - 1); + } + moveBackFileForward(back, backIdNumber, nBackBlocks, emptySpaces[emptySpaceIndices[nBackBlocks]]); + UpdateEmptySpaceIndices(emptySpaces, emptySpaceIndices); + } + else + { + AddNewEmptySpaces(emptySpaces, line, front, back, nBackBlocks, position); + if (front < back) + { + moveBackFileForward(back, backIdNumber, nBackBlocks, emptySpaces.back()); + } + else + { + keepBackFile(line, back, backIdNumber, nBackBlocks, position); + } + UpdateEmptySpaceIndices(emptySpaces, emptySpaceIndices); + } + } +} + +void DiskFragmenter::AddNewEmptySpaces(Intervals& emptySpaces, const std::string& line, size_t& front, + const size_t back, const int nBackBlocks, size_t& position) +{ + int nFrontBlocks{ 0 }; + while (front < back && nFrontBlocks < nBackBlocks) + { + // Adds more empty spaces. + nFrontBlocks = getDigit(line, front); + emptySpaces.emplace_back(position, nFrontBlocks); + front++; + position += static_cast(nFrontBlocks) + getDigit(line, front++); + } +} + +void DiskFragmenter::UpdateEmptySpaceIndices(const Intervals& emptySpaces, DigitIndexArray& emptySpaceIndices) +{ + for (size_t i = 1; i < emptySpaceIndices.size(); i++) + { + while (emptySpaceIndices[i] < emptySpaces.size() && emptySpaces[emptySpaceIndices[i]].length < i) + { + emptySpaceIndices[i]++; + } + } +} + +// Moves the 'back' file into the empty space. +void DiskFragmenter::moveBackFileForward(size_t& back, size_t& backIdNumber, const int nBackBlocks, + Interval& emptySpace) +{ + part2 += calcChecksumPart(backIdNumber, nBackBlocks, emptySpace.start); + emptySpace.length -= nBackBlocks; + back -= 2; + backIdNumber--; +} + +// Does not move the 'back' file, there is no space for it. +void DiskFragmenter::keepBackFile(const std::string& line, size_t& back, size_t& backIdNumber, const int nBackBlocks, + size_t& position) +{ + part2 -= calcChecksumPart(backIdNumber, -nBackBlocks, position); + back--; + position -= getDigit(line, back--); + backIdNumber--; +} + void DiskFragmenter::finish() { } -int DiskFragmenter::getDigit(const std::string& line, const size_t index) const +unsigned int DiskFragmenter::getDigit(const std::string& line, const size_t index) const { return line[index] - '0'; } @@ -99,5 +199,7 @@ int DiskFragmenter::getDigit(const std::string& line, const size_t index) const long long int DiskFragmenter::calcChecksumPart(const size_t idNumber, const int nBlocks, size_t& position) const { position += nBlocks; - return idNumber * ((nBlocks * (2 * position - nBlocks - 1)) / 2); + // Casting the parameters is required to allow negative block count, resulting in a negative return value. + return static_cast(idNumber) * + ((nBlocks * (2 * static_cast(position) - nBlocks - 1)) / 2); } diff --git a/tests/src/TestCases.cpp b/tests/src/TestCases.cpp index e0a875c..2248abc 100644 --- a/tests/src/TestCases.cpp +++ b/tests/src/TestCases.cpp @@ -140,11 +140,11 @@ TEST_CASE("[DiskFragmenterTests]") TestContext test; SECTION("FullData") { - test.run(std::make_unique(), 6401092019345, 0, test.getInputPaths()); + test.run(std::make_unique(), 6401092019345, 6431472344710, test.getInputPaths()); } SECTION("ExampleData") { - test.run(std::make_unique(), 1928, 0, test.getExampleInputPaths()); + test.run(std::make_unique(), 1928, 2858, test.getExampleInputPaths()); } }