//============================================================================ // Author : Sven Göthel // Version : 0.1 // Copyright : MIT // Description : C++ Lesson 4.0 A custom interval map using C++ //============================================================================ #include // #include #include #include #include #include #include namespace test_env { /** * Test Key value type implementing constraints * * Properties: * - copyable * - assignable * - comparable via < * * NOT properties (not implement): * - comparable via ==, <=, >, >= * - arithmetic operators */ class KeyType { private: int64_t store; public: constexpr KeyType() noexcept : store(0) { } constexpr KeyType(int64_t v) noexcept : store(v) { } constexpr KeyType(const KeyType& o) noexcept = default; constexpr KeyType(KeyType&& o) noexcept = default; constexpr KeyType& operator=(const KeyType&) noexcept = default; constexpr KeyType& operator=(KeyType&&) noexcept = default; constexpr int64_t value() const noexcept { return store; } bool operator<(const KeyType& b) const noexcept { return store < b.store; } std::string toString() const noexcept { return std::to_string(store); } }; /** * Test Value value type implementing constraints * * Properties: * - copyable * - assignable * - comparable via == * * NOT properties (not implement): * - comparable via <, <=, >, >= * - arithmetic operators */ class ValueType { private: int64_t store; public: constexpr ValueType() noexcept : store(0) { } constexpr ValueType(int64_t v) noexcept : store(v) { } constexpr ValueType(const ValueType& o) noexcept = default; constexpr ValueType(ValueType&& o) noexcept = default; constexpr ValueType& operator=(const ValueType&) noexcept = default; constexpr ValueType& operator=(ValueType&&) noexcept = default; constexpr int64_t value() const noexcept { return store; } constexpr bool operator==(const ValueType& b) const noexcept { return store == b.store; } std::string toString() const noexcept { return std::to_string(store); } }; } // namespace test_env namespace std { inline std::string to_string(const test_env::KeyType& v) noexcept { return std::to_string(v.value()); } inline std::ostream& operator<<(std::ostream& out, const test_env::KeyType& v) { return out << v.toString(); } inline std::string to_string(const test_env::ValueType& v) noexcept { return std::to_string(v.value()); } inline std::ostream& operator<<(std::ostream& out, const test_env::ValueType& v) { return out << v.toString(); } } void test_interval_map(); namespace feature { template concept IntervalMapKey = requires(T a) { // { a < a } -> std::convertible_to; { a < a } -> std::same_as; }; /** * Custom interval map * * - Using a unique (canonical) K -> V m_map, i.e. mapping to a set of values w/o duplicates * - std map, only using std::less, i.e. operator< for key comparison, see below * - Using a dedicated interval begin value * - Mapped to all keys not covered by map * - Not contained in first entry of m_map, * - Initially covers whole range of K * * Each interval [keyBegin, keyEnd) includes keyBegin, but excludes keyEnd. * * Example: * m_valBegin = 'A', m_map{ (1,'B'), (3,'A') } * where value 'B' is mapped to range [1..3) * * TODO: * - Add corner case of no_value, IFF value is contained in map * - Inject constraints of K, V via concepts in class template declaration * * @tparam K only provides operator< * @tparam V only provides operator== */ template // requires std::equality_comparable_with class IntervalMap { private: typedef std::map /* default */> map_t; typedef typename map_t::iterator map_iterator_t; typedef typename map_t::const_iterator const_map_iterator_t; map_t m_map; V m_valBegin; friend void ::test_interval_map(); public: constexpr IntervalMap(const V& valBegin) noexcept : m_map(), m_valBegin(valBegin) {} constexpr const V& operator[](const K& key) const noexcept { const_map_iterator_t it = m_map.upper_bound(key); if ( it == m_map.cend() || it == m_map.cbegin() ) { return m_valBegin; // includes empty case: it == cend() } else { return (--it)->second; } } /** * Adds an interval to this map * * Pre-existing intervals are overwritten or split. * * Invalid given interval keyBegin >= keyEnd is a nop. * * Complexity O(log(n)) or O(m) with m = period-len (keyEnd-keyBegin) * * @param keyBegin interval inclusive start * @param keyEnd interval exclusive end * @param val mapped value to given interval * @return true if interval has been successfully added, otherwise false */ bool add( const K& keyBegin, const K& keyEnd, const V& val ) noexcept { if( !( keyBegin < keyEnd ) ) { return false; } map_iterator_t it_b = m_map.lower_bound(keyBegin); // O(log(n)) if( it_b == m_map.end() ) { // no key >= b exists, hence [b, e) not included (includes empty + tail) m_map.insert(m_map.end(), { keyBegin, val }); // O(1), inserting just before iterator m_map.insert(m_map.end(), { keyEnd, m_valBegin }); // O(1), inserting just before iterator return true; } // it_b >= b (keyBegin) V end_val = m_valBegin; map_iterator_t it; if( keyBegin < it_b->first ) { // it_b > b (keyBegin) it = it_b; m_map.insert_or_assign(it, keyBegin, V(val)); // O(1), inserting just before it } else { // it_b == b (keyBegin) end_val = it_b->second; it = it_b; ++it; if( !( it_b->second == val ) ) { end_val = it_b->second; m_map.insert_or_assign(it, keyBegin, V(val)); // O(1), overwrite just before it } else { // it_b->second == val // nop, reuse existing start-point } } while( it != m_map.end() && it->first < keyEnd ) { // O(m) w/ m = period-len (keyEnd-keyBegin) end_val = it->second; it = m_map.erase(it); // O(1) } if( it != m_map.end() ) { // it >= e (keyEnd) if( keyEnd < it->first ) { // it > e (keyEnd) m_map.insert_or_assign(it, keyEnd, end_val); // O(1), inserting just before it } else { // it == e (keyEnd) // nop, reuse existing end-point } } else { m_map.insert_or_assign(it, keyEnd, end_val); // O(1), inserting just before it } return true; } std::string toString() const noexcept { std::string s = "size "; s.append(std::to_string(m_map.size())).append(": "); for (auto const &pair: m_map) { s.append( std::to_string( pair.first )).append(" -> ") .append( std::to_string( pair.second ) ) .append(", "); } return s; } }; } // namespace feature namespace std { template std::string to_string(const feature::IntervalMap& v) noexcept { return std::to_string(v.value()); } template std::ostream& operator<<(std::ostream& out, const feature::IntervalMap& v) noexcept { return out << v.toString(); } } // // test code // typedef feature::IntervalMap test_interval_map_t; void dumpMap(const std::string& prefix, const test_interval_map_t& m, int keyBegin, int keyEnd) { std::cout << prefix << ": " << m << std::endl; std::cout << " : "; for(int k=keyBegin; k