// 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 . #include #include const std::string DiskFragmenter::getPuzzleName() const { return "Day 9: Disk Fragmenter"; } const std::string DiskFragmenter::getInputFileName() const { return "disk_fragmenter.txt"; } void DiskFragmenter::processDataLine(const std::string& line) { moveFileBlocks(line); moveWholeFiles(line); } void DiskFragmenter::moveFileBlocks(const std::string& line) { // Index of the first unprocessed digit in the disk map. 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{ 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. 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{ 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'. unsigned int nFileBlocks = getDigit(line, front); part1 += calcChecksumPart(frontIdNumber, nFileBlocks, position); frontIdNumber++; } else { unsigned int nFreeBlocks = getDigit(line, front); while (nFreeBlocks > 0) { if (nFreeBlocks >= nRemainingBackBlocks) { // Adds the checksum for all the blocks of the file at 'back'. part1 += calcChecksumPart(backIdNumber, nRemainingBackBlocks, position); backIdNumber--; nFreeBlocks -= nRemainingBackBlocks; back -= 2; nRemainingBackBlocks = getDigit(line, back); } else { // Adds the checksum for the blocks of the file at 'back' that fit into the empty blocks at 'front'. part1 += calcChecksumPart(backIdNumber, nFreeBlocks, position); nRemainingBackBlocks -= nFreeBlocks; nFreeBlocks = 0; } } } front++; isFile = !isFile; } // Adds the checksum for the remaining blocks of the file at 'back'. 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() { } unsigned int DiskFragmenter::getDigit(const std::string& line, const size_t index) const { return line[index] - '0'; } long long int DiskFragmenter::calcChecksumPart(const size_t idNumber, const int nBlocks, size_t& position) const { position += nBlocks; // 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); }