diff --git a/src/core/short_code/convert.cc b/src/core/short_code/convert.cc index aee2eaa..b057672 100644 --- a/src/core/short_code/convert.cc +++ b/src/core/short_code/convert.cc @@ -13,6 +13,45 @@ using klotski::codec::offset::RANGE_PREFIX_OFFSET; namespace klotski { namespace codec { +/// FIXME: temporarily used to implement tidy conversion +static uint32_t check_range(uint32_t head, uint32_t range) noexcept { + /// M_1x1 | M_1x2 | M_2x1 | M_2x2 + /// 1 0 0 0 | 1 1 0 0 | 1 0 0 0 | 1 1 0 0 + /// 0 0 0 0 | 0 0 0 0 | 1 0 0 0 | 1 1 0 0 + /// ... | ... | ... | ... + constexpr uint32_t M_1x1 = 0b1; + constexpr uint32_t M_1x2 = 0b11; + constexpr uint32_t M_2x1 = 0b10001; + constexpr uint32_t M_2x2 = 0b110011; + + uint32_t offset = 1; + uint32_t tmp = M_2x2 << head; // fill 2x2 block + for (int addr = 0; range; range >>= 2, ++offset) { // traverse every 2-bits + while ((tmp >> addr) & 0b1) { + ++addr; // search next unfilled block + } + switch (range & 0b11) { + case 0b00: /// space + case 0b11: /// 1x1 block + tmp |= M_1x1 << addr; // fill space or 1x1 block + break; + case 0b10: /// 2x1 block + if (addr > 15 || tmp >> (addr + 4) & 0b1) { // invalid address + return offset; // broken block number + } + tmp |= M_2x1 << addr; // fill 2x1 block + break; + case 0b01: /// 1x2 block + if ((addr & 0b11) == 0b11 || tmp >> (addr + 1) & 0b1) { // invalid address + return offset; // broken block number + } + tmp |= M_1x2 << addr; // fill 1x2 block + break; + } + } + return 0; // pass check +} + /// Convert CommonCode to ShortCode based on AllCases data. uint32_t ShortCode::fast_encode(uint64_t common_code) noexcept { auto head = common_code >> 32; @@ -28,64 +67,56 @@ uint64_t ShortCode::fast_decode(uint32_t short_code) noexcept { return (head << 32) | AllCases::instance().fetch()[head][short_code - *offset]; } -/// NOTE: ensure that input common code is valid! -uint32_t ShortCode::tiny_encode(uint64_t common_code) noexcept { // common code --> short code - /// load head index and range prefix -// uint32_t head = common_code >> 32; -// uint32_t prefix = (common_code >> 20) & 0xFFF; +/// Convert CommonCode to ShortCode based on BasicRanges data. +uint32_t ShortCode::tiny_encode(uint64_t common_code) noexcept { + uint32_t head = common_code >> 32; + uint32_t prefix = (common_code >> 20) & 0xFFF; - /// search for target range -// uint32_t offset = 0; -// auto index = BASIC_RANGES_OFFSET[prefix]; -// const auto &basic_ranges = BasicRanges::fetch(); -// auto target = Common::range_reverse((uint32_t)common_code); // target range -// for (; index < basic_ranges.size(); ++index) { -// auto broken_offset = Common::check_range(head, basic_ranges[index]); -// if (!broken_offset) { // valid case -// if (basic_ranges[index] == target) { -// break; // found target range -// } -// ++offset; // record sub offset -// } else { -// auto delta = (uint32_t)1 << (32 - broken_offset * 2); // delta to next possible range -// auto next_min = (Common::range_reverse(basic_ranges[index]) & ~(delta - 1)) + delta; -// while (Common::range_reverse(basic_ranges[++index]) < next_min); // located next range -// --index; -// } -// } -// return ALL_CASES_OFFSET[head] + RANGE_PREFIX_OFFSET[head][prefix] + offset; + uint32_t offset = 0; + auto index = BASIC_RANGES_OFFSET[prefix]; + const auto &basic_ranges = BasicRanges::instance().fetch(); + auto target = range_reverse((uint32_t)common_code); // target range + for (; index < basic_ranges.size(); ++index) { + auto broken_offset = check_range(head, basic_ranges[index]); + if (!broken_offset) { // valid case + if (basic_ranges[index] == target) { + break; // found target range + } + ++offset; // record sub offset + } else { + auto delta = (uint32_t)1 << (32 - broken_offset * 2); // delta to next possible range + auto next_min = (range_reverse(basic_ranges[index]) & ~(delta - 1)) + delta; + while (range_reverse(basic_ranges[++index]) < next_min); // located next range + --index; + } + } + return ALL_CASES_OFFSET[head] + RANGE_PREFIX_OFFSET[head][prefix] + offset; } /// NOTE: ensure that input short code is valid! uint64_t ShortCode::tiny_decode(uint32_t short_code) noexcept { // short code --> common code - /// match head index -// auto offset = std::upper_bound( // binary search -// ALL_CASES_OFFSET, ALL_CASES_OFFSET + 16, short_code -// ) - 1; -// auto head = offset - ALL_CASES_OFFSET; // head index -// short_code -= *offset; + auto offset = std::upper_bound(ALL_CASES_OFFSET, ALL_CASES_OFFSET + 16, short_code) - 1; + auto head = offset - ALL_CASES_OFFSET; // head index + short_code -= *offset; - /// match range prefix -// offset = std::upper_bound( // binary search -// RANGE_PREFIX_OFFSET[head], RANGE_PREFIX_OFFSET[head] + 4096, short_code -// ) - 1; -// auto prefix = offset - RANGE_PREFIX_OFFSET[head]; // range prefix -// short_code -= *offset; + offset = std::upper_bound(RANGE_PREFIX_OFFSET[head], RANGE_PREFIX_OFFSET[head] + 4096, short_code) - 1; + auto prefix = offset - RANGE_PREFIX_OFFSET[head]; // range prefix + short_code -= *offset; /// search for target range -// auto index = BASIC_RANGES_OFFSET[prefix]; -// const auto &basic_ranges = BasicRanges::fetch(); -// for (; index < basic_ranges.size(); ++index) { // traverse basic ranges -// auto broken_offset = Common::check_range(head, basic_ranges[index]); -// if (!broken_offset && !short_code--) { // valid case -> short code approximate -// break; -// } -// auto delta = (uint32_t)1 << (32 - broken_offset * 2); // delta to next possible range -// auto next_min = (Common::range_reverse(basic_ranges[index]) & ~(delta - 1)) + delta; -// while (Common::range_reverse(basic_ranges[++index]) < next_min); // located next range -// --index; -// } -// return (uint64_t)head << 32 | Common::range_reverse(basic_ranges[index]); + auto index = BASIC_RANGES_OFFSET[prefix]; + const auto &basic_ranges = BasicRanges::instance().fetch(); + for (; index < basic_ranges.size(); ++index) { // traverse basic ranges + auto broken_offset = check_range(head, basic_ranges[index]); + if (!broken_offset && !short_code--) { // valid case -> short code approximate + break; + } + auto delta = (uint32_t)1 << (32 - broken_offset * 2); // delta to next possible range + auto next_min = (range_reverse(basic_ranges[index]) & ~(delta - 1)) + delta; + while (range_reverse(basic_ranges[++index]) < next_min); // located next range + --index; + } + return (uint64_t)head << 32 | range_reverse(basic_ranges[index]); } } // namespace codec diff --git a/src/core_test/CMakeLists.txt b/src/core_test/CMakeLists.txt index 90589e4..8188cf2 100644 --- a/src/core_test/CMakeLists.txt +++ b/src/core_test/CMakeLists.txt @@ -39,6 +39,7 @@ add_test(NAME klotski_ffi COMMAND test_klotski_ffi) ############################################################################################### set(KLOTSKI_TEST_CODEC_SRC + codec/short_code.cc codec/common_code.cc ) diff --git a/src/core_test/codec/common_code.cc b/src/core_test/codec/common_code.cc index 6b1a908..d5cb8fe 100644 --- a/src/core_test/codec/common_code.cc +++ b/src/core_test/codec/common_code.cc @@ -1,8 +1,8 @@ #include #include "sample.h" -#include "all_cases.h" #include "raw_code.h" +#include "all_cases.h" #include "short_code.h" #include "common_code.h" #include "gtest/gtest.h" @@ -27,7 +27,7 @@ TEST(CommonCode, validity) { } TEST(CommonCode, operators) { - auto common_code = CommonCode::create(TEST_C_CODE).value(); + auto common_code = CommonCode::unsafe_create(TEST_C_CODE); std::ostringstream tmp; tmp << common_code; // ostream capture @@ -46,13 +46,32 @@ TEST(CommonCode, operators) { EXPECT_GT(CommonCode::unsafe_create(TEST_C_CODE + 1), common_code); // CommonCode > CommonCode } +TEST(CommonCode, exporter) { + auto common_code = CommonCode::unsafe_create(TEST_C_CODE); + EXPECT_EQ(common_code.unwrap(), TEST_C_CODE); + EXPECT_EQ(common_code.to_string(), TEST_C_CODE_STR); + EXPECT_EQ(common_code.to_raw_code(), TEST_R_CODE); + EXPECT_EQ(common_code.to_short_code(), TEST_S_CODE); + + auto code_shorten = common_code.to_string(true); + EXPECT_EQ(CommonCode::from_string(code_shorten), common_code); // l-value + EXPECT_EQ(CommonCode::from_string(std::move(code_shorten)), common_code); // r-value + + auto code_normal = common_code.to_string(false); + EXPECT_EQ(CommonCode::from_string(code_normal), common_code); // l-value + EXPECT_EQ(CommonCode::from_string(std::move(code_normal)), common_code); // r-value +} + TEST(CommonCode, initializate) { auto raw_code = RawCode::unsafe_create(TEST_R_CODE); auto short_code = ShortCode::unsafe_create(TEST_S_CODE); + auto common_code = CommonCode::unsafe_create(TEST_C_CODE); // CommonCode(...) EXPECT_EQ(CommonCode(raw_code), TEST_C_CODE); EXPECT_EQ(CommonCode(short_code), TEST_C_CODE); + EXPECT_EQ(CommonCode(common_code), TEST_C_CODE); // l-value + EXPECT_EQ(CommonCode(CommonCode(common_code)), TEST_C_CODE); // r-value // CommonCode::create(uint64_t) EXPECT_TRUE(CommonCode::create(TEST_C_CODE).has_value()); @@ -147,23 +166,7 @@ TEST(CommonCode, code_string) { pool.wait_for_tasks(); } -TEST(CommonCode, code_convert) { - auto common_code = CommonCode::unsafe_create(TEST_C_CODE); - EXPECT_EQ(common_code.unwrap(), TEST_C_CODE); - EXPECT_EQ(common_code.to_string(), TEST_C_CODE_STR); - EXPECT_EQ(common_code.to_raw_code(), TEST_R_CODE); - EXPECT_EQ(common_code.to_short_code(), TEST_S_CODE); - - auto code_shorten = common_code.to_string(true); - EXPECT_EQ(CommonCode::from_string(code_shorten), common_code); // l-value - EXPECT_EQ(CommonCode::from_string(std::move(code_shorten)), common_code); // r-value - - auto code_normal = common_code.to_string(false); - EXPECT_EQ(CommonCode::from_string(code_normal), common_code); // l-value - EXPECT_EQ(CommonCode::from_string(std::move(code_normal)), common_code); // r-value -} - -TEST(CommonCode, DISABLED_global) { +TEST(CommonCode, DISABLED_global_verify) { std::vector common_codes; common_codes.reserve(ALL_CASES_NUM_); for (uint64_t head = 0; head < 16; ++head) { diff --git a/src/core_test/codec/short_code.cc b/src/core_test/codec/short_code.cc new file mode 100644 index 0000000..c4c5f85 --- /dev/null +++ b/src/core_test/codec/short_code.cc @@ -0,0 +1,224 @@ +#include + +#include "sample.h" +#include "exposer.h" +#include "all_cases.h" +#include "short_code.h" +#include "common_code.h" +#include "gtest/gtest.h" +#include "BS_thread_pool.hpp" + +using klotski::cases::AllCases; +using klotski::cases::BasicRanges; + +using klotski::codec::ShortCode; +using klotski::codec::CommonCode; + +using klotski::cases::ALL_CASES_NUM; +using klotski::cases::ALL_CASES_NUM_; +using klotski::codec::SHORT_CODE_LIMIT; + +static const auto TEST_THREAD_NUM = 256; + +/// Forcibly modify private variables to reset state. +PRIVATE_ACCESS(AllCases, available_, bool) +PRIVATE_ACCESS(BasicRanges, available_, bool) + +/// Reset basic ranges build state, note it is thread-unsafe. +void basic_ranges_reset() { + access_BasicRanges_available_(BasicRanges::instance()) = false; +} + +/// Reset all cases build state, note it is thread-unsafe. +void all_cases_reset() { + access_AllCases_available_(AllCases::instance()) = false; +} + +TEST(ShortCode, limit) { + auto all_cases_num = std::accumulate(ALL_CASES_NUM.begin(), ALL_CASES_NUM.end(), 0); + EXPECT_EQ(all_cases_num, SHORT_CODE_LIMIT); +} + +TEST(ShortCode, validity) { + EXPECT_FALSE(ShortCode::check(-1)); // out of short code range + EXPECT_FALSE(ShortCode::check(29670987)); // out of short code range + + EXPECT_FALSE(ShortCode::create(SHORT_CODE_LIMIT).has_value()); // invalid code + EXPECT_FALSE(ShortCode::from_string("R50EH").has_value()); // with invalid `0` + EXPECT_FALSE(ShortCode::from_string("123456").has_value()); // length != 5 + EXPECT_FALSE(ShortCode::from_string("Z9EFV").has_value()); // out of short code range +} + +TEST(ShortCode, operators) { + auto short_code = ShortCode::unsafe_create(TEST_S_CODE); + + std::ostringstream tmp; + tmp << short_code; // ostream capture + EXPECT_EQ(tmp.str(), TEST_S_CODE_STR); + EXPECT_EQ((uint32_t)short_code, TEST_S_CODE); // convert as uint32_t + + EXPECT_EQ(TEST_S_CODE, short_code); // uint32_t == ShortCode + EXPECT_EQ(short_code, TEST_S_CODE); // ShortCode == uint32_t + EXPECT_EQ(short_code, short_code); // ShortCode == ShortCode + + EXPECT_NE(TEST_S_CODE + 1, short_code); // uint32_t != ShortCode + EXPECT_NE(short_code, TEST_S_CODE + 1); // ShortCode != uint32_t + EXPECT_NE(short_code, ShortCode::unsafe_create(TEST_S_CODE + 1)); // ShortCode != ShortCode + + EXPECT_LT(short_code, ShortCode::unsafe_create(TEST_S_CODE + 1)); // ShortCode < ShortCode + EXPECT_GT(ShortCode::unsafe_create(TEST_S_CODE + 1), short_code); // ShortCode > ShortCode +} + +TEST(ShortCode, exporter) { + auto short_code = ShortCode::unsafe_create(TEST_S_CODE); + EXPECT_EQ(short_code.unwrap(), TEST_S_CODE); + EXPECT_EQ(short_code.to_string(), TEST_S_CODE_STR); + EXPECT_EQ(short_code.to_common_code(), TEST_C_CODE); +} + +TEST(ShortCode, initializate) { + auto short_code = ShortCode::unsafe_create(TEST_S_CODE); + auto common_code = CommonCode::unsafe_create(TEST_C_CODE); + + // ShortCode(...) + EXPECT_EQ(ShortCode(common_code), TEST_S_CODE); + EXPECT_EQ(ShortCode(short_code), TEST_S_CODE); // l-value + EXPECT_EQ(ShortCode(ShortCode(short_code)), TEST_S_CODE); // r-value + + // ShortCode::create(uint32_t) + EXPECT_TRUE(ShortCode::create(TEST_S_CODE).has_value()); + EXPECT_FALSE(ShortCode::create(TEST_S_CODE_ERR).has_value()); + EXPECT_EQ(ShortCode::create(TEST_S_CODE), TEST_S_CODE); + + // ShortCode::unsafe_create(uint32_t) + EXPECT_EQ(ShortCode::unsafe_create(TEST_S_CODE), TEST_S_CODE); + + // ShortCode::from_string(const std::string &) + EXPECT_TRUE(ShortCode::from_string(TEST_S_CODE_STR).has_value()); + EXPECT_FALSE(ShortCode::from_string(TEST_S_CODE_STR_ERR).has_value()); + EXPECT_EQ(ShortCode::from_string(TEST_S_CODE_STR), TEST_S_CODE); + + // ShortCode::from_string(std::string&&) + EXPECT_TRUE(ShortCode::from_string(TEST_S_CODE_STR_RV).has_value()); + EXPECT_FALSE(ShortCode::from_string(TEST_S_CODE_STR_ERR_RV).has_value()); + EXPECT_EQ(ShortCode::from_string(TEST_S_CODE_STR_RV), TEST_S_CODE); + + // ShortCode::from_common_code(CommonCode) + EXPECT_EQ(ShortCode::from_common_code(common_code), TEST_S_CODE); + + // ShortCode::from_common_code(uint64_t) + EXPECT_TRUE(ShortCode::from_common_code(TEST_C_CODE).has_value()); + EXPECT_FALSE(ShortCode::from_common_code(TEST_C_CODE_ERR).has_value()); + EXPECT_EQ(ShortCode::from_common_code(TEST_C_CODE), TEST_S_CODE); + + // ShortCode::from_common_code(const std::string &) + EXPECT_TRUE(ShortCode::from_common_code(TEST_C_CODE_STR).has_value()); + EXPECT_FALSE(ShortCode::from_common_code(TEST_C_CODE_STR_ERR).has_value()); + EXPECT_EQ(ShortCode::from_common_code(TEST_C_CODE_STR), TEST_S_CODE); + + // ShortCode::from_common_code(std::string&&) + EXPECT_TRUE(ShortCode::from_common_code(TEST_C_CODE_STR_RV).has_value()); + EXPECT_FALSE(ShortCode::from_common_code(TEST_C_CODE_STR_ERR_RV).has_value()); + EXPECT_EQ(ShortCode::from_common_code(TEST_C_CODE_STR_RV), TEST_S_CODE); +} + +TEST(ShortCode, speed_up) { + all_cases_reset(); + basic_ranges_reset(); + BS::thread_pool pool(TEST_THREAD_NUM); + + for (auto i = 0; i < TEST_THREAD_NUM; ++i) { + pool.push_task(ShortCode::speed_up, false); + } + EXPECT_FALSE(BasicRanges::instance().is_available()); + EXPECT_FALSE(AllCases::instance().is_available()); + pool.wait_for_tasks(); + EXPECT_TRUE(BasicRanges::instance().is_available()); + EXPECT_FALSE(AllCases::instance().is_available()); + + for (auto i = 0; i < TEST_THREAD_NUM; ++i) { + pool.push_task(ShortCode::speed_up, true); + } + EXPECT_TRUE(BasicRanges::instance().is_available()); + EXPECT_FALSE(AllCases::instance().is_available()); + pool.wait_for_tasks(); + EXPECT_TRUE(BasicRanges::instance().is_available()); + EXPECT_TRUE(AllCases::instance().is_available()); +} + +TEST(ShortCode, code_verify) { + BS::thread_pool pool; + ShortCode::speed_up(true); + for (int head = 0; head < 16; ++head) { + pool.push_task([](uint64_t head) { + std::vector archive; + for (auto range : AllCases::instance().fetch()[head]) { + auto code = ShortCode::from_common_code(head << 32 | range); + EXPECT_TRUE(code.has_value()); + EXPECT_TRUE(ShortCode::check(code->unwrap())); + archive.emplace_back(code->unwrap()); + } + if (!archive.empty()) { + EXPECT_TRUE(std::is_sorted(archive.begin(), archive.end())); // increasingly one by one + EXPECT_EQ(archive[archive.size() - 1] - archive[0], archive.size() - 1); + EXPECT_EQ(std::accumulate(ALL_CASES_NUM.begin(), ALL_CASES_NUM.begin() + head, 0), archive[0]); + } + }, head); + } + pool.wait_for_tasks(); +} + +TEST(ShortCode, code_string) { + auto test_func = [](ShortCode code) { + auto code_str = code.to_string(); + EXPECT_EQ(code_str.size(), 5); // length = 5 + for (auto c : code_str) { + EXPECT_TRUE((c >= '1' && c <= '9') || (c >= 'A' && c <= 'Z')); + EXPECT_TRUE(c != 'I' && c != 'L' && c != 'O'); + } + EXPECT_EQ(ShortCode::from_string(code_str), code); // test upper cases + std::transform(code_str.begin(), code_str.end(), code_str.begin(), ::tolower); + EXPECT_EQ(ShortCode::from_string(code_str), code); // test lower cases + }; + + BS::thread_pool pool; + pool.push_loop(SHORT_CODE_LIMIT, [&test_func](uint32_t start, uint32_t end) { + for (uint32_t short_code = start; short_code < end; ++short_code) { + test_func(ShortCode::unsafe_create(short_code)); + } + }, 0x1000); // split as 4096 pieces + pool.wait_for_tasks(); +} + +TEST(ShortCode, DISABLED_global_verify) { + std::vector common_codes; + common_codes.reserve(ALL_CASES_NUM_); + for (uint64_t head = 0; head < 16; ++head) { + for (auto range : AllCases::instance().fetch()[head]) { + common_codes.emplace_back(head << 32 | range); + } + } + + all_cases_reset(); + BS::thread_pool pool; + auto futures = pool.parallelize_loop(SHORT_CODE_LIMIT, [](uint32_t start, uint32_t end) { + std::vector archive; + archive.reserve(end - start); + for (uint32_t short_code = start; short_code < end; ++short_code) { + auto common_code = CommonCode::from_short_code(short_code).value(); + EXPECT_EQ(common_code.to_short_code(), short_code); + archive.emplace_back(common_code.unwrap()); + } + return archive; + }, 0x1000); // split as 4096 pieces + + std::vector result; + result.reserve(ALL_CASES_NUM_); + for (size_t i = 0; i < futures.size(); ++i) { + auto data = futures[i].get(); + std::cout << i << std::endl; + result.insert(result.end(), data.begin(), data.end()); // combine sections + } + pool.wait_for_tasks(); + EXPECT_EQ(result, common_codes); +}