206 lines
7.2 KiB
C++
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);
|
|
}
|