Day 12: Christmas Tree Farm
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465


C++
Hah, got me good too Mr Wastl. Was wondering how long this could possibly take to run - the worst-case is very bad indeed. This prints out the first “fit” it finds, for verification purposes, and keeps the successful ones in case they’re needed for “part 2”.
Merry Xmas, everyone.
#include <boost/describe.hpp> #include <boost/describe/operators.hpp> #include <boost/log/trivial.hpp> #include <boost/unordered/unordered_flat_set.hpp> #include <fstream> #include <iostream> #include <ostream> #include <sstream> #include <string> #include <vector> namespace { struct Point { int x, y; }; BOOST_DESCRIBE_STRUCT(Point, (), (x, y)) using boost::describe::operators::operator==; using Present = boost::unordered::unordered_flat_set<Point>; using PresentList = std::vector<size_t>; struct Tree { int width; int height; PresentList presents; }; auto operator<<(std::ostream &o, const Tree &t) -> std::ostream & { o << t.width << 'x' << t.height << ": "; auto total = size_t{}; for (auto &p : t.presents) { o << p << ' '; total += p; } return o << "(" << total << ")"; } auto copy_if(const Present &in, Present &out, const Point &a, const Point &b) { if (in.contains(a)) out.insert(b); } auto rotate(const Present &in) { auto out = Present{}; copy_if(in, out, {0, 0}, {2, 0}); copy_if(in, out, {1, 0}, {2, 1}); copy_if(in, out, {2, 0}, {2, 2}); copy_if(in, out, {0, 1}, {1, 0}); copy_if(in, out, {1, 1}, {1, 1}); copy_if(in, out, {2, 1}, {1, 2}); copy_if(in, out, {0, 2}, {0, 0}); copy_if(in, out, {1, 2}, {0, 1}); copy_if(in, out, {2, 2}, {0, 2}); return out; } auto hflip(const Present &in) { auto out = Present{}; for (auto x = 0; x < 3; ++x) for (auto y = 0; y < 3; ++y) copy_if(in, out, {x, y}, {2 - x, y}); return out; } auto vflip(const Present &in) { auto out = Present{}; for (auto x = 0; x < 3; ++x) for (auto y = 0; y < 3; ++y) copy_if(in, out, {x, y}, {x, 2 - y}); return out; } struct Puzzle { std::vector<Present> presents; std::vector<Tree> trees; }; auto read() { auto rval = Puzzle{}; auto ih = std::ifstream{"12.txt"}; auto line = std::string{}; for (auto i = 0; i < 6; ++i) { std::getline(ih, line); // number auto present = Present{}; for (auto y = size_t{}; y < 3; ++y) { std::getline(ih, line); for (auto x = size_t{}; x < 3; ++x) { if (line.at(x) == '#') present.emplace(x, y); } } std::getline(ih, line); // following space rval.presents.push_back(std::move(present)); } while (std::getline(ih, line)) { auto tree = Tree{}; auto times = line.find('x'); auto colon = line.find(':'); tree.width = std::stoi(line.substr(0, times)); tree.height = std::stoi(line.substr(times + 1, colon - times - 1)); auto count = size_t{}; auto ss = std::istringstream{line.substr(colon + 1)}; while (ss >> count) tree.presents.push_back(count); rval.trees.push_back(std::move(tree)); } return rval; } using Occupied = boost::unordered::unordered_flat_set<Point>; using PresentFlips = boost::unordered::unordered_flat_set<Present>; using PresentFlipsList = std::vector<PresentFlips>; auto place_present( Occupied occupied, const Present &present, const Point &origin ) -> std::optional<Occupied> { for (const auto &point : present) { auto px = origin.x + point.x; auto py = origin.y + point.y; if (occupied.contains({px, py})) return {}; occupied.insert({px, py}); } return {occupied}; } auto draw_occupied(const Tree &t, const Occupied &occupied) { for (auto x = 0; x < t.width; ++x) { for (auto y = 0; y < t.height; ++y) { if (occupied.contains({x, y})) std::cout << '#'; else std::cout << '.'; } std::cout << '\n'; } } auto can_place( const Tree &tree, const PresentFlipsList &flips, Occupied occupied, PresentList list ) -> bool { auto j = size_t{}; for (; j < list.size(); ++j) { if (list.at(j) > 0) break; } if (j == list.size()) { draw_occupied(tree, occupied); return true; // yeah! } list[j]--; for (auto x = 0; x < tree.width - 2; ++x) for (auto y = 0; y < tree.height - 2; ++y) { for (auto &flip : flips.at(j)) { auto test = place_present(occupied, flip, {x, y}); if (!test.has_value()) continue; auto works = can_place(tree, flips, test.value(), list); if (works) return true; } } return false; } auto part1(const Puzzle &puzzle) { auto possible = std::vector<Tree>{}; for (const auto &tree : puzzle.trees) { auto area = size_t(tree.width * tree.height); auto used = size_t{}; for (auto present = size_t{}; present < puzzle.presents.size(); ++present) { used += tree.presents.at(present) * puzzle.presents.at(present).size(); } if (used > area) continue; possible.push_back(tree); } auto flips = PresentFlipsList{}; for (auto j = size_t{}; j < puzzle.presents.size(); ++j) { auto flip = PresentFlips{}; auto rotation = puzzle.presents.at(j); for (auto i = 0; i < 4; ++i) { flip.insert(rotation); flip.insert(hflip(rotation)); flip.insert(vflip(rotation)); flip.insert(hflip(vflip(rotation))); rotation = rotate(rotation); } BOOST_LOG_TRIVIAL(debug) << j << " has " << flip.size() << " flips"; flips.push_back(std::move(flip)); } auto confirmed = std::vector<Tree>{}; for (auto &tree : possible) if (can_place(tree, flips, Occupied{}, tree.presents)) { BOOST_LOG_TRIVIAL(debug) << tree << " can be placed"; confirmed.push_back(std::move(tree)); } else { BOOST_LOG_TRIVIAL(debug) << tree << " can't be placed"; } return confirmed.size(); } } // namespace auto main() -> int { auto puzzle = read(); BOOST_LOG_TRIVIAL(info) << "Day 12: read " << puzzle.presents.size() << " / " << puzzle.trees.size(); BOOST_LOG_TRIVIAL(info) << "1:" << part1(puzzle); }Ha, that is beautiful. How long did it run for?
For the test cases (ironically, the only difficult ones) it finds the solution for 1 basically instantly, 2 in 0.2 seconds, and checks all possibilities for 3 in about 18 seconds before rejecting it.
For the thousand ‘puzzle’ cases, about half are rejected since the area is simply too small for the presents in an instant. A possible solution is found for all of them in about 80 seconds, so about five per second.
I was concerned that the puzzle cases were about four times the area, and some of them had 255 presents (which might be the maximum stack depth in some languages - not C++, though). So maybe some of them would find a solution quickly, and excluding the worst might take ~ 4 times the size * 30 times the presents * 20 seconds = the best part of an hour. Multithreading it and hoping not too many of them were ‘worst case’, and I could leave my computer on overnight number-crunching it. Box packing is NP-complete and we need a ‘true’ answer rather than an approximation, so I couldn’t see any better way of doing it than checking every possibility. Sorting the list didn’t really show any evidence of puzzles that could be ‘pruned’ - areas that were the same but with increasing numbers of presents, say, so that you could reject the larger ones after the first failure.
Was kicking myself when it ran so quickly.