494 lines
17 KiB
C++
494 lines
17 KiB
C++
/* Check .vimrc.local defs.
|
||
VIM: let g:lcppflags="-std=c++11 -O2 -pthread"
|
||
VIM: let g:wcppflags="/O2 /EHsc /DWIN32"
|
||
VIM: let g:argv=""
|
||
*/
|
||
#include <assert.h>
|
||
#include <map>
|
||
#include <limits>
|
||
#include <iostream>
|
||
#include <sstream>
|
||
|
||
|
||
// 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 have attached an excerpt of the C++1x draft standard at the end of this
|
||
// file for your convenience.
|
||
|
||
// Each key-value-pair (k,v) in the m_map member means that the value v is associated to the interval from k (including) to
|
||
// the next key (excluding) in m_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<key>::max()
|
||
|
||
// The representation in m_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.
|
||
template<class K, class V>
|
||
class interval_map {
|
||
friend void IntervalMapTest();
|
||
|
||
private:
|
||
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.begin(),std::make_pair(std::numeric_limits<K>::min(),val));
|
||
};
|
||
|
||
// Assign value val to interval [keyBegin, keyEnd).
|
||
// Overwrite previous values in this interval. Do not change values outside 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, const V& val ) {
|
||
//
|
||
// If !( keyBegin < keyEnd ), this designates an empty interval, and
|
||
// assign must do nothing.
|
||
//
|
||
if ( !(keyBegin < keyEnd ) )
|
||
return;
|
||
//
|
||
// - is bounded below, with the lowest value being std::numeric_limits<K>::min();
|
||
// std::numeric_limits<duble>::min() actually is not the smalles number.
|
||
//
|
||
if (keyBegin < std::numeric_limits<K>::min())
|
||
throw std::runtime_error("K should be bounded below, with the lowest value being std::numeric_limits<K>::min().");
|
||
//
|
||
// Find the interval pointer before which the new interval ends.
|
||
//
|
||
auto ie = m_map.lower_bound(keyEnd);
|
||
//
|
||
// If there is a need to break the last interval with keyEnd do it now.
|
||
// Everything prior 'ie' till 'ib' will be removed later.
|
||
//
|
||
if ( ie == m_map.end() && keyEnd < std::numeric_limits<K>::max()
|
||
|| ie != m_map.end() && keyEnd < ie->first ) {
|
||
if (!( std::prev(ie)->second == val ))
|
||
ie = m_map.insert(ie, std::make_pair(keyEnd,std::prev(ie)->second));
|
||
}
|
||
//
|
||
// If the interval next to the inserting one should be joined?
|
||
//
|
||
else if (ie != m_map.end() && ie->second == val)
|
||
++ie;
|
||
//
|
||
// Find the interval pointer before which the new interval begins.
|
||
//
|
||
auto ib = m_map.lower_bound( keyBegin );
|
||
//
|
||
// If the interval before should be joined then ...
|
||
// Note: there should always be std::numeric_limits<K>::min()
|
||
//
|
||
if (ib != m_map.begin() && std::prev(ib)->second == val)
|
||
--ib;
|
||
//
|
||
// If keyBegin is less than 'ib' then insert an new interval.
|
||
//
|
||
else if ( ib == m_map.end() || keyBegin < ib->first )
|
||
ib = m_map.insert(ib, std::make_pair(keyBegin, val));
|
||
//
|
||
// Otherwise just change the value since keyBegin matches with an
|
||
// existing interval.
|
||
//
|
||
else
|
||
ib->second = val;
|
||
//
|
||
// Removed the range between ib and ie.
|
||
//
|
||
m_map.erase( ++ib, ie );
|
||
}
|
||
|
||
// look-up of the value associated with key
|
||
V const& operator[]( K const& key ) const {
|
||
return ( --m_map.upper_bound(key) )->second;
|
||
}
|
||
|
||
private:
|
||
//
|
||
// Since this is a template class, I assume this method will be
|
||
// instantiated only in IntervalMapTest. Hence, key and value are pretty
|
||
// printable.
|
||
//
|
||
std::ostream& print( std::ostream& os ) const {
|
||
for( auto e: m_map )
|
||
os << '[' << e.first << ',' << e.second << ']';
|
||
return os;
|
||
}
|
||
};
|
||
|
||
// 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>::min();
|
||
// - does not implement any other operations, in particular no equality comparison or arithmetic operators.
|
||
template<typename T>
|
||
class test_key {
|
||
T value;
|
||
public:
|
||
/*explicit*/ test_key( T v )
|
||
: value(v)
|
||
{}
|
||
|
||
bool operator < ( const test_key& op ) const {
|
||
return value < op.value;
|
||
}
|
||
|
||
std::ostream& print( std::ostream& os ) const {
|
||
return os << value;
|
||
}
|
||
};
|
||
namespace std {
|
||
//
|
||
// I hope this much of implementation of numeric_limits is enough for my
|
||
// tests.
|
||
//
|
||
template <typename T>
|
||
struct numeric_limits<::test_key<T> > : public numeric_limits<T> {
|
||
typedef ::test_key<T> value_type;
|
||
typedef numeric_limits<T> base_type;
|
||
static value_type min() {
|
||
return value_type( base_type::min() );
|
||
}
|
||
static value_type max() {
|
||
return value_type( base_type::max() );
|
||
}
|
||
};
|
||
}
|
||
template <typename T>
|
||
inline std::ostream& operator << ( std::ostream& os, test_key<T> t ) {
|
||
return t.print(os);
|
||
}
|
||
template <typename T>
|
||
void test_test_key() {
|
||
|
||
typedef test_key<T> test_type;
|
||
test_type a(T(3.5));
|
||
test_type b(T(5.5));
|
||
|
||
std::cout << "a<b before a=b: " << (a < b) << std::endl;
|
||
a = b;
|
||
std::cout << "a<b after a=b: " << (a < b) << std::endl;
|
||
std::cout << "min: " << std::numeric_limits<test_type>::min() << std::endl;
|
||
std::cout << "max: " << std::numeric_limits<test_type>::max() << std::endl;
|
||
}
|
||
|
||
// Value type V
|
||
// - besides being copyable and assignable, is equality-comparable via operator== ;
|
||
// - does not implement any other operations.
|
||
template<typename T>
|
||
class test_value {
|
||
T value;
|
||
public:
|
||
/*explicit*/ test_value( T v )
|
||
: value(v)
|
||
{}
|
||
|
||
bool operator == ( const test_value& op ) const {
|
||
return value == op.value;
|
||
}
|
||
|
||
std::ostream& print( std::ostream& os ) const {
|
||
return os << value;
|
||
}
|
||
};
|
||
template <typename T>
|
||
void test_test_value() {
|
||
test_value<T> a(T(3.5));
|
||
test_value<T> b(T(5.5));
|
||
|
||
std::cout << "a==b before a=b: " << (a == b) << std::endl;
|
||
a = b;
|
||
std::cout << "a==b after a=b: " << (a == b) << std::endl;
|
||
}
|
||
template <typename T>
|
||
inline std::ostream& operator << ( std::ostream& os, test_value<T> t ) {
|
||
return t.print(os);
|
||
}
|
||
|
||
//
|
||
// Test throws this exception if fails.
|
||
//
|
||
struct test_failed : public std::logic_error {
|
||
test_failed()
|
||
: logic_error("Test failed.")
|
||
{}
|
||
};
|
||
//
|
||
// The representation in m_map must be canonical, that is, consecutive map entries must not have the same value:
|
||
// ..., (0,'A'), (3,'A'), ... is not allowed.
|
||
//
|
||
template <class C>
|
||
void test_if_canonical(const C& c) {
|
||
for (auto it = std::next(c.begin()); it != c.end(); ++it) {
|
||
if (it->second == std::prev(it)->second)
|
||
throw test_failed();
|
||
}
|
||
}
|
||
// Provide a function IntervalMapTest() here that tests the functionality of the interval_map,
|
||
// for example using a map of unsigned int intervals to char.
|
||
// Many solutions we receive are incorrect. Consider using a randomized test to discover
|
||
// the cases that your implementation does not handle correctly.
|
||
void IntervalMapTest() {
|
||
try {
|
||
#if 0
|
||
std::cout << "=============================================" << std::endl;
|
||
std::cout << "Test test_key<int>" << std::endl;
|
||
test_test_key<int>();
|
||
|
||
std::cout << "=============================================" << std::endl;
|
||
std::cout << "Test test_key<double>" << std::endl;
|
||
test_test_key<double>();
|
||
|
||
std::cout << "=============================================" << std::endl;
|
||
std::cout << "Test test_value<int>" << std::endl;
|
||
test_test_value<int>();
|
||
|
||
std::cout << "=============================================" << std::endl;
|
||
std::cout << "Test test_value<double>" << std::endl;
|
||
test_test_value<double>();
|
||
#endif
|
||
|
||
// Check that key value is not less the std::numeric_limits<key>::min().
|
||
try {
|
||
interval_map<test_key<double>, test_value<double>> im(0);
|
||
im.assign(0, 500, 3);
|
||
throw test_failed();
|
||
}
|
||
catch (const std::runtime_error&) {
|
||
}
|
||
|
||
//
|
||
// Check map for some interesting values.
|
||
//
|
||
typedef test_key<int> key;
|
||
typedef test_value<int> val;
|
||
interval_map<key, val> im(0);
|
||
|
||
std::stringstream log;
|
||
auto TEST = [&log,&im](const std::string& golden ) {
|
||
test_if_canonical(im.m_map);
|
||
std::stringstream ss;
|
||
im.print(ss);
|
||
log << ss.str() << std::endl;
|
||
if (ss.str() != golden)
|
||
throw test_failed();
|
||
};
|
||
// check initial state.
|
||
TEST("[-2147483648,0]");
|
||
|
||
im.assign(std::numeric_limits<key>::min(), std::numeric_limits<key>::min(), -1);
|
||
TEST("[-2147483648,0]");
|
||
|
||
im.assign(std::numeric_limits<key>::max(), std::numeric_limits<key>::max(), -1);
|
||
TEST("[-2147483648,0]");
|
||
|
||
im.assign(0, 0, -1);
|
||
TEST("[-2147483648,0]");
|
||
|
||
im.assign(std::numeric_limits<key>::min(), std::numeric_limits<key>::max(), -1);
|
||
TEST("[-2147483648,-1]");
|
||
|
||
im.assign(1000, 2000, 1);
|
||
TEST("[-2147483648,-1][1000,1][2000,-1]");
|
||
|
||
im.assign(500, 1500, 1);
|
||
TEST("[-2147483648,-1][500,1][2000,-1]");
|
||
|
||
im.assign(1000, 1500, 2);
|
||
TEST("[-2147483648,-1][500,1][1000,2][1500,1][2000,-1]");
|
||
|
||
im.assign(0, 500, 3);
|
||
TEST("[-2147483648,-1][0,3][500,1][1000,2][1500,1][2000,-1]");
|
||
|
||
im.assign(-1000, -500, 3);
|
||
TEST("[-2147483648,-1][-1000,3][-500,-1][0,3][500,1][1000,2][1500,1][2000,-1]");
|
||
|
||
im.assign(-500, 0, 3);
|
||
TEST("[-2147483648,-1][-1000,3][500,1][1000,2][1500,1][2000,-1]");
|
||
|
||
im.assign(-500, 0, 4);
|
||
TEST("[-2147483648,-1][-1000,3][-500,4][0,3][500,1][1000,2][1500,1][2000,-1]");
|
||
|
||
im.assign(2000, std::numeric_limits<key>::max(), 4);
|
||
TEST("[-2147483648,-1][-1000,3][-500,4][0,3][500,1][1000,2][1500,1][2000,4]");
|
||
|
||
im.assign(3000, std::numeric_limits<key>::max(), 5);
|
||
TEST("[-2147483648,-1][-1000,3][-500,4][0,3][500,1][1000,2][1500,1][2000,4][3000,5]");
|
||
|
||
im.assign(2000, 3000, 5);
|
||
TEST("[-2147483648,-1][-1000,3][-500,4][0,3][500,1][1000,2][1500,1][2000,5]");
|
||
|
||
im.assign(std::numeric_limits<key>::min(), -1000, 4);
|
||
TEST("[-2147483648,4][-1000,3][-500,4][0,3][500,1][1000,2][1500,1][2000,5]");
|
||
|
||
im.assign(std::numeric_limits<key>::min(), -2000, 5);
|
||
TEST("[-2147483648,5][-2000,4][-1000,3][-500,4][0,3][500,1][1000,2][1500,1][2000,5]");
|
||
|
||
im.assign(-2000, -1000, 5);
|
||
TEST("[-2147483648,5][-1000,3][-500,4][0,3][500,1][1000,2][1500,1][2000,5]");
|
||
|
||
im.assign(-750, 1750, 4);
|
||
TEST("[-2147483648,5][-1000,3][-750,4][1750,1][2000,5]");
|
||
|
||
im.assign(-1000, 2000, 5);
|
||
TEST("[-2147483648,5]");
|
||
|
||
std::cout << log.str() << std::endl;
|
||
std::cout << "PASS" << std::endl;
|
||
}
|
||
catch (const test_failed&) {
|
||
std::cerr << "FAIL: test_failed" << std::endl;
|
||
}
|
||
catch (const std::exception& e)
|
||
{
|
||
std::cerr << std::endl
|
||
<< "FAIL: std::exception(\"" << e.what() << "\")" << std::endl;
|
||
}
|
||
catch (...)
|
||
{
|
||
std::cerr << std::endl
|
||
<< "FAIL: unknown exception." << std::endl;
|
||
}}
|
||
|
||
int main(int argc, char* argv[]) {
|
||
IntervalMapTest();
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
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
|
||
|
||
<EFBFBD>1 Containers are objects that store other objects. They control allocation and deallocation of
|
||
these objects through constructors, destructors, insert and erase operations.
|
||
|
||
<EFBFBD>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
|
||
|
||
<EFBFBD>1 Iterators are a generalization of pointers that allow a C++ program to work with different
|
||
data structures.
|
||
|
||
<EFBFBD>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.
|
||
|
||
<EFBFBD>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. <20>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.
|
||
|
||
<EFBFBD>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
|
||
|
||
<EFBFBD>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.
|
||
|
||
<EFBFBD>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.
|
||
|
||
<EFBFBD>5 For map and multimap the value type is equal to std::pair<const Key, T>. Keys in an associative
|
||
container are immutable.
|
||
|
||
<EFBFBD>6 iterator of an associative container is of the bidirectional iterator category.
|
||
(i.e., an iterator i can be incremented and decremented: ++i; --i;)
|
||
|
||
<EFBFBD>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.
|
||
|
||
<EFBFBD>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 const& 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
|
||
|
||
<EFBFBD>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.
|
||
*/
|