Browse Source

update: enhance AllCases module

master
Dnomd343 1 year ago
parent
commit
86d6c0c3ca
  1. 78
      src/klotski_core/all_cases/all_cases.cc
  2. 71
      src/klotski_core/all_cases/all_cases.h
  3. 76
      src/klotski_core/all_cases/basic_ranges.cc
  4. 70
      src/klotski_core/all_cases/basic_ranges.h
  5. 4
      src/klotski_core/benchmark/benchmark.cc
  6. 4
      test/basic/all_cases.cc

78
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<uint32_t> AllCases::data[];
std::mutex AllCases::building_;
bool AllCases::available_ = false;
std::vector<uint32_t> AllCases::data_[];
const std::vector<uint32_t> (&AllCases::fetch())[16] { // get all cases content
const std::vector<uint32_t> (&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<uint64_t> AllCases::release() {
std::vector<uint64_t> 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

71
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 <mutex>
#include <vector>
@ -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<uint32_t> (&fetch())[16];
private:
static bool available;
static std::mutex building;
static std::vector<uint32_t> 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<uint32_t> (&fetch())[16];
/// Export all possible common codes.
static std::vector<uint64_t> release();
private:
static bool available_;
static std::mutex building_;
static std::vector<uint32_t> data_[16];
static void build_data();
};
} // namespace klotski

76
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<uint32_t> BasicRanges::data;
std::mutex BasicRanges::building_;
bool BasicRanges::available_ = false;
std::vector<uint32_t> BasicRanges::data_;
const std::vector<uint32_t>& BasicRanges::fetch() { // get basic ranges content
const std::vector<uint32_t>& 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

70
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 <cstdint>
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<uint32_t> &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<uint32_t> 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<uint32_t>& 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<uint32_t> data_;
static void build_data();
static void generate(generate_t info);
};
} // namespace klotski

4
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

4
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);
}

Loading…
Cancel
Save