#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using IntType = long long; class CRange { public: IntType l = 0; IntType r = 0; CRange() = default; CRange(IntType l, IntType r) : l(l), r(r) { if (l > r) throw std::logic_error("CRange bad initialisation"); } friend std::ostream &operator<<(std::ostream &out, const CRange &p) { std::ios::fmtflags f(out.flags()); out << std::dec << std::resetiosflags(std::ios_base::hex) << std::resetiosflags(std::ios_base::left); if (p.l == p.r) out << p.l; else out << "<" << p.l << ".." << p.r << ">"; out.flags(f); return out; } bool includes(const IntType p) const { return p >= l && p <= r; } bool includes(const CRange &p) const { return includes(p.l) && includes(p.r); } bool operator==(const CRange &p) const { return p.l == l && p.r == r; } operator std::pair() const { return {l, r}; } }; template class Storage = std::map> class CRangeList { private: Storage> data; public: // constructor CRangeList() { auto p = std::make_pair(LLONG_MIN, std::make_pair(CRange(LLONG_MIN, LLONG_MAX), false)); data.emplace(p.first, p.second); } CRangeList(std::initializer_list l) { auto p = std::make_pair(LLONG_MIN, std::make_pair(CRange(LLONG_MIN, LLONG_MAX), false)); data.emplace(p.first, p.second); for (auto const &r: l) { *this += r; } } // includes long long / range bool includes(const IntType l) const { return findInData(l)->second.second; } bool includes(const CRange &r) const { auto f = findInData(r.l); if (!f->second.second) return false; return f->second.first.includes(r); } CRangeList operator+(const CRange &rhs) const { auto nl = CRangeList(); nl += *this; nl += rhs; return nl; } CRangeList operator-(const CRange &rhs) const { auto nl = CRangeList(); nl += *this; nl -= rhs; return nl; } typename decltype(data)::const_iterator findInData(IntType l) const { typename decltype(data)::const_iterator ret = data.upper_bound(l); --ret; return ret; } void set(CRange r, bool visible) { auto lb = findInData(r.l); if (lb != data.begin() && !lb->second.first.includes(r.l - 1)) { auto lbm = lb; --lbm; if (lbm->second.second == visible) { r.l--; lb = lbm; } } auto ub = findInData(r.r); if (ub != (--data.end()) && !ub->second.first.includes(r.r + 1)) { auto ubp = ub; ++ubp; if (ubp->second.second == visible) { r.r++; ub = ubp; } } if (lb == data.end() || ub == data.end()) throw; auto lbc = lb->second; auto ubc = ub->second; ub++; std::map> toInsert; if (r.l != lbc.first.l) { if (lbc.second == visible) { r.l = std::min(lbc.first.l, r.l); } else { toInsert.emplace(lbc.first.l, std::make_pair(CRange(lbc.first.l, r.l - 1), lbc.second)); } } if (r.r != ubc.first.r) { if (ubc.second == visible) r.r = std::max(ubc.first.r, r.r); else { toInsert.emplace(r.r + 1, std::make_pair(CRange(r.r + 1, ubc.first.r), ubc.second)); } } toInsert.emplace(r.l, std::make_pair(r, visible)); data.erase(lb, ub); for (auto &r: toInsert) { data.emplace(std::move(r)); } } // += range / range list CRangeList &operator+=(const CRange &r) { set(r, true); return *this; } CRangeList &operator+=(const CRangeList &r) { for (auto const &l: r.data) { if (l.second.second) *this += l.second.first; } return *this; } // -= range / range list CRangeList &operator-=(const CRange &r) { set(r, false); return *this; } CRangeList &operator-=(const CRangeList &r) { for (auto const &l: r.data) { if (l.second.second) *this -= l.second.first; } return *this; } // = range / range list CRangeList &operator=(const CRange &r) { auto p = std::make_pair(LLONG_MIN, std::make_pair(CRange(LLONG_MIN, LLONG_MAX), false)); data.emplace(p.first, p.second); *this += (r); return *this; } // operator == bool operator==(const CRangeList &r) const { return this->data == r.data; } // operator != bool operator!=(const CRangeList &r) const { return !(this->data == r.data); } // operator << friend std::ostream &operator<<(std::ostream &out, const CRangeList &rl) { out << "{"; bool first = true; for (const auto &i: rl.data) { if (i.second.second) { if (!first) out << ","; out << i.second.first; first = false; } } out << "}"; return out; } struct CRangeListIterator { using iterator_category = std::forward_iterator_tag; using difference_type = std::ptrdiff_t; using value_type = const CRange; using pointer = value_type *; using reference = value_type &; CRangeListIterator(CRangeListIterator const &it) : it(it.it){}; CRangeListIterator(decltype(data)::const_iterator it, decltype(data)::const_iterator end) : it(it), end(end) { if (it != end && !it->second.second) this->it++; } reference operator*() const { return (it->second.first); } pointer operator->() const { return &(it->second.first); } CRangeListIterator &operator++() { ++it; if (it == end) return *this; ++it; return *this; } CRangeListIterator operator++(int) { CRangeListIterator tmp = *this; ++(*this); return tmp; } friend bool operator==(const CRangeListIterator &a, const CRangeListIterator &b) { return a.it == b.it; }; friend bool operator!=(const CRangeListIterator &a, const CRangeListIterator &b) { return !(a.it == b.it); }; private: decltype(data)::const_iterator it; decltype(data)::const_iterator end; }; using const_iterator = CRangeListIterator; const_iterator begin() const { return CRangeListIterator(data.begin(), data.end()); } const_iterator end() const { return CRangeListIterator(data.end(), data.end()); } }; template std::string toString(const T &x) { std::ostringstream oss; oss << x; return oss.str(); } using sl_detailed_test_list = testing::Types, CRangeList>; template struct SkipListDetailedTestFixture : testing::Test {}; TYPED_TEST_SUITE(SkipListDetailedTestFixture, sl_detailed_test_list); TYPED_TEST(SkipListDetailedTestFixture, ItWorks) { assert(std::is_trivially_copyable::value); using CRangeListTested = TypeParam; CRangeListTested t; t = CRange(10, 20); // t.data.print(); auto ts = toString(t); // std::cout << ts; assert(ts == "{<10..20>}"); // return 0; t = CRangeListTested{{10, 15}}; assert(toString(t) == "{<10..15>}"); t = CRange(10, 20); // t.data.print(); assert(toString(t) == "{<10..20>}"); t += CRange(LLONG_MIN, LLONG_MAX); assert(t.includes(CRange(LLONG_MIN, LLONG_MAX))); ASSERT_EQ(toString(t), "{<-9223372036854775808..9223372036854775807>}"); assert(t.includes(LLONG_MIN)); assert(t.includes(LLONG_MAX)); t -= CRange(LLONG_MIN, LLONG_MAX); ASSERT_EQ(toString(t), "{}"); t += CRange(LLONG_MIN, LLONG_MAX - 1); ASSERT_EQ(toString(t), "{<-9223372036854775808..9223372036854775806>}"); t += CRange(LLONG_MAX, LLONG_MAX); ASSERT_EQ(toString(t), "{<-9223372036854775808..9223372036854775807>}"); t -= CRange(LLONG_MIN, LLONG_MAX); assert(toString(t) == "{}"); t += CRange(LLONG_MIN, LLONG_MAX); t += CRange(LLONG_MIN, LLONG_MAX - 1); assert(toString(t) == "{<-9223372036854775808..9223372036854775807>}"); t -= CRange(LLONG_MIN, LLONG_MAX); assert(toString(t) == "{}"); t += CRange(LLONG_MAX, LLONG_MAX); t += CRange(LLONG_MIN, LLONG_MAX - 1); assert(toString(t) == "{<-9223372036854775808..9223372036854775807>}"); t -= CRange(LLONG_MIN, LLONG_MAX); assert(toString(t) == "{}"); t += CRange(LLONG_MIN, LLONG_MAX); t -= CRange(LLONG_MAX, LLONG_MAX); assert(toString(t) == "{<-9223372036854775808..9223372036854775806>}"); t -= CRange(LLONG_MIN, LLONG_MAX); assert(toString(t) == "{}"); t += CRange(LLONG_MIN, LLONG_MAX - 1); t += CRange(LLONG_MIN, LLONG_MAX); assert(toString(t) == "{<-9223372036854775808..9223372036854775807>}"); t -= CRange(LLONG_MIN, LLONG_MAX); t += CRange(10, 20); assert(toString(t) == "{<10..20>}"); t += CRange(30, 40); assert(toString(t) == "{<10..20>,<30..40>}"); t += CRange(2, 4); assert(toString(t) == "{<2..4>,<10..20>,<30..40>}"); t += CRange(15, 35); auto s = toString(t); assert(toString(t) == "{<2..4>,<10..40>}"); t += CRange(15, 50); s = toString(t); assert(toString(t) == "{<2..4>,<10..50>}"); t -= CRange(5, 6); s = toString(t); assert(toString(t) == "{<2..4>,<10..50>}"); t += CRange(100, 150); assert(toString(t) == "{<2..4>,<10..50>,<100..150>}"); t -= CRange(70, 80); assert(toString(t) == "{<2..4>,<10..50>,<100..150>}"); t += CRange(60, 70); assert(toString(t) == "{<2..4>,<10..50>,<60..70>,<100..150>}"); t += CRange(70, 80); assert(toString(t) == "{<2..4>,<10..50>,<60..80>,<100..150>}"); t += CRange(55, 60); assert(toString(t) == "{<2..4>,<10..50>,<55..80>,<100..150>}"); t += CRange(50, 55); assert(toString(t) == "{<2..4>,<10..80>,<100..150>}"); auto t2 = CRangeListTested() + CRange(6, 9) + CRange(151, 160); assert(toString(t2) == "{<6..9>,<151..160>}"); t += t2; s = toString(t); assert(toString(t) == "{<2..4>,<6..80>,<100..160>}"); t += CRange(-100, 200); assert(toString(t) == "{<-100..200>}"); t -= CRange(-1000, -900); t -= CRange(1000, 1100); assert(toString(t) == "{<-100..200>}"); t = CRangeListTested{{10, 15}, {20, 25}, {30, 40}}; // t.data.print(); assert(toString(t) == "{<10..15>,<20..25>,<30..40>}"); t -= CRange(40, 9999); assert(toString(t) == "{<10..15>,<20..25>,<30..39>}"); t -= CRange(35, 9999); assert(toString(t) == "{<10..15>,<20..25>,<30..34>}"); t -= CRange(35, 9999); assert(toString(t) == "{<10..15>,<20..25>,<30..34>}"); t -= CRange(-1000, 10); assert(toString(t) == "{<11..15>,<20..25>,<30..34>}"); t -= CRange(-1000, 13); assert(toString(t) == "{<14..15>,<20..25>,<30..34>}"); t -= CRange(22, 22); assert(toString(t) == "{<14..15>,<20..21>,<23..25>,<30..34>}"); t = CRangeListTested(); t += CRange(0, 100); assert(toString(t) == "{<0..100>}"); t += CRange(200, 300); assert(toString(t) == "{<0..100>,<200..300>}"); t -= CRange(150, 250); assert(toString(t) == "{<0..100>,<251..300>}"); t += CRange(160, 180); assert(toString(t) == "{<0..100>,<160..180>,<251..300>}"); t -= CRange(170, 170); assert(toString(t) == "{<0..100>,<160..169>,<171..180>,<251..300>}"); t = CRangeListTested(); t += CRange(10, 90); t -= CRange(20, 30); t -= CRange(40, 50); t -= CRange(60, 90); t += CRange(70, 80); t2 = CRangeListTested() + CRange(10, 90) - CRange(20, 30) - CRange(40, 50) - CRange(60, 90) + CRange(70, 80); ts = toString(t); auto ts2 = toString(t2); assert(ts == ts2); CRangeListTested a, b; assert(sizeof(CRange) <= 2 * sizeof(long long)); a = CRange(5, 10); a += CRange(25, 100); assert(toString(a) == "{<5..10>,<25..100>}"); a += CRange(-5, 0); a += CRange(8, 50); assert(toString(a) == "{<-5..0>,<5..100>}"); a += CRangeListTested() + CRange(101, 105) + CRange(120, 150) + CRange(160, 180) + CRange(190, 210); assert(toString(a) == "{<-5..0>,<5..105>,<120..150>,<160..180>,<190..210>}"); a += CRangeListTested() + CRange(106, 119) + CRange(152, 158); assert(toString(a) == "{<-5..0>,<5..150>,<152..158>,<160..180>,<190..210>}"); a += CRange(-3, 170); a += CRange(-30, 1000); assert(toString(a) == "{<-30..1000>}"); b = CRangeListTested() + CRange(-500, -300) + CRange(2000, 3000) + CRange(700, 1001); a += b; assert(toString(a) == "{<-500..-300>,<-30..1001>,<2000..3000>}"); a -= CRange(-400, -400); assert(toString(a) == "{<-500..-401>,<-399..-300>,<-30..1001>,<2000..3000>}"); a -= CRangeListTested() + CRange(10, 20) + CRange(900, 2500) + CRange(30, 40) + CRange(10000, 20000); assert(toString(a) == "{<-500..-401>,<-399..-300>,<-30..9>,<21..29>,<41..899>,<2501..3000>}"); try { a += CRangeListTested() + CRange(15, 18) + CRange(10, 0) + CRange(35, 38); assert("Exception not thrown" == nullptr); } catch (const std::logic_error &e) { } catch (...) { assert("Invalid exception thrown" == nullptr); } assert(toString(a) == "{<-500..-401>,<-399..-300>,<-30..9>,<21..29>,<41..899>,<2501..3000>}"); b = a; assert(a == b); assert(!(a != b)); b += CRange(2600, 2700); assert(toString(b) == "{<-500..-401>,<-399..-300>,<-30..9>,<21..29>,<41..899>,<2501..3000>}"); assert(toString(a) == "{<-500..-401>,<-399..-300>,<-30..9>,<21..29>,<41..899>,<2501..3000>}"); assert(a == b); assert(!(a != b)); b += CRange(15, 15); assert(toString(b) == "{<-500..-401>,<-399..-300>,<-30..9>,15,<21..29>,<41..899>,<2501..3000>}"); assert(!(a == b)); assert(a != b); assert(b.includes(15)); assert(b.includes(2900)); assert(b.includes(CRange(15, 15))); assert(b.includes(CRange(-350, -350))); assert(b.includes(CRange(100, 200))); assert(!b.includes(CRange(800, 900))); assert(!b.includes(CRange(-1000, -450))); assert(!b.includes(CRange(0, 500))); a += CRangeListTested() + CRange(-10000, 10000) + CRange(10000000, 1000000000); assert(toString(a) == "{<-10000..10000>,<10000000..1000000000>}"); b += a; assert(toString(b) == "{<-10000..10000>,<10000000..1000000000>}"); b -= a; assert(toString(b) == "{}"); b += CRangeListTested() + CRange(0, 100) + CRange(200, 300) - CRange(150, 250) + CRange(160, 180) - CRange(170, 170); // b.data.print(); assert(toString(b) == "{<0..100>,<160..169>,<171..180>,<251..300>}"); // b.data.print(); b -= CRangeListTested() + CRange(10, 90) - CRange(20, 30) - CRange(40, 50) - CRange(60, 90) + CRange(70, 80); s = toString(b); assert(toString(b) == "{<0..9>,<20..30>,<40..50>,<60..69>,<81..100>,<160..169>,<171..180>,<251..300>}"); CRangeListTested x{{5, 20}, {150, 200}, {-9, 12}, {48, 93}}; assert(toString(x) == "{<-9..20>,<48..93>,<150..200>}"); std::ostringstream oss; oss << std::setfill('=') << std::hex << std::left; for (const auto &v: x + CRange(-100, -100)) { // std::cout << "-" << v << "-" << std::endl; oss << v << std::endl; } oss << std::setw(10) << 1024; // std::cout << oss.str() << std::endl; assert(oss.str() == "-100\n<-9..20>\n<48..93>\n<150..200>\n400======="); // FIXME: I'll leave it a "stress test" for now for (long long x = 10; x <= 10; x *= 10) { CRangeListTested t; std::cout << x << "====" << std::endl; auto start = std::chrono::high_resolution_clock::now(); auto stop = std::chrono::high_resolution_clock::now(); for (long long i = -x; i < x; i += 2) { start = std::chrono::high_resolution_clock::now(); t += CRange(i, i); stop = std::chrono::high_resolution_clock::now(); } auto duration = std::chrono::duration_cast(stop - start); std::cout << duration.count() << std::endl; for (long long i = -x; i < x; i += 2) { start = std::chrono::high_resolution_clock::now(); t = t + CRange(i, i); stop = std::chrono::high_resolution_clock::now(); } duration = std::chrono::duration_cast(stop - start); std::cout << duration.count() << std::endl; // t.data.printS(); for (long long i = -x; i < x; i += 2) { start = std::chrono::high_resolution_clock::now(); assert(t.includes(i)); stop = std::chrono::high_resolution_clock::now(); } duration = std::chrono::duration_cast(stop - start); std::cout << duration.count() << std::endl; t -= CRange(LLONG_MIN, LLONG_MAX); for (long long i = x; i > -x; i -= 2) { start = std::chrono::high_resolution_clock::now(); t += CRange(i, i); stop = std::chrono::high_resolution_clock::now(); } duration = std::chrono::duration_cast(stop - start); std::cout << duration.count() << std::endl; for (long long i = x; i > -x; i -= 2) { start = std::chrono::high_resolution_clock::now(); t = t + CRange(i, i); stop = std::chrono::high_resolution_clock::now(); } duration = std::chrono::duration_cast(stop - start); std::cout << duration.count() << std::endl; for (long long i = x; i > -x; i -= 2) { start = std::chrono::high_resolution_clock::now(); assert(t.includes(i)); stop = std::chrono::high_resolution_clock::now(); } duration = std::chrono::duration_cast(stop - start); std::cout << duration.count() << std::endl; for (long long i = x; i > -x; i -= 2) { start = std::chrono::high_resolution_clock::now(); if (rand() % 100 > 50) { t += CRange(-rand(), rand()); } else { t -= CRange(-rand(), rand()); } stop = std::chrono::high_resolution_clock::now(); } duration = std::chrono::duration_cast(stop - start); std::cout << duration.count() << std::endl; } }