Change implementation to iterative.

Previously our implementation was recursive (although less so than the
OCaml version).

Moving to an iterative implementation improves performance as we have
fewer function calls, and so fewer function prologues and epilogues.
This commit is contained in:
2025-09-02 19:11:01 +02:00
parent 52a73a3d05
commit f5021aca30

99
main.cc
View File

@@ -13,6 +13,7 @@
#include <iostream>
#include <set>
namespace {
/** (x, y) pair storing a position. */
using Pos = std::pair<int, int>;
@@ -195,7 +196,7 @@ private:
std::vector<char> grid_; ///< The grid
int length_; ///< Side length
static const int empty = '.'; ///< Character used for an empty cell.
static constexpr char empty = '.'; ///< Character used for an empty cell.
};
/** Get the n'th triangular number. */
@@ -204,52 +205,82 @@ auto triangle_num(int n) noexcept -> int { return (n * (n + 1)) / 2; }
/** Vector used to identify the available squares. */
using Avail = std::vector<int>;
/** Recursive part of solution finder.
/** Find a solution to the \a n th Partridge problem.
*
* Fills \a grid with squares from \a avail_sqs, starting at the current
* position \a pos. \a n is the side-length of the largest square.
*
* Returns \c true if a solution is found, \a c false if not.
* Returns the grid of the solution.
*/
auto find_solution_impl(Grid &grid, int n, Pos const &pos, Avail &avail_sqs) noexcept -> bool {
if (x(pos) == 0 && y(pos) == grid.length()) { return true; }
/* Walk through the possible squares from the largest that will fit down to 0.
* We don't want to walk up from 1 to the largest because we can show that a
* 1x1 piece will never fit along an edge (or one in from the edge) - as
* there is no other 1x1 piece to go alongside it). This means we know the
* first guess is definitely wrong if we start small.
auto find_solution(int const n) noexcept -> Grid {
/* Implementation is iterative, as opposed to recursive.
*
* If we know a square of side length N will fit then we know a square of side length
* N - 1 will fit.
* The recursive implementation is easier to understand - but is
* slightly slower because of the repeated function calls (and
* entry/exit).
*
* The basic algorithm is to start at the origin of the grid we
* want to place squares on and iterate over the permutations of
* available squares until we find one that fits.
*/
for (auto idx = grid.largest_square(pos, n); idx != 0; --idx) {
if (avail_sqs[idx] == 0) { continue; }
// grid is our inprogress grid of square positions.
auto const length = triangle_num(n);
Grid grid(length);
/* avail_sqs is a vector indexed by square length indicating how many
* squares are available. Initially set up so that avail_sqs[i] = i.
*/
Avail avail_sqs;
for (auto i = 0; i <= n; ++i) { avail_sqs.push_back(i); }
/* sqs is a vector used as a stack of the squares currently placed.
* We reserve the length we need so as not to have too many allocations.
*/
std::vector<Square> sqs;
sqs.reserve(length);
// Start at the origin with a square of longest side length.
Pos pos{0, 0};
int idx = n;
while (true) {
/* If the idx is 0 we've looked at all possible square lengths for this
* position, and they've failed. Pop the last square of the stack, remove
* it from the grid and try the next smaller one in the same position.
*/
if (idx == 0) {
// No squares on the stack -> failed to find a solution.
if (sqs.empty()) { break; }
auto sq = sqs.back();
sqs.pop_back();
grid.clear(sq);
++avail_sqs[sq.length()];
pos = std::make_pair(sq.x(), sq.y());
idx = sq.length() - 1;
continue;
}
// If there are no squares available of the current size try the next one.
if (avail_sqs[idx] == 0) { --idx; continue; }
/* Place a square of side length idx at pos, push this onto the stack and
* set up to look at the next position.
*/
auto const sq = Square(pos, idx);
--avail_sqs[idx];
grid.add(sq);
if (find_solution_impl(grid, n, grid.next_pos(pos, idx), avail_sqs)) { return true; }
++avail_sqs[idx];
grid.clear(sq);
sqs.push_back(sq);
pos = grid.next_pos(pos, idx);
idx = grid.largest_square(pos, n);
// Have we reached the end? If so success!
if (x(pos) == 0 && y(pos) == grid.length()) { break; }
}
return false;
}
/** Find a solution to the partridge problem of size n.
*
* Returns the solution grid.
*/
auto find_solution(int n) -> Grid {
auto length = triangle_num(n);
Grid grid(length);
Avail avail_sqs;
for (auto i = 0; i <= n; ++i) { avail_sqs.push_back(i); }
find_solution_impl(grid, n, std::make_pair(0, 0), avail_sqs);
grid.prettify();
return grid;
}
} // anon namespace
int main(int argc, char **argv) {
auto n = (argc == 1) ? 8 : atoi(argv[1]);