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
+64 -19
View File
@@ -8,6 +8,7 @@
#include <algorithm>
#include <array>
#include <span>
#include <sstream>
#include <string>
@@ -16,6 +17,8 @@ namespace {
std::uint64_t x;
std::uint64_t y;
std::uint64_t side;
auto operator==(Placement const &) const noexcept -> bool = default;
};
struct Validation {
@@ -148,21 +151,6 @@ namespace {
return Results(side, std::move(squares));
}
auto construct_next_odd(std::uint64_t even_order,
std::vector<Placement> placements)
-> std::vector<Placement> {
auto const old_side = even_order * (even_order + 1) / 2;
auto const square_side = even_order + 1;
for (std::uint64_t y = 0; y < old_side; y += square_side) {
placements.push_back({old_side, y, square_side});
}
for (std::uint64_t x = 0; x <= old_side; x += square_side) {
placements.push_back({x, old_side, square_side});
}
return placements;
}
auto expect(bool condition, std::string const &message) -> int {
if (condition) {
return 0;
@@ -225,10 +213,64 @@ namespace {
}
auto test_construction() -> int {
auto const constructed = construct_next_odd(8, known_order_8());
auto const validation = validate(9, 45, 45, constructed);
return expect(validation.valid(),
"even-to-odd construction was rejected:\n" + validation.text());
auto predecessor = renderable_result(36, known_order_8());
auto const predecessor_count = predecessor.squares().size();
auto const constructed = construct_odd_solution(9, std::move(predecessor));
auto const converted = to_independent(constructed);
auto const validation = validate(9, constructed);
int failures = 0;
failures += expect(
validation.valid(),
"even-to-odd construction was rejected:\n" + validation.text());
failures += expect(constructed.length() == 45,
"constructed board has the wrong side length");
failures += expect(converted.placements.size() == predecessor_count + 9,
"construction did not add exactly nine squares");
auto const expected_border = std::array<Placement, 9>{{
{36, 0, 9}, {36, 9, 9}, {36, 18, 9}, {36, 27, 9},
{0, 36, 9}, {9, 36, 9}, {18, 36, 9}, {27, 36, 9},
{36, 36, 9},
}};
failures += expect(
std::ranges::equal(
std::span(converted.placements).subspan(predecessor_count),
expected_border),
"constructed border coordinates are incorrect");
failures += expect(
std::ranges::equal(
std::span(converted.placements).first(predecessor_count),
known_order_8()),
"construction translated predecessor coordinates unexpectedly");
return failures;
}
auto test_odd_solver_route() -> int {
SearchCounters even_counters;
SearchCounters odd_counters;
auto const even = find_solution_instrumented(8, even_counters);
auto const odd = find_solution_instrumented(9, odd_counters);
int failures = 0;
failures += expect(
!uses_odd_construction(1) && !uses_odd_construction(7) &&
!uses_odd_construction(8) && uses_odd_construction(9) &&
!uses_odd_construction(10) && uses_odd_construction(11),
"odd construction route does not preserve direct handling boundaries");
auto const validation = validate(9, odd);
failures += expect(validation.valid(),
"order-9 routed result is invalid:\n" + validation.text());
failures += expect(odd.squares().size() == even.squares().size() + 9,
"order-9 route did not construct from order 8");
failures += expect(
odd_counters.search_nodes == even_counters.search_nodes &&
odd_counters.loop_iterations == even_counters.loop_iterations &&
odd_counters.attempted_placements ==
even_counters.attempted_placements &&
odd_counters.backtracks == even_counters.backtracks,
"order-9 route did not perform exactly the order-8 search");
return failures;
}
auto test_rendering() -> int {
@@ -328,6 +370,9 @@ int main(int argc, char **argv) {
if (test == "construction") {
return test_construction();
}
if (test == "solver-odd-route") {
return test_odd_solver_route();
}
if (test == "rendering") {
return test_rendering();
}