Implemented class Timeline<T>

This commit is contained in:
Daniel Wolf 2016-04-04 20:10:28 +02:00
parent 2be3751a4f
commit 83291aa96c
8 changed files with 579 additions and 14 deletions

View File

@ -120,6 +120,7 @@ set(SOURCE_FILES
src/logging.cpp
src/Timed.cpp
src/TimeRange.cpp
src/Timeline.cpp
)
add_executable(rhubarb ${SOURCE_FILES})
target_link_libraries(rhubarb ${Boost_LIBRARIES} cppFormat sphinxbase pocketSphinx)
@ -129,7 +130,11 @@ target_compile_options(rhubarb PUBLIC ${enableWarningsFlags})
#include_directories("${gtest_SOURCE_DIR}/include")
set(TEST_FILES
tests/stringToolsTests.cpp
tests/TimelineTests.cpp
src/stringTools.cpp
src/Timeline.cpp
src/TimeRange.cpp
src/centiseconds.cpp
)
add_executable(runTests ${TEST_FILES})
target_link_libraries(runTests gtest gmock gmock_main)

View File

@ -1,21 +1,45 @@
#include "TimeRange.h"
#include <stdexcept>
#include <ostream>
TimeRange::TimeRange(centiseconds start, centiseconds end) :
using time_type = TimeRange::time_type;
TimeRange::TimeRange(time_type start, time_type end) :
start(start),
end(end)
{
if (start > end) throw std::invalid_argument("start must not be less than end.");
if (start > end) throw std::invalid_argument("Start must not be less than end.");
}
centiseconds TimeRange::getStart() const {
time_type TimeRange::getStart() const {
return start;
}
centiseconds TimeRange::getEnd() const {
time_type TimeRange::getEnd() const {
return end;
}
centiseconds TimeRange::getLength() const {
time_type TimeRange::getLength() const {
return end - start;
}
void TimeRange::resize(const TimeRange& newRange) {
start = newRange.start;
end = newRange.end;
}
void TimeRange::resize(time_type start, time_type end) {
resize(TimeRange(start, end));
}
bool TimeRange::operator==(const TimeRange& rhs) const {
return start == rhs.start && end == rhs.end;
}
bool TimeRange::operator!=(const TimeRange& rhs) const {
return !operator==(rhs);
}
std::ostream& operator<<(std::ostream& stream, const TimeRange& timeRange) {
return stream << "TimeRange(" << timeRange.getStart() << ", " << timeRange.getEnd() << ")";
}

View File

@ -3,11 +3,26 @@
class TimeRange {
public:
TimeRange(centiseconds start, centiseconds end);
centiseconds getStart() const;
centiseconds getEnd() const;
centiseconds getLength() const;
using time_type = centiseconds;
TimeRange(time_type start, time_type end);
TimeRange(const TimeRange&) = default;
TimeRange(TimeRange&&) = default;
TimeRange& operator=(const TimeRange&) = default;
TimeRange& operator=(TimeRange&&) = default;
time_type getStart() const;
time_type getEnd() const;
time_type getLength() const;
void resize(const TimeRange& newRange);
void resize(time_type start, time_type end);
bool operator==(const TimeRange& rhs) const;
bool operator!=(const TimeRange& rhs) const;
private:
const centiseconds start, end;
time_type start, end;
};
std::ostream& operator<<(std::ostream& stream, const TimeRange& timeRange);

View File

@ -1,19 +1,52 @@
#pragma once
#include <TimeRange.h>
#include <iostream>
template<typename TValue>
class Timed : public TimeRange {
public:
Timed(centiseconds start, centiseconds end, TValue value) :
Timed(time_type start, time_type end, TValue value) :
TimeRange(start, end),
value(value)
{}
Timed(TimeRange timeRange, TValue value) :
TimeRange(timeRange),
value(value)
{}
Timed(const Timed&) = default;
Timed(Timed&&) = default;
Timed& operator=(const Timed&) = default;
Timed& operator=(Timed&&) = default;
TValue& getValue() {
return value;
}
const TValue& getValue() const {
return value;
}
void setValue(TValue value) {
this->value = value;
}
bool operator==(const Timed& rhs) const {
return TimeRange::operator==(rhs) && value == rhs.value;
}
bool operator!=(const Timed& rhs) const {
return !operator==(rhs);
}
private:
const TValue value;
TValue value;
};
template<typename T>
std::ostream& operator<<(std::ostream& stream, const Timed<T>& timedValue) {
return stream << "Timed(" << timedValue.getStart() << ", " << timedValue.getEnd() << ", " << timedValue.getValue() << ")";
}

1
src/Timeline.cpp Normal file
View File

@ -0,0 +1 @@
#include "Timeline.h"

250
src/Timeline.h Normal file
View File

@ -0,0 +1,250 @@
#pragma once
#include "Timed.h"
#include <set>
#include <algorithm>
template<typename T>
class Timeline {
public:
using time_type = TimeRange::time_type;
private:
struct compare {
bool operator()(const Timed<T>& lhs, const Timed<T>& rhs) const {
return lhs.getStart() < rhs.getStart();
}
bool operator()(const time_type& lhs, const Timed<T>& rhs) const {
return lhs < rhs.getStart();
}
using is_transparent = int;
};
public:
using set_type = std::set<Timed<T>, compare>;
using const_iterator = typename set_type::const_iterator;
using iterator = const_iterator;
using reverse_iterator = typename set_type::reverse_iterator;
using size_type = size_t;
using value_type = Timed<T>;
class reference {
public:
using const_reference = const T&;
operator const_reference() const {
return timeline.get(time).getValue();
}
reference& operator=(const T& value) {
timeline.set(time, time + time_type(1), value);
return *this;
}
private:
friend class Timeline;
reference(Timeline& timeline, time_type time) :
timeline(timeline),
time(time)
{}
Timeline& timeline;
time_type time;
};
explicit Timeline(const Timed<T> timedValue) :
elements(),
range(timedValue)
{
if (timedValue.getLength() != time_type::zero()) {
elements.insert(timedValue);
}
};
explicit Timeline(const TimeRange& timeRange, const T& value = T()) :
Timeline(Timed<T>(timeRange, value))
{ }
Timeline(time_type start, time_type end, const T& value = T()) :
Timeline(Timed<T>(start, end, value))
{}
template<typename InputIterator>
Timeline(InputIterator first, InputIterator last, const T& value = T()) :
Timeline(getRange(first, last), value)
{
for (auto it = first; it != last; ++it) {
set(*it);
}
}
explicit Timeline(std::initializer_list<Timed<T>> initializerList, const T& value = T()) :
Timeline(initializerList.begin(), initializerList.end(), value)
{}
bool empty() const {
return elements.empty();
}
size_type size() const {
return elements.size();
}
const TimeRange& getRange() const {
return range;
}
iterator begin() const {
return elements.begin();
}
iterator end() const {
return elements.end();
}
reverse_iterator rbegin() const {
return elements.rbegin();
}
reverse_iterator rend() const {
return elements.rend();
}
iterator find(time_type time) const {
if (time < range.getStart() || time >= range.getEnd()) {
return elements.end();
}
iterator it = elements.upper_bound(time);
--it;
return it;
}
const Timed<T>& get(time_type time) const {
iterator it = find(time);
if (it == elements.end()) {
throw std::invalid_argument("Argument out of range.");
}
return *it;
}
iterator set(Timed<T> timedValue) {
// Make sure the timed value overlaps with our range
if (timedValue.getEnd() <= range.getStart() || timedValue.getStart() >= range.getEnd()) {
return elements.end();
}
// Make sure the timed value is not empty
if (timedValue.getLength() == time_type::zero()) {
return elements.end();
}
// Trim the timed value to our range
timedValue.resize(
std::max(timedValue.getStart(), range.getStart()),
std::min(timedValue.getEnd(), range.getEnd()));
// Extend the timed value if it touches elements with equal value
bool isFlushLeft = timedValue.getStart() == range.getStart();
if (!isFlushLeft) {
iterator elementBefore = find(timedValue.getStart() - time_type(1));
if (elementBefore->getValue() == timedValue.getValue()) {
timedValue.resize(elementBefore->getStart(), timedValue.getEnd());
}
}
bool isFlushRight = timedValue.getEnd() == range.getEnd();
if (!isFlushRight) {
iterator elementAfter = find(timedValue.getEnd());
if (elementAfter->getValue() == timedValue.getValue()) {
timedValue.resize(timedValue.getStart(), elementAfter->getEnd());
}
}
// Split overlapping elements
splitAt(timedValue.getStart());
splitAt(timedValue.getEnd());
// Erase overlapping elements
elements.erase(find(timedValue.getStart()), find(timedValue.getEnd()));
// Add timed value
return elements.insert(timedValue).first;
}
iterator set(const TimeRange& timeRange, const T& value) {
return set(Timed<T>(timeRange, value));
}
iterator set(time_type start, time_type end, const T& value) {
return set(Timed<T>(start, end, value));
}
reference operator[](time_type time) {
if (time < range.getStart() || time >= range.getEnd()) {
throw std::invalid_argument("Argument out of range.");
}
return reference(*this, time);
}
// ReSharper disable once CppConstValueFunctionReturnType
const reference operator[](time_type time) const {
return reference(*this, time);
}
Timeline(const Timeline&) = default;
Timeline(Timeline&&) = default;
Timeline& operator=(const Timeline&) = default;
Timeline& operator=(Timeline&&) = default;
bool operator==(const Timeline& rhs) const {
return range == rhs.range && elements == rhs.elements;
}
bool operator!=(const Timeline& rhs) const {
return !operator==(rhs);
}
private:
template<typename InputIterator>
static TimeRange getRange(InputIterator first, InputIterator last) {
if (first == last) {
return TimeRange(time_type::zero(), time_type::zero());
}
time_type start = time_type::max();
time_type end = time_type::min();
for (auto it = first; it != last; ++it) {
start = std::min(start, it->getStart());
end = std::max(end, it->getEnd());
}
return TimeRange(start, end);
}
void splitAt(time_type splitTime) {
if (splitTime == range.getStart() || splitTime == range.getEnd()) return;
iterator elementBefore = find(splitTime - time_type(1));
iterator elementAfter = find(splitTime);
if (elementBefore != elementAfter) return;
Timed<T> tmp = *elementBefore;
elements.erase(elementBefore);
elements.insert(Timed<T>(tmp.getStart(), splitTime, tmp.getValue()));
elements.insert(Timed<T>(splitTime, tmp.getEnd(), tmp.getValue()));
}
set_type elements;
TimeRange range;
};
template<typename T>
std::ostream& operator<<(std::ostream& stream, const Timeline<T>& timeline) {
stream << "Timeline{";
bool isFirst = true;
for (auto element : timeline) {
if (!isFirst) stream << ", ";
isFirst = false;
stream << element;
}
return stream << "}";
}

View File

@ -1,4 +1,3 @@
#include <ratio>
#include <chrono>
#include <ostream>
#include "Centiseconds.h"
@ -6,4 +5,3 @@
std::ostream& operator <<(std::ostream& stream, const centiseconds cs) {
return stream << cs.count() << "cs";
}

239
tests/TimelineTests.cpp Normal file
View File

@ -0,0 +1,239 @@
#include <gmock/gmock.h>
#include "Timeline.h"
#include <limits>
#include <functional>
using namespace testing;
using cs = centiseconds;
using std::vector;
TEST(Timeline, constructors_initializeState) {
EXPECT_THAT(
Timeline<int>(Timed<int>(cs(10), cs(30), 42)),
ElementsAre(Timed<int>(cs(10), cs(30), 42))
);
EXPECT_THAT(
Timeline<int>(TimeRange(cs(10), cs(30)), 42),
ElementsAre(Timed<int>(cs(10), cs(30), 42))
);
EXPECT_THAT(
Timeline<int>(cs(10), cs(30), 42),
ElementsAre(Timed<int>(cs(10), cs(30), 42))
);
auto args = {
Timed<int>(cs(-10), cs(30), 1),
Timed<int>(cs(10), cs(40), 2),
Timed<int>(cs(50), cs(60), 3)
};
auto expected = {
Timed<int>(cs(-10), cs(10), 1),
Timed<int>(cs(10), cs(40), 2),
Timed<int>(cs(40), cs(50), 42),
Timed<int>(cs(50), cs(60), 3)
};
EXPECT_THAT(
Timeline<int>(args.begin(), args.end(), 42),
ElementsAreArray(expected)
);
EXPECT_THAT(
Timeline<int>(args, 42),
ElementsAreArray(expected)
);
}
TEST(Timeline, constructors_throwForInvalidArgs) {
EXPECT_THROW(
Timeline<int>(cs(10), cs(9)),
std::invalid_argument
);
}
TEST(Timeline, empty) {
Timeline<int> empty1{};
EXPECT_TRUE(empty1.empty());
EXPECT_THAT(empty1, IsEmpty());
Timeline<int> empty2(cs(1), cs(1));
EXPECT_TRUE(empty2.empty());
EXPECT_THAT(empty2, IsEmpty());
Timeline<int> nonEmpty(cs(1), cs(2));
EXPECT_FALSE(nonEmpty.empty());
EXPECT_THAT(nonEmpty, Not(IsEmpty()));
}
TEST(Timeline, size) {
Timeline<int> empty1{};
EXPECT_EQ(0, empty1.size());
EXPECT_THAT(empty1, SizeIs(0));
Timeline<int> empty2(cs(1), cs(1));
EXPECT_EQ(0, empty2.size());
EXPECT_THAT(empty2, SizeIs(0));
Timeline<int> size1(cs(1), cs(10));
EXPECT_EQ(1, size1.size());
EXPECT_THAT(size1, SizeIs(1));
Timeline<int> size2{Timed<int>(cs(-10), cs(10), 1), Timed<int>(cs(10), cs(11), 5)};
EXPECT_EQ(2, size2.size());
EXPECT_THAT(size2, SizeIs(2));
}
TEST(Timeline, getRange) {
Timeline<int> empty1{};
EXPECT_EQ(TimeRange(cs(0), cs(0)), empty1.getRange());
Timeline<int> empty2(cs(1), cs(1));
EXPECT_EQ(TimeRange(cs(1), cs(1)), empty2.getRange());
Timeline<int> nonEmpty1(cs(1), cs(10));
EXPECT_EQ(TimeRange(cs(1), cs(10)), nonEmpty1.getRange());
Timeline<int> nonEmpty2{ Timed<int>(cs(-10), cs(10), 1), Timed<int>(cs(10), cs(11), 5) };
EXPECT_EQ(TimeRange(cs(-10), cs(11)), nonEmpty2.getRange());
}
TEST(Timeline, iterators) {
Timeline<int> timeline{ Timed<int>(cs(-5), cs(0), 10), Timed<int>(cs(5), cs(15), 9) };
auto expected = { Timed<int>(cs(-5), cs(0), 10), Timed<int>(cs(0), cs(5), 0), Timed<int>(cs(5), cs(15), 9) };
EXPECT_THAT(timeline, ElementsAreArray(expected));
vector<Timed<int>> reversedActual;
std::copy(timeline.rbegin(), timeline.rend(), back_inserter(reversedActual));
vector<Timed<int>> reversedExpected;
std::reverse_copy(expected.begin(), expected.end(), back_inserter(reversedExpected));
EXPECT_THAT(reversedActual, ElementsAreArray(reversedExpected));
}
TEST(Timeline, find) {
vector<Timed<int>> elements = {
Timed<int>(cs(1), cs(2), 1), // #0
Timed<int>(cs(2), cs(4), 2), // #1
Timed<int>(cs(4), cs(5), 3) // #2
};
Timeline<int> timeline(elements.begin(), elements.end());
EXPECT_EQ(timeline.end(), timeline.find(cs(-1)));
EXPECT_EQ(timeline.end(), timeline.find(cs(0)));
EXPECT_EQ(elements[0], *timeline.find(cs(1)));
EXPECT_EQ(elements[1], *timeline.find(cs(2)));
EXPECT_EQ(elements[1], *timeline.find(cs(3)));
EXPECT_EQ(elements[2], *timeline.find(cs(4)));
EXPECT_EQ(timeline.end(), timeline.find(cs(5)));
}
TEST(Timeline, get) {
vector<Timed<int>> elements = {
Timed<int>(cs(1), cs(2), 1), // #0
Timed<int>(cs(2), cs(4), 2), // #1
Timed<int>(cs(4), cs(5), 3) // #2
};
Timeline<int> timeline(elements.begin(), elements.end());
EXPECT_THROW(timeline.get(cs(-1)), std::invalid_argument);
EXPECT_THROW(timeline.get(cs(0)), std::invalid_argument);
EXPECT_EQ(elements[0], timeline.get(cs(1)));
EXPECT_EQ(elements[1], timeline.get(cs(2)));
EXPECT_EQ(elements[1], timeline.get(cs(3)));
EXPECT_EQ(elements[2], timeline.get(cs(4)));
EXPECT_THROW(timeline.get(cs(5)), std::invalid_argument);
}
void testSetter(std::function<void(const Timed<int>&, Timeline<int>&)> set) {
const Timed<int> initial(cs(0), cs(10), 42);
Timeline<int> timeline(initial);
vector<int> expectedValues(10, 42);
auto newElements = {
Timed<int>(cs(1), cs(2), 4),
Timed<int>(cs(3), cs(6), 4),
Timed<int>(cs(7), cs(9), 5),
Timed<int>(cs(9), cs(10), 6),
Timed<int>(cs(2), cs(3), 4),
Timed<int>(cs(0), cs(1), 7),
Timed<int>(cs(-10), cs(1), 8),
Timed<int>(cs(-10), cs(0), 9),
Timed<int>(cs(-10), cs(-1), 10),
Timed<int>(cs(9), cs(20), 11),
Timed<int>(cs(10), cs(20), 12),
Timed<int>(cs(11), cs(20), 13),
Timed<int>(cs(4), cs(6), 14),
Timed<int>(cs(4), cs(6), 15),
Timed<int>(cs(8), cs(10), 15),
Timed<int>(cs(6), cs(8), 15),
Timed<int>(cs(6), cs(8), 16)
};
for (const auto& newElement : newElements) {
// Set element in timeline
set(newElement, timeline);
// Update expected value for every index
cs elementStart = max(newElement.getStart(), cs(0));
cs elementEnd = min(newElement.getEnd(), cs(10));
for (cs t = elementStart; t < elementEnd; ++t) {
expectedValues[t.count()] = newElement.getValue();
}
// Check timeline via indexer
for (cs t = cs(0); t < cs(10); ++t) {
EXPECT_EQ(expectedValues[t.count()], timeline[t]);
}
// Check timeline via iterators
int lastValue = std::numeric_limits<int>::min();
for (const auto& element : timeline) {
// No element shound have zero-length
EXPECT_LT(cs(0), element.getLength());
// No two adjacent elements should have the same value; they should have been merged
EXPECT_NE(lastValue, element.getValue());
lastValue = element.getValue();
// Element should match expected values
for (cs t = element.getStart(); t < element.getEnd(); ++t) {
EXPECT_EQ(expectedValues[t.count()], element.getValue());
}
}
}
}
TEST(Timeline, set) {
testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
timeline.set(element);
});
testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
timeline.set(element, element.getValue());
});
testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
timeline.set(element.getStart(), element.getEnd(), element.getValue());
});
}
TEST(Timeline, indexer_set) {
testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
for (cs t = element.getStart(); t < element.getEnd(); ++t) {
if (t >= timeline.getRange().getStart() && t < timeline.getRange().getEnd()) {
timeline[t] = element.getValue();
} else {
EXPECT_THROW(timeline[t] = element.getValue(), std::invalid_argument);
}
}
});
}
TEST(Timeline, equality) {
vector<Timeline<int>> timelines = {
Timeline<int>{},
Timeline<int>(cs(1), cs(1)),
Timeline<int>(cs(1), cs(2)),
Timeline<int>(cs(-10), cs(0))
};
for (size_t i = 0; i < timelines.size(); ++i) {
for (size_t j = 0; j < timelines.size(); ++j) {
if (i == j) {
EXPECT_EQ(timelines[i], Timeline<int>(timelines[j]));
} else {
EXPECT_NE(timelines[i], timelines[j]);
}
}
}
}