Where exactly does my code not adhere to the specification of the key and value type?
Asked Answered
C

8

20

Task Description

interval_map<K,V> is a data structure that efficiently associates intervals of keys of type K with values of type V. Your task is to implement the assign member function of this data structure, which is outlined below.

interval_map<K, V> is implemented on top of std::map. In case you are not entirely sure which functions std::map provides, what they do and which guarantees they provide, we provide an excerpt of the C++ standard here. (at the end)

Each key-value-pair (k,v) in the std::map means that the value v is associated with the interval from k (including) to the next key (excluding) in the std::map.

Example: the std::map (0,'A'), (3,'B'), (5,'A') represents the mapping

  • 0 -> 'A'
  • 1 -> 'A'
  • 2 -> 'A'
  • 3 -> 'B'
  • 4 -> 'B'
  • 5 -> 'A'
  • 6 -> 'A'
  • 7 -> 'A'

... all the way to numeric_limits<int>::max()

The representation in the std::map must be canonical, that is, consecutive map entries must not have the same value: ..., (0,'A'), (3,'A'), ... is not allowed. Initially, the whole range of K is associated with a given initial value, passed to the constructor of the interval_map data structure.

Here's my implementation of assign(): (rest of the code comes by default).

#include <map>
#include <limits>

template<typename K, typename V>
class interval_map {
    std::map<K,V> m_map;

public:
  // constructor associates whole range of K with val by inserting (K_min, val)
  // into the map
  interval_map( V const& val) {
      m_map.insert(m_map.end(),std::make_pair(std::numeric_limits<K>::lowest(),val));
  }

  // Assign value val to interval [keyBegin, keyEnd).
  // Overwrite previous values in this interval.
  // Conforming to the C++ Standard Library conventions, the interval
  // includes keyBegin, but excludes keyEnd.
  // If !( keyBegin < keyEnd ), this designates an empty interval,
  // and assign must do nothing.
  void assign( K const& keyBegin, K const& keyEnd, V const& val ) {

        // insert code here   
        if (!(keyBegin < keyEnd)) return;

        std::pair<K,V> beginExtra;
        std::pair<K,V> endExtra;
        bool beginHasExtra = false;
        bool endHasExtra = false;

        typename std::map<K,V>::iterator itBegin;
        itBegin = m_map.lower_bound(keyBegin);
        if ( itBegin!=m_map.end() && keyBegin < itBegin->first ) {
            if (itBegin != m_map.begin()) {
                beginHasExtra = true;
                --itBegin;
                beginExtra = std::make_pair(itBegin->first, itBegin->second);
            }
            // openRange for erase is prevIterator
            // insert (prevIterator->first, prevIterator->second) as well!
        }

        typename std::map<K,V>::iterator itEnd;
        itEnd = m_map.lower_bound(keyEnd);
        if ( itEnd!=m_map.end() && keyEnd < itEnd->first ) {
            endHasExtra = true;
            typename std::map<K,V>::iterator extraIt = itEnd;
            --extraIt;
            endExtra = std::make_pair(keyEnd, extraIt->second);
            // closeRange for erase is this iterator
            // insert (keyEnd, prevIterator->second) as well!
        }

        // 4 canonical conflicts:
        //   beginExtra w/ mid
        //   before-mid w/ mid (beginHasExtra==false)
        //   mid w/ endExtra
        //   mid w/ after-mid (endHasExtra==false)

        bool insertMid = true;
        if (beginHasExtra) {
            if (beginExtra.second == val)
                insertMid = false;
        } else {
            if (itBegin != m_map.begin()) {
                typename std::map<K,V>::iterator beforeMid = itBegin;
                --beforeMid;
                if (beforeMid->second == val)
                    insertMid = false;
            }
        }


        if (endHasExtra) {
            if ( (insertMid && endExtra.second == val) || (!insertMid && endExtra.second == beginExtra.second) )
                endHasExtra = false;
        } else {
            if ( (insertMid && itEnd!=m_map.end() && itEnd->second == val) || (!insertMid && itEnd!=m_map.end() && itEnd->second == beginExtra.second) )
                itEnd = m_map.erase(itEnd);
        }

        itBegin = m_map.erase(itBegin, itEnd);
        if (beginHasExtra)
            itBegin = m_map.insert(itBegin, beginExtra);
        if (insertMid)
            itBegin = m_map.insert(itBegin, std::make_pair(keyBegin, val));
        if (endHasExtra)
            m_map.insert(itBegin, endExtra);
  }

  // look-up of the value associated with key
  V const& operator[]( K const& key ) const {
      return ( --m_map.upper_bound(key) )->second;
  }
};

Key type

K

  • besides being copyable and assignable, is less-than comparable via operator<
  • is bounded below, with the lowest value being std::numeric_limits::lowest()
  • does not implement any other operations, in particular no equality comparison or arithmetic operators

Value type

V

  • besides being copyable and assignable, is equality-comparable via operator==
  • does not implement any other operations

===========================================================================

This code works perfectly fine on my local machine. However after submitting the code I got You must adhere to the specification of the key and value type given above. Can anyone tell me what I did wrong? I know I should've used const_iterator for my iterators but the error is talking about K, V.

===========================================================================

The following paragraphs from the final draft of the C++1x ISO standard describe the available 
operations on a std::map container, their effects and their complexity.

23.2.1 General container requirements 

§1 Containers are objects that store other objects. They control allocation and deallocation of 
these objects through constructors, destructors, insert and erase operations.

§6 begin() returns an iterator referring to the first element in the container. end() returns 
an iterator which is the past-the-end value for the container. If the container is empty, 
then begin() == end();

24.2.1 General Iterator Requirements

§1 Iterators are a generalization of pointers that allow a C++ program to work with different 
data structures.

§2 Since iterators are an abstraction of pointers, their semantics is a generalization of most 
of the semantics of pointers in C++. This ensures that every function template that takes 
iterators works as well with regular pointers.

§5 Just as a regular pointer to an array guarantees that there is a pointer value pointing past 
the last element of the array, so for any iterator type there is an iterator value that points 
past the last element of a corresponding sequence. These values are called past-the-end values. 
Values of an iterator i for which the expression *i is defined are called dereferenceable. 
The library never assumes that past-the-end values are dereferenceable. Iterators can also have 
singular values that are not associated with any sequence. [ Example: After the declaration of 
an uninitialized pointer x (as with int* x;), x  must always be assumed to have a singular 
value of a pointer. -end example ] Results of most expressions are undefined for singular 
values; the only exceptions are destroying an iterator that holds a singular value, the 
assignment of a non-singular value to an iterator that holds a singular value, and, for 
iterators that satisfy the DefaultConstructible requirements, using a value-initialized 
iterator as the source of a copy or move operation.

§10 An invalid iterator is an iterator that may be singular. (This definition applies to 
pointers, since pointers are iterators. The effect of dereferencing an iterator that has been 
invalidated is undefined.)

23.2.4 Associative containers

§1 Associative containers provide fast retrieval of data based on keys. The library provides 
four basic kinds of associative containers: set, multiset, map and multimap.

§4 An associative container supports unique keys if it may contain at most one element for each 
key. Otherwise, it supports equivalent keys. The set and map classes support unique keys; the 
multiset and multimap classes support equivalent keys.

§5 For map and multimap the value type is equal to std::pair<const Key, T>. Keys in an 
associative container are immutable.

§6 iterator of an associative container is of the bidirectional iterator category.
(i.e., an iterator i can be incremented and decremented: ++i; --i;)

§9 The insert member functions (see below) shall not affect the validity of iterators and 
references to the container, and the erase members shall invalidate only iterators and 
references to the erased elements.

§10 The fundamental property of iterators of associative containers is that they iterate 
through the containers in the non-descending order of keys where non-descending is defined by 
the comparison that was used to construct them.

Associative container requirements (in addition to general container requirements):

std::pair<iterator, bool> insert(std::pair<const key_type, T> const" t)
Effects: Inserts t if and only if there is no element in the container with key equivalent to 
the key of t. The bool component of the returned pair is true if and only if the insertion 
takes place, and the iterator component of the pair points to the element with key equivalent 
to the key of t.
Complexity: logarithmic

iterator insert(const_iterator p, std::pair<const key_type, T> const" t)
Effects: Inserts t if and only if there is no element with key equivalent to the key of t in 
containers with unique keys. Always returns the iterator pointing to the element with key 
equivalent to the key of t. 
Complexity: logarithmic in general, but amortized constant if t is inserted right before p.

size_type erase(key_type const" k)  
Effects: Erases all elements in the container with key equivalent to k. Returns the number of 
erased elements.
Complexity: log(size of container) + number of elements with key k

iterator erase(const_iterator q) 
Effects: Erases the element pointed to by q. Returns an iterator pointing to the element 
immediately following q prior to the element being erased. If no such element exists, returns 
end().
Complexity: Amortized constant

iterator erase(const_iterator q1, const_iterator q2)
Effects: Erases all the elements in the left-inclusive and right-exclusive range [q1,q2). 
Returns q2.
Complexity: Amortized O(N) where N has the value distance(q1, q2).

void clear() 
Effects: erase(begin(), end())
Post-Condition: empty() returns true
Complexity: linear in size().

iterator find(key_type const" k);
Effects: Returns an iterator pointing to an element with the key equivalent to k, or end() if 
such an element is not found.
Complexity: logarithmic

size_type count(key_type constquot;& k) 
Effects: Returns the number of elements with key equivalent to k
Complexity: log(size of map) + number of elements with key equivalent to k

iterator lower_bound(key_type const" k)
Effects: Returns an iterator pointing to the first element with key not less than k, or end() 
if such an element is not found.
Complexity: logarithmic

iterator upper_bound(key_type const" k)
Effects: Returns an iterator pointing to the first element with key greater than k, or end() 
if such an element is not found.
Complexity: logarithmic

23.4.1 Class template map

§1 A map is an associative container that supports unique keys (contains at most one of each 
key value) and provides for fast retrieval of values of another type T based on the keys. The 
map class supports bidirectional iterators.

23.4.1.2 map element access

T" operator[](const key_type" x);
Effects: If there is no key equivalent to x in the map, inserts value_type(x, T()) into the map. 
Returns: A reference to the mapped_type corresponding to x in *this.
Complexity: logarithmic.

T" at(const key_type" x);
const T" at(const key_type" x) const;
Returns: A reference to the element whose key is equivalent to x.
Throws: An exception object of type out_of_range if no such element is present.
Complexity: logarithmic.
Cookson answered 7/1, 2019 at 4:14 Comment(10)
What is giving the message "You must adhere to the specification..."? Are you sure it's talking about the same specification as the requirements of std::map?Tinishatinker
@Tinishatinker I had to submit the code online and it gave me this exact error in the quotation. Honestly I don't know but it probably has something to do with std::map requirements.Cookson
It's hard to tell which parts are from the problem statement and which are your comments on the issue, but I think the "Key type" and "Value type" are describing types the judge will provide as template arguments. So you need to rely on only those guaranteed operations - in particular, I see a use of std::numeric_limits<K>::lowest(), which is not guaranteed to be valid or meaningful.Tinishatinker
@Tinishatinker I re-edited my post & code for better readability. My code is only the part inside assign block.Cookson
Oh wait, it does say numeric_limits<K>::lowest() is okay, right there. I must need sleep.Tinishatinker
very intresting question, I don't know why people downvote you :|Ventilator
Why --upper_bound(key) instead of lower_bound(key)? In any event, this is a "Why is my code not working?" question without really much in the way of what is actually going wrong. It's literally "Here, debug this for me." That doesn't typically make for a great question. Do you have any particular examples that go wrong or should go wrong? The question doesn't even include what you're trying to do - I'm assuming just writing assign() was the problem but even that isn't clear.Cicerone
Maybe downvoted because of cross-posting hereOutshine
Can i know which compiler gives this issue? I'm able to run it on every compiler.Alek
@d9ngle: have you found the passing solution in the end?Aviatrix
C
8

You are requiring your types to be default constructible:

std::pair<K,V> beginExtra;
std::pair<K,V> endExtra;

That is probably the source of the complaint.

Cicerone answered 9/1, 2019 at 17:34 Comment(5)
Can you please elaborate? I think this is it.Cookson
@Cookson Which part do you need elaborated? You're requiring something extra.Cicerone
Curious, how would you have set the beginExtra from inside the ifs? Using a pointer to pair, a pair of pointers or simply constructing it with some random values (from parameters in the function) ?Cookson
@Cookson The approach seems suspect to me. But it seems like you're after optional<pair<K, V>> (which would obviate the extra flag that you're using to track state)Cicerone
Can I know the final changes here? Still use std::pair<K,V> beginExtra; std::pair<K,V> endExtra; Or need to provide default constructible functions?Jeana
E
9

Like others have said, the problem in your code is the assumption that K, V both can be default constructed. This becomes clear when you test a key type that is not default-constructible (see my test below)

'std::pair<K,V>::pair': no appropriate default constructor available

Here's my implementation, which passed the correctness check, but failed the runtime complexity check. I can't see how it is possible to erase N keys but keep the complexity O(logN), consider the following legit scenario:

Before assigning

'A' ................. 'B' ....A MILLION INTERVALS........ 'C' ..........................'A'..

After assigning a new interval, overwriting previous ones:

'A' ......... 'D'................................................................... 'A' ........................

I am pretty sure erasing N nodes takes at least O(N) time, since deallocating the memory for each node alone would be linear. No matter what smart way, dropping nodes between the new begin and new end would be linear. Another equivalent way would be extracting nodes and change their keys; However, that would only shift redundant keys towards the end rather than the middle.

Probably the right answer is somewhere in the newly added member functions - map::extract or map::merge. It would also be possible to find both the start and the end insertion position with just one call, if the declaration of std::map allowed heterogeneous lookup (equal_range with a specifically designed "range key" type). However that wouldn't help the linear O(N) erasure part.

#define CATCH_CONFIG_MAIN
#include "catch.hpp"


#include <map>
#include <limits>

template<typename K, typename V>
class interval_map {
public:
    std::map<K, V> m_map;


    // constructor associates whole range of K with val by inserting (K_min, val)
    // into the map
    interval_map(V const& val) {
        m_map.insert(m_map.end(), std::make_pair(std::numeric_limits<K>::lowest(), val));
    }

    // Assign value val to interval [keyBegin, keyEnd).
    // Overwrite previous values in this interval.
    // Conforming to the C++ Standard Library conventions, the interval
    // includes keyBegin, but excludes keyEnd.
    // If !( keyBegin < keyEnd ), this designates an empty interval,
    // and assign must do nothing.
    void assign(K const& keyBegin, K const& keyEnd, V const& val) {
        if (!(keyBegin < keyEnd))
            return;

        typename std::map<K, V>::iterator iterBegin; /*The new begin with val, can be begin()*/
        typename std::map<K, V>::iterator iterEnd;   /*the new end of val, can be end()*/

        auto lowerKeyBegin = m_map.lower_bound(keyBegin); //either end() or some iter whose key is not less than keyBegin. [1st O(logN)]
        auto upperKeyEnd = m_map.upper_bound(keyEnd); //some iter where keyEnd < key, or end()  [2nd O(logN)]
        auto prevKeyEnd = std::prev(upperKeyEnd);

        /*
        The next interval of the new interval starts at keyEnd if the previous value at keyEnd differed from val
        */
        if (!(prevKeyEnd->second == val))
        {
            // prevKeyEnd is either less than the new end we are inserting, or the same (no update to avoid copying from erased node)
            if (!(prevKeyEnd->first < keyEnd) && !(keyEnd < prevKeyEnd->first))
                iterEnd = prevKeyEnd;
            else
                iterEnd = m_map.insert_or_assign(upperKeyEnd, keyEnd, prevKeyEnd->second);
        }
        else
        {
            iterEnd = upperKeyEnd;
        }

        /*
        The new interval starts at keyBegin if the would-be previous interval has a different value.
        Previous interval is either a key in the map less than keyBegin, or non-existent when lower_bound is m_map.begin()
        The new interval's start is merged with previous interval, if the previous interval has the same value.
        */
        if (lowerKeyBegin != m_map.begin())
        {
            auto prevIter = std::prev(lowerKeyBegin); //safe when end(), because we always have at least one value
            if (!(prevIter->second == val))
            {
                iterBegin = m_map.insert_or_assign(lowerKeyBegin, keyBegin, val);
            }
            else iterBegin = prevIter;
        }
        else
        {
            iterBegin = m_map.insert_or_assign(lowerKeyBegin, keyBegin, val);
        }

        /*
        Erase all keys between the new begin and end (excluding) so that there is only one value after iterBegin
        This is fine when iterEnd is end()
        */
        {
            auto nextIterOfBegin = std::next(iterBegin);//somehow msvc doesn't support if-initialization
            if (nextIterOfBegin != m_map.end())
            {
                //I would be very interested in a smarter way to get rid of this part without additional storage ...
                m_map.erase(nextIterOfBegin, iterEnd); 
            }
        }

        ////debug - check canonical
        //for (auto iter = m_map.begin(); iter != m_map.end(); ++iter)
        //{
        //  auto next = std::next(iter);
        //  if (next != m_map.end() && iter->second == next->second)
        //  {
        //      throw;
        //  }
        //}
    }

    // look-up of the value associated with key
    V const& operator[](K const& key) const {
        return (--m_map.upper_bound(key))->second;
    }
};

// Many solutions we receive are incorrect. Consider using a randomized test
// to discover the cases that your implementation does not handle correctly.
// We recommend to implement a test function that tests the functionality of
// the interval_map, for example using a map of unsigned int intervals to char.

struct TestKeyType
{
    unsigned int val;
    constexpr TestKeyType(unsigned int val) : val(val) {}
    constexpr bool operator<(const TestKeyType& other) const { return val < other.val; }
};

namespace std {
    template<> class numeric_limits<TestKeyType> {
    public:
        static constexpr TestKeyType lowest() { return TestKeyType(numeric_limits<unsigned int>::lowest()); }
        //static constexpr TestKeyType lowest() { return TestKeyType(-250); }
    };
}

using TestValueType = char;

struct TestFloatKeyType
{
    float val;

    TestFloatKeyType() = default;

    TestFloatKeyType(float val) : val(val) {}
    bool operator< (TestFloatKeyType other) const
    {
        return other.val - val > 1.e-4f;
    }
};

namespace std {
    template<> class numeric_limits<TestFloatKeyType> {
    public:
        static TestFloatKeyType lowest() { return TestFloatKeyType(numeric_limits<float>::lowest()); }
    };
}

TEST_CASE("EmptyRange")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(3, 3, 'B');
    REQUIRE(m.m_map.count(3) == 0);

    m.assign(3, 2, 'B');
    REQUIRE(m.m_map.count(2) == 0);
    REQUIRE(m.m_map.count(3) == 0);
}


TEST_CASE("TrivialRange")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(1, 10, 'B');
    REQUIRE(m[0] == 'A');
    for (int i = 1; i < 10; i++)
    {
        REQUIRE(m[i] == 'B');
    }
    REQUIRE(m[10] == 'A');
}

TEST_CASE("TrivialTwoRange")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(1, 3, 'B');
    m.assign(6, 8, 'C');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'B');
    REQUIRE(m[2] == 'B');
    REQUIRE(m[3] == 'A');
    REQUIRE(m[4] == 'A');
    REQUIRE(m[5] == 'A');
    REQUIRE(m[6] == 'C');
    REQUIRE(m[7] == 'C');
    REQUIRE(m[8] == 'A');
}

TEST_CASE("OverwriteLowest")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(std::numeric_limits<TestKeyType>::lowest(), 10000, 'B');
    REQUIRE(m[0] == 'B');
    REQUIRE(m[9999] == 'B');
    REQUIRE(m[10000] == 'A');
}

TEST_CASE("Merge")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(std::numeric_limits<TestKeyType>::lowest(), 10, 'B');
    m.assign(10, 20, 'B');
    REQUIRE(m[0] == 'B');
    REQUIRE(m[10] == 'B');
    REQUIRE(m[19] == 'B');
    REQUIRE(m[20] == 'A');
}

TEST_CASE("FloatKey")
{
    interval_map<TestFloatKeyType, TestValueType> m('A');
    m.assign(1.f, 5.f, 'B');
    REQUIRE(m[0.f] == 'A');
    REQUIRE(m[.999999999f] == 'B');
    REQUIRE(m[1.f] == 'B');
    REQUIRE(m[4.999f] == 'B');
    REQUIRE(m[5.f] == 'A');

}

TEST_CASE("OverlappingRangeComplete")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(3, 5, 'B');
    m.assign(1, 6, 'C');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'C');
    REQUIRE(m[2] == 'C');
    REQUIRE(m[3] == 'C');
    REQUIRE(m[4] == 'C');
    REQUIRE(m[5] == 'C');
    REQUIRE(m[6] == 'A');
}

TEST_CASE("OverlappingRangeInner")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(1, 6, 'C');
    m.assign(3, 5, 'B');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'C');
    REQUIRE(m[2] == 'C');
    REQUIRE(m[3] == 'B');
    REQUIRE(m[4] == 'B');
    REQUIRE(m[5] == 'C');
    REQUIRE(m[6] == 'A');
}

TEST_CASE("OverlappingRangeSmallToLarge")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(1, 5, 'B');
    m.assign(3, 6, 'C');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'B');
    REQUIRE(m[2] == 'B');
    REQUIRE(m[3] == 'C');
    REQUIRE(m[4] == 'C');
    REQUIRE(m[5] == 'C');
    REQUIRE(m[6] == 'A');
}

TEST_CASE("OverlappingRangeLargeToSmall")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(3, 6, 'C');
    m.assign(1, 5, 'B');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'B');
    REQUIRE(m[2] == 'B');
    REQUIRE(m[3] == 'B');
    REQUIRE(m[4] == 'B');
    REQUIRE(m[5] == 'C');
    REQUIRE(m[6] == 'A');
}

TEST_CASE("ExtendingRangeBegin")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(3, 5, 'B');
    m.assign(1, 4, 'B');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'B');
    REQUIRE(m[2] == 'B');
    REQUIRE(m[3] == 'B');
    REQUIRE(m[4] == 'B');
    REQUIRE(m[5] == 'A');
}

TEST_CASE("ExtendingRangeEnd")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(1, 5, 'B');
    m.assign(3, 6, 'B');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'B');
    REQUIRE(m[2] == 'B');
    REQUIRE(m[3] == 'B');
    REQUIRE(m[4] == 'B');
    REQUIRE(m[5] == 'B');
    REQUIRE(m[6] == 'A');
}

TEST_CASE("ExtendingRangeBothBeginEnd")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(2, 3, 'B');
    m.assign(1, 5, 'B');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'B');
    REQUIRE(m[2] == 'B');
    REQUIRE(m[3] == 'B');
    REQUIRE(m[4] == 'B');
    REQUIRE(m[5] == 'A');
}

TEST_CASE("OverwriteEndValueSafety")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(2, 5, 'B');
    m.assign(5, 8, 'C');
    m.assign(4, 5, 'A');
}

TEST_CASE("ReusingExistingRangeBothBeginEnd")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(1, 5, 'B');
    m.assign(2, 3, 'B');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'B');
    REQUIRE(m[2] == 'B');
    REQUIRE(m[3] == 'B');
    REQUIRE(m[4] == 'B');
    REQUIRE(m[5] == 'A');
}

TEST_CASE("ReusingEnd")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(1, 5, 'B');
    m.assign(4, 6, 'A');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'B');
    REQUIRE(m[2] == 'B');
    REQUIRE(m[3] == 'B');
    REQUIRE(m[4] == 'A');
    REQUIRE(m[5] == 'A');
}

TEST_CASE("RestoringInitial")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(1, 5, 'B');
    m.assign(1, 5, 'A');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'A');
    REQUIRE(m[2] == 'A');
    REQUIRE(m[3] == 'A');
    REQUIRE(m[4] == 'A');
    REQUIRE(m[5] == 'A');
}

TEST_CASE("RestoringInitial2")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(1, 5, 'B');
    m.assign(0, 7, 'A');
    REQUIRE(m[0] == 'A');
    REQUIRE(m[1] == 'A');
    REQUIRE(m[2] == 'A');
    REQUIRE(m[3] == 'A');
    REQUIRE(m[4] == 'A');
    REQUIRE(m[5] == 'A');
}

TEST_CASE("VeryComplex")
{
    interval_map<TestKeyType, TestValueType> m('A');
    m.assign(3, 6, 'B');
    m.assign(2, 5, 'C');
    m.assign(4, 7, 'A');

    REQUIRE(m[1] == 'A');
    REQUIRE(m[2] == 'C');
    REQUIRE(m[3] == 'C');
    REQUIRE(m[4] == 'A');
    REQUIRE(m[5] == 'A');
    REQUIRE(m[6] == 'A');
    REQUIRE(m[7] == 'A');
}
Elianore answered 11/2, 2019 at 21:47 Comment(6)
It is an answer demonstrating what a correct solution to the you-know-which question looks.Elianore
Sorry, I interpreted it as "why my solution to this task doesn't pass time requirements" at first. :/ Then I suggest explicitly listing changes that make it pass the correctness check.Valona
I am pretty sure erasing N nodes takes at least O(N) time. This is true. But it takes amortized O(1) time, and it is amortized time that is mentioned in the problem statement.Rexrexana
auto prevKeyEnd = std::prev(upperKeyEnd); </br> @Elianore I am surprised that you had this line and passed their correctness check.Quintus
Do you have a working solution?Aviatrix
Your implementation likely failed the runtime complexity check because you called lower_bound before inserting the end boundary. This means that when you insert the beginning of the interval, it is no longer guaranteed that the operation will happen in amortized O(1). Here's how it's described on cppreference "Logarithmic in the size of the container in general, but amortized constant if the new element is inserted just before hint." Since iterEnd might end up between iterBegin and lowerKeyBegin, it breaks this requirement leading to O(log N) complexity.Clearway
C
8

You are requiring your types to be default constructible:

std::pair<K,V> beginExtra;
std::pair<K,V> endExtra;

That is probably the source of the complaint.

Cicerone answered 9/1, 2019 at 17:34 Comment(5)
Can you please elaborate? I think this is it.Cookson
@Cookson Which part do you need elaborated? You're requiring something extra.Cicerone
Curious, how would you have set the beginExtra from inside the ifs? Using a pointer to pair, a pair of pointers or simply constructing it with some random values (from parameters in the function) ?Cookson
@Cookson The approach seems suspect to me. But it seems like you're after optional<pair<K, V>> (which would obviate the extra flag that you're using to track state)Cicerone
Can I know the final changes here? Still use std::pair<K,V> beginExtra; std::pair<K,V> endExtra; Or need to provide default constructible functions?Jeana
D
4

Shortest solution I could come up with for the assign() method.

void assign( K const& keyBegin, K const& keyEnd, V const& val ) {
    if ( not ( keyBegin < keyEnd ) ) {
        return;
    }

    // no need to try to add the same value to the end
    if ( ( --m_map.upper_bound(keyEnd) )->second == val ) {
        return;
    }

    auto beginLowerBound = m_map.lower_bound( keyBegin );
    auto endLowerBound = m_map.lower_bound( keyEnd );
    auto calculatedKeyBegin = keyBegin;
    bool partial = false;

    std::pair<K,V> additionalElement = *std::prev( m_map.end() );

    if ( additionalElement.first < keyEnd ) {
        additionalElement.first = keyEnd;
    }

    auto eraseEnd = endLowerBound;

    if ( keyEnd < eraseEnd->first ) {
        additionalElement = std::pair( keyEnd, std::prev( endLowerBound )->second );
        partial = true;
    }

    if ( beginLowerBound != m_map.end() ) {
        if ( eraseEnd != m_map.end() && eraseEnd->second == val && not partial ) {
            ++eraseEnd;
        }

        m_map.erase( beginLowerBound, eraseEnd );
    }

    m_map.insert( std::pair( calculatedKeyBegin, val ) );

    if ( not ( additionalElement.second == val ) ) {
        m_map.insert( additionalElement );
    }
}
Disconnected answered 6/5, 2020 at 7:6 Comment(2)
I tested this but this solution does not satisfy the cardinality requirement.Dewyeyed
Also this solution incorrect for testcase: map.assign(2, 6, 'A'); map.assign(3, 5, 'B'); // [-,-,A,B,B,A,-,-]Botheration
D
3

I don't think the error message is entirely correct. But just this small piece of code will show you that your code is not correct:

interval_map<uint8_t, std::string> moo("A");
moo.assign(1, 15, "B"); 
std::cout << moo[255];

Expected value is A, returned value is B.

Dunleavy answered 9/1, 2019 at 10:20 Comment(2)
Oh yeah, I'd actually thought of this case but forgot to write it in code. Should check insert position of our range and if it's at the beginning or end and not covering the whole range, mirror it's neighbour. However my code didn't even go that far and was stuck at adhere to the specification of the key/value after compilation.Cookson
I removed the NDA notes as it does not feel right for a technical question. Secondly, this sounds like a comment, not an answer to me.Aviatrix
S
1

Here is my implementation but it didn't pass the correctness test for some reason.

    void assign( K const& keyBegin, K const& keyEnd, V const& val ) {

        if (!(keyBegin < keyEnd))
            return;

        auto beginIt = m_map.lower_bound(keyBegin);
        auto endIt = m_map.upper_bound(keyEnd);
        auto endVal = (*this)[keyEnd]; //store endvalue for later use

        m_map.erase(beginIt, endIt);

        //insert new elements
        auto newIt = m_map.insert_or_assign(keyBegin, val).first; // equivalent to m_map[keyBegin] = val;
        auto it1 = newIt;
        //store the previous iterator if exists for canonical check
        if(--it1 == m_map.end()){
            it1 = newIt;
        }

        auto it2 = ++m_map.insert_or_assign(keyEnd, endVal).first; //store the next iterator for canonical check, equivalent to m_map[keyEnd] = endVal;

        //check for duplicated keys between our inserted elements and erase them
        V checkVal = it1->second;
        auto next_it = ++it1;
        do{
            next_it++;
            if(it1->second == checkVal){
                m_map.erase(it1);
            }else{
                checkVal = it1->second;
            }
            it1 = next_it;

        }while(it1 != it2);

        if(m_map.begin()->second == m_valBegin){
            m_map.erase(m_map.begin());
        }

    }
Softwood answered 27/5, 2023 at 17:37 Comment(1)
What was the error message you got?Aviatrix
G
0

d9ngle. You would have wanted to work with references or even pointers to Keys and Values to avoid constructing them.

Got lured like a fool into this, too. What a heartbreaker indeed this was... Here's my fastest, smallest, and most compliant solution so far:

void assign(K const& keyBegin, K const& keyEnd, V const& val) {
    if (!(keyBegin < keyEnd))
        return;
    if (m_map.begin() == m_map.end())
        m_map.insert_or_assign(m_map.begin(), keyEnd, m_valBegin); // avoiding lowest();
    const auto& l = m_map.lower_bound(keyBegin);            // O(log(N))
    auto R = l;
    while (R != m_map.end() && !(keyEnd < R->first)) ++R;   // O(D) is faster than O(log(N) for D << N.
    const auto& r = std::prev(R);                           // r is never at the end.
    m_map.erase(                                            // (B,E) is the erase opened segment
        /* auto& E = */ std::next(                          // E
        l == m_map.begin() || !(std::prev(l)->second == val) ?
        m_map.insert_or_assign(l, keyBegin, val) :
        /* auto& L = */ std::prev(l)),
        /* auto& B = */ r->second == val ? R :              // B
        !(r->first < keyEnd) && !(keyEnd < r->first) ? r :
        m_map.insert_or_assign(R, keyEnd, r->second));
}

... after I, too, got “Type requirements are met: You must adhere to the specification of the key and value type given above.” with the following submission:

void assign(K const& keyBegin, K const& keyEnd, V const& val) {
    if (!(keyBegin < keyEnd))
        return;                                      // trivial rejection of degenerated segments.
    // Adding the Z infimum element to the map simplifies the algorithm in that any key value would be guaranteed to have a lower bound.
    const auto& z = std::numeric_limits<K>::lowest();
    if (m_map.begin() == m_map.end())
        m_map.insert(std::make_pair(z, m_valBegin)).first;
    const auto& l = m_map.lower_bound(keyBegin);            // O(log(N))
#if 1
    auto R = l;                                             // lower bound is guaranteed by the Z imfimum. 
    while (R != m_map.end() && !(keyEnd < R->first)) ++R;   // O(D) is faster than O(log(N) for D << N.
#else
    const auto& R = m_map.upper_bound(keyEnd);              // O(log(N)), is slower than O(D) for short [b,e) distances
#endif
    const auto& r = std::prev(R);                           // r is never at the end.
    const auto& E = r->second == val ? R :
        !(r->first < keyEnd) && !(keyEnd < r->first) ? r :
        m_map.insert_or_assign(R, keyEnd, r->second);
    const auto& B = std::next(                              // Culd be made a bit faster if lowest is guaranteed not to be used as a valid key, 
        l == m_map.begin() || !(std::prev(l)->second == val) ? m_map.insert_or_assign(l, keyBegin, val) :
        std::prev(l));
    m_map.erase(B, E);                                      // (B,E) is the erase range.
}

I, too, submit my framework for testing in regression and in performance various versions of this algorithm. Comment-out the inclusion of windows.h, if you are on a different platform.

/* 
* Test framework ans attempted solution for the interval_map problem.
* Created: 2024_03_04, Modified: 2024_03_07
*/

#include <map>

/*
Task Description
interval_map<K,V> is a data structure that efficiently associates intervals of keys of type K with values of type V. Your task is to implement the assign member function of this data structure, which is outlined below.
interval_map<K, V> is implemented on top of std::map. In case you are not entirely sure which functions std::map provides, what they do and which guarantees they provide, we provide an excerpt of the C++ standard here:
Each key-value-pair (k,v) in the std::map means that the value v is associated with the interval from k (including) to the next key (excluding) in the std::map.
Example: the std::map (0,'A'), (3,'B'), (5,'A') represents the mapping
0 -> 'A'
1 -> 'A'
2 -> 'A'
3 -> 'B'
4 -> 'B'
5 -> 'A'
6 -> 'A'
7 -> 'A'
... all the way to numeric_limits<int>::max()
The representation in the std::map must be canonical, that is, consecutive map entries must not have the same value: ..., (0,'A'), (3,'A'), ... is not allowed. Initially, the whole range of K is associated with a given initial value, passed to the constructor of the interval_map<K,V> data structure.
Key type K
besides being copyable and assignable, is less-than comparable via operator<
is bounded below, with the lowest value being std::numeric_limits<K>::lowest()
does not implement any other operations, in particular no equality comparison or arithmetic operators
Value type V
besides being copyable and assignable, is equality-comparable via operator==
does not implement any other operations
*/

#define STYLE 2

enum EAalgorithms {
    AMsReference,
    AMsSubmission, 
    AMsFastest,
    ALGMAX
};
const char* algorithms[] = { 
    "AM's Reference",
    "AM's Submission", 
    "AM's Fastest",
};
static int algorithm{ 0 };
static int alg_min = 0;
static int alg_max = ALGMAX;
static int index_op_warnings = 0;

template<typename K, typename V>
class interval_map
{

public:
    std::map<K, V> m_map;
#if STYLE==2
    V m_valBegin;
    // constructor associates whole range of K with val
    interval_map(V const& val)
        : m_valBegin(val)
    {}
    // look-up of the value associated with key
    V const& operator[](K const& key) const {
        index_op_warnings++;
        auto it = m_map.upper_bound(key);
        if (it == m_map.begin())
            return m_valBegin;
        return (--it)->second;
    }
#else
    // constructor associates whole range of K with val by inserting (K_min, val) into the map
    interval_map(V const& val) { m_map.insert(m_map.end(), std::make_pair(std::numeric_limits<K>::lowest(), val)); }
    // look-up of the value associated with key
    V const& operator[](K const& key) const { return (--m_map.upper_bound(key))->second; }
#endif

    // Assign value val to interval [keyBegin, keyEnd).
    // Overwrite previous values in this interval.
    // Conforming to the C++ Standard Library conventions, the interval
    // includes keyBegin, but excludes keyEnd.
    // If !( keyBegin < keyEnd ), this designates an empty interval,
    // and assign must do nothing.
    void assign(K const& keyBegin, K const& keyEnd, V const& val)
    {
        if (!(keyBegin < keyEnd))
            return;                                      // trivial rejection of degenerated segments.

        switch (algorithm)
        {
        case AMsReference:
        {
#if STYLE==2
            if (m_map.begin() == m_map.end())
                m_map.insert_or_assign(m_map.begin(), keyEnd, m_valBegin); // avoiding std::numeric_limits<K>::lowest();
#endif
            auto l = m_map.lower_bound(keyBegin);                  // O(log(N))
            auto R = m_map.upper_bound(keyEnd);                  // O(log(N))
            auto r = std::prev(R);
            m_map.erase(                                    // B 
                std::next(l == m_map.begin() ? m_map.insert_or_assign(l, keyBegin, val) :
                    std::prev(l)->second == val ? std::prev(l) :
                    m_map.insert_or_assign(l, keyBegin, val))
                , 
                r->second == val ? R :                    // E
                !(r->first < keyEnd) && !(keyEnd < r->first) ? r :
                m_map.insert_or_assign(R, keyEnd, r->second)
            );
            break;
        }
        case AMsSubmission:
        {
            //          keyBegin   keyEnd
            //          vvvvvvvvvvvv-
            //          [-----------)            <- interval [b,e) as argument
            //  [-----X----X-----X-------X-----) <- map content
            //  Z     L    l     r       R     end
            //  aaaaaabbbbbccccccbbbbbbbbaaaaaa-

            // Adding the Z infimum element to the map simplifies the algorithm in that any key value would be guaranteed to have a lower bound.
            const auto& z = std::numeric_limits<K>::lowest();
#if STYLE==2
            if (m_map.begin() == m_map.end())
                m_map.insert(std::make_pair(z, m_valBegin)).first;
#endif
            const auto& l = m_map.lower_bound(keyBegin);            // O(log(N))
#if 1
            auto R = l;                                             // lower bound is guaranteed by the Z imfimum. 
            while (R != m_map.end() && !(keyEnd < R->first)) ++R;   // O(D) is faster than O(log(N) for D << N.
#else
            const auto& R = m_map.upper_bound(keyEnd);              // O(log(N)), is slower than O(D) for short [b,e) distances
#endif
            const auto& r = std::prev(R);                           // r is never at the end.
            const auto& E = r->second == val ? R :
                !(r->first < keyEnd) && !(keyEnd < r->first) ? r :
                m_map.insert_or_assign(R, keyEnd, r->second);
            const auto& B = std::next(                              // Culd be made a bit faster if lowest is guaranteed not to be used as a valid key, 
                l == m_map.begin() || !(std::prev(l)->second == val) ? m_map.insert_or_assign(l, keyBegin, val) :
                std::prev(l));
            m_map.erase(B, E);                                      // (B,E) is the erase range.

            //  [-----X-X-----------X----X-----) <- resulting map content
            //  Z     L B           E    R     end
            //  aaaaaabbvvvvvvvvvvvvbbbbbaaaaaa-
            //  0123456789012345678901234567890-
            //  0         1         2         3
            break;
        }
        case AMsFastest:
        {
#if STYLE==2
            if (m_map.begin() == m_map.end())
                m_map.insert_or_assign(m_map.begin(), keyEnd, m_valBegin); // avoiding std::numeric_limits<K>::lowest();
#endif
            //          b           e
            //          vvvvvvvvvvvv-
            //          [-----------)            <- interval [b,e) as argument
            //  [-----X----X-----X-------X-----) <- map content
            //  Z     L    l     r       R     end
            //  aaaaaabbbbbccccccbbbbbbbbaaaaaa-

            const auto& l = m_map.lower_bound(keyBegin);            // O(log(N))
            auto R = l;
            while (R != m_map.end() && !(keyEnd < R->first)) ++R;     // O(D) is faster than O(log(N) for D << N.
            const auto& r = std::prev(R);                           // r is never at the end.
            m_map.erase(                                            // (B,E) is the erase segment, opened at both ends
                /* auto& E = */ std::next(                          // E
                    l == m_map.begin() || !(std::prev(l)->second == val) ?
                    m_map.insert_or_assign(l, keyBegin, val) :
                    /* auto& L = */ std::prev(l)),
                /* auto& B = */ r->second == val ? R :              // B
                !(r->first < keyEnd) && !(keyEnd < r->first) ? r :
                m_map.insert_or_assign(R, keyEnd, r->second));

            //  [-----X-X-----------X----X-----) <- resulting map content
            //  Z     L B           E    R     end
            //  aaaaaabbvvvvvvvvvvvvbbbbbaaaaaa-
            //  0123456789012345678901234567890-
            //  0         1         2         3
            break;
        }
        }
    }
};

// Many solutions we receive are incorrect. Consider using a randomized test
// to discover the cases that your implementation does not handle correctly.
// We recommend to implement a test function that tests the functionality of
// the interval_map, for example using a map of unsigned int intervals to char.

#include <iostream>
#include <windows.h>
//#include <math.h>

class KT
{
    friend std::ostream& operator<<(std::ostream& os, const KT& K);
    unsigned int val;
public:
    static int errors;
    static int warnings;
    KT() : val(0) { errors++; } //KT() = delete;
    constexpr KT(unsigned int val) : val(val) { warnings++; }
    constexpr bool operator<(const KT& other) const { return val < other.val; }
};
int KT::errors = 0;
int KT::warnings = 0;

class KF
{
    friend std::ostream& operator<<(std::ostream& os, const KF& K);
    float val;
public:
    static int errors;
    static int warnings;
    KF() : val(0) { errors++; } //KF() = delete;
    KF(float val) : val(val) { warnings++; }
    bool operator< (KF other) const { return other.val - val > 1.e-4f; }
};
int KF::errors = 0;
int KF::warnings = 0;

class KS
{
    friend std::ostream& operator<<(std::ostream& os, const KS& K);
    std::string val;
public:
    static int errors;
    static int warnings;
    KS() : val("") { errors++; } //KS() = delete;
    KS(const char* cstr) : val(std::string(cstr)) { warnings++; }
    bool operator< (KS other) const { return val < other.val; }
};
int KS::errors = 0;
int KS::warnings = 0;

class VT
{
    friend std::ostream& operator<<(std::ostream& os, const VT& K);
    char val;
public:
    static int errors;
    static int warnings;
    VT() : val(0) { errors++; } //VT() = delete;
    constexpr VT(unsigned int val) : val(val) { warnings++; }
    constexpr bool operator==(const VT& other) const { return val == other.val; }
};
int VT::errors = 0;
int VT::warnings = 0;

std::ostream& operator<<(std::ostream& os, const KF& K) { os << K.val; return os; }
std::ostream& operator<<(std::ostream& os, const VT& V) { os << V.val; return os; }
std::ostream& operator<<(std::ostream& os, const KT& K) { os << K.val; return os; }
std::ostream& operator<<(std::ostream& os, const KS& K) { os << K.val; return os; }

// uncomment to include performance counters 
// #include <windows.h>
#include <assert.h>

void IntervalMapTest()
{
    bool error = false;

#if 1 // These defines are a bit much, but they essentialise the unit tests that follow.
#define START_TEST(name) bool error = false; printf("%30s: ", #name);
#define LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))
#define START_TEST(name) bool error = false; printf("%30s: ", #name);
#define TEST(cond) if (error = !(cond)) printf(" error %s ", #cond)
#define CLEAR_COUNTERS \
    index_op_warnings = 0; \
    KT::errors = KF::errors = KS::errors = VT::errors = 0; \
    KT::warnings = KF::warnings = KS::warnings = VT::warnings = 0;
#define TEST_COUNTERS\
    index_op_warnings && printf("Use of operator [] warning, "); \
    error |= KT::errors && printf("KT default ctor error, "); \
    error |= KF::errors && printf("KF default ctor error, "); \
    error |= KS::errors && printf("KS default ctor error, "); \
    error |= VT::errors && printf("VT default ctor error, "); \
    error |= KT::warnings && printf("KT-ctor warning, "); \
    error |= KF::warnings && printf("KF-ctor warning, "); \
    error |= KS::warnings && printf("KS-ctor warning, "); \
    error |= VT::warnings && printf("VT-ctor warning, ");
#define SET_INTERVALS\
    for (const auto &interval : intervals)\
        m.assign(interval.L, interval.R, interval.Label);
#define TEST_EXPECTATIONS \
    TEST_COUNTERS; \
    for (const auto &exp : expectations)\
        if (!(m[exp.pos] == exp.Label))\
        {\
            std::cout  << std::endl << "\t\t\t m[" << exp.pos << "] = " << m[exp.pos] << "<>" << exp.Label << " ";\
            error = true;\
        }
#define TEST_VERIFY \
    for (auto it = m.m_map.begin(); it != m.m_map.end(); ++it)\
    {\
      auto next = std::next(it);\
      if (next != m.m_map.end() && it->second == next->second)\
      {\
          printf(" Map has duplicated consecutive values. ");\
          error = true;\
          break;\
      }\
    }\
    std::cout << "size: " << m.m_map.size();\
    for (auto entry : m.m_map) \
        std::cout << ", {" << entry.first << "," << entry.second << "}"; \
    printf("%s\n", error ? ", FAIL." : ", ok.");
#define END_TEST {CLEAR_COUNTERS; SET_INTERVALS; TEST_EXPECTATIONS; TEST_VERIFY;}
#endif

    {
        {
            START_TEST(PredefinedTest);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {1,3,'B'} };
            struct { int pos; char Label; } expectations[] = { {-2,'A'}, {-1, 'A'},  {0,'A'}, {1,'B'}, {2,'B'}, {3,'A'}, {4,'A'}, {5, 'A'} };
            END_TEST;
        }
        {
            START_TEST(Simple);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {1,3,'B'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'A'} };
            END_TEST;
        }
#if 1
        {
            START_TEST(DistinctSegments);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {1,3,'B'}, {6,8,'C'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'A'}, {4,'A'}, {5,'A'}, {6,'C'}, {7,'C'}, {8,'A'} };
            END_TEST;
        }
        {
            START_TEST(PyramidUp);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {1, 6, 'B'}, {3, 5, 'C'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'C'}, {4,'C'}, {5,'B'},{6, 'A'} };
            END_TEST;
        }
        {
            START_TEST(PyramidDown);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {3, 5, 'B'}, {1, 6, 'C'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'C'}, {2,'C'}, {3,'C'}, {4,'C'}, {5,'C'},{6, 'A'} };
            END_TEST;
        }
        {
            START_TEST(GrowRight);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {1, 5, 'B'}, {3, 6, 'B'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'B'}, {4,'B'}, {5,'B'},{6, 'A'} };
            END_TEST;
        }
        {
            START_TEST(GrowLeftRight);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {2, 3, 'B'}, {1, 5, 'B'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'B'}, {4,'B'}, {5,'A'} };
            END_TEST;
        }
        {
            START_TEST(OverrideLeft);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {3, 6, 'C'}, {1, 5, 'B'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'B'}, {4,'B'}, {5,'C'},{6, 'A'} };
            END_TEST;
        }
        {
            START_TEST(OverrideRight);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {1, 5, 'B'}, {3, 6, 'C'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'C'}, {4,'C'}, {5,'C'},{6, 'A'} };
            END_TEST;
        }
        {
            START_TEST(GrowLeft);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {3, 5, 'B'}, {1, 4, 'B'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'B'}, {4,'B'}, {5,'A'} };
            END_TEST;
        }
        {
            START_TEST(OverrideRight);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {2, 5, 'B'}, {5, 8, 'C'}, {4, 5, 'A'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'A'}, {2,'B'}, {3,'B'}, {4,'A'}, {5,'C'}, {6, 'C'}, {7, 'C'}, {8, 'A'} };
            END_TEST;
        }
        {
            START_TEST(InbetweenPriorLimits);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {1, 5, 'B'}, {2, 3, 'B'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'B'}, {4,'B'}, {5,'A'} };
            END_TEST;
        }
        {
            START_TEST(ResetRight);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {1, 5, 'B'}, {4, 6, 'A'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'B'}, {2,'B'}, {3,'B'}, {4,'A'}, {5,'A'} };
            END_TEST;
        }
        {
            START_TEST(SetResetSameInterval);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {1, 5, 'B'}, {1, 5, 'A'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'A'}, {2,'A'}, {3,'A'}, {4,'A'}, {5,'A'} };
            END_TEST;
        }
        {
            START_TEST(RestoreInfimum);
            interval_map<unsigned int, char> m('A');
            struct { unsigned int L, R; char Label; } intervals[] = { {1, 5, 'B'}, {0, 7, 'A'} };
            struct { unsigned int pos; char Label; } expectations[] = { {0,'A'}, {1,'A'}, {2,'A'}, {3,'A'}, {4,'A'}, {5,'A'} };
            END_TEST;
        }
#endif
        {
            START_TEST(DevelopmentTest0);
            interval_map<KT, VT> m('a');
            struct { KT L, R; VT Label; } intervals[] = { {0, 6, 'a'}, {6,10,'b'}, {11, 17,'c'}, {17,25,'b'}, {8,20,'v'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'a'}, {6,'b'}, {7,'b'}, {8,'v'}, {19,'v'}, {20,'b'}, {24, 'b'}, {25, 'a'}, {100, 'a'} };
            END_TEST;
        }
        {
            START_TEST(DevelopmentTest1);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = { {3, 6, 'B'}, {2, 5, 'C'}, {4, 7, 'A'} };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'A'}, {2,'C'}, {3,'C'}, {4,'A'}, {5,'A'}, {6, 'A'}, {7, 'A'} };
            END_TEST;
        }
        {
            START_TEST(DevelopmentTest2);
            interval_map<KT, VT> m('A');
            struct { KT L, R; VT Label; } intervals[] = {
                {8, 12, 'B'},
                {2, 12, 'B'},
                {2, 12, 'C'},
                {5, 12, 'C'},
                {4, 10, 'C'},
                {4, 12, 'C'},
                {8, 13, 'A'},
                {6,  9, 'D'},
            };
            struct { KT pos; VT Label; } expectations[] = { {0,'A'}, {1,'A'}, {2,'C'}, {3,'C'}, {4,'C'}, {5,'C'}, {6, 'D'}, {7, 'D'}, {8, 'D'}, {9, 'A'} };
            END_TEST;
        }
        {
            START_TEST(FloatKey);
            interval_map<KF, VT> m('A');
            struct { KF L, R; VT Label; } intervals[] = { {-1, 1, 'B'} };
            struct { KF pos; VT Label; } expectations[] = { {-2.f,'A'}, {-1.1f,'A'}, {-1.f,'B'}, {1.f,'A'}, {1.1f,'A'} };
            END_TEST;
        }
        {
            START_TEST(StringKey);
            interval_map<KS, VT> m('A');
            struct { KS L, R; VT Label; } intervals[] = { {"Alpha", "Beta", 'B'}, {"Delta", "Epsilon", 'C'} };
            struct { KS pos; VT Label; } expectations[] = { {"_LessThanAlpha",'A'}, {"Alpha",'B'}, {"Beta", 'A'}, {"Delta",'C'}, {"Epsilon",'A'}, {"Zeta",'A'} };
            END_TEST;
        }
    }
    const char* perf_tests[] = { "AddRight", "Pyramid", "Skew", "Remove" };
    for (int type = 0; type < LENGTH(perf_tests); type++)
    {
        struct Counter
        {
            double Freq = 0.0;
            __int64 Start = 0;
            Counter()
            {
#ifdef _WINDOWS_
                LARGE_INTEGER li;
                if (!QueryPerformanceFrequency(&li))
                    std::cout << "Cannot obtain a performance counter!\n";
                Freq = double(li.QuadPart);
                QueryPerformanceCounter(&li);
                Start = li.QuadPart;
#endif
            }
            operator double() const
            {
#ifdef _WINDOWS_
                LARGE_INTEGER li;
                QueryPerformanceCounter(&li);
                return double(li.QuadPart - Start) / Freq;
#else
                return 0;
#endif
            }
        };

        START_TEST(PerformanceTest);
        int v = 0;
        int factor = 100;
#ifndef _DEBUG
        factor *= 60;
#endif
        if (type == 0)
        {
            interval_map<KT, VT> m(0);
            const int width = 10;
            const int max_test = 1000 * factor;
            const int expsz = max_test + 1;
            Counter counter;
            for (int i = 0; i < max_test; i++)
                m.assign(i * width, (i + 1) * width, --v);
            std::cout << "AddRight time: " << counter << " m.size: " << m.m_map.size()
                << " expected sz: " << expsz << (m.m_map.size() == expsz ? " okay" : " mismatch") << "\n";
        }
        else if (type == 1)
        {
            interval_map<int, unsigned int> m(0);
            const int max_test = factor * 5000;
            const int expsz = max_test + 1;
            Counter counter;
            for (int i = 0; i < max_test; i++)
                m.assign(i, max_test - i, --v);
            std::cout << "Pyramid  time: " << counter << " m.size: " << m.m_map.size()
                << " expected sz: " << expsz << (m.m_map.size() == expsz ? " okay" : " mismatch") << "\n";
        }
        else if (type == 2)
        {
            interval_map<KT, VT> m(0);
            int sqrtf = (int)sqrt(factor);
            const int max_test = 100 * sqrtf;
            const int max_drift = 100 * sqrtf;
            const int expsz = max_test + max_drift - 1;
            Counter counter;
            for (int k = 0; k < max_drift; k++)
                for (int i = 0; i < max_test; i++)
                    m.assign(k + i, k + max_test - i, --v);
            std::cout << "Skew     time: " << counter << " m.size: " << m.m_map.size()
                << " expected sz: " << expsz << (m.m_map.size() == expsz ? " okay" : " mismatch") << "\n";
        }
        else if (type == 3)
        {
            interval_map<KT, VT> m(0);
            int sqrtf = (int)sqrt(factor);
            const int width = 1;
            const int stride = 10;
            const int max_drift = 100 * sqrtf;
            const int max_test = 100 * sqrtf;
            const int expsz = 3;
            Counter counter;
            for (int k = 0; k < max_drift; k++)
            {
                for (int i = 0; i < max_test; i++)
                    m.assign(k * i * width, k * (i + 1) * width, --v);
                int from = k * (max_test - 1) * width;
                int to = k * (max_test + 1) * width;
                for (int i = max_test; i > 0; i -= stride)
                    m.assign(i, to, --v);
            }
            std::cout << "Remove   time: " << counter << " m.size: " << m.m_map.size()
                << " expected sz: " << expsz << (m.m_map.size() == expsz ? " okay" : " mismatch") << "\n";
        }
    }
}

int main(int argc, char* argv[])
{
    for (algorithm = alg_min; algorithm < alg_max; algorithm++)
    {
        std::cout << algorithms[algorithm] << " algorithm:" << std::endl;
        IntervalMapTest();
    }
}
Grind answered 8/3 at 9:28 Comment(0)
D
0

Here is one take without using any upper/lower_bound() checks. I shortly checked against a normal array and if it is canonical, but didn't check the boundaries. One concern I had was whether the eraseEnd iterator will be invalidated if the lower bound is inserted but according to the standard it should not be the case for std::map. It is not a submitted code, so use the idea at your own risk.

void assign( K const& keyBegin, K const& keyEnd, V const& val ) {
    // incorrect key
    if ( keyEnd < keyBegin )
        return;

    const auto &[ upperBoundItr, upperBoundSuccess ] = m_map.insert( { keyEnd, val } );
    auto eraseEnd = upperBoundItr;

    // upper boundary should be the continuation of the previous range. if it is a new insertion
    // it should have the previous range value. if the insertion was not successful, the value should not change.
    if ( upperBoundSuccess ) {
        upperBoundItr->second = std::prev( upperBoundItr )->second;

        // if it is a new insertion, we need to check if the next boundary has the same value.
        if ( auto n = std::next( eraseEnd ); n != m_map.cend() && n->second == eraseEnd->second )
            m_map.erase( n );
    }

    const auto &[ lowerBoundItr, lowerBoundSuccess ] = m_map.insert_or_assign( keyBegin, val );
    auto eraseStart = std::next( lowerBoundItr );

    // if the end of the range has the same value, the end boundary should be removed.
    if ( lowerBoundItr->second == eraseEnd->second )
        eraseEnd = std::next( eraseEnd );

    // if the previous start boundary has the same value, remove the new duplicate.
    if ( lowerBoundItr != m_map.cbegin() && std::prev( lowerBoundItr )->second == val )
        eraseStart = lowerBoundItr;

    m_map.erase( eraseStart, eraseEnd );

}

Disconnected answered 23/7 at 22:24 Comment(0)
S
0

Another potential answer - is that your map doesn't handle the following correctly:

interval_map('a');
assign(1,2, 'a');  //should still be empty
assign(5,10, 'g');
assign(7,8,  'g'); // should result in no entry
assign(9,12, 'g'); // should just have 5->g; 12->a
Snowstorm answered 10/8 at 22:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.