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:
+64
-19
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user