solver: construct odd-order solutions

Avoid repeating the exponential search for odd orders at least nine. Search the even predecessor, translate its row-major placements to the enlarged board, and tile the new border.

Keep direct search and construction explicit so benchmarks can report their costs separately. Verify the routed order-9 result independently and require its search counters to match order 8.

Tests: Release, Debug, ASan and UBSan CTest (8 passed each)

Refs: #6
This commit was merged in pull request #21.
This commit is contained in:
Codex instance
2026-07-30 17:24:06 +01:00
parent ddf07e730a
commit 3e667d6de0
6 changed files with 189 additions and 44 deletions
+51 -5
View File
@@ -245,12 +245,12 @@ namespace {
size_t completed_tasks = 0;
};
/** Find a solution to the \a n th Partridge problem.
/** Search directly for a solution to the \a n th Partridge problem.
*
* Returns the grid of the solution.
*/
template<bool Instrument>
auto find_solution_impl(size_t const n, SearchCounters *const counters) noexcept
auto search_solution_impl(size_t const n, SearchCounters *const counters) noexcept
-> Results {
/* Implementation is iterative, as opposed to recursive.
*
@@ -343,14 +343,60 @@ namespace {
return {length, sqs};
}
auto search_solution(size_t const n) noexcept -> Results {
return search_solution_impl<false>(n, nullptr);
}
auto search_solution_instrumented(size_t const n,
SearchCounters &counters) noexcept -> Results {
counters = {};
return search_solution_impl<true>(n, &counters);
}
/** Construct an odd-order solution from its even-order predecessor. */
auto construct_odd_solution(size_t const odd_order, Results predecessor)
-> Results {
assert(odd_order >= 9);
assert(odd_order % 2 == 1);
assert(predecessor.length() == triangle_num(odd_order - 1));
auto const old_length = predecessor.length();
auto const new_length = triangle_num(odd_order);
std::vector<Square> squares;
squares.reserve(predecessor.squares().size() + odd_order);
for (auto const &square: predecessor.squares()) {
auto const x = square.pos() % old_length;
auto const y = square.pos() / old_length;
squares.emplace_back(x + y * new_length, square.length());
}
for (size_t y = 0; y < old_length; y += odd_order) {
squares.emplace_back(old_length + y * new_length, odd_order);
}
for (size_t x = 0; x <= old_length; x += odd_order) {
squares.emplace_back(x + old_length * new_length, odd_order);
}
return {new_length, std::move(squares)};
}
[[nodiscard]] auto uses_odd_construction(size_t const n) noexcept -> bool {
return n >= 9 && n % 2 == 1;
}
auto find_solution(size_t const n) noexcept -> Results {
return find_solution_impl<false>(n, nullptr);
if (uses_odd_construction(n)) {
return construct_odd_solution(n, search_solution(n - 1));
}
return search_solution(n);
}
auto find_solution_instrumented(size_t const n,
SearchCounters &counters) noexcept -> Results {
counters = {};
return find_solution_impl<true>(n, &counters);
if (uses_odd_construction(n)) {
return construct_odd_solution(
n, search_solution_instrumented(n - 1, counters));
}
return search_solution_instrumented(n, counters);
}
} // anon namespace