Add solution for "Day 21: Keypad Conundrum", part 2
This commit is contained in:
parent
ab26563e34
commit
83c66682ef
10
README.md
10
README.md
@ -156,6 +156,16 @@ Initially for part 1, the solver was tracking the time index for each path posit
|
||||
|
||||
So instead of tracking the path times, the algorithm checks, for each new path position, all previous positions whether they are in range for a cheat, and whether this cheat would cut enough time off to be valid. The number of positions to check can be reduced by only looking at positions that are far enough behind the current position, and by skipping several positions at once if the check for a cheat failed.
|
||||
|
||||
### Day 21: Keypad Conundrum
|
||||
|
||||
:mag_right: Puzzle: <https://adventofcode.com/2024/day/21>, :white_check_mark: Solver: [`KeypadConundrum.cpp`](src/KeypadConundrum.cpp)
|
||||
|
||||
It's trivial to see that it requires less instruction for the previous robot in the chain if a robot avoids switching directions as much as possible when the arm has to move more than once in one direction. For example if we want to move the arm from the `A` button to the `<` button, letting the previous robot push `v<<A` is of course better than `<v<A`.
|
||||
|
||||
However, it was not immediately obvious to me, that the order of directions in a diagonal movement matters, too. For example `<vA` is better than `v<A`, which becomes only apparent when looking at the input of the robot before the previous robot in the chain. The reason for this is the need to minimize presses of `A` and `<` buttons immediately following each other, since they are the furthest apart. And of course, we have to restrict certain movements, for example `<vA`, if they would move over the gap on the keypad.
|
||||
|
||||
Since the length of the target button pattern roughly doubles with each robot along the chain, the algorithm cannot calculate the list of instructions for each robot explicitly. Instead, it considers only the twelve patterns without duplicate buttons that start at the `A` button and end there again. For example, instead of `v<<A>A^>A`, it takes its parts `v<A`, `>A`, and `^>A`, and keeps iterating with those. That means the solver can iteratively calculate the length of each of these short patterns per robot to find the length of the original one at the end of the chain.
|
||||
|
||||
## Thanks
|
||||
|
||||
* [Alexander Brouwer](https://github.com/Bromvlieg) for getting the project set up with CMake.
|
||||
|
@ -15,6 +15,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <aoc/extra/KeypadPatternTransformation.hpp>
|
||||
#include <aoc/extra/KeypadRobot.hpp>
|
||||
#include <aoc/framework/Solver-types.hpp>
|
||||
|
||||
@ -29,6 +32,12 @@ class KeypadConundrum
|
||||
virtual void finish() override;
|
||||
private:
|
||||
static constexpr char getStartPositionChar();
|
||||
static constexpr size_t getPart1NRobots();
|
||||
static constexpr size_t getPart2NRobots();
|
||||
KeypadRobot numericKeyboardRobot_;
|
||||
KeypadRobot directionalKeyboardRobot_;
|
||||
std::unordered_map<std::string, KeypadPatternTransformation> transformations_;
|
||||
void updateTransformationMap(KeypadPatternTransformation& targetTransformation);
|
||||
void updateTransformationMapLengths(const std::vector<KeypadPatternTransformation*>& added);
|
||||
int64_t calcAccumulatedLength(KeypadPatternTransformation& transformation, const size_t index);
|
||||
};
|
||||
|
28
include/aoc/extra/KeypadPatternTransformation.hpp
Normal file
28
include/aoc/extra/KeypadPatternTransformation.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
// 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class KeypadPatternTransformation
|
||||
{
|
||||
public:
|
||||
int nDuplicates{ 0 };
|
||||
std::vector<std::string> parts{};
|
||||
std::vector<KeypadPatternTransformation*> partPtrs{};
|
||||
std::vector<int64_t> accumulatedLengths{};
|
||||
};
|
@ -18,15 +18,18 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <aoc/extra/KeypadPatternTransformation.hpp>
|
||||
#include <aoc/common/Point2.hpp>
|
||||
|
||||
class KeypadRobot
|
||||
{
|
||||
public:
|
||||
KeypadRobot(const std::map<char, Point2>&& keypad, const Point2&& forbidden);
|
||||
std::string calcInputKeys(const std::string& targetOutputKeys) const;
|
||||
KeypadPatternTransformation calcTransformation(const std::string& targetOutputKeys) const;
|
||||
private:
|
||||
const std::map<char, Point2> keypad_;
|
||||
const Point2 forbidden_;
|
||||
void move(std::ostringstream& stream, const int delta, const char positive, const char negative) const;
|
||||
void move(std::ostringstream& stream, const int delta, const char positive, const char negative,
|
||||
int& nDuplicates) const;
|
||||
bool isForbidden(const int x, const int y) const;
|
||||
};
|
||||
|
@ -15,17 +15,16 @@
|
||||
|
||||
#include <aoc/KeypadConundrum.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
#include <aoc/common/Point2.hpp>
|
||||
#include <stack>
|
||||
|
||||
KeypadConundrum::KeypadConundrum()
|
||||
: numericKeyboardRobot_{ { { 'A', { 0, 0 } }, { '0', { -1, 0 } }, { '1', { -2, -1 } }, { '2', { -1, -1 } },
|
||||
{ '3', { 0, -1 } }, { '4', { -2, -2 } }, { '5', { -1, -2 } }, { '6', { 0, -2 } }, { '7', { -2, -3 } },
|
||||
{ '8', { -1, -3 } }, { '9', { 0, -3 } } }, { -2, 0 } },
|
||||
directionalKeyboardRobot_{ { { 'A', { 0, 0 } }, { '<', { -2, 1 } }, { '>', { 0, 1 } }, { '^', { -1, 0 } },
|
||||
{ 'v', { -1, 1 } } }, { -2, 0 } }
|
||||
{ 'v', { -1, 1 } } }, { -2, 0 } },
|
||||
transformations_{}
|
||||
{
|
||||
}
|
||||
|
||||
@ -41,15 +40,15 @@ const int KeypadConundrum::getPuzzleDay() const
|
||||
|
||||
void KeypadConundrum::processDataLine(const std::string& line)
|
||||
{
|
||||
KeypadPatternTransformation target{ numericKeyboardRobot_.calcTransformation(line) };
|
||||
updateTransformationMap(target);
|
||||
|
||||
std::istringstream stream{ line };
|
||||
int64_t number;
|
||||
stream >> number;
|
||||
std::string inputKeys{ numericKeyboardRobot_.calcInputKeys(line) };
|
||||
for (size_t i = 0; i < 2; i++)
|
||||
{
|
||||
inputKeys = directionalKeyboardRobot_.calcInputKeys(inputKeys);
|
||||
}
|
||||
part1 += number * static_cast<int64_t>(inputKeys.size());
|
||||
|
||||
part1 += number * calcAccumulatedLength(target, getPart1NRobots());
|
||||
part2 += number * calcAccumulatedLength(target, getPart2NRobots());
|
||||
}
|
||||
|
||||
void KeypadConundrum::finish()
|
||||
@ -60,3 +59,69 @@ constexpr char KeypadConundrum::getStartPositionChar()
|
||||
{
|
||||
return 'A';
|
||||
}
|
||||
|
||||
constexpr size_t KeypadConundrum::getPart1NRobots()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
constexpr size_t KeypadConundrum::getPart2NRobots()
|
||||
{
|
||||
return 25;
|
||||
}
|
||||
|
||||
void KeypadConundrum::updateTransformationMap(KeypadPatternTransformation& targetTransformation)
|
||||
{
|
||||
std::stack<KeypadPatternTransformation*> stack{};
|
||||
stack.push(&targetTransformation);
|
||||
|
||||
std::vector<KeypadPatternTransformation*> added{};
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
auto transformation = stack.top();
|
||||
stack.pop();
|
||||
|
||||
for (const auto& part : transformation->parts)
|
||||
{
|
||||
auto it = transformations_.find(part);
|
||||
if (it == transformations_.end())
|
||||
{
|
||||
const auto emplaceResult =
|
||||
transformations_.emplace(part, directionalKeyboardRobot_.calcTransformation(part));
|
||||
it = emplaceResult.first;
|
||||
stack.push(&it->second);
|
||||
added.push_back(&it->second);
|
||||
}
|
||||
transformation->partPtrs.push_back(&it->second);
|
||||
}
|
||||
}
|
||||
|
||||
updateTransformationMapLengths(added);
|
||||
}
|
||||
|
||||
void KeypadConundrum::updateTransformationMapLengths(const std::vector<KeypadPatternTransformation*>& added)
|
||||
{
|
||||
for (auto& transformation : added)
|
||||
{
|
||||
transformation->accumulatedLengths.reserve(getPart2NRobots());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < getPart2NRobots(); i++)
|
||||
{
|
||||
for (auto& transformation : added)
|
||||
{
|
||||
transformation->accumulatedLengths.push_back(calcAccumulatedLength(*transformation, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int64_t KeypadConundrum::calcAccumulatedLength(KeypadPatternTransformation& transformation, const size_t index)
|
||||
{
|
||||
int64_t n{ transformation.nDuplicates };
|
||||
for (const auto& part : transformation.partPtrs)
|
||||
{
|
||||
n += part->accumulatedLengths[index];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
@ -22,8 +22,11 @@ KeypadRobot::KeypadRobot(const std::map<char, Point2>&& keypad, const Point2&& f
|
||||
{
|
||||
}
|
||||
|
||||
std::string KeypadRobot::calcInputKeys(const std::string& targetOutputKeys) const
|
||||
KeypadPatternTransformation KeypadRobot::calcTransformation(const std::string& targetOutputKeys) const
|
||||
{
|
||||
KeypadPatternTransformation result{};
|
||||
result.accumulatedLengths.push_back(targetOutputKeys.size());
|
||||
|
||||
std::ostringstream stream{};
|
||||
Point2 position{ 0, 0 };
|
||||
for (const char c : targetOutputKeys)
|
||||
@ -32,37 +35,44 @@ std::string KeypadRobot::calcInputKeys(const std::string& targetOutputKeys) cons
|
||||
|
||||
// This specific order of robot arm movements aims to reduce resulting combinations of 'A' and '<' for the
|
||||
// second robot, which expand to more key presses starting with the third robot.
|
||||
bool horizontalFirst{ (next.x < position.x && !(next.x == forbidden_.x && position.y == forbidden_.y)) ||
|
||||
(position.x == forbidden_.x && next.y == forbidden_.y) };
|
||||
bool horizontalFirst{ (next.x < position.x && !isForbidden(next.x, position.y)) ||
|
||||
isForbidden(position.x, next.y) };
|
||||
if (horizontalFirst)
|
||||
{
|
||||
move(stream, next.x - position.x, '>', '<');
|
||||
move(stream, next.x - position.x, '>', '<', result.nDuplicates);
|
||||
}
|
||||
move(stream, next.y - position.y, 'v', '^');
|
||||
move(stream, next.y - position.y, 'v', '^', result.nDuplicates);
|
||||
if (!horizontalFirst)
|
||||
{
|
||||
move(stream, next.x - position.x, '>', '<');
|
||||
move(stream, next.x - position.x, '>', '<', result.nDuplicates);
|
||||
}
|
||||
stream << 'A';
|
||||
|
||||
result.parts.push_back(stream.str());
|
||||
stream.str("");
|
||||
|
||||
position = next;
|
||||
}
|
||||
return stream.str();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void KeypadRobot::move(std::ostringstream& stream, const int delta, const char positive, const char negative) const
|
||||
void KeypadRobot::move(std::ostringstream& stream, const int delta, const char positive, const char negative,
|
||||
int& nDuplicates) const
|
||||
{
|
||||
if (delta > 0)
|
||||
{
|
||||
for (int i{ 0 }; i < delta; i++)
|
||||
{
|
||||
stream << positive;
|
||||
}
|
||||
stream << positive;
|
||||
nDuplicates += delta - 1;
|
||||
}
|
||||
else
|
||||
else if (delta < 0)
|
||||
{
|
||||
for (int i{ 0 }; i < -delta; i++)
|
||||
{
|
||||
stream << negative;
|
||||
}
|
||||
stream << negative;
|
||||
nDuplicates -= delta + 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool KeypadRobot::isForbidden(const int x, const int y) const
|
||||
{
|
||||
return x == forbidden_.x && y == forbidden_.y;
|
||||
}
|
||||
|
@ -375,11 +375,11 @@ TEST_CASE("[KeypadConundrumTests]")
|
||||
TestContext test;
|
||||
SECTION("FullData")
|
||||
{
|
||||
test.runFull(std::make_unique<KeypadConundrum>(), 136780, 0);
|
||||
test.runFull(std::make_unique<KeypadConundrum>(), 136780, 167538833832712);
|
||||
}
|
||||
SECTION("ExampleData")
|
||||
{
|
||||
test.runExample(std::make_unique<KeypadConundrum>(), 126384, 0);
|
||||
test.runExamplePart1(std::make_unique<KeypadConundrum>(), 126384);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user