diff options
-rw-r--r-- | CMakeLists.txt | 62 | ||||
-rw-r--r-- | src/model/RangeSet.hpp | 324 | ||||
-rw-r--r-- | test/model/RangeSet.cpp | 220 |
3 files changed, 594 insertions, 12 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index e6a6ae4..8c9956a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,28 +1,61 @@ +# Ousía +# Copyright (C) 2014 Benjamin Paaßen, Andreas Stöckel +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +################################################################################ +# Basic Project Definitions and Dependencies # +################################################################################ + PROJECT(basicwriter) CMAKE_MINIMUM_REQUIRED(VERSION 2.8.9) SET(CMAKE_AUTOMOC ON) FIND_PACKAGE(Qt5Widgets REQUIRED) -# Enable C++11 and all warnings -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -std=c++11") - # Option for enabling testing. Turn on with 'cmake -Dtest=ON'. OPTION(test "Build all tests." OFF) # Makes boolean 'test' available. +# Enable C++11 and all warnings +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -Wall -pedantic -std=c++11") + +################################################################################ +# Inclusion of gtest # +################################################################################ + # Unit test handling -if(test) +IF(test) # Compile gtest - add_subdirectory(lib/gtest-1.7.0) + ADD_SUBDIRECTORY(lib/gtest-1.7.0) # Enable testing and declare some variables used for compiling the test cases - enable_testing() - set(GTEST_INCLUDE_DIR ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) - set(GTEST_LIBRARIES gtest gtest_main) -endif() + ENABLE_TESTING() + SET(GTEST_INCLUDE_DIR ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) + SET(GTEST_LIBRARIES gtest gtest_main) +ENDIF() -INCLUDE_DIRECTORIES(src/) +################################################################################ +# Commands for building Ousía # +################################################################################ + +# Includ directories +INCLUDE_DIRECTORIES( + src/ +) +# ousia_core library (containing the code of the most important data structures +# and deduction algorithms) ADD_LIBRARY(ousia_core src/model/GraphNode src/model/domain/Structure @@ -34,17 +67,21 @@ ADD_LIBRARY(ousia_core src/model/types/Value ) +# Definition of the main program ADD_EXECUTABLE(ousia src/main ) +# Link the ousia executable against ousia_core TARGET_LINK_LIBRARIES(ousia ousia_core ) +# Link the ousia executable against the Widgets module of Qt5 QT5_USE_MODULES(ousia Widgets) -if(test) +# If testing is enabled, build the unit tests +IF(test) # Include the gtest include files and the src directory INCLUDE_DIRECTORIES( ${GTEST_INCLUDE_DIR} @@ -53,6 +90,7 @@ if(test) # Add all unit test files ADD_EXECUTABLE(ousia_test + test/model/RangeSet test/model/GraphNode test/model/domain/Layer ) @@ -64,5 +102,5 @@ if(test) # Register the unit test ADD_TEST(ousia_test ousia_test) -endif() +ENDIF() diff --git a/src/model/RangeSet.hpp b/src/model/RangeSet.hpp new file mode 100644 index 0000000..3768431 --- /dev/null +++ b/src/model/RangeSet.hpp @@ -0,0 +1,324 @@ +/* + Ousía + Copyright (C) 2014 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _RANGE_SET_HPP_ +#define _RANGE_SET_HPP_ + +#include <limits> + +namespace ousia { + +/** + * The Range structure represents an interval of numerical values of type T. + */ +template <typename T> +struct Range { + /** + * Start is the start value of the range. + */ + T start; + + /** + * End is the end value of the range (inclusively). + */ + T end; + + /** + * Default constructor of the range class. The range is initialized as + * invalid, with start being set to the maximum possible value of the + * numerical type T, and end being set to the minimum possible value. + */ + Range() : + start(std::numeric_limits<T>::max()), + end(std::numeric_limits<T>::min()) + { + // Do nothing here + } + + /** + * Copies the given start and end value. The given values are not checked + * for validity. Use the "isValid" + * + * @param start is the minimum value the range still covers. + * @param end is the maximum value the range still covers. + */ + Range(const T &start, const T &end) : + start(start), end(end) + { + // Do nothing here + } + + /** + * Creates a range that covers exactly one element, namely the value given + * as parameter n. + */ + Range(const T &n) : + start(n), end(n) + { + // Do nothing here + } + + /** + * Returns true if this range is valid, e.g. its start value is smaller or + * equal to its end value. + * + * @return true if start is smaller or equal to end, false otherwise. + */ + bool isValid() const + { + return start <= end; + } + + /** + * Checks whether the given value lies inside the range. + * + * @param v is the value that is being checked. + * @return true if the value lies within the range, false otherwise. + */ + bool inRange(T v) const + { + return (v >= start) && (v <= end); + } + + /** + * Checks whether the given range overlapps with another range. Not that + * this check is only meaningful if both ranges are valid. + * + * @param r is the range that should be checked for overlapping with this + * range. + */ + bool overlapps(const Range<T> &r) const + { + return (((r.start >= start) || (r.end >= start)) + && ((r.start <= end) || (r.end <= end))); + } + + /** + * Returns true if the two given ranges are neighbours (their limits only + * differ in the smallest representable difference between them). + */ + bool neighbours(const Range<T> &r) const + { + constexpr T eps = std::numeric_limits<T>::is_integer + ? 1 : std::numeric_limits<T>::epsilon(); + return ((r.start > end) && ((r.start - eps) <= end)) + || ((r.end < start) && ((r.end + eps) >= start)); + } + + /** + * Checks whether the given range completely covers this range. + */ + bool coveredBy(const Range<T> &r) const + { + return (r.start <= start) && (r.end >= end); + } + + /** + * Checks whether this range completely covers the given range. + */ + bool covers(const Range<T> &r) const + { + return r.coveredBy(*this); + } + + /** + * Calculates the union of the two ranges -- not that this operation is only + * valid if the ranges overlapp. Use the RangeSet class if you cannot + * guarantee that. + */ + Range<T> merge(const Range<T> &r) const + { + return Range(std::min(start, r.start), std::max(end, r.end)); + } + + /** + * Returns a range that represents the spans the complete set defined by the + * given type T. + */ + static Range<T> hull() + { + return Range(std::numeric_limits<T>::min(), + std::numeric_limits<T>::max()); + } + + /** + * Returns a range that represents the spans the complete set defined by the + * given type T up to a given value. + * + * @param till is the value up to which the range should be defined (till is + * included in the set). + */ + static Range<T> hullUntil(const T &till) + { + return Range(std::numeric_limits<T>::min(), till); + } + + /** + * Returns a range that represents the spans the complete set defined by the + * given type T up to a given value. + * + * @param from is the value from which the range should be defined (from is + * included in the set). + */ + static Range<T> hullFrom(const T &from) + { + return Range(from, std::numeric_limits<T>::max()); + } +}; + +/** + * RangeComp is a comperator used to order to sort the ranges within the + * ranges list. Sorts by the start element. + */ +template<typename T> +struct RangeComp { + bool operator() (const Range<T>& lhs, const Range<T>& rhs) const + { + return lhs.start < rhs.start; + } +}; + +/** + * RangeSet represents a set of ranges of the given numerical type and is thus + * capable of representing any possible subset of the given numerical type T. + */ +template<typename T> +class RangeSet { + +protected: + /** + * Set of ranges used internally. + */ + std::set<Range<T>, RangeComp<T>> ranges; + + /** + * Returns an iterator to the first element in the ranges list that overlapps + * with the given range. + * + * @param r is the range for which the first overlapping element should be + * found. + * @return an iterator pointing to the first overlapping element or to the + * end of the list if no such element was found. + */ + typename std::set<Range<T>, RangeComp<T>>::iterator firstOverlapping( + const Range<T> &r, const bool allowNeighbours) + { + // Find the element with the next larger start value compared to the + // start value given in r. + auto it = ranges.upper_bound(r); + + // Go back one element + if (it != ranges.begin()) { + it--; + } + + // Iterate until an overlapping element is found + while (!(it->overlapps(r) || (allowNeighbours && it->neighbours(r))) + && (it != ranges.end())) { + it++; + } + return it; + } + +public: + /** + * Calculates the union of this range set and the given range. + * + * @param range is the range that should be merged into this range set. + */ + void merge(Range<T> r) + { + // Calculate a new range that covers both the new range and all old + // ranges in the set -- delete all old elements on the way + auto it = firstOverlapping(r, true); + while ((it->overlapps(r) || it->neighbours(r)) && it != ranges.end()) { + r = r.merge(*it); + it = ranges.erase(it); + } + + // Insert the new range + ranges.insert(r); + } + + /** + * Calculates the union of this range set and the given range set. + * + * @param ranges is another range set for which the union with this set + * should be calculated. + */ + void merge(const RangeSet<T> &s) + { + for (Range<T> &r : s.ranges) { + merge(r); + } + } + + /** + * Checks whether this range set S contains the given range R: + * S u R = R + * (The intersection between R and S equals the given range) + * + * @param r is the range for which the containment should be checked. + * @return true if the above condition is met, false otherwise. + */ + bool contains(const Range<T> &r) + { + auto it = firstOverlapping(r, false); + if (it != ranges.end()) { + return (*it).covers(r); + } + return false; + } + + /** + * Checks whether this range set S1 contains the given range set S2: + * + * @param s is the range for which the containment should be checked. + * @return true if the above condition is met, false otherwise. + */ + bool contains(const RangeSet<T> &s) + { + bool res = true; + for (Range<T> &r : s.ranges) { + res = res && contains(r); + } + return res; + } + + /** + * Empties the set. + */ + void clear() + { + ranges.clear(); + } + + /** + * Returns the current list of ranges as a const reference. + */ + const std::set<Range<T>, RangeComp<T>>& getRanges() + { + return this->ranges; + } + +}; + +} + +#endif /* _RANGE_SET_HPP_ */ + diff --git a/test/model/RangeSet.cpp b/test/model/RangeSet.cpp new file mode 100644 index 0000000..f3aa209 --- /dev/null +++ b/test/model/RangeSet.cpp @@ -0,0 +1,220 @@ +/* + Ousía + Copyright (C) 2014 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <gtest/gtest.h> + +#include <model/RangeSet.hpp> + +namespace ousia { + +TEST(Range, IsValid) +{ + ASSERT_FALSE(Range<int>().isValid()); + ASSERT_TRUE(Range<int>(10).isValid()); + ASSERT_TRUE(Range<int>(10, 20).isValid()); + ASSERT_FALSE(Range<int>(20, 10).isValid()); +} + +TEST(Range, InRange) +{ + Range<int> r(10, 20); + ASSERT_FALSE(r.inRange(0)); + ASSERT_FALSE(r.inRange(21)); + ASSERT_TRUE(r.inRange(10)); + ASSERT_TRUE(r.inRange(20)); + ASSERT_TRUE(r.inRange(15)); +} + +TEST(Range, Overlapps) +{ + ASSERT_FALSE(Range<int>(10, 20).overlapps(Range<int>(0, 9))); + ASSERT_FALSE(Range<int>(10, 20).overlapps(Range<int>(21, 30))); + ASSERT_TRUE(Range<int>(10, 20).overlapps(Range<int>(0, 10))); + ASSERT_TRUE(Range<int>(10, 20).overlapps(Range<int>(20, 30))); + ASSERT_TRUE(Range<int>(10, 20).overlapps(Range<int>(5, 15))); + ASSERT_TRUE(Range<int>(10, 20).overlapps(Range<int>(15, 25))); + ASSERT_TRUE(Range<int>(10, 20).overlapps(Range<int>(15, 19))); + ASSERT_TRUE(Range<int>(10, 20).overlapps(Range<int>(15, 15))); + ASSERT_TRUE(Range<int>(10, 20).overlapps(Range<int>(10, 20))); + ASSERT_TRUE(Range<int>(10, 20).overlapps(Range<int>(0, 30))); +} + +TEST(Range, CoveredBy) +{ + ASSERT_FALSE(Range<int>(10, 20).coveredBy(Range<int>(0, 9))); + ASSERT_FALSE(Range<int>(10, 20).coveredBy(Range<int>(21, 30))); + ASSERT_FALSE(Range<int>(10, 20).coveredBy(Range<int>(0, 10))); + ASSERT_FALSE(Range<int>(10, 20).coveredBy(Range<int>(20, 30))); + ASSERT_FALSE(Range<int>(10, 20).coveredBy(Range<int>(5, 15))); + ASSERT_FALSE(Range<int>(10, 20).coveredBy(Range<int>(15, 25))); + ASSERT_FALSE(Range<int>(10, 20).coveredBy(Range<int>(15, 19))); + ASSERT_FALSE(Range<int>(10, 20).coveredBy(Range<int>(15, 15))); + ASSERT_TRUE(Range<int>(10, 20).coveredBy(Range<int>(10, 20))); + ASSERT_TRUE(Range<int>(10, 20).coveredBy(Range<int>(0, 30))); +} + +TEST(Range, Covers) +{ + ASSERT_FALSE(Range<int>(10, 20).covers(Range<int>(0, 9))); + ASSERT_FALSE(Range<int>(10, 20).covers(Range<int>(21, 30))); + ASSERT_FALSE(Range<int>(10, 20).covers(Range<int>(0, 10))); + ASSERT_FALSE(Range<int>(10, 20).covers(Range<int>(20, 30))); + ASSERT_FALSE(Range<int>(10, 20).covers(Range<int>(5, 15))); + ASSERT_FALSE(Range<int>(10, 20).covers(Range<int>(15, 25))); + ASSERT_TRUE(Range<int>(10, 20).covers(Range<int>(15, 19))); + ASSERT_TRUE(Range<int>(10, 20).covers(Range<int>(15, 15))); + ASSERT_TRUE(Range<int>(10, 20).covers(Range<int>(10, 20))); + ASSERT_FALSE(Range<int>(10, 20).covers(Range<int>(0, 30))); +} + +TEST(RangeSet, Neighbours) +{ + ASSERT_TRUE(Range<int>(10, 19).neighbours(Range<int>(20, 30))); + ASSERT_TRUE(Range<int>(20, 29).neighbours(Range<int>(10, 19))); +} + +TEST(Range, Merge) +{ + Range<int> r1(10, 20); + Range<int> r2(15, 25); + Range<int> r3(5, 15); + Range<int> rM = r1.merge(r2).merge(r3); + ASSERT_EQ(rM.start, 5); + ASSERT_EQ(rM.end, 25); +} + +TEST(RangeSet, Merge) +{ + RangeSet<int> s; + auto &ranges = s.getRanges(); + + // Insert some non-overlapping elements into the range. We expect these to + // be just inserted into the ranges. + s.merge(Range<int>( 0, 10)); + s.merge(Range<int>(20, 30)); + s.merge(Range<int>(40, 50)); + s.merge(Range<int>(60, 70)); + { + ASSERT_EQ(ranges.size(), 4); + + auto it = ranges.begin(); + ASSERT_EQ((*it).start, 0); + ASSERT_EQ((*it).end, 10); + + it++; + ASSERT_EQ((*it).start, 20); + ASSERT_EQ((*it).end, 30); + + it++; + ASSERT_EQ((*it).start, 40); + ASSERT_EQ((*it).end, 50); + + it++; + ASSERT_EQ((*it).start, 60); + ASSERT_EQ((*it).end, 70); + } + + // Now insert an element which spans the second and third element + s.merge(Range<int>(15, 55)); + { + ASSERT_EQ(ranges.size(), 3); + + auto it = ranges.begin(); + ASSERT_EQ((*it).start, 0); + ASSERT_EQ((*it).end, 10); + + it++; + ASSERT_EQ((*it).start, 15); + ASSERT_EQ((*it).end, 55); + + it++; + ASSERT_EQ((*it).start, 60); + ASSERT_EQ((*it).end, 70); + } + + // Now insert an element which expands the first element + s.merge(Range<int>(-10, 11)); + { + ASSERT_EQ(ranges.size(), 3); + + auto it = ranges.begin(); + ASSERT_EQ((*it).start, -10); + ASSERT_EQ((*it).end, 11); + + it++; + ASSERT_EQ((*it).start, 15); + ASSERT_EQ((*it).end, 55); + + it++; + ASSERT_EQ((*it).start, 60); + ASSERT_EQ((*it).end, 70); + } + + // Now insert an element which merges the last two elements + s.merge(Range<int>(13, 70)); + { + ASSERT_EQ(ranges.size(), 2); + + auto it = ranges.begin(); + ASSERT_EQ((*it).start, -10); + ASSERT_EQ((*it).end, 11); + + it++; + ASSERT_EQ((*it).start, 13); + ASSERT_EQ((*it).end, 70); + } + + // Now insert an element which merges the remaining elements + s.merge(Range<int>(-9, 12)); + { + ASSERT_EQ(ranges.size(), 1); + + auto it = ranges.begin(); + ASSERT_EQ((*it).start, -10); + ASSERT_EQ((*it).end, 70); + } + +} + +TEST(RangeSet, Contains) +{ + RangeSet<int> s; + + // Insert some non-overlapping elements into the range. We expect these to + // be just inserted into the ranges. + s.merge(Range<int>( 0, 10)); + s.merge(Range<int>(20, 30)); + s.merge(Range<int>(40, 50)); + s.merge(Range<int>(60, 70)); + s.merge(Range<int>(71)); + s.merge(Range<int>(72)); + s.merge(Range<int>(73)); + s.merge(Range<int>(74)); + + ASSERT_TRUE(s.contains(60)); + ASSERT_TRUE(s.contains(0)); + ASSERT_TRUE(s.contains(25)); + ASSERT_TRUE(s.contains(73)); + ASSERT_TRUE(s.contains(Range<int>(25, 30))); + ASSERT_FALSE(s.contains(Range<int>(25, 35))); + ASSERT_TRUE(s.contains(Range<int>(0, 10))); + ASSERT_TRUE(s.contains(Range<int>(70, 74))); +} + +} + |