diff --git a/main.cc b/main.cc index 25b08a5..199c88f 100644 --- a/main.cc +++ b/main.cc @@ -13,243 +13,274 @@ #include #include -/** (x, y) pair storing a position. */ -using Pos = std::pair; +namespace { + /** (x, y) pair storing a position. */ + using Pos = std::pair; -/** Get x co-ordinate from position */ -auto x(Pos const &p) noexcept -> int { return p.first; } + /** Get x co-ordinate from position */ + auto x(Pos const &p) noexcept -> int { return p.first; } -/** Get y co-ordinate from position */ -auto y(Pos const &p) noexcept -> int { return p.second; } + /** Get y co-ordinate from position */ + auto y(Pos const &p) noexcept -> int { return p.second; } -/** A square - consisting of position of closest corner to origin, and side-length. - */ -struct Square { - /** Construct a square. - * \param pos Position of closest corner to origin - * \param length Side length. + /** A square - consisting of position of closest corner to origin, and side-length. */ - Square(Pos const &pos, int const length) noexcept : pos_(pos), length_(length) { - } - - Square(Square const &other) noexcept = default; - - Square(Square &&other) noexcept = default; - - Square &operator=(Square const &other) noexcept = default; - - Square &operator=(Square &&other) noexcept = default; - - ~Square() noexcept = default; - - /** Get x co-ordinate of closest corner to origin. */ - auto x() const noexcept -> int { return ::x(pos_); } - - /** Get y co-ordinate of closest corner to origin. */ - auto y() const noexcept -> int { return ::y(pos_); } - - /** Get side length. */ - auto length() const noexcept -> int { return length_; } - -private: - Pos pos_; ///< Position of corner closest to origin - int length_; ///< Side length -}; - -/** An N * N grid of characters. */ -struct Grid { - /** Construct a grid of given side-length. */ - Grid(int length) : grid_(length * length, empty), length_(length) { - } - - Grid(Grid const &other) = delete; - - Grid(Grid &&other) noexcept = default; - - Grid &operator=(Grid const &other) = delete; - - Grid &operator=(Grid &&other) noexcept = default; - - ~Grid() noexcept = default; - - /** Get grid length */ - auto length() const noexcept -> int { return length_; } - - /** Add a square to the grid. */ - auto add(Square const &sq) noexcept -> void { - for (auto i = sq.x(); i < sq.x() + sq.length(); ++i) { - for (auto j = sq.y(); j < sq.y() + sq.length(); ++j) { - set(i, j, sq.length()); - } - } - } - - /** Clear a square from the grid. */ - auto clear(Square const &sq) noexcept -> void { - for (auto i = sq.x(); i < sq.x() + sq.length(); ++i) { - for (auto j = sq.y(); j < sq.y() + sq.length(); ++j) { - set(i, j, empty); - } - } - } - - auto prettify() noexcept -> void { - for (auto idx = 0; idx < grid_.size(); ++idx) { - if (grid_[idx] < 32) { - auto const pos = std::make_pair(idx % length_, idx / length_); - prettify_sq(Square(pos, grid_[idx])); - } - } - } - - /** Output the grid. */ - auto output() const -> void { - for (auto idx = 0; idx < length_ * length_; idx += length_) { - std::cout << std::string_view(grid_.data() + idx, length_) << '\n'; - } - } - - /** \brief Get length of the largest square that fits at \a pos in the grid. - */ - auto largest_square(Pos const &pos, int n) const noexcept -> int { - assert(x(pos) < length_); - assert(y(pos) < length_); - - /* Because of how we walk through the grid (starting at 0,0 then increasing - * x followed by y) we can assume that if the position (b, y) is clear - * (i.e. a '.') then (b, y + i) is clear for all i > 0. - * - * This means we only need to look for the first non-clear position along the - * current row. + struct Square { + /** Construct a square. + * \param pos Position of closest corner to origin + * \param length Side length. */ - auto b = x(pos); - // Make sure we don't go looking in the next row. - auto e = std::min(x(pos) + n, length_); - while (b < e) { - if (grid_[b + y(pos) * length_] != '.') { break; } - ++b; + Square(Pos const &pos, int const length) noexcept : pos_(pos), length_(length) { } - // Check that this length fits vertically as well. - auto len = b - x(pos); - auto ye = std::min(y(pos) + len, length_); - return ye - y(pos); - } - /** Get the next position to check. n is the size of the square we just - * added. - */ - auto next_pos(Pos const &pos, int n) const noexcept -> Pos { - auto const b = grid_.begin() + x(pos) + n + y(pos) * length_; - auto const p = std::find(b, grid_.end(), empty); - auto const v = p - grid_.begin(); - return std::make_pair(v % length_, v / length_); - } + Square(Square const &other) noexcept = default; -private: - auto prettify_sq(Square const &sq) noexcept -> void { - switch (sq.length()) { - case 1: set(sq.x(), sq.y(), '*'); - break; - case 2: set(sq.x(), sq.y(), '+'); - set(sq.x() + 1, sq.y(), '+'); - set(sq.x(), sq.y() + 1, '+'); - set(sq.x() + 1, sq.y() + 1, '+'); - break; - default: { - auto n = sq.length(); - set(sq.x(), sq.y(), '+'); - set(sq.x() + n - 1, sq.y(), '+'); - set(sq.x(), sq.y() + n - 1, '+'); - set(sq.x() + n - 1, sq.y() + n - 1, '+'); - for (int i = 1; i < n - 1; ++i) { - set(sq.x() + i, sq.y(), '-'); - set(sq.x() + i, sq.y() + n - 1, '-'); - set(sq.x(), sq.y() + i, '|'); - for (int j = 1; j < n - 1; ++j) { - set(sq.x() + j, sq.y() + i, ' '); - } - set(sq.x() + n - 1, sq.y() + i, '|'); - } + Square(Square &&other) noexcept = default; - int i = sq.x() + n - 1; - while (n != 0) { - set(--i, sq.y() + 1, '0' + (n % 10)); - n /= 10; + Square &operator=(Square const &other) noexcept = default; + + Square &operator=(Square &&other) noexcept = default; + + ~Square() noexcept = default; + + /** Get x co-ordinate of closest corner to origin. */ + auto x() const noexcept -> int { return ::x(pos_); } + + /** Get y co-ordinate of closest corner to origin. */ + auto y() const noexcept -> int { return ::y(pos_); } + + /** Get side length. */ + auto length() const noexcept -> int { return length_; } + + private: + Pos pos_; ///< Position of corner closest to origin + int length_; ///< Side length + }; + + /** An N * N grid of characters. */ + struct Grid { + /** Construct a grid of given side-length. */ + Grid(int length) : grid_(length * length, empty), length_(length) { + } + + Grid(Grid const &other) = delete; + + Grid(Grid &&other) noexcept = default; + + Grid &operator=(Grid const &other) = delete; + + Grid &operator=(Grid &&other) noexcept = default; + + ~Grid() noexcept = default; + + /** Get grid length */ + auto length() const noexcept -> int { return length_; } + + /** Add a square to the grid. */ + auto add(Square const &sq) noexcept -> void { + for (auto i = sq.x(); i < sq.x() + sq.length(); ++i) { + for (auto j = sq.y(); j < sq.y() + sq.length(); ++j) { + set(i, j, sq.length()); } } } - } - /** Set the grid position (x, y) to the character c. + /** Clear a square from the grid. */ + auto clear(Square const &sq) noexcept -> void { + for (auto i = sq.x(); i < sq.x() + sq.length(); ++i) { + for (auto j = sq.y(); j < sq.y() + sq.length(); ++j) { + set(i, j, empty); + } + } + } + + auto prettify() noexcept -> void { + for (auto idx = 0; idx < grid_.size(); ++idx) { + if (grid_[idx] < 32) { + auto const pos = std::make_pair(idx % length_, idx / length_); + prettify_sq(Square(pos, grid_[idx])); + } + } + } + + /** Output the grid. */ + auto output() const -> void { + for (auto idx = 0; idx < length_ * length_; idx += length_) { + std::cout << std::string_view(grid_.data() + idx, length_) << '\n'; + } + } + + /** \brief Get length of the largest square that fits at \a pos in the grid. + */ + auto largest_square(Pos const &pos, int n) const noexcept -> int { + assert(x(pos) < length_); + assert(y(pos) < length_); + + /* Because of how we walk through the grid (starting at 0,0 then increasing + * x followed by y) we can assume that if the position (b, y) is clear + * (i.e. a '.') then (b, y + i) is clear for all i > 0. + * + * This means we only need to look for the first non-clear position along the + * current row. + */ + auto b = x(pos); + // Make sure we don't go looking in the next row. + auto e = std::min(x(pos) + n, length_); + while (b < e) { + if (grid_[b + y(pos) * length_] != '.') { break; } + ++b; + } + // Check that this length fits vertically as well. + auto len = b - x(pos); + auto ye = std::min(y(pos) + len, length_); + return ye - y(pos); + } + + /** Get the next position to check. n is the size of the square we just + * added. + */ + auto next_pos(Pos const &pos, int n) const noexcept -> Pos { + auto const b = grid_.begin() + x(pos) + n + y(pos) * length_; + auto const p = std::find(b, grid_.end(), empty); + auto const v = p - grid_.begin(); + return std::make_pair(v % length_, v / length_); + } + + private: + auto prettify_sq(Square const &sq) noexcept -> void { + switch (sq.length()) { + case 1: set(sq.x(), sq.y(), '*'); + break; + case 2: set(sq.x(), sq.y(), '+'); + set(sq.x() + 1, sq.y(), '+'); + set(sq.x(), sq.y() + 1, '+'); + set(sq.x() + 1, sq.y() + 1, '+'); + break; + default: { + auto n = sq.length(); + set(sq.x(), sq.y(), '+'); + set(sq.x() + n - 1, sq.y(), '+'); + set(sq.x(), sq.y() + n - 1, '+'); + set(sq.x() + n - 1, sq.y() + n - 1, '+'); + for (int i = 1; i < n - 1; ++i) { + set(sq.x() + i, sq.y(), '-'); + set(sq.x() + i, sq.y() + n - 1, '-'); + set(sq.x(), sq.y() + i, '|'); + for (int j = 1; j < n - 1; ++j) { + set(sq.x() + j, sq.y() + i, ' '); + } + set(sq.x() + n - 1, sq.y() + i, '|'); + } + + int i = sq.x() + n - 1; + while (n != 0) { + set(--i, sq.y() + 1, '0' + (n % 10)); + n /= 10; + } + } + } + } + + /** Set the grid position (x, y) to the character c. + * + * It is an error if (x, y) is already set to c - as that means we have overlapping + * squares. + */ + auto set(int x, int y, char c) noexcept -> void { + assert(x < length_); + assert(y < length_); + assert(grid_[x + y * length_] != c); + grid_[x + y * length_] = c; + } + + std::vector grid_; ///< The grid + int length_; ///< Side length + + static constexpr char empty = '.'; ///< Character used for an empty cell. + }; + + /** Get the n'th triangular number. */ + auto triangle_num(int n) noexcept -> int { return (n * (n + 1)) / 2; } + + /** Vector used to identify the available squares. */ + using Avail = std::vector; + + /** Find a solution to the \a n th Partridge problem. * - * It is an error if (x, y) is already set to c - as that means we have overlapping - * squares. + * Returns the grid of the solution. */ - auto set(int x, int y, char c) noexcept -> void { - assert(x < length_); - assert(y < length_); - assert(grid_[x + y * length_] != c); - grid_[x + y * length_] = c; + auto find_solution(int const n) noexcept -> Grid { + /* Implementation is iterative, as opposed to recursive. + * + * 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. + */ + + // 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 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); + 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; } + } + + grid.prettify(); + return grid; } - - std::vector grid_; ///< The grid - int length_; ///< Side length - - static const int empty = '.'; ///< Character used for an empty cell. -}; - -/** Get the n'th triangular number. */ -auto triangle_num(int n) noexcept -> int { return (n * (n + 1)) / 2; } - -/** Vector used to identify the available squares. */ -using Avail = std::vector; - -/** Recursive part of solution finder. - * - * 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. - */ -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. - * - * If we know a square of side length N will fit then we know a square of side length - * N - 1 will fit. - */ - for (auto idx = grid.largest_square(pos, n); idx != 0; --idx) { - if (avail_sqs[idx] == 0) { continue; } - - 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); - } - - 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]);