Compare commits

...

9 Commits

Author SHA1 Message Date
65c748aab3 Update results file with latest timings. 2025-09-04 20:08:34 +02:00
5a01260281 Code tidy as reported by clang-tidy 2025-09-04 11:35:37 +02:00
ff43ba5287 Make add and clear use indices.
This is the last commit of the workstream and we
have a ~10% speed improvement.
2025-09-04 11:27:49 +02:00
f1445b7544 Make next_pos iterate over indices. 2025-09-04 11:12:51 +02:00
ed71280dc2 Convert largest_square to use pos indices.
This makes the code a bit more confusing but no functional change.
2025-09-04 11:09:22 +02:00
a066fdb552 Convert Pos to an index.
Pos is now just an index into the grid.

This will be a slow down for the moment as we haven't worked through the
 changes to the algorithms yet.
2025-09-04 11:02:54 +02:00
e557933590 [1/n]: Refactor x() & y() calls on position
The goal of this sequence of patches is to end up representing a
position as a single integer index into the grid instead of an (x, y)
pair.  I believe this will have better performance.

First step move all calls of x(), y() into calls on methods of Results &
Grid as in the future these will need to use the length of the grid.
2025-09-04 10:53:20 +02:00
65569982ee Abstract away types and use 64-bit everywhere
We abstract away the types being used from 'int' and 'char'.  The one
exception being for output.

This allows us to experiment to see if using different types can improve
performance.

It turns out it does - and that using 64-bit integers everywhere is a
good idea.
2025-09-03 09:30:33 +02:00
4b9feefbf4 Split out results structure
We now return the results in a separate structure to the grid we have
been working on.

This ultimately makes it easier to play with the implementation of the
solution finder.
2025-09-03 08:59:06 +02:00
2 changed files with 150 additions and 129 deletions

261
main.cc
View File

@@ -1,6 +1,6 @@
/** \file main.cc
* \author Matthew Gretton-Dann
* \brief Solves the partirige problem for user specified size.
* \brief Solves the Partridge problem for user specified size.
*
* Copyright 2025, Matthew-Gretton-Dann
* SPDX: Apache-2.0
@@ -11,17 +11,12 @@
#include <string_view>
#include <vector>
#include <iostream>
#include <set>
namespace {
using size_t = std::uint64_t;
/** (x, y) pair storing a position. */
using Pos = std::pair<int, int>;
/** 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; }
using Pos = size_t;
/** A square - consisting of position of closest corner to origin, and side-length.
*/
@@ -30,7 +25,7 @@ namespace {
* \param pos Position of closest corner to origin
* \param length Side length.
*/
Square(Pos const &pos, int const length) noexcept : pos_(pos), length_(length) {
Square(Pos pos, size_t const length) noexcept : pos_(pos), length_(length) {
}
Square(Square const &other) noexcept = default;
@@ -44,23 +39,101 @@ namespace {
~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_); }
[[nodiscard]] auto pos() const noexcept -> Pos { return pos_; }
/** Get side length. */
auto length() const noexcept -> int { return length_; }
[[nodiscard]] auto length() const noexcept -> size_t { return length_; }
private:
Pos pos_; ///< Position of corner closest to origin
int length_; ///< Side length
size_t length_; ///< Side length
};
/** Structure holding the results.
*/
struct Results {
Results(size_t length, std::vector<Square> squares) : length_(length), squares_(std::move(squares)) {
}
Results(Results const &other) noexcept = delete;
Results &operator=(Results const &other) noexcept = delete;
Results &operator=(Results &&other) noexcept = default;
Results(Results &&other) noexcept = default;
~Results() noexcept = default;
[[nodiscard]] auto length() const noexcept -> size_t { return length_; }
/** Output the grid. */
auto output() const -> void {
std::string out(length_ * length_, '.');
for (auto const &sq: squares_) {
prettify_sq(out, sq);
}
for (size_t idx = 0; idx < length_ * length_; idx += length_) {
std::cout << std::string_view(out.data() + idx, length_) << '\n';
}
}
private:
auto set(std::string &s, size_t x, size_t y, char c) const noexcept -> void {
assert(x < length_);
assert(y < length_);
assert(grid_[x + y * length_] != c);
s[x + y * length_] = c;
}
[[nodiscard]] auto sq_x(Square const &sq) const noexcept -> size_t { return sq.pos() % length_; }
[[nodiscard]] auto sq_y(Square const &sq) const noexcept -> size_t { return sq.pos() / length_; }
auto prettify_sq(std::string &s, Square const &sq) const noexcept -> void {
switch (sq.length()) {
case 1: set(s, sq_x(sq), sq_y(sq), '*');
break;
case 2: set(s, sq_x(sq), sq_y(sq), '+');
set(s, sq_x(sq) + 1, sq_y(sq), '+');
set(s, sq_x(sq), sq_y(sq) + 1, '+');
set(s, sq_x(sq) + 1, sq_y(sq) + 1, '+');
break;
default: {
auto n = sq.length();
set(s, sq_x(sq), sq_y(sq), '+');
set(s, sq_x(sq) + n - 1, sq_y(sq), '+');
set(s, sq_x(sq), sq_y(sq) + n - 1, '+');
set(s, sq_x(sq) + n - 1, sq_y(sq) + n - 1, '+');
for (size_t i = 1; i < n - 1; ++i) {
set(s, sq_x(sq) + i, sq_y(sq), '-');
set(s, sq_x(sq) + i, sq_y(sq) + n - 1, '-');
set(s, sq_x(sq), sq_y(sq) + i, '|');
for (size_t j = 1; j < n - 1; ++j) {
set(s, sq_x(sq) + j, sq_y(sq) + i, ' ');
}
set(s, sq_x(sq) + n - 1, sq_y(sq) + i, '|');
}
size_t i = sq_x(sq) + n - 1;
while (n != 0) {
set(s, --i, sq_y(sq) + 1, static_cast<char>('0' + static_cast<char>(n % 10)));
n /= 10;
}
}
}
}
size_t length_;
std::vector<Square> squares_;
};
/** An N * N grid of characters. */
struct Grid {
// Type to use for the grid contents
using T = std::int_fast64_t;
/** Construct a grid of given side-length. */
Grid(int length) : grid_(length * length, empty), length_(length) {
explicit Grid(size_t length) : grid_(length * length, empty), length_(length) {
}
Grid(Grid const &other) = delete;
@@ -74,47 +147,35 @@ namespace {
~Grid() noexcept = default;
/** Get grid length */
auto length() const noexcept -> int { return length_; }
[[nodiscard]] auto end() const noexcept -> size_t { return static_cast<size_t>(grid_.size()); }
/** 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());
/* One would expect the fastest way to do this would be to have x be the
* fastest increasing index so we store [pos, pos + 1,..., pos+length, ...]
* But experimentation tells us this isn't so, and storing
* [pos, pos + length, ..., pos + 1, ...] is faster!
*/
for (auto x = 0; x < sq.length(); ++x) {
for (auto y = sq.pos(); y < sq.pos() + sq.length() * length_; y += length_) {
grid_[x + y] = filled;
}
}
}
/** 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);
for (auto x = 0; x < sq.length(); ++x) {
for (auto y = sq.pos(); y < sq.pos() + sq.length() * length_; y += length_) {
grid_[x + y] = 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_);
[[nodiscard]] auto largest_square(Pos pos, size_t n) const noexcept -> size_t {
assert(pos < end());
/* 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
@@ -123,93 +184,51 @@ namespace {
* This means we only need to look for the first non-clear position along the
* current row.
*/
auto b = x(pos);
auto const pos_x = pos % length_;
auto const pos_y0 = pos - pos_x;
auto b = pos;
// Make sure we don't go looking in the next row.
auto e = std::min(x(pos) + n, length_);
auto const e = std::min(pos + n, pos_y0 + length_);
while (b < e) {
if (grid_[b + y(pos) * length_] != '.') { break; }
if (grid_[b] != empty) { 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);
auto const len = b - pos;
auto const pos_y = pos / length_;
auto const ye = std::min(pos_y + len, length_);
return ye - pos_y;
}
/** Get the next position to check. n is the size of the square we just
* added.
/** Get the next position to check starting at pos.
*
* Returns grid_.length() if no more positions available.
*/
auto next_pos(Pos const &pos, int n) const noexcept -> Pos {
auto const b = grid_.begin() + x(pos) + n + y(pos) * length_;
[[nodiscard]] auto next_pos(Pos pos) const noexcept -> Pos {
auto const b = grid_.begin() + static_cast<std::ptrdiff_t>(pos);
auto const p = std::find(b, grid_.end(), empty);
auto const v = p - grid_.begin();
return std::make_pair(v % length_, v / length_);
return p - grid_.begin();
}
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, '|');
}
std::vector<T> grid_; ///< The grid
size_t length_; ///< Side length
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<char> grid_; ///< The grid
int length_; ///< Side length
static constexpr char empty = '.'; ///< Character used for an empty cell.
static constexpr char empty = 0; ///< Character used for an empty cell.
static constexpr char filled = 1; ///< Character used for a filled cell,
};
/** Get the n'th triangular number. */
auto triangle_num(int n) noexcept -> int { return (n * (n + 1)) / 2; }
/** Get the n-th triangular number. */
auto triangle_num(size_t n) noexcept -> size_t { return (n * (n + 1)) / 2; }
/** Vector used to identify the available squares. */
using Avail = std::vector<int>;
using Avail = std::vector<size_t>;
/** Find a solution to the \a n th Partridge problem.
*
* Returns the grid of the solution.
*/
auto find_solution(int const n) noexcept -> Grid {
auto find_solution(size_t const n) noexcept -> Results {
/* Implementation is iterative, as opposed to recursive.
*
* The recursive implementation is easier to understand - but is
@@ -221,7 +240,7 @@ namespace {
* available squares until we find one that fits.
*/
// grid is our inprogress grid of square positions.
// grid is our in-progress grid of square positions.
auto const length = triangle_num(n);
Grid grid(length);
@@ -238,13 +257,13 @@ namespace {
sqs.reserve(length);
// Start at the origin with a square of longest side length.
Pos pos{0, 0};
int idx = n;
Pos pos = 0;
size_t 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.
* it from the grid and try the next smaller size in the same position.
*/
if (idx == 0) {
// No squares on the stack -> failed to find a solution.
@@ -254,13 +273,16 @@ namespace {
sqs.pop_back();
grid.clear(sq);
++avail_sqs[sq.length()];
pos = std::make_pair(sq.x(), sq.y());
pos = sq.pos();
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; }
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.
@@ -270,21 +292,20 @@ namespace {
grid.add(sq);
sqs.push_back(sq);
pos = grid.next_pos(pos, idx);
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; }
if (pos == grid.end()) { break; }
}
grid.prettify();
return grid;
return {length, sqs};
}
} // anon namespace
int main(int argc, char **argv) {
auto n = (argc == 1) ? 8 : atoi(argv[1]);
auto grid = find_solution(n);
auto n = (argc == 1) ? 8 : std::atol(argv[1]);
auto const grid = find_solution(n);
std::cout << "Partridge problem " << n << " side length " << grid.length() << '\n';
grid.output();
return 0;

View File

@@ -5,9 +5,9 @@ The following show some of the results produced by running `partridge_cpp`.
# Partridge 8
Timings on Macbook Air M1:
* 2.05s user
* 0.01s system
* 2.063 total
* 1.76s user
* 0.00s system
* 1.765 total
```text
+------++------++------++------++--+
@@ -50,9 +50,9 @@ Timings on Macbook Air M1:
# Partridge 9
Timings on Macbook Air M1:
* 176.48s user
* 0.25s system
* 2:59.03 total
* 156.99s user
* 0.18s system
* 2:38.69 total
```text
+-------++-------++-------++-------++-------+
@@ -105,9 +105,9 @@ Timings on Macbook Air M1:
# Partridge 10
Timings on Macbook Air M1:
* 30578.68s user
* 88.19s system
* 8:40:24.11 total
* 23828.35s user
* 5.62s system
* 6:38:02.83 total
```text
+--------++--------++--------++--------++--------++---+