From 472fca06556aa15c66ae9877ea073d9813ead997 Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Sat, 23 Sep 2023 14:09:32 +0800 Subject: [PATCH] update: enhance BasicRanges module --- src/core/all_cases/all_cases.h | 74 ++++++++++++++ src/core/all_cases/basic_ranges.cc | 153 ++++++++++++++--------------- src/core/all_cases/basic_ranges.h | 80 --------------- src/core/all_cases/reverse.h | 20 ++++ src/core/main.cc | 41 ++------ 5 files changed, 179 insertions(+), 189 deletions(-) create mode 100644 src/core/all_cases/all_cases.h delete mode 100644 src/core/all_cases/basic_ranges.h create mode 100644 src/core/all_cases/reverse.h diff --git a/src/core/all_cases/all_cases.h b/src/core/all_cases/all_cases.h new file mode 100644 index 0000000..795d22b --- /dev/null +++ b/src/core/all_cases/all_cases.h @@ -0,0 +1,74 @@ +#pragma once + +/// Based on the requirements of valid klotski, the 2x2 block that must exist +/// and only one, witch will occupy 4 empty slots, and the remaining 16 slots +/// will be allocated to 1x2, 2x1, 1x1 and space. Then, according to the rules +/// of CommonCode, they are coded as `00`, `01`, `10`, `11` respectively, and +/// the remaining positions are filled with 0 and stored as 32-bit variables. + +/// As we all know, a space or 1x1 block will occupy 1 slot, 1x2 and 2x1 block +/// will occupy 2 slots, and together they fill 16 positions, so all possible +/// combinations can be calculated, this number is 204, each combination can +/// produce different permutations. After mathematical calculations, there are +/// a total of 7311921 possible permutations. The goal of BasicRanges is to +/// find these permutations, sort them and store in a uint32_t array. + +/// AllCases is used to generate all valid CommonCodes, its works based on all +/// permutations generated by BasicRanges, which will use different 2x2 block +/// positions to check respectively. On the 5x4 chessboard, it has 12 possible +/// positions, and the numbers are distributed in 0 ~ 15, witch called `head`. +/// For the convenience of calculation, here use an array of length 16 for +/// storage, of course, the position of 3/7/11/15 will be empty. + +/// 00 01 02 03 +/// 04 05 06 07 00 01 02 +/// 08 09 10 11 04 05 06 <- head of 2x2 block +/// 12 13 14 15 08 09 10 +/// 16 17 18 19 12 13 14 + +/// After all the work is done, we will have 29334498 cases, distributed in 16 +/// arrays. Each of them is a uint32_t array storing the ranges, this is to +/// save memory, otherwise the 64-bit length must be consumed. + +/// By the way, due to the performance considerations of the checking process, +/// the resulting data of BasicRanges will be flipped every 2-bit, which will +/// not consume too much time, but it can almost double the speed of the case +/// checking subsequent. + +#include +#include +#include + +namespace klotski { +namespace cases { + +typedef uint32_t Range; +typedef std::vector Ranges; + +const auto BASIC_RANGES_NUM = 7311921; + +class BasicRanges { +public: + void Build(); + const Ranges& Fetch() noexcept; + bool IsAvailable() const noexcept; + + BasicRanges(BasicRanges&&) = delete; + BasicRanges(const BasicRanges&) = delete; + BasicRanges& operator=(BasicRanges&&) = delete; + BasicRanges& operator=(const BasicRanges&) = delete; + + static BasicRanges& Instance() noexcept; + +private: + std::mutex building_; + bool available_ = false; + + BasicRanges() = default; + static Ranges& GetRanges() noexcept; + static void BuildRanges(Ranges &ranges); + static void SpawnRanges(Ranges&, int, int, int, int) noexcept; +}; + +} // namespace cases +} // namespace klotski diff --git a/src/core/all_cases/basic_ranges.cc b/src/core/all_cases/basic_ranges.cc index c160be1..f661823 100644 --- a/src/core/all_cases/basic_ranges.cc +++ b/src/core/all_cases/basic_ranges.cc @@ -1,121 +1,120 @@ #include -#include #include -#include "basic_ranges.h" +#include "reverse.h" +#include "all_cases.h" -#include +namespace klotski { +namespace cases { -typedef uint32_t Range; -typedef std::vector Ranges; typedef std::vector::iterator RangeIter; typedef std::tuple RangeType; - -static const auto RangeTypeNum = 204; - -static const uint32_t BASIC_RANGES_NUM = 7311921; - -class BasicRanges { -public: - static void build_ranges(); - - static const Ranges& fetch(); - -private: - static Ranges data_; - - static void spawn_ranges(int, int, int, int); -}; - -Ranges BasicRanges::data_; - -static Range range_reverse(Range bin) noexcept { -#if defined(__GNUC__) || defined(__clang__) - bin = __builtin_bswap32(bin); - // TODO: using `std::byteswap` (c++23) -#else - // FIXME: `_byteswap_ulong` under MSVC - bin = ((bin << 16) & 0xFFFF0000) | ((bin >> 16) & 0x0000FFFF); - bin = ((bin << 8) & 0xFF00FF00) | ((bin >> 8) & 0x00FF00FF); -#endif - bin = ((bin << 4) & 0xF0F0F0F0) | ((bin >> 4) & 0x0F0F0F0F); - return ((bin << 2) & 0xCCCCCCCC) | ((bin >> 2) & 0x33333333); -} - -consteval static std::array basic_types() { - std::array data; - for (int i = 0, n = 0; n <= 7; ++n) // number of 1x2 and 2x1 block -> 0 ~ 7 - for (int n_2x1 = 0; n_2x1 <= n; ++n_2x1) // number of 2x1 block -> 0 ~ n - for (int n_1x1 = 0; n_1x1 <= (14 - n * 2); ++n_1x1) // number of 1x1 block -> 0 ~ (14 - 2n) +typedef std::array RangeTypes; + +/// Calculate all possible basic-ranges permutations. +consteval static RangeTypes range_types() { + RangeTypes data; + for (int i = 0, n = 0; n <= 7; ++n) // 1x2 and 2x1 -> 0 ~ 7 + for (int n_2x1 = 0; n_2x1 <= n; ++n_2x1) // 2x1 -> 0 ~ n + for (int n_1x1 = 0; n_1x1 <= (14 - n * 2); ++n_1x1) // 1x1 -> 0 ~ (14 - 2n) data[i++] = {16 - n * 2 - n_1x1, n - n_2x1, n_2x1, n_1x1}; return data; } -static void combine_sort(RangeIter begin, RangeIter mid, RangeIter end) { - Ranges tmp = {begin, mid}; +/// Combine two consecutive sorted arrays into one sorted arrays. +static void combine_sort(RangeIter begin, RangeIter mid, RangeIter end) noexcept { + Ranges tmp = {begin, mid}; // left array backup auto p = tmp.begin(); for (;;) { - if (*p < *mid) { - *(begin++) = *(p++); - if (p == tmp.end()) + if (*p <= *mid) { + *(begin++) = *(p++); // stored in original span + if (p == tmp.end()) // left array is consumed return; - } else { - *(begin++) = *(mid++); - if (mid == end) { - std::copy(p, tmp.end(), begin); - return; - } + continue; + } + *(begin++) = *(mid++); // stored in original span + if (mid == end) { // right array is consumed + std::copy(p, tmp.end(), begin); // left array remaining + return; } } } -void BasicRanges::spawn_ranges(int n1, int n2, int n3, int n4) { +/// Spawn all ranges of specified conditions. +void BasicRanges::SpawnRanges(Ranges &ranges, int n1, int n2, int n3, int n4) noexcept { + auto num = n1 + n2 + n3 + n4; + auto offset = (16 - num) << 1; // offset of low bits + std::vector series; - auto n = n1 + n2 + n3 + n4; - auto offset = (16 - n) << 1; + series.reserve(num); series.insert(series.end(), n1, 0b00); series.insert(series.end(), n2, 0b01); series.insert(series.end(), n3, 0b10); series.insert(series.end(), n4, 0b11); - do { + do { // full permutation traversal uint32_t range = 0; - for (auto x : series) + for (auto x : series) // store every 2-bit (range <<= 2) |= x; - data_.emplace_back(range << offset); + ranges.emplace_back(range << offset); } while (next_permutation(series.begin(), series.end())); } -void BasicRanges::build_ranges() { - data_.reserve(BASIC_RANGES_NUM); - std::list flags {data_.begin()}; +/// Search and sort all possible basic-ranges permutations. +void BasicRanges::BuildRanges(Ranges &ranges) { + ranges.clear(); + ranges.reserve(BASIC_RANGES_NUM); + std::list flags {ranges.begin()}; // mark ordered interval - for (auto &t : basic_types()) { - spawn_ranges(std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t)); - flags.emplace_back(data_.end()); + for (auto &t : range_types()) { + SpawnRanges(ranges, std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t)); + flags.emplace_back(ranges.end()); } do { - std::list::iterator begin = flags.begin(), mid, end; + decltype(flags.begin()) begin = flags.begin(), mid, end; while (++(mid = begin) != flags.end() && ++(end = mid) != flags.end()) { - combine_sort(*begin, *mid, *end); + combine_sort(*begin, *mid, *end); // merge two ordered interval flags.erase(mid); begin = end; } - } while (flags.size() > 2); + } while (flags.size() > 2); // merge until only one interval remains + + for (auto &x : ranges) { + x = range_reverse(x); // flip every 2-bit + } +} - for (auto &x : data_) { - x = range_reverse(x); +/// Execute the build process and ensure thread safety. +void BasicRanges::Build() { + if (!available_) { + if (building_.try_lock()) { // mutex lock success + BuildRanges(GetRanges()); + available_ = true; + } else { + building_.lock(); // blocking waiting + } + building_.unlock(); // release mutex } } -const Ranges& BasicRanges::fetch() { - return data_; +Ranges& BasicRanges::GetRanges() noexcept { + static Ranges ranges; + return ranges; } -void demo() { - BasicRanges::build_ranges(); +BasicRanges& BasicRanges::Instance() noexcept { + static BasicRanges instance; + return instance; +} - for (auto x : BasicRanges::fetch()) { - printf("%08X\n", x); - } +const Ranges& BasicRanges::Fetch() noexcept { + Build(); + return GetRanges(); } + +bool BasicRanges::IsAvailable() const noexcept { + return available_; +} + +} // namespace cases +} // namespace klotski diff --git a/src/core/all_cases/basic_ranges.h b/src/core/all_cases/basic_ranges.h deleted file mode 100644 index baa32a5..0000000 --- a/src/core/all_cases/basic_ranges.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -/// Based on the requirements of valid klotski, the `2x2` block that must exist -/// and only one, witch will occupy 4 empty slots, and the remaining 16 slots -/// will be allocated to space, `1x2`, `2x1` and `1x1`. Then, according to the -/// rules of CommonCode, they are coded as `00` `01` `10` `11` respectively, and -/// the remaining positions are filled with `0` and stored as 32-bit variables. - -/// As we all know, a space or `1x1` block will occupy 1 slot, `1x2` or `2x1` -/// block will occupy 2 slots, and together they fill 16 positions, so all -/// possible combinations can be calculated, this number is 204. Each combination -/// can produce different permutations. After verification, there are a total of -/// 7311921 possible permutations. The goal of BasicRanges is to find these -/// permutations, sort them and store them in a `uint32_t` array. - -/// In terms of algorithms, there are two options: the first is to generate -/// out-of-order data and then quickly arrange them; the second is to generate -/// ordered data for 204 combinations, and then merge and sort them. After testing, -/// the former is faster in generation (consuming T time), but it will consume -/// more time in sorting (about 7T), and the latter will cost about 2T in -/// generation due to the loss of the tree structure queue. But it can save more -/// time in sorting, which is about 2T, so the second solution will get the result -/// faster. - -/// Finally, due to the performance considerations of AllCases, the resulting data -/// will be flipped every two bits, which will not consume too much time (less than -/// 10% of T), but can almost double the speed of the subsequent `check_range`. - -#include -#include -#include - -void demo(); - -namespace klotski { - -/// basic ranges count - const uint32_t BASIC_RANGES_SIZE = 7311921; - -// class BasicRanges { -// public: -// /// Three basic states, one-way transition. -// /// {NOT_INIT} -> {BUILDING} -> {AVAILABLE} -// enum Status { -// NOT_INIT, -// BUILDING, -// AVAILABLE, -// }; -// typedef std::vector basic_ranges_t; -// -// /// Trigger the build process, from `NOT_INIT` to `BUILDING`. -// static void build(); -// -// /// Get current status of BasicRanges. -// static Status status() noexcept; -// -// /// Blocking access to constructed data. -// static const basic_ranges_t& fetch(); -// -// private: -// static bool available_; -// static std::mutex building_; -// static basic_ranges_t data_; -// -// static void build_data(); -// -// public: -// /// The number of types of blocks. -// struct generate_t { -// int n1; // number of `00` -> space -// int n2; // number of `01` -> 1x2 block -// int n3; // number of `10` -> 2x1 block -// int n4; // number of `11` -> 1x1 block -// }; -// -// /// Generate all basic-ranges of the specified type. -// static void generate(basic_ranges_t &release, generate_t info); -// }; - -} // namespace klotski diff --git a/src/core/all_cases/reverse.h b/src/core/all_cases/reverse.h new file mode 100644 index 0000000..dfcaa13 --- /dev/null +++ b/src/core/all_cases/reverse.h @@ -0,0 +1,20 @@ +#pragma once + +namespace klotski { +namespace cases { + +inline uint32_t range_reverse(uint32_t bin) noexcept { +#if defined(__GNUC__) || defined(__clang__) + bin = __builtin_bswap32(bin); + // TODO: using `std::byteswap` (c++23) +#else + // FIXME: `_byteswap_ulong` under MSVC + bin = ((bin << 16) & 0xFFFF0000) | ((bin >> 16) & 0x0000FFFF); + bin = ((bin << 8) & 0xFF00FF00) | ((bin >> 8) & 0x00FF00FF); +#endif + bin = ((bin << 4) & 0xF0F0F0F0) | ((bin >> 4) & 0x0F0F0F0F); + return ((bin << 2) & 0xCCCCCCCC) | ((bin >> 2) & 0x33333333); +} + +} // namespace cases +} // namespace klotski diff --git a/src/core/main.cc b/src/core/main.cc index 2069348..c3a5b52 100644 --- a/src/core/main.cc +++ b/src/core/main.cc @@ -1,43 +1,20 @@ #include -#include -#include "all_cases/basic_ranges.h" +#include "all_cases/all_cases.h" -int main() { +using klotski::cases::BasicRanges; +int main() { auto start = clock(); - demo(); - - -// for (int n = 0; n <= 7; ++n) // number of 1x2 and 2x1 block -> 0 ~ 7 -// for (int n_2x1 = 0; n_2x1 <= n; ++n_2x1) // number of 2x1 block -> 0 ~ n -// for (int n_1x1 = 0; n_1x1 <= (14 - n * 2); ++n_1x1) // number of 1x1 block -> 0 ~ (14 - 2n) -// build(16 - n * 2 - n_1x1, n - n_2x1, n_2x1, n_1x1); - - - -// generate(generate_t { // generate target ranges -// .n1 = 16 - n * 2 - n_1x1, /// space -> 00 -// .n2 = n - n_2x1, /// 1x2 -> 01 -// .n3 = n_2x1, /// 2x1 -> 10 -// .n4 = n_1x1, /// 1x1 -> 11 -// }); - - -// std::stable_sort(result.begin(), result.end()); - -// build(4, 2, 1, 6); +// std::cout << BasicRanges::Instance().IsAvailable() << std::endl; +// BasicRanges::Instance().Build(); +// std::cout << BasicRanges::Instance().IsAvailable() << std::endl; + for (auto x : BasicRanges::Instance().Fetch()) { + printf("%08X\n", x); + } std::cerr << ((clock() - start) * 1000 / CLOCKS_PER_SEC) << "ms" << std::endl; -// std::cout << result.size() << std::endl; - -// std::vector demo {0b00, 0b01, 0b10, 0b11}; - -// do { -// std::cout << demo[0] << " " << demo[1] << " " << demo[2] << " " << demo[3] << std::endl; -// } while (next_permutation(demo.begin(), demo.end())); - return 0; }