AdventOfCode2024/src/DiskFragmenter.cpp

206 lines
7.2 KiB
C++

// 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 <http://www.gnu.org/licenses/>.
#include <aoc/DiskFragmenter.hpp>
#include <algorithm>
const std::string DiskFragmenter::getPuzzleName() const
{
return "Disk Fragmenter";
}
const int DiskFragmenter::getPuzzleDay() const
{
return 9;
}
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<size_t>(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<long long int>(idNumber) *
((nBlocks * (2 * static_cast<long long int>(position) - nBlocks - 1)) / 2);
}