Optimize: if sq N fits then sq (N - 1) does too.
As an optimisation we know that if a square with side length N fits in a certain position, then one of side length N - 1 does too. And conversely, if a square of side length N does not fit then one of side length N + 1 does not. So our implementation which asked if every square fitted in a certain position called Grid::fits() too many times, once for each side length. We replace this by a function Grid::largest_square which gives the appropriate side length to start with. Naïvely the optimisation here is that instead of checking whether N squares fit, and so having to do (N * (N + 1)) / 2 comparisons in fits per position, we instead do one scan in largest_square and do at most N comparisons. A performance improvement is also seen in real life.
This commit is contained in:
136
main.cc
136
main.cc
@@ -11,15 +11,21 @@
|
|||||||
|
|
||||||
using Pos = std::pair<int, int>;
|
using Pos = std::pair<int, int>;
|
||||||
|
|
||||||
auto x(Pos const& p) noexcept -> int { return p.first; }
|
auto x(Pos const &p) noexcept -> int { return p.first; }
|
||||||
auto y(Pos const& p) noexcept -> int { return p.second; }
|
auto y(Pos const &p) noexcept -> int { return p.second; }
|
||||||
|
|
||||||
struct Square {
|
struct Square {
|
||||||
Square(Pos const& pos, int length) noexcept : pos_(pos), length_(length) {}
|
Square(Pos const &pos, int 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(Square const &other) noexcept = default;
|
||||||
Square& operator=(Square&& other) noexcept = default;
|
|
||||||
|
Square(Square &&other) noexcept = default;
|
||||||
|
|
||||||
|
Square &operator=(Square const &other) noexcept = default;
|
||||||
|
|
||||||
|
Square &operator=(Square &&other) noexcept = default;
|
||||||
|
|
||||||
~Square() noexcept = default;
|
~Square() noexcept = default;
|
||||||
|
|
||||||
auto x() const noexcept -> int { return ::x(pos_); }
|
auto x() const noexcept -> int { return ::x(pos_); }
|
||||||
@@ -30,48 +36,60 @@ struct Square {
|
|||||||
int length_;
|
int length_;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OverlappingSquares { Pos pos_; };
|
struct OverlappingSquares {
|
||||||
|
Pos pos_;
|
||||||
|
};
|
||||||
|
|
||||||
struct Grid {
|
struct Grid {
|
||||||
Grid(int length) : grid_(length * length, '.'), length_(length) {}
|
Grid(int length) : grid_(length * length, '.'), length_(length) {
|
||||||
Grid(Grid const& other) = default;
|
}
|
||||||
Grid(Grid&& other) noexcept = default;
|
|
||||||
Grid& operator=(Grid const& other) = default;
|
Grid(Grid const &other) = default;
|
||||||
Grid& operator=(Grid&& other) noexcept = default;
|
|
||||||
|
Grid(Grid &&other) noexcept = default;
|
||||||
|
|
||||||
|
Grid &operator=(Grid const &other) = default;
|
||||||
|
|
||||||
|
Grid &operator=(Grid &&other) noexcept = default;
|
||||||
|
|
||||||
~Grid() noexcept = default;
|
~Grid() noexcept = default;
|
||||||
|
|
||||||
auto length() const noexcept -> int { return length_; }
|
auto length() const noexcept -> int { return length_; }
|
||||||
|
|
||||||
auto set(int x, int y, char c) noexcept -> void {
|
auto set(int x, int y, char c) noexcept -> void {
|
||||||
assert (x < length_);
|
assert(x < length_);
|
||||||
assert (y < length_);
|
assert(y < length_);
|
||||||
assert (grid_[x + y * length_] != c);
|
assert(grid_[x + y * length_] != c);
|
||||||
grid_[x + y * length_] = c;
|
grid_[x + y * length_] = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto get(int x, int y) const noexcept -> char {
|
auto get(int x, int y) const noexcept -> char {
|
||||||
assert (x < length_);
|
assert(x < length_);
|
||||||
assert (y < length_);
|
assert(y < length_);
|
||||||
return grid_[x + y * length_];
|
return grid_[x + y * length_];
|
||||||
}
|
}
|
||||||
|
|
||||||
auto add(Square const& sq) -> void {
|
auto add(Square const &sq) -> void {
|
||||||
switch (sq.length()) {
|
switch (sq.length()) {
|
||||||
case 1: set(sq.x(), sq.y(), '*'); break;
|
case 1: set(sq.x(), sq.y(), '*');
|
||||||
|
break;
|
||||||
case 2: set(sq.x(), sq.y(), '+');
|
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, '+');
|
set(sq.x() + 1, sq.y(), '+');
|
||||||
|
set(sq.x(), sq.y() + 1, '+');
|
||||||
|
set(sq.x() + 1, sq.y() + 1, '+');
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
auto n = sq.length();
|
auto n = sq.length();
|
||||||
set(sq.x(), sq.y(), '+');
|
set(sq.x(), sq.y(), '+');
|
||||||
set(sq.x() + n - 1, sq.y(), '+');
|
set(sq.x() + n - 1, sq.y(), '+');
|
||||||
set(sq.x(), sq.y() + n -1, '+');
|
set(sq.x(), sq.y() + n - 1, '+');
|
||||||
set(sq.x() + n - 1, sq.y() + n -1, '+');
|
set(sq.x() + n - 1, sq.y() + n - 1, '+');
|
||||||
for (int i = 1; i < n -1; ++i) {
|
for (int i = 1; i < n - 1; ++i) {
|
||||||
set(sq.x() + i, sq.y(), '-');
|
set(sq.x() + i, sq.y(), '-');
|
||||||
set(sq.x() + i, sq.y() + n - 1, '-');
|
set(sq.x() + i, sq.y() + n - 1, '-');
|
||||||
set(sq.x(), sq.y() + i, '|');
|
set(sq.x(), sq.y() + i, '|');
|
||||||
for (int j = 1; j < n -1; ++j) {
|
for (int j = 1; j < n - 1; ++j) {
|
||||||
set (sq.x() + j, sq.y() + i, ' ');
|
set(sq.x() + j, sq.y() + i, ' ');
|
||||||
}
|
}
|
||||||
set(sq.x() + n - 1, sq.y() + i, '|');
|
set(sq.x() + n - 1, sq.y() + i, '|');
|
||||||
}
|
}
|
||||||
@@ -85,7 +103,7 @@ struct Grid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clear(Square const& sq) {
|
auto clear(Square const &sq) {
|
||||||
for (auto i = sq.x(); i < sq.x() + sq.length(); ++i) {
|
for (auto i = sq.x(); i < sq.x() + sq.length(); ++i) {
|
||||||
for (auto j = sq.y(); j < sq.y() + sq.length(); ++j) {
|
for (auto j = sq.y(); j < sq.y() + sq.length(); ++j) {
|
||||||
set(i, j, '.');
|
set(i, j, '.');
|
||||||
@@ -99,21 +117,31 @@ struct Grid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto fits(Square const& sq) const noexcept -> bool {
|
/** \brief Get length of the largest square that fits at \a pos in the grid.
|
||||||
assert (sq.x() < length_);
|
*/
|
||||||
assert (sq.y() < length_);
|
auto largest_square(Pos const &pos, int n) const noexcept -> int {
|
||||||
if (sq.x() + sq.length() > length_) { return false; }
|
assert(x(pos) < length_);
|
||||||
else if (sq.y() + sq.length() > length_) { return false; }
|
assert(y(pos) < length_);
|
||||||
else {
|
|
||||||
auto pos = grid_.begin() + sq.x() + sq.y() * length_;
|
/* Because of how we walk through the grid (starting at 0,0 then increasing
|
||||||
std::string_view v(pos, pos + sq.length());
|
* x followed by y) we can assume that if the position (b, y) is clear
|
||||||
return v.find_first_not_of('.') == std::string_view::npos;
|
* (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;
|
||||||
}
|
}
|
||||||
|
return b - x(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto next_pos(Pos const& pos) const noexcept -> Pos {
|
auto next_pos(Pos const &pos) const noexcept -> Pos {
|
||||||
if (const auto p = x(pos) + 1 + y(pos) * length_; p >= grid_.size()) { return std::make_pair(0, length_); }
|
if (const auto p = x(pos) + 1 + y(pos) * length_; p >= grid_.size()) { return std::make_pair(0, length_); } else {
|
||||||
else {
|
|
||||||
const auto next_p = grid_.find('.', p);
|
const auto next_p = grid_.find('.', p);
|
||||||
if (next_p == std::string::npos) {
|
if (next_p == std::string::npos) {
|
||||||
return std::make_pair(0, length_);
|
return std::make_pair(0, length_);
|
||||||
@@ -130,26 +158,33 @@ auto triangle_num(int n) { return (n * (n + 1)) / 2; }
|
|||||||
|
|
||||||
using Avail = std::vector<int>;
|
using Avail = std::vector<int>;
|
||||||
|
|
||||||
auto find_solution_impl(Grid& grid, int n, Pos const& pos, Avail& avail_sqs)-> bool {
|
auto find_solution_impl(Grid &grid, int n, Pos const &pos, Avail &avail_sqs) -> bool {
|
||||||
if (x(pos) == 0 && y(pos) == grid.length()) { return true; }
|
if (x(pos) == 0 && y(pos) == grid.length()) { return true; }
|
||||||
|
|
||||||
for (auto idx = n; idx != 0; --idx) {
|
/* 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; }
|
if (avail_sqs[idx] == 0) { continue; }
|
||||||
|
|
||||||
auto const sq = Square(pos, idx);
|
auto const sq = Square(pos, idx);
|
||||||
if (grid.fits(sq)) {
|
--avail_sqs[idx];
|
||||||
--avail_sqs[idx];
|
grid.add(sq);
|
||||||
grid.add(sq);
|
if (find_solution_impl(grid, n, grid.next_pos(pos), avail_sqs)) { return true; }
|
||||||
if (find_solution_impl(grid, n, grid.next_pos(pos), avail_sqs)) { return true; }
|
++avail_sqs[idx];
|
||||||
++avail_sqs[idx];
|
grid.clear(sq);
|
||||||
grid.clear(sq);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto find_solution(int n) -> Grid{
|
auto find_solution(int n) -> Grid {
|
||||||
auto length = triangle_num(n);
|
auto length = triangle_num(n);
|
||||||
Grid grid(length);
|
Grid grid(length);
|
||||||
Avail avail_sqs;
|
Avail avail_sqs;
|
||||||
@@ -158,8 +193,7 @@ auto find_solution(int n) -> Grid{
|
|||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char **argv) {
|
||||||
{
|
|
||||||
auto n = (argc == 1) ? 8 : atoi(argv[1]);
|
auto n = (argc == 1) ? 8 : atoi(argv[1]);
|
||||||
auto grid = find_solution(n);
|
auto grid = find_solution(n);
|
||||||
std::cout << "Partridge problem " << n << " side length " << grid.length() << '\n';
|
std::cout << "Partridge problem " << n << " side length " << grid.length() << '\n';
|
||||||
|
|||||||
Reference in New Issue
Block a user