Change part 1 of day 20 to use a generator

master
Nick Krichevsky 2020-12-24 00:52:19 -05:00
parent 1b5fcd12f7
commit 1601cfebbf
1 changed files with 202 additions and 60 deletions

View File

@ -8,6 +8,7 @@
#include <optional> #include <optional>
#include <regex> #include <regex>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
constexpr auto TILE_ID_PATTERN = R"(Tile (\d+):)"; constexpr auto TILE_ID_PATTERN = R"(Tile (\d+):)";
@ -15,6 +16,9 @@ constexpr int NUM_CAMERA_LINES = 10;
// Plus one for the ID line, plus one for the newline after // Plus one for the ID line, plus one for the newline after
constexpr int NUM_INPUT_BLOCK_LINES = NUM_CAMERA_LINES + 2; constexpr int NUM_INPUT_BLOCK_LINES = NUM_CAMERA_LINES + 2;
/**
* Represents a single frame captured by the camera
*/
class CameraFrame { class CameraFrame {
public: public:
CameraFrame(int id, const std::vector<std::string> &frame) : id(id), frame(frame) { CameraFrame(int id, const std::vector<std::string> &frame) : id(id), frame(frame) {
@ -24,18 +28,31 @@ class CameraFrame {
return this->id; return this->id;
} }
/**
* Get the contents of this frame
* @return const std::vector<std::string>& The frame contents
*/
const std::vector<std::string> &getFrame() const { const std::vector<std::string> &getFrame() const {
return this->frame; return this->frame;
} }
/**
* @return const std::string The top edge of this frame
*/
const std::string getTopEdge() const { const std::string getTopEdge() const {
return this->frame.front(); return this->frame.front();
} }
/**
* @return const std::string The bottom edge of this frame
*/
const std::string getBottomEdge() const { const std::string getBottomEdge() const {
return this->frame.back(); return this->frame.back();
} }
/**
* @return const std::string The left edge of this frame
*/
const std::string getLeftEdge() const { const std::string getLeftEdge() const {
std::string edge; std::string edge;
std::transform( std::transform(
@ -47,6 +64,9 @@ class CameraFrame {
return edge; return edge;
} }
/**
* @return const std::string The right edge of this frame
*/
const std::string getRightEdge() const { const std::string getRightEdge() const {
std::string edge; std::string edge;
std::transform( std::transform(
@ -66,16 +86,25 @@ class CameraFrame {
return !(*this == frame); return !(*this == frame);
} }
/**
* Flip this frame along the horizontal axis
*/
void flipFrameVertically() { void flipFrameVertically() {
std::reverse(this->frame.begin(), this->frame.end()); std::reverse(this->frame.begin(), this->frame.end());
} }
/**
* Flip this frame along the vertical axis
*/
void flipFrameHorizontally() { void flipFrameHorizontally() {
std::for_each(this->frame.begin(), this->frame.end(), [](std::string &frameLine) { std::for_each(this->frame.begin(), this->frame.end(), [](std::string &frameLine) {
std::reverse(frameLine.begin(), frameLine.end()); std::reverse(frameLine.begin(), frameLine.end());
}); });
} }
/**
* Rotate this frame 90 degrees
*/
void rotateFrame90Deg() { void rotateFrame90Deg() {
// This algorithm works by rotating each "ring" of the frame 90 degrees // This algorithm works by rotating each "ring" of the frame 90 degrees
// Consider the following 5x5 frame // Consider the following 5x5 frame
@ -122,11 +151,17 @@ class CameraFrame {
this->frame = std::move(rotatedFrame); this->frame = std::move(rotatedFrame);
} }
/**
* Rotate this frame 180 degrees
*/
void rotateFrame180Deg() { void rotateFrame180Deg() {
this->rotateFrame90Deg(); this->rotateFrame90Deg();
this->rotateFrame90Deg(); this->rotateFrame90Deg();
} }
/**
* Rotate this frame 270 degrees
*/
void rotateFrame270Deg() { void rotateFrame270Deg() {
this->rotateFrame180Deg(); this->rotateFrame180Deg();
this->rotateFrame90Deg(); this->rotateFrame90Deg();
@ -137,6 +172,106 @@ class CameraFrame {
std::vector<std::string> frame; std::vector<std::string> frame;
}; };
/**
* Generates all of the possible transforms for a given camera frame.
*/
class TransformGenerator {
public:
class const_iterator {
public:
using difference_type = int;
using value_type = std::function<CameraFrame()>;
using pointer = const value_type *;
using reference = const value_type &;
using iterator_category = std::input_iterator_tag;
const_iterator &operator++() {
++this->baseIterator;
return *this;
}
const_iterator operator++(int) {
const_iterator res = *this;
++(*this);
return res;
}
bool operator==(const const_iterator &other) {
return this->baseIterator == other.baseIterator;
}
bool operator!=(const const_iterator &other) {
return !(*this == other);
}
const value_type operator*() {
return *(this->baseIterator);
}
private:
friend TransformGenerator;
const_iterator(const TransformGenerator &container)
: container(container), baseIterator(container.operationList.cbegin()) {
}
const_iterator(const TransformGenerator &container, const std::vector<value_type>::const_iterator &baseIterator)
: container(container), baseIterator(baseIterator) {
}
const TransformGenerator &container;
std::vector<value_type>::const_iterator baseIterator;
};
TransformGenerator(const CameraFrame &frame)
: frame(frame),
operationList{
[frame]() { return frame; },
[frame = frame]() mutable {
frame.rotateFrame90Deg();
return frame;
},
[frame = frame]() mutable {
frame.rotateFrame180Deg();
return frame;
},
[frame = frame]() mutable {
frame.rotateFrame270Deg();
return frame;
},
[frame = frame]() mutable {
frame.flipFrameVertically();
return frame;
},
[frame = frame]() mutable {
frame.flipFrameHorizontally();
return frame;
},
[frame = frame]() mutable {
frame.flipFrameHorizontally();
frame.rotateFrame90Deg();
return frame;
},
// We don't need one for rotating 180 after flipping, as it's equivalent to flipping vertically
[frame = frame]() mutable {
frame.flipFrameHorizontally();
frame.rotateFrame270Deg();
return frame;
},
} {
}
const_iterator cbegin() const {
return const_iterator(*this);
}
const_iterator cend() const {
return const_iterator(*this, this->operationList.cend());
}
private:
CameraFrame frame;
std::vector<std::function<CameraFrame()>> operationList;
};
std::vector<std::string> readInput(const std::string &filename) { std::vector<std::string> readInput(const std::string &filename) {
std::vector<std::string> input; std::vector<std::string> input;
std::string line; std::string line;
@ -148,6 +283,11 @@ std::vector<std::string> readInput(const std::string &filename) {
return input; return input;
} }
/**
* Get the frame ID from an input line containing one
* @param line The frame ID line
* @return int The frame ID
*/
int getFrameIDFromIDLine(const std::string &line) { int getFrameIDFromIDLine(const std::string &line) {
std::regex pattern(TILE_ID_PATTERN); std::regex pattern(TILE_ID_PATTERN);
std::smatch matches; std::smatch matches;
@ -158,6 +298,11 @@ int getFrameIDFromIDLine(const std::string &line) {
return std::stoi(matches[1]); return std::stoi(matches[1]);
} }
/**
* Parse the puzzle input
* @param input The puzzle input
* @return std::vector<CameraFrame> The frames from the camera input
*/
std::vector<CameraFrame> parseInput(const std::vector<std::string> &input) { std::vector<CameraFrame> parseInput(const std::vector<std::string> &input) {
int i = 0; int i = 0;
std::vector<CameraFrame> cameraFrames; std::vector<CameraFrame> cameraFrames;
@ -192,6 +337,12 @@ std::ostream &operator<<(std::ostream &os, const CameraFrame &frame) {
return os; return os;
} }
/**
* Debugging method to print the entire board
* @param board The board
* @param maxRow The maximum row in the board
* @param maxCol The maximum column in the board
*/
void printBoard(const std::map<std::pair<int, int>, CameraFrame> &board, int maxRow, int maxCol) { void printBoard(const std::map<std::pair<int, int>, CameraFrame> &board, int maxRow, int maxCol) {
CameraFrame emptyFrame(0, std::vector<std::string>(NUM_CAMERA_LINES, std::string(NUM_CAMERA_LINES, ' '))); CameraFrame emptyFrame(0, std::vector<std::string>(NUM_CAMERA_LINES, std::string(NUM_CAMERA_LINES, ' ')));
for (int i = 0; i < maxRow; i++) { for (int i = 0; i < maxRow; i++) {
@ -209,36 +360,14 @@ void printBoard(const std::map<std::pair<int, int>, CameraFrame> &board, int max
} }
} }
std::vector<CameraFrame> getPossibleTransforms(const CameraFrame &frame) { /**
CameraFrame identity(frame); * Check if the board has all tiles filled
CameraFrame rotated90Deg(frame); * @param board The board
rotated90Deg.rotateFrame90Deg(); * @param maxRow The maximum row of the board
CameraFrame rotated180Deg(frame); * @param maxCol The maximum column of the board
rotated180Deg.rotateFrame180Deg(); * @return true If the board is filled
CameraFrame rotated270Deg(frame); * @return false If the board is not filled
rotated270Deg.rotateFrame270Deg(); */
CameraFrame flippedVertically(frame);
flippedVertically.flipFrameVertically();
CameraFrame flippedHorizontally(frame);
flippedHorizontally.flipFrameHorizontally();
CameraFrame flippedAndRotated90(flippedHorizontally);
flippedAndRotated90.rotateFrame90Deg();
// We don't need flip and rotate 180 because it is equivalent to flipVertically
CameraFrame flippedAndRotated270(flippedHorizontally);
flippedAndRotated270.rotateFrame270Deg();
std::vector<CameraFrame> operations{
identity,
rotated90Deg,
rotated180Deg,
rotated270Deg,
flippedVertically,
flippedHorizontally,
flippedAndRotated90,
flippedAndRotated270};
return operations;
}
bool isBoardFilled(const std::map<std::pair<int, int>, CameraFrame> board, int maxRow, int maxCol) { bool isBoardFilled(const std::map<std::pair<int, int>, CameraFrame> board, int maxRow, int maxCol) {
for (int i = 0; i < maxRow; i++) { for (int i = 0; i < maxRow; i++) {
for (int j = 0; j < maxCol; j++) { for (int j = 0; j < maxCol; j++) {
@ -251,16 +380,34 @@ bool isBoardFilled(const std::map<std::pair<int, int>, CameraFrame> board, int m
return true; return true;
} }
/**
* Find a possible matching frame for this frame
* @param frameToMatch The frame to check
* @param frameMatches A function to check if a given frame matches this one
* @return std::optional<CameraFrame> The matching frame, if ti exists
*/
std::optional<CameraFrame> findPossibleFrame( std::optional<CameraFrame> findPossibleFrame(
const std::vector<CameraFrame> &availableFrames, std::function<bool(const CameraFrame &)> findFrame) { const CameraFrame &frameToMatch, std::function<bool(const CameraFrame &)> frameMatches) {
auto result = std::find_if(availableFrames.cbegin(), availableFrames.cend(), findFrame); auto transformations = TransformGenerator(frameToMatch);
if (result == availableFrames.cend()) { auto result = std::find_if(transformations.cbegin(), transformations.cend(), [frameMatches](auto transformation) {
CameraFrame transformedFrame = transformation();
return frameMatches(transformedFrame);
});
if (result == transformations.cend()) {
return std::nullopt; return std::nullopt;
} }
return *result; return (*result)();
} }
/**
* Find a board that correctly lines everything up
* @param startingFrame The frame to start with
* @param frames All the other frames available for use
* @param maxRow The maximum row size
* @param maxCol The maximum column size
* @return std::optional<std::map<std::pair<int, int>, CameraFrame>> The board that solves part 1
*/
std::optional<std::map<std::pair<int, int>, CameraFrame>> findLinedUpArrangement( std::optional<std::map<std::pair<int, int>, CameraFrame>> findLinedUpArrangement(
const CameraFrame &startingFrame, const std::vector<CameraFrame> &frames, int maxRow, int maxCol) { const CameraFrame &startingFrame, const std::vector<CameraFrame> &frames, int maxRow, int maxCol) {
std::map<std::pair<int, int>, CameraFrame> board; std::map<std::pair<int, int>, CameraFrame> board;
@ -274,31 +421,24 @@ std::optional<std::map<std::pair<int, int>, CameraFrame>> findLinedUpArrangement
bool found = false; bool found = false;
for (const CameraFrame &currentFrame : availableFrames) { for (const CameraFrame &currentFrame : availableFrames) {
auto transformed = getPossibleTransforms(currentFrame); std::optional<CameraFrame> matchingFrame;
for (const CameraFrame &currentTransformedFrame : transformed) { if (col == 0) {
std::optional<CameraFrame> matchingFrame; CameraFrame &aboveFrame = board.at(std::make_pair(row - 1, col));
if (col == 0) { matchingFrame = findPossibleFrame(currentFrame, [aboveFrame](const CameraFrame &frame) {
CameraFrame &aboveFrame = board.at(std::make_pair(row - 1, col)); return aboveFrame.getBottomEdge() == frame.getTopEdge();
matchingFrame = findPossibleFrame(transformed, [aboveFrame](const CameraFrame &frame) { });
return aboveFrame.getBottomEdge() == frame.getTopEdge(); } else {
}); CameraFrame &leftFrame = board.at(std::make_pair(row, col - 1));
} else { matchingFrame = findPossibleFrame(currentFrame, [leftFrame](const CameraFrame &frame) {
CameraFrame &leftFrame = board.at(std::make_pair(row, col - 1)); return leftFrame.getRightEdge() == frame.getLeftEdge();
matchingFrame = findPossibleFrame(transformed, [leftFrame](const CameraFrame &frame) { });
return leftFrame.getRightEdge() == frame.getLeftEdge();
});
}
if (matchingFrame) {
found = true;
board.emplace(std::make_pair(row, col), *matchingFrame);
availableFrames.erase(
std::remove(availableFrames.begin(), availableFrames.end(), currentFrame));
// printBoard(board, maxRow, maxCol);
break;
}
} }
if (found) {
if (matchingFrame) {
found = true;
board.emplace(std::make_pair(row, col), *matchingFrame);
availableFrames.erase(std::remove(availableFrames.begin(), availableFrames.end(), currentFrame));
// printBoard(board, maxRow, maxCol);
break; break;
} }
} }
@ -321,8 +461,10 @@ long part1(const std::vector<CameraFrame> &frames) {
std::vector<CameraFrame> availableFrames(frames); std::vector<CameraFrame> availableFrames(frames);
availableFrames.erase(std::remove(availableFrames.begin(), availableFrames.end(), frame)); availableFrames.erase(std::remove(availableFrames.begin(), availableFrames.end(), frame));
auto transformed = getPossibleTransforms(frame); const TransformGenerator transforms(frame);
for (const CameraFrame &transformedFrame : transformed) { for (auto it = transforms.cbegin(); it != transforms.cend(); ++it) {
auto transformation = *it;
CameraFrame transformedFrame = transformation();
auto res = findLinedUpArrangement(transformedFrame, availableFrames, boardSize, boardSize); auto res = findLinedUpArrangement(transformedFrame, availableFrames, boardSize, boardSize);
if (res) { if (res) {
return 1L * res->at(std::make_pair(0, 0)).getID() * res->at(std::make_pair(0, boardSize - 1)).getID() * return 1L * res->at(std::make_pair(0, 0)).getID() * res->at(std::make_pair(0, boardSize - 1)).getID() *