Add solution for "Day 19: Linen Layout", part 1

This commit is contained in:
Stefan Müller 2025-06-02 22:20:15 +02:00
parent 12fdc184cf
commit 1cf8a70cfd
5 changed files with 47 additions and 27 deletions

View File

@ -140,6 +140,14 @@ After the solver tracked the first 1024 corrupted memory positions, it construct
Conveniently, Dijkstra's algorithm also finds the shortest path itself, so after each additional falling byte, the algorithm checks whether it hit the current shortest path and if so, tries to find a new one to the exit. The solution for part two is found once this there is no path any more.
### Day 19: Linen Layout
:mag_right: Puzzle: <https://adventofcode.com/2024/day/19>, :white_check_mark: Solver: [`LinenLayout.cpp`](src/LinenLayout.cpp)
This solver uses a flat tree data structure, implemented in [`LinenTowelPatterns.cpp`](src/extra/LinenTowelPatterns.cpp), to represent all valid towel patterns and to allow quick pattern matching.
Since a valid placement of a towel in a design is independent of other towel placements, this puzzle can be solved by iteratively finding all possible start positions of towel patterns, and which towels can be placed starting at those positions. While calculating these, the number of possible arrangements can be tracked per start position, by adding up the possible arrangements from previous start positions that lead to this position.
## Thanks
* [Alexander Brouwer](https://github.com/Bromvlieg) for getting the project set up with CMake.

View File

@ -24,9 +24,21 @@ class LinenTowelPatterns
public:
LinenTowelPatterns();
void add(const std::string& towelPattern);
bool isPossible(const std::string& design);
int64_t countArrangements(const std::string& design);
private:
/// <summary>
/// Tree data structure of all valid towel patterns. Each element is a vertex in the tree and represents a stripe in
/// a towel pattern given by 's = i mod n', where 's' is the unique index offset of the stripe as defined in
/// 'stripes_', 'i' is the index of the element, and 'n' is the number of stripes, i.e. the 'stripes_.size()'.
/// The first value of each element indicates the next set of stripes to continue the pattern accumulated by
/// following the path in the tree. If it is 0, this element is a leaf, and the pattern does not continue.
/// Otherwise, it is the index of its first of 'n' children, which are contiguous in the vector. The second value of
/// each element indicates whether there is a towel with the pattern ending at this element.
/// </summary>
std::vector<std::pair<size_t, bool>> patterns_;
/// <summary>
/// Maps stripes (represented by chars) to an index offset in the tree of towel patterns.
/// </summary>
std::map<char, size_t> stripes_;
void addBranch();
};

View File

@ -31,10 +31,12 @@ void LinenLayout::processDataLine(const std::string& line)
{
if (isReadingDesigns_)
{
if (patterns_.isPossible(line))
auto nArrangements = patterns_.countArrangements(line);
if (nArrangements > 0)
{
part1++;
}
part2 += nArrangements;
}
else if (line.empty())
{

View File

@ -46,50 +46,48 @@ void LinenTowelPatterns::add(const std::string& towelPattern)
patterns_[current].second = true;
}
bool LinenTowelPatterns::isPossible(const std::string& design)
int64_t LinenTowelPatterns::countArrangements(const std::string& design)
{
std::vector<bool> canStartFrom(design.size(), false);
std::vector<bool> canStartFrom(design.size() + 1, false);
canStartFrom[0] = true;
std::set<size_t> unexploredStart;
unexploredStart.insert(0);
std::vector<int64_t> nArrangements(design.size() + 1, 0);
nArrangements[0] = 1;
size_t startPosition{ 0 };
while (!unexploredStart.empty())
while (startPosition < design.size())
{
size_t designPosition = *unexploredStart.rbegin();
unexploredStart.erase(designPosition);
size_t designPosition{ startPosition };
size_t i{ stripes_[design[designPosition]] };
while (++designPosition < design.size() && patterns_[i].first > 0)
{
if (patterns_[i].second && !canStartFrom[designPosition])
// Checks if there is a towel with the current accumulated stripe pattern.
if (patterns_[i].second)
{
// The current accumulated stripe pattern exists and it ends at a new position within the design.
canStartFrom[designPosition] = true;
unexploredStart.insert(designPosition);
nArrangements[designPosition] += nArrangements[startPosition];
}
// Checks if the current accumulated stripe pattern can be extended further.
if (patterns_[i].first > 0)
{
// The current accumulated stripe pattern can be extended further.
i = patterns_[i].first + stripes_[design[designPosition]];
}
}
// Checks if there is a towel with the current accumulated stripe pattern, after its final extension.
if (patterns_[i].second)
{
if (designPosition == design.size())
{
// The current accumulated stripe pattern exists and finishes the design.
return true;
}
else if (!canStartFrom[designPosition])
{
// The current accumulated stripe pattern exists and it ends at a new position within the design.
canStartFrom[designPosition] = true;
unexploredStart.insert(designPosition);
}
canStartFrom[designPosition] = true;
nArrangements[designPosition] += nArrangements[startPosition];
}
// Seeks the next start position.
do
{
startPosition++;
} while (startPosition < canStartFrom.size() && !canStartFrom[startPosition]);
}
return false;
return *nArrangements.rbegin();
}
void LinenTowelPatterns::addBranch()

View File

@ -346,11 +346,11 @@ TEST_CASE("[LinenLayoutTests]")
TestContext test;
SECTION("FullData")
{
test.run(std::make_unique<LinenLayout>(), 272, 0, test.getInputPaths());
test.run(std::make_unique<LinenLayout>(), 272, 1041529704688380, test.getInputPaths());
}
SECTION("ExampleData")
{
test.run(std::make_unique<LinenLayout>(), 6, 0, test.getExampleInputPaths());
test.run(std::make_unique<LinenLayout>(), 6, 16, test.getExampleInputPaths());
}
}