From 427beebe331a02b986a7ad3133ede11759f3cbaf Mon Sep 17 00:00:00 2001 From: "Jordan P. Lefebvre" Date: Fri, 6 Aug 2021 12:41:10 -0400 Subject: [PATCH] Added Greg Davidson's ranges implementation. --- radixcore/CMakeLists.txt | 2 + radixcore/range.hh | 204 ++++++++++++++++++++++++++++++ radixcore/range.i.hh | 218 +++++++++++++++++++++++++++++++++ radixcore/tests/CMakeLists.txt | 1 + radixcore/tests/tstRange.cc | 81 ++++++++++++ 5 files changed, 506 insertions(+) create mode 100644 radixcore/range.hh create mode 100644 radixcore/range.i.hh create mode 100644 radixcore/tests/tstRange.cc diff --git a/radixcore/CMakeLists.txt b/radixcore/CMakeLists.txt index 6eb36ec..f2cc361 100644 --- a/radixcore/CMakeLists.txt +++ b/radixcore/CMakeLists.txt @@ -8,6 +8,8 @@ json.hh system.hh stringfunctions.i.hh stringfunctions.hh +range.hh +range.i.hh value.hh ) SET(SOURCES diff --git a/radixcore/range.hh b/radixcore/range.hh new file mode 100644 index 0000000..0de9f7c --- /dev/null +++ b/radixcore/range.hh @@ -0,0 +1,204 @@ +/* + * @file: range.hh + * @author: Gregory G. Davidson + * + * Created on July 28, 2021 + * + * Provides the ability to iterate over ranges in a ranged-for construct + */ + +#ifndef RADIX_RADIXCORE_RANGE_HH_ +#define RADIX_RADIXCORE_RANGE_HH_ + +#include +#include + +namespace radix +{ +namespace detail +{ +// Forward declaration of iterator classes +template +class RangeIterator; + +template +class StridedRangeIterator; +} // namespace detail + +/** + * @brief Standard range class for iterating over a range + */ +template +class Range +{ + public: + using value_type = T; + using iterator = detail::RangeIterator; + using size_type = std::size_t; + + // >>> CONSTRUCTORS + // Default constructor for an empty range + inline Range(); + + // Construct with a range ending (begins at zero) + inline explicit Range(value_type end); + + // Construct from a given beginning and ending + inline Range(value_type begin, value_type end); + + // >>> BASIC CONTAINER FUNCTIONALITY + //! Return beginning of range + iterator begin() const { return d_begin; } + + //! Return ending of range + iterator end() const { return d_end; } + + //! Return size of range + size_type size() const { return *d_end - *d_begin; } + + //! Return whether range is empty + bool empty() const { return d_begin == d_end; } + + private: + iterator d_begin; + iterator d_end; +}; + +/** + * @brief Strided range class for iterating over a range by some stride + */ +template +class StridedRange +{ + public: + using value_type = T; + using iterator = detail::StridedRangeIterator; + using size_type = std::size_t; + + // >>> CONSTRUCTORS + // Construct with a range ending and stride + inline StridedRange(value_type end, value_type stride); + + // Construct with a range beginning, ending, and stride + inline StridedRange(value_type begin, value_type end, value_type stride); + + // >>> BASIC CONTAINER FUNCTIONALITY + //! Return beginning of range + iterator begin() const { return d_begin; } + + //! Return ending of range + iterator end() const { return d_end; } + + //! Return size of range + size_type size() const { return *d_end - *d_begin; } + + //! Return whether range is empty + bool empty() const { return d_begin == d_end; } + + private: + iterator d_begin; + iterator d_end; +}; + +//***************************************************************************** +// HELPER FUNCTIONS +//***************************************************************************** +// Build an iterable range with a beginning and ending +template +Range range(T begin, T end); + +// Build an iterable range with just an ending +template +Range range(T begin, T end); + +// Build a strided range with a beginning, ending, and stride +template +StridedRange range(T begin, T end, T stride); + +namespace detail +{ +/** + * @brief Range iterator class + */ +template +class RangeIterator +{ + public: + //@{ + //! Public types + using iterator_category = std::input_iterator_tag; + using value_type = T; + using difference_type = typename std::make_signed::type; + using pointer = const T*; + using reference = const T&; + using This = RangeIterator; + //@} + + public: + // Construct with value + inline explicit RangeIterator(value_type value); + + // >>> ITERATOR INTERFACE + //! Dereference operator + value_type operator*() const { return d_value; } + + //! Indirection operator + pointer operator->() const { return &d_value; } + + // Prefix increment operator + inline This& operator++(); + + // Postfix increment operator + inline This operator++(int); + + //! Equality operator + inline bool operator==(const This& iter) const; + + //! Inequality operator + inline bool operator!=(const This& iter) const; + + protected: + value_type d_value; +}; + +//***************************************************************************** +/** + * @brief Strided range iterator class + */ +template +class StridedRangeIterator : public RangeIterator +{ + using Base = RangeIterator; + + public: + using typename Base::value_type; + using This = StridedRangeIterator; + + public: + // Construct with value and stride + inline StridedRangeIterator(value_type value, value_type stride); + + // >>> OVERWRITTEN ITERATOR INTERFACE + // Prefix increment operator + inline This& operator++(); + + //! Equality operator + inline bool operator==(const This& iter) const; + + // >>> CLASS INTERFACE + value_type stride() const { return d_stride; } + + private: + value_type d_stride; +}; + +} // namespace detail + +} // namespace radix + +//***************************************************************************** +// Inline function definitions +#include "range.i.hh" +//***************************************************************************** + +#endif /** RADIX_RADIXCORE_RANGE_HH_ */ diff --git a/radixcore/range.i.hh b/radixcore/range.i.hh new file mode 100644 index 0000000..7c39fa3 --- /dev/null +++ b/radixcore/range.i.hh @@ -0,0 +1,218 @@ +/* + * @file: range.i.hh + * @author: Gregory G. Davidson + * + * Created on July 28, 2021 + */ + +#include + +#include "radixbug/bug.hh" + +namespace +{ +// Return 1 or -1 matching the sign of the argument +template +T sign(T value) +{ + return std::signbit(value) ? -1 : 1; +} +} // namespace + +namespace radix +{ +//***************************************************************************** +// RANGE +//***************************************************************************** +/*! + * \brief Default constructor for empty range + */ +template +Range::Range() + : Range(value_type(0), value_type(0)) +{ + radix_ensure(this->empty()); +} + +//***************************************************************************** +/*! + * \brief Constructor with range beginning at zero + */ +template +Range::Range(value_type end) + : Range(T(0), end) +{ + /* * */ +} + +//***************************************************************************** +/*! + * \brief Constructor with a beginnning and ending range + */ +template +Range::Range(value_type begin, value_type end) + : d_begin(begin) + , d_end(end) +{ + radix_require(begin <= end); +} + +//***************************************************************************** +// STRIDEDRANGE +//***************************************************************************** +/*! + * \brief Construct with a range ending and stride + */ +template +StridedRange::StridedRange(value_type end, value_type stride) + : StridedRange(value_type(0), end, stride) +{ + /* * */ +} + +//***************************************************************************** +/*! + * \brief Construct with a range begining, ending, and stride + */ +template +StridedRange::StridedRange(value_type begin, value_type end, + value_type stride) + : d_begin(begin, stride) + , d_end(end, stride) +{ + radix_require(end >= sign(stride) * begin); +} + +//***************************************************************************** +// HELPER FUNCTIONS +//***************************************************************************** +/*! + * \brief Build an iterable range with a beginning and ending + */ +template +Range range(T begin, T end) +{ + return Range(begin, end); +} + +//***************************************************************************** +/*! + * \brief Build an iterable range with just an ending + */ +template +Range range(T end) +{ + return Range(end); +} + +//***************************************************************************** +/*! + * \brief Build a strided, iterable range with a beginning, ending, and stride + */ +template +StridedRange range(T begin, T end, T stride) +{ + return StridedRange(begin, end, stride); +} + +namespace detail +{ +//***************************************************************************** +// RANGEITERATOR +//***************************************************************************** +/*! + * \brief Constructor + */ +template +RangeIterator::RangeIterator(value_type value) + : d_value(value) +{ + /* * */ +} + +//***************************************************************************** +/*! + * \brief Prefix increment operator + */ +template +auto RangeIterator::operator++() -> This& +{ + ++d_value; + return *this; +} + +//***************************************************************************** +/*! + * \brief Postfix increment operator + */ +template +auto RangeIterator::operator++(int) -> This +{ + This copy = *this; + ++(*this); + return copy; +} + +//***************************************************************************** +/*! + * \brief Equality operator + */ +template +bool RangeIterator::operator==(const This& iter) const +{ + return d_value == iter.d_value; +} + +//***************************************************************************** +/*! + * \brief Inequality operator + */ +template +bool RangeIterator::operator!=(const This& iter) const +{ + return !this->operator==(iter); +} + +//***************************************************************************** +// STRIDEDRANGEITERATOR +//***************************************************************************** +/*! + * \brief Constructor taking a value and stride + */ +template +StridedRangeIterator::StridedRangeIterator(value_type value, + value_type stride) + : Base(value) + , d_stride(stride) +{ + radix_require(stride != 0); +} + +/*! + * \brief Prefix increment + * operator + */ +template +auto StridedRangeIterator::operator++() -> This& +{ + Base::d_value += d_stride; + return *this; +} + +//***************************************************************************** +/*! + * \brief Equality operator + * + * Only iterators of equal stride + * should be compared + */ +template +bool StridedRangeIterator::operator==(const This& iter) const +{ + radix_require(iter.stride() == this->stride()); + return Base::d_value == iter.d_value; +} + +} // namespace detail + +} // namespace radix diff --git a/radixcore/tests/CMakeLists.txt b/radixcore/tests/CMakeLists.txt index 295ec07..0ed688d 100644 --- a/radixcore/tests/CMakeLists.txt +++ b/radixcore/tests/CMakeLists.txt @@ -1,6 +1,7 @@ INCLUDE(GoogleTest) ADD_GOOGLE_TEST(tstJson.cc NP 1) +ADD_GOOGLE_TEST(tstRange.cc NP 1) ADD_GOOGLE_TEST(tstString.cc NP 1) ADD_GOOGLE_TEST(tstSystem.cc NP 1) ADD_GOOGLE_TEST(tstValue.cc NP 1) diff --git a/radixcore/tests/tstRange.cc b/radixcore/tests/tstRange.cc new file mode 100644 index 0000000..c009034 --- /dev/null +++ b/radixcore/tests/tstRange.cc @@ -0,0 +1,81 @@ +#include "gtest/gtest.h" + +#include + +#include "radixcore/range.hh" + +// Tests simple range starting from zero +TEST(RangeTest, simple_range) +{ + std::vector v; + for (auto i : radix::range(10)) + { + v.push_back(i); + } + + std::vector v_ref = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + EXPECT_EQ(v_ref.size(), v.size()); + + auto v_ref_iter = v_ref.begin(); + for (int v_elem : v) + { + EXPECT_DOUBLE_EQ(*v_ref_iter++, v_elem); + } +} + +// Tests range with a non-zero beginning +TEST(RangeTest, nonzero_begin_range) +{ + std::vector v; + for (auto i : radix::range(5, 10)) + { + v.push_back(i); + } + + std::vector v_ref = {5, 6, 7, 8, 9}; + EXPECT_EQ(v_ref.size(), v.size()); + + auto v_ref_iter = v_ref.begin(); + for (int v_elem : v) + { + EXPECT_DOUBLE_EQ(*v_ref_iter++, v_elem); + } +} + +// Tests range with a positive stride +TEST(RangeTest, pos_stride_test) +{ + std::vector v; + for (auto i : radix::range(0, 10, 2)) + { + v.push_back(i); + } + + std::vector v_ref = {0, 2, 4, 6, 8}; + EXPECT_EQ(v_ref.size(), v.size()); + + auto v_ref_iter = v_ref.begin(); + for (int v_elem : v) + { + EXPECT_DOUBLE_EQ(*v_ref_iter++, v_elem); + } +} + +// Tests range with a negative stride +TEST(RangeTest, neg_stride_test) +{ + std::vector v; + for (auto i : radix::range(9, -1, -1)) + { + v.push_back(i); + } + + std::vector v_ref = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + EXPECT_EQ(v_ref.size(), v.size()); + + auto v_ref_iter = v_ref.begin(); + for (int v_elem : v) + { + EXPECT_DOUBLE_EQ(*v_ref_iter++, v_elem); + } +} -- GitLab