Compare commits
10 commits
9d2e062c38
...
dc3408524b
Author | SHA1 | Date | |
---|---|---|---|
Bruno BELANYI | dc3408524b | ||
Bruno BELANYI | bdccd17936 | ||
Bruno BELANYI | 8165fc6f99 | ||
Bruno BELANYI | 67ed4d4d68 | ||
Bruno BELANYI | 301945884a | ||
Bruno BELANYI | 9d90916307 | ||
Bruno BELANYI | 1126ee30d8 | ||
Bruno BELANYI | ce7cc4492c | ||
Bruno BELANYI | 5868b5d36c | ||
Bruno BELANYI | b23af215e8 |
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
// Testing class forward declaration
|
||||||
|
class IntervalMapTest;
|
||||||
|
|
||||||
namespace amby {
|
namespace amby {
|
||||||
|
|
||||||
template <typename K, typename V> class interval_map {
|
template <typename K, typename V> class interval_map {
|
||||||
|
@ -9,17 +12,48 @@ public:
|
||||||
interval_map(V const& init) : init_(init) {}
|
interval_map(V const& init) : init_(init) {}
|
||||||
|
|
||||||
void assign(K const& begin, K const& end, V const& val) {
|
void assign(K const& begin, K const& end, V const& val) {
|
||||||
// TODO: implement
|
if (!(begin < end))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto it = underlying_.upper_bound(end);
|
||||||
|
auto const end_val = at_upper_bound(it);
|
||||||
|
|
||||||
|
bool insert_begin = !(val == init_);
|
||||||
|
|
||||||
|
while (it != underlying_.begin()) {
|
||||||
|
it = std::prev(it);
|
||||||
|
auto begin_found = it->first < begin;
|
||||||
|
if (begin_found) {
|
||||||
|
insert_begin = !(val == it->second);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (it != underlying_.end())
|
||||||
|
// Account for up-coming `std::prev` with `++`
|
||||||
|
underlying_.erase(it++);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insert_begin)
|
||||||
|
it = underlying_.insert(it, {begin, val});
|
||||||
|
// Get the proper upper-bound for `end`
|
||||||
|
it = (it == underlying_.end()) ? it : std::next(it);
|
||||||
|
if (!(at_upper_bound(it) == end_val))
|
||||||
|
underlying_.insert(it, {end, end_val});
|
||||||
}
|
}
|
||||||
|
|
||||||
V const& operator[](K const& key) const {
|
V const& operator[](K const& key) const {
|
||||||
auto it = underlying_.upper_bound(key);
|
return at_upper_bound(underlying_.upper_bound(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used in testing
|
||||||
|
friend class ::IntervalMapTest;
|
||||||
|
|
||||||
|
private:
|
||||||
|
V const& at_upper_bound(std::map<K, V>::const_iterator it) const {
|
||||||
if (it == underlying_.begin())
|
if (it == underlying_.begin())
|
||||||
return init_;
|
return init_;
|
||||||
return std::prev(it)->second;
|
return std::prev(it)->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
V init_;
|
V init_;
|
||||||
std::map<K, V> underlying_{};
|
std::map<K, V> underlying_{};
|
||||||
};
|
};
|
||||||
|
|
35
tests/unit/model.hh
Normal file
35
tests/unit/model.hh
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
template <typename K, typename V> struct Range {
|
||||||
|
K begin;
|
||||||
|
K end;
|
||||||
|
V val;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Same behaviour as interval_map, but implementation is trivally correct
|
||||||
|
template <typename K, typename V> class Model {
|
||||||
|
public:
|
||||||
|
Model(V const& init) : init_(init) {}
|
||||||
|
|
||||||
|
void assign(K const& begin, K const& end, V const& val) {
|
||||||
|
if (!(begin < end))
|
||||||
|
return;
|
||||||
|
ranges_.emplace_back(begin, end, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
V const& operator[](K const& key) const {
|
||||||
|
for (auto it = ranges_.rbegin(); it != ranges_.rend(); ++it) {
|
||||||
|
if (key < it->begin)
|
||||||
|
continue;
|
||||||
|
if (it->end <= key)
|
||||||
|
continue;
|
||||||
|
return it->val;
|
||||||
|
}
|
||||||
|
return init_;
|
||||||
|
}
|
||||||
|
|
||||||
|
V init_;
|
||||||
|
std::vector<Range<K, V>> ranges_{};
|
||||||
|
};
|
|
@ -2,8 +2,12 @@
|
||||||
|
|
||||||
#include <interval-map/interval-map.hh>
|
#include <interval-map/interval-map.hh>
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
#include <sstream>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "model.hh"
|
||||||
|
|
||||||
template <typename T> class KeyInterface {
|
template <typename T> class KeyInterface {
|
||||||
public:
|
public:
|
||||||
explicit KeyInterface(T val) : underlying_(val) {}
|
explicit KeyInterface(T val) : underlying_(val) {}
|
||||||
|
@ -58,7 +62,82 @@ static_assert(std::is_same_v<
|
||||||
ValueInterface<int>,
|
ValueInterface<int>,
|
||||||
decltype(std::numeric_limits<ValueInterface<int>>::lowest())>);
|
decltype(std::numeric_limits<ValueInterface<int>>::lowest())>);
|
||||||
|
|
||||||
TEST(interval_map, minimal_interface) {
|
class IntervalMapTest : public testing::Test {
|
||||||
|
protected:
|
||||||
|
using key_type = char;
|
||||||
|
using value_type = int;
|
||||||
|
using map_type = amby::interval_map<key_type, value_type>;
|
||||||
|
using model_type = Model<key_type, value_type>;
|
||||||
|
|
||||||
|
map_type map{0};
|
||||||
|
model_type model{0};
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
map = map_type{0};
|
||||||
|
model = model_type{0};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign(key_type const& begin, key_type const& end,
|
||||||
|
value_type const& val) {
|
||||||
|
map.assign(begin, end, val);
|
||||||
|
model.assign(begin, end, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void check() const {
|
||||||
|
SCOPED_TRACE(stringify_map());
|
||||||
|
SCOPED_TRACE(stringify_operations());
|
||||||
|
check_ranges();
|
||||||
|
check_canonicity();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string stringify_map() const {
|
||||||
|
std::ostringstream out;
|
||||||
|
out << "map: ";
|
||||||
|
for (const auto& [key, val] : map.underlying_)
|
||||||
|
out << "[" << +key << ": " << +val << "]";
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string stringify_operations() const {
|
||||||
|
std::ostringstream out;
|
||||||
|
out << "ops: ";
|
||||||
|
for (const auto& [start, end, val] : model.ranges_)
|
||||||
|
out << "[" << +start << ":" << +end << " => " << +val << "]";
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare against the fake 'Model' implementation
|
||||||
|
void check_ranges() const {
|
||||||
|
auto i = std::numeric_limits<key_type>::min();
|
||||||
|
for (; i < std::numeric_limits<key_type>::max(); ++i) {
|
||||||
|
ASSERT_EQ(map[i], model[i]) << "(i: " << +i << ")";
|
||||||
|
}
|
||||||
|
ASSERT_EQ(map[i], model[i]) << "(i: " << +i << ")";
|
||||||
|
};
|
||||||
|
|
||||||
|
void check_canonicity() const {
|
||||||
|
// Consecutive map entries must not contain the same value
|
||||||
|
for (auto it = map.underlying_.begin(); it != map.underlying_.end();
|
||||||
|
++it) {
|
||||||
|
const auto next = std::next(it, 1);
|
||||||
|
if (next == map.underlying_.end())
|
||||||
|
break;
|
||||||
|
EXPECT_NE(it->second, next->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first entry must not contain the initial value
|
||||||
|
if (const auto it = map.underlying_.begin();
|
||||||
|
it != map.underlying_.end()) {
|
||||||
|
EXPECT_NE(it->second, map.init_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, minimal_interface) {
|
||||||
using Key = KeyInterface<char>;
|
using Key = KeyInterface<char>;
|
||||||
using Value = ValueInterface<int>;
|
using Value = ValueInterface<int>;
|
||||||
|
|
||||||
|
@ -66,3 +145,121 @@ TEST(interval_map, minimal_interface) {
|
||||||
ASSERT_EQ(map[Key(0)], Value(0));
|
ASSERT_EQ(map[Key(0)], Value(0));
|
||||||
map.assign(Key(0), Key(1), Value(1));
|
map.assign(Key(0), Key(1), Value(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, no_insertion) {}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_begin_equal_end) {
|
||||||
|
assign(0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_begin_bigger_than_end) {
|
||||||
|
assign(1, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_one_range) {
|
||||||
|
assign(std::numeric_limits<key_type>::min(), 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_non_overlapping_ranges) {
|
||||||
|
assign(std::numeric_limits<key_type>::min(), 0, 1);
|
||||||
|
assign(10, std::numeric_limits<key_type>::max(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_up_to_max) {
|
||||||
|
assign(std::numeric_limits<key_type>::min(),
|
||||||
|
std::numeric_limits<key_type>::max(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_range_right_after) {
|
||||||
|
assign(0, 10, 1);
|
||||||
|
assign(10, 20, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_range_right_before) {
|
||||||
|
assign(10, 20, 1);
|
||||||
|
assign(0, 10, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_range_middle) {
|
||||||
|
assign(0, 10, 1);
|
||||||
|
assign(20, 30, 1);
|
||||||
|
assign(10, 20, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_range_inside_another) {
|
||||||
|
assign(0, 20, 1);
|
||||||
|
assign(5, 15, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_range_around_another) {
|
||||||
|
assign(5, 15, 2);
|
||||||
|
assign(0, 20, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_range_overlaps_many) {
|
||||||
|
assign(0, 10, 1);
|
||||||
|
assign(10, 20, 2);
|
||||||
|
assign(20, 30, 3);
|
||||||
|
assign(30, 40, 4);
|
||||||
|
assign(40, 50, 5);
|
||||||
|
assign(0, 50, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_range_overlaps_many_init_value) {
|
||||||
|
assign(0, 10, 1);
|
||||||
|
assign(10, 20, 2);
|
||||||
|
assign(20, 30, 3);
|
||||||
|
assign(30, 40, 4);
|
||||||
|
assign(40, 50, 5);
|
||||||
|
assign(0, 50, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, insert_range_overlaps_many_oversize) {
|
||||||
|
assign(0, 10, 1);
|
||||||
|
assign(10, 20, 2);
|
||||||
|
assign(20, 30, 3);
|
||||||
|
assign(30, 40, 4);
|
||||||
|
assign(40, 50, 5);
|
||||||
|
assign(-10, 60, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, fuzzing_001) {
|
||||||
|
assign(-50, 20, 1);
|
||||||
|
assign(40, 80, 2);
|
||||||
|
assign(-100, -10, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, fuzzing_002) {
|
||||||
|
assign(-100, 90, 1);
|
||||||
|
assign(0, 120, 2);
|
||||||
|
assign(-60, 60, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, fuzzing_003) {
|
||||||
|
assign(-80, 70, 1);
|
||||||
|
assign(-50, 40, 2);
|
||||||
|
assign(-40, 20, 3);
|
||||||
|
assign(-110, -10, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(IntervalMapTest, randomized_test) {
|
||||||
|
auto const seed = []() {
|
||||||
|
std::random_device r;
|
||||||
|
return r();
|
||||||
|
}();
|
||||||
|
SCOPED_TRACE(seed);
|
||||||
|
auto random = std::mt19937_64(seed);
|
||||||
|
|
||||||
|
auto keys = std::uniform_int_distribution<key_type>(
|
||||||
|
std::numeric_limits<key_type>::min(),
|
||||||
|
std::numeric_limits<key_type>::max());
|
||||||
|
auto values = std::uniform_int_distribution<value_type>(0, 10);
|
||||||
|
|
||||||
|
for (auto i = 0; i < 1000; ++i) {
|
||||||
|
auto const start = keys(random);
|
||||||
|
auto const end = keys(random);
|
||||||
|
auto const value = values(random);
|
||||||
|
assign(start, end, value);
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue