Add day 22 part 2 solution

master
Nick Krichevsky 2020-12-25 19:47:57 -05:00
parent bdcc189c89
commit 11249d06ee
2 changed files with 92 additions and 26 deletions

View File

@ -1,7 +1,7 @@
CC=g++
BIN_NAME=day22
CCFLAGS=-o $(BIN_NAME) -g -std=c++17
LDFLAGS=-lfolly
CCFLAGS=-o $(BIN_NAME) -O2 -std=c++17
LDFLAGS=
.PHONY: all, clean

View File

@ -1,18 +1,13 @@
#include <folly/String.h>
#include <fstream>
#include <functional>
#include <iostream>
#include <numeric>
#include <queue>
#include <regex>
#include <set>
#include <string>
#include <vector>
// Pair of ingredients and allergens
using IngredientLineItem = std::pair<std::vector<std::string>, std::vector<std::string>>;
auto constexpr INGREDIENT_PATTERN = R"((.*) \(contains (.*)\))";
enum Player { PLAYER1, PLAYER2 };
std::vector<std::string> readInput(const std::string &filename) {
std::vector<std::string> input;
@ -41,27 +36,58 @@ std::pair<std::vector<std::string>, std::vector<std::string>> splitInput(const s
std::move(beforeEmptyLine), std::move(afterEmptyLine));
}
template <typename Iterator, typename IntContainer>
void containerStoi(Iterator begin, Iterator end, IntContainer &out) {
std::transform(begin, end, std::back_inserter(out), [](const std::string &item) { return std::stoi(item); });
/**
* Perform std::stoi on every element in the container, pushing the items to the output iterator.
* @tparam Iterator The iterator for the input container
* @tparam IntContainer The container to output to
* @param begin The start of the range
* @param end The end of the range
* @param out The output iterator
*/
template <typename InputIterator, typename OutputIterator>
void containerStoi(InputIterator begin, InputIterator end, OutputIterator out) {
std::transform(begin, end, out, [](const std::string &item) { return std::stoi(item); });
}
/**
* Parse the decks from the puzzle input
* @param input The puzzle input
* @return std::pair<std::vector<int>, std::vector<int>> Both decks to play the game (player 1, player 2)
*/
std::pair<std::vector<int>, std::vector<int>> parseDecks(const std::vector<std::string> &input) {
std::pair<std::vector<std::string>, std::vector<std::string>> inputComponents = splitInput(input);
std::pair<std::vector<int>, std::vector<int>> decks;
containerStoi(inputComponents.first.cbegin() + 1, inputComponents.first.cend(), decks.first);
containerStoi(inputComponents.second.cbegin() + 1, inputComponents.second.cend(), decks.second);
containerStoi(inputComponents.first.cbegin() + 1, inputComponents.first.cend(), std::back_inserter(decks.first));
containerStoi(inputComponents.second.cbegin() + 1, inputComponents.second.cend(), std::back_inserter(decks.second));
return decks;
}
std::vector<int> playBasicGame(const std::pair<std::vector<int>, std::vector<int>> &initialDecks) {
/**
* Calculate the score for a game
* @tparam ReverseIterator A reverse iterator for the winning deck. Could technically be any input iterator but the
* score calculation requires going backwards
* @param begin The start of the deck
* @param end The end of the deck
* @return int The game score
*/
template <typename ReverseIterator>
int calculateScore(ReverseIterator begin, ReverseIterator end) {
int i = 1;
int total = 0;
for (auto it = begin; it != end; (++it, i++)) {
int value = *it;
total += i * value;
}
return total;
}
int part1(const std::pair<std::vector<int>, std::vector<int>> &initialDecks) {
std::pair<std::deque<int>, std::deque<int>> decks{
std::deque<int>(initialDecks.first.cbegin(), initialDecks.first.cend()),
std::deque<int>(initialDecks.second.cbegin(), initialDecks.second.cend())};
int i = 0;
while (!decks.first.empty() && !decks.second.empty()) {
i++;
int player1Card = decks.first.front();
int player2Card = decks.second.front();
decks.first.pop_front();
@ -77,19 +103,58 @@ std::vector<int> playBasicGame(const std::pair<std::vector<int>, std::vector<int
std::deque<int> &winnerDeck = decks.first.empty() ? decks.second : decks.first;
return std::vector<int>(winnerDeck.cbegin(), winnerDeck.cend());
return calculateScore(winnerDeck.crbegin(), winnerDeck.crend());
}
int part1(const std::pair<std::vector<int>, std::vector<int>> &decks) {
std::vector<int> winnerDeck = playBasicGame(decks);
int i = 1;
int total = 0;
for (auto it = winnerDeck.crbegin(); it != winnerDeck.crend(); (++it, i++)) {
int value = *it;
total += i * value;
}
int part2(const std::pair<std::vector<int>, std::vector<int>> &initialDecks) {
using DeckPair = std::pair<std::deque<int>, std::deque<int>>;
std::function<std::pair<Player, DeckPair>(DeckPair &)> playGame =
[&playGame](DeckPair &decks) -> std::pair<Player, std::pair<std::deque<int>, std::deque<int>>> {
// Holds game states that have already been used, preventing their resuse within a single sub-game
std::set<DeckPair> usedDecks;
while (!decks.first.empty() && !decks.second.empty()) {
if (usedDecks.find(decks) != usedDecks.end()) {
return std::make_pair(PLAYER1, decks);
}
return total;
usedDecks.insert(decks);
int player1Card = decks.first.front();
int player2Card = decks.second.front();
Player roundWinner;
if (player1Card < decks.first.size() && player2Card < decks.second.size()) {
// Must be done after the size check...
decks.first.pop_front();
decks.second.pop_front();
DeckPair truncatedDecks;
std::copy_n(decks.first.cbegin(), player1Card, std::back_inserter(truncatedDecks.first));
std::copy_n(decks.second.cbegin(), player2Card, std::back_inserter(truncatedDecks.second));
roundWinner = playGame(truncatedDecks).first;
} else {
roundWinner = player1Card > player2Card ? PLAYER1 : PLAYER2;
decks.first.pop_front();
decks.second.pop_front();
}
if (roundWinner == PLAYER1) {
decks.first.push_back(player1Card);
decks.first.push_back(player2Card);
} else {
decks.second.push_back(player2Card);
decks.second.push_back(player1Card);
}
}
return std::make_pair(decks.first.empty() ? PLAYER2 : PLAYER1, decks);
};
std::pair<std::deque<int>, std::deque<int>> decks{
std::deque<int>(initialDecks.first.cbegin(), initialDecks.first.cend()),
std::deque<int>(initialDecks.second.cbegin(), initialDecks.second.cend())};
auto gameResult = playGame(decks);
auto winningDeck = gameResult.first == PLAYER1 ? gameResult.second.first : gameResult.second.second;
return calculateScore(winningDeck.crbegin(), winningDeck.crend());
}
int main(int argc, char *argv[]) {
@ -102,4 +167,5 @@ int main(int argc, char *argv[]) {
auto decks = parseDecks(input);
std::cout << part1(decks) << std::endl;
std::cout << part2(decks) << std::endl;
}