From 86d6c0c3ca2720dd956604123d8e6082b343b0fa Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Sat, 8 Apr 2023 14:10:52 +0800 Subject: [PATCH] update: enhance AllCases module --- src/klotski_core/all_cases/all_cases.cc | 78 +++++++++++++--------- src/klotski_core/all_cases/all_cases.h | 71 ++++++++++---------- src/klotski_core/all_cases/basic_ranges.cc | 76 +++++++++++---------- src/klotski_core/all_cases/basic_ranges.h | 70 ++++++++++--------- src/klotski_core/benchmark/benchmark.cc | 4 +- test/basic/all_cases.cc | 4 +- 6 files changed, 170 insertions(+), 133 deletions(-) diff --git a/src/klotski_core/all_cases/all_cases.cc b/src/klotski_core/all_cases/all_cases.cc index d077b7f..41c4838 100644 --- a/src/klotski_core/all_cases/all_cases.cc +++ b/src/klotski_core/all_cases/all_cases.cc @@ -1,67 +1,85 @@ #include "common.h" #include "all_cases.h" -using klotski::AllCases; +namespace klotski { + +using Common::check_range; +using Common::range_reverse; /// static variable initialize -std::mutex AllCases::building; -bool AllCases::available = false; -std::vector AllCases::data[]; +std::mutex AllCases::building_; +bool AllCases::available_ = false; +std::vector AllCases::data_[]; -const std::vector (&AllCases::fetch())[16] { // get all cases content +const std::vector (&AllCases::fetch())[16] { if (status() != AVAILABLE) { - AllCases::build(); // all cases initialize + build(); } - return AllCases::data; // return const ref + return data_; // return const reference } -AllCases::Status AllCases::status() { // get all cases status - if (AllCases::available) { - return AVAILABLE; // all cases already built +AllCases::Status AllCases::status() { + if (available_) { + return AVAILABLE; // data already built } - if (!AllCases::building.try_lock()) { // fail to lock mutex + if (!building_.try_lock()) { // fail to lock mutex return BUILDING; // another thread working } - AllCases::building.unlock(); // release mutex - return NO_INIT; + building_.unlock(); // release mutex + return NOT_INIT; } -void AllCases::build() { // ensure that all cases available - if (!AllCases::available) { - if (AllCases::building.try_lock()) { // mutex lock success +void AllCases::build() { // ensure that data is available + if (!available_) { + if (building_.try_lock()) { // mutex lock success build_data(); // start build process - AllCases::available = true; // set available flag + available_ = true; } else { - AllCases::building.lock(); // blocking waiting + building_.lock(); // blocking waiting + } + building_.unlock(); + } +} + +std::vector AllCases::release() { + std::vector data; + data.reserve(ALL_CASES_SIZE_SUM); // memory pre-allocated + for (uint64_t head = 0; head < 16; ++head) { + for (const auto &range : fetch()[head]) { + data.emplace_back(head << 32 | range); // release common codes } - AllCases::building.unlock(); } + return data; } -void AllCases::build_data() { // find all cases +/// Search all possible layouts based on basic-ranges. +void AllCases::build_data() { const auto &basic_ranges = BasicRanges::fetch(); for (uint32_t head = 0; head < 15; ++head) { // address of 2x2 block + /// head -> 0/1/2 / 4/5/6 / 8/9/10 / 12/13/14 if ((head & 0b11) == 0b11) { ++head; // skip invalid address } - /// head -> 0/1/2 / 4/5/6 / 8/9/10 / 12/13/14 - data[head].reserve(ALL_CASES_SIZE[head]); // memory pre-allocated - /// head(4-bits) + basic ranges(32-bits) --check--> valid cases + data_[head].reserve(ALL_CASES_SIZE[head]); // memory pre-allocated + + /// head(4-bit) + basic-range(32-bit) --check--> valid cases for (uint32_t index = 0; index < basic_ranges.size(); ++index) { - auto broken_offset = Common::check_range(head, basic_ranges[index]); + auto broken_offset = check_range(head, basic_ranges[index]); if (broken_offset) { // case invalid auto delta = (uint32_t)1 << (32 - broken_offset * 2); // delta to next possible range /// !! -> broken /// ( xx xx xx ) xx xx xx ... (reversed range) /// +1 00 00 00 ... (delta) - auto next_min = (Common::range_reverse(basic_ranges[index]) & ~(delta - 1)) + delta; - while (Common::range_reverse(basic_ranges[++index]) < next_min); // located next range + auto at_least = (range_reverse(basic_ranges[index]) & ~(delta - 1)) + delta; + while (range_reverse(basic_ranges[++index]) < at_least); // located next range --index; - } else { - AllCases::data[head].emplace_back( - Common::range_reverse(basic_ranges[index]) // release valid cases - ); + continue; } + data_[head].emplace_back( + range_reverse(basic_ranges[index]) // release valid cases + ); } } } + +} // namespace klotski diff --git a/src/klotski_core/all_cases/all_cases.h b/src/klotski_core/all_cases/all_cases.h index 7c5a0ca..62731f0 100644 --- a/src/klotski_core/all_cases/all_cases.h +++ b/src/klotski_core/all_cases/all_cases.h @@ -14,15 +14,8 @@ /// 16 17 18 19 12 13 14 /// After checking, each head has a different valid `range`, and they are -/// stored in different arrays to save memory (otherwise the 64-bits length -/// must be consumed), and all CommonCodes can be exported by using the -/// following code, which is also integrated in FFI. - -/// for (uint64_t head = 0; head < 16; ++head) { -/// for (const auto &range : AllCases::fetch()[head]) { -/// printf("%09lX\n", head << 32 | range); -/// } -/// } +/// stored in different arrays to save memory (otherwise the 64-bit length +/// must be consumed). #include #include @@ -31,28 +24,38 @@ #include "basic_ranges.h" namespace klotski { - /// all cases count - const uint32_t ALL_CASES_SIZE[16] = { - 2942906, 2260392, 2942906, 0, - 2322050, 1876945, 2322050, 0, - 2322050, 1876945, 2322050, 0, - 2942906, 2260392, 2942906, 0, - }; - const uint32_t ALL_CASES_SIZE_SUM = std::accumulate( // aka 29334498 - ALL_CASES_SIZE, ALL_CASES_SIZE + 16, (uint32_t)0 - ); - - class AllCases : public BasicRanges { - public: - static void build(); - static Status status(); - static const std::vector (&fetch())[16]; - - private: - static bool available; - static std::mutex building; - static std::vector data[16]; - - static void build_data(); - }; -} + +/// all cases count +const uint32_t ALL_CASES_SIZE[16] = { + 2942906, 2260392, 2942906, 0, + 2322050, 1876945, 2322050, 0, + 2322050, 1876945, 2322050, 0, + 2942906, 2260392, 2942906, 0, +}; +const uint32_t ALL_CASES_SIZE_SUM = std::accumulate( // aka 29334498 + ALL_CASES_SIZE, ALL_CASES_SIZE + 16, (uint32_t)0 +); + +class AllCases : public BasicRanges { +public: + /// Trigger the build process. + static void build(); + + /// Get current status of AllCases. + static Status status(); + + /// Blocking access to constructed data. + static const std::vector (&fetch())[16]; + + /// Export all possible common codes. + static std::vector release(); + +private: + static bool available_; + static std::mutex building_; + static std::vector data_[16]; + + static void build_data(); +}; + +} // namespace klotski diff --git a/src/klotski_core/all_cases/basic_ranges.cc b/src/klotski_core/all_cases/basic_ranges.cc index 8778b2f..571fab7 100644 --- a/src/klotski_core/all_cases/basic_ranges.cc +++ b/src/klotski_core/all_cases/basic_ranges.cc @@ -3,71 +3,75 @@ #include "common.h" #include "basic_ranges.h" -using klotski::BasicRanges; +namespace klotski { + +using Common::range_reverse; /// static variable initialize -std::mutex BasicRanges::building; -bool BasicRanges::available = false; -std::vector BasicRanges::data; +std::mutex BasicRanges::building_; +bool BasicRanges::available_ = false; +std::vector BasicRanges::data_; -const std::vector& BasicRanges::fetch() { // get basic ranges content +const std::vector& BasicRanges::fetch() { if (status() != AVAILABLE) { - BasicRanges::build(); // basic ranges initialize + build(); } - return BasicRanges::data; // return const ref + return data_; // return const reference } -BasicRanges::Status BasicRanges::status() { // get basic ranges status - if (BasicRanges::available) { - return AVAILABLE; // basic ranges already built +BasicRanges::Status BasicRanges::status() { + if (available_) { + return AVAILABLE; // data already built } - if (!BasicRanges::building.try_lock()) { // fail to lock mutex + if (!building_.try_lock()) { // fail to lock mutex return BUILDING; // another thread working } - BasicRanges::building.unlock(); // release mutex - return NO_INIT; + building_.unlock(); // release mutex + return NOT_INIT; } -void BasicRanges::build() { // ensure that basic ranges available - if (!BasicRanges::available) { - if (BasicRanges::building.try_lock()) { // mutex lock success +void BasicRanges::build() { // ensure that data is available + if (!available_) { + if (building_.try_lock()) { // mutex lock success build_data(); // start build process - BasicRanges::available = true; // set available flag + available_ = true; } else { - BasicRanges::building.lock(); // blocking waiting + building_.lock(); // blocking waiting } - BasicRanges::building.unlock(); // release mutex + building_.unlock(); // release mutex } } -void BasicRanges::build_data() { // build basic ranges - BasicRanges::data.reserve(BASIC_RANGES_SIZE); // memory pre-allocated - 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) +/// Search and sort all possible basic-ranges combinations. +void BasicRanges::build_data() { + data_.reserve(BASIC_RANGES_SIZE); // memory pre-allocated + + /// This will create 204 different combinations. + 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) 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 }); - } - } - } + /// NOTE: multiple sets of ordered data -> use merge sort instead of quick sort - std::stable_sort(BasicRanges::data.begin(), BasicRanges::data.end()); // sort basic ranges - for (auto &range : BasicRanges::data) { - range = Common::range_reverse(range); // basic ranges reverse + std::stable_sort(data_.begin(), data_.end()); // sort basic ranges + for (auto &range : data_) { + range = range_reverse(range); // basic ranges reversed } } -void BasicRanges::generate(generate_t info) { // generate specific basic ranges +/// Generate basic-ranges of specific type. +void BasicRanges::generate(generate_t info) { constexpr auto MASK_01 = (uint32_t)0b01 << 30; constexpr auto MASK_10 = (uint32_t)0b10 << 30; constexpr auto MASK_11 = (uint32_t)0b11 << 30; /// nx: n4 n3 n2 n1 - /// 00000000 00000000 00000000 00000000 (32-bits) + /// 00000000 00000000 00000000 00000000 (32-bit) struct build_t { uint32_t nx; // (n4, n3, n2, n1) uint32_t prefix; @@ -84,9 +88,9 @@ void BasicRanges::generate(generate_t info) { // generate specific basic ranges while (!cache.empty()) { // queue without element -> work complete auto current = cache.front(); if (!current.nx) { // both n1, n2, n3, n4 -> 0 - BasicRanges::data.emplace_back(current.prefix); // release prefix as basic range + data_.emplace_back(current.prefix); // release prefix as basic range cache.pop(); - continue; // skip search + continue; // skip current search } if (current.nx & 0xFF) { // n1 -> `00` cache.emplace(build_t { @@ -116,6 +120,8 @@ void BasicRanges::generate(generate_t info) { // generate specific basic ranges .offset = current.offset + 2, }); } - cache.pop(); // remove searched case + cache.pop(); // remove handled case } } + +} // namespace klotski diff --git a/src/klotski_core/all_cases/basic_ranges.h b/src/klotski_core/all_cases/basic_ranges.h index 56756b8..18688ea 100644 --- a/src/klotski_core/all_cases/basic_ranges.h +++ b/src/klotski_core/all_cases/basic_ranges.h @@ -4,7 +4,7 @@ /// 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-bits variables. +/// 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 @@ -31,33 +31,43 @@ #include namespace klotski { - /// basic ranges count - const uint32_t BASIC_RANGES_SIZE = 7311921; - - class BasicRanges { - public: - enum Status { - NO_INIT, - BUILDING, - AVAILABLE, - }; - static void build(); - static Status status(); - static const std::vector &fetch(); - - private: - struct generate_t { - int n1; // number of `00` - int n2; // number of `01` - int n3; // number of `10` - int n4; // number of `11` - }; - - static bool available; - static std::mutex building; - static std::vector data; - - static void build_data(); - static void generate(generate_t info); + +/// basic ranges count +const uint32_t BASIC_RANGES_SIZE = 7311921; + +class BasicRanges { +public: + /// Three basic states, one-way transition. + /// {NO_INIT} -> {BUILDING} -> {AVAILABLE} + enum Status { + NOT_INIT, + BUILDING, + AVAILABLE, + }; + + /// Trigger the build process, from `NOT_INIT` to `BUILDING`. + static void build(); + + /// Get current status of BasicRanges. + static Status status(); + + /// Blocking access to constructed data. + static const std::vector& fetch(); + +private: + struct generate_t { + int n1; // number of `00` + int n2; // number of `01` + int n3; // number of `10` + int n4; // number of `11` }; -} + + static bool available_; + static std::mutex building_; + static std::vector data_; + + static void build_data(); + static void generate(generate_t info); +}; + +} // namespace klotski diff --git a/src/klotski_core/benchmark/benchmark.cc b/src/klotski_core/benchmark/benchmark.cc index 4da3dbb..afa2f1a 100644 --- a/src/klotski_core/benchmark/benchmark.cc +++ b/src/klotski_core/benchmark/benchmark.cc @@ -19,7 +19,7 @@ double Benchmark::range_flip(TIME format) noexcept { /// --------------------------- Benchmark AllCases ---------------------------- double Benchmark::basic_ranges(TIME format) noexcept { - if (BasicRanges::status() != BasicRanges::NO_INIT) { + if (BasicRanges::status() != BasicRanges::NOT_INIT) { return -1; // data already built -> skip } auto start = clock(); @@ -28,7 +28,7 @@ double Benchmark::basic_ranges(TIME format) noexcept { } double Benchmark::all_cases(TIME format) noexcept { - if (AllCases::status() != AllCases::NO_INIT) { + if (AllCases::status() != AllCases::NOT_INIT) { return -1; // data already built -> skip } BasicRanges::build(); // preparing benchmark data diff --git a/test/basic/all_cases.cc b/test/basic/all_cases.cc index f741605..140b20f 100644 --- a/test/basic/all_cases.cc +++ b/test/basic/all_cases.cc @@ -18,7 +18,7 @@ const char ALL_CASES_MD5[] = "3888e9fab8d3cbb50908b12b147cfb23"; /// basic ranges mutex check TEST(AllCases, basic_ranges_mutex) { std::thread threads[4]; - EXPECT_EQ(BasicRanges::status(), BasicRanges::NO_INIT); + EXPECT_EQ(BasicRanges::status(), BasicRanges::NOT_INIT); for (auto &t : threads) { t = std::thread(BasicRanges::build); } @@ -51,7 +51,7 @@ TEST(AllCases, basic_ranges_data) { /// basic ranges mutex check TEST(AllCases, all_cases_mutex) { std::thread threads[4]; - EXPECT_EQ(AllCases::status(), AllCases::NO_INIT); + EXPECT_EQ(AllCases::status(), AllCases::NOT_INIT); for (auto &t : threads) { t = std::thread(AllCases::build); }