diff --git a/cmake/unit_testing.cmake b/cmake/unit_testing.cmake index bc6c57e..07342c5 100644 --- a/cmake/unit_testing.cmake +++ b/cmake/unit_testing.cmake @@ -29,3 +29,6 @@ endfunction() add_subdirectory( src/tools/Log/test ) add_subdirectory( src/tools/ranges/IteratorView/test ) +add_subdirectory( src/tools/ranges/TransformIterator/test ) +add_subdirectory( src/tools/ranges/make_view/test ) +add_subdirectory( src/tools/ranges/make_transform_view/test ) diff --git a/src/tools/ranges.hpp b/src/tools/ranges.hpp index 5a7bb4a..4ae40af 100644 --- a/src/tools/ranges.hpp +++ b/src/tools/ranges.hpp @@ -1 +1,5 @@ #include "tools/ranges/IteratorView.hpp" +#include "tools/ranges/TransformIterator.hpp" + +#include "tools/ranges/make_view.hpp" +#include "tools/ranges/make_transform_view.hpp" diff --git a/src/tools/ranges/IteratorView.hpp b/src/tools/ranges/IteratorView.hpp index 040bb97..ead8246 100644 --- a/src/tools/ranges/IteratorView.hpp +++ b/src/tools/ranges/IteratorView.hpp @@ -49,7 +49,7 @@ class IteratorView { constexpr IteratorView() : IteratorView( Iterator{}, Iterator{} ) {} /** - * @brief IteratorView constructor + * @brief Constructor * * @param[in] begin the iterator to the beginning of the view * @param[in] end the iterator to the end of the view @@ -149,43 +149,6 @@ class IteratorView { } }; -/** - * @brief Make an IteratorView based on two iterators - * - * @param[in] begin the iterator to the beginning of the view - * @param[in] end the iterator to the end of the view - */ -template < typename Iterator > -constexpr auto make_view( Iterator&& begin, Iterator&& end ) { - - return IteratorView< Iterator >{ std::forward< Iterator >( begin ), - std::forward< Iterator >( end ) }; -} - -/** - * @brief Make an IteratorView based on a container - * - * @param[in] container the container - */ -template < typename Container > -constexpr auto make_view( Container& container ) { - - return IteratorView< typename Container::iterator >{ container.begin(), - container.end() }; -} - -/** - * @brief Make an IteratorView based on a container - * - * @param[in] container the container - */ -template < typename Container > -constexpr auto make_view( const Container& container ) { - - return IteratorView< typename Container::const_iterator >{ container.cbegin(), - container.cend() }; -} - /** * @brief Verify if the IteratorView is equal to another container * diff --git a/src/tools/ranges/IteratorView/test/IteratorView.test.cpp b/src/tools/ranges/IteratorView/test/IteratorView.test.cpp index 7db549e..3a35bcd 100644 --- a/src/tools/ranges/IteratorView/test/IteratorView.test.cpp +++ b/src/tools/ranges/IteratorView/test/IteratorView.test.cpp @@ -49,41 +49,6 @@ SCENARIO( "IteratorView" ) { CHECK_THROWS( chunk.at( 10 ) ); } // THEN } // WHEN - - WHEN( "when make functions are used" ) { - - auto chunk = make_view( values ); - - THEN( "an IteratorView can be constructed and members can be tested" ) { - - CHECK( 5 == chunk.size() ); - CHECK( false == chunk.empty() ); - - CHECK( -2 == chunk[0] ); - CHECK( -1 == chunk[1] ); - CHECK( 0 == chunk[2] ); - CHECK( 1 == chunk[3] ); - CHECK( 2 == chunk[4] ); - - CHECK( -2 == chunk.at( 0 ) ); - CHECK( -1 == chunk.at( 1 ) ); - CHECK( 0 == chunk.at( 2 ) ); - CHECK( 1 == chunk.at( 3 ) ); - CHECK( 2 == chunk.at( 4 ) ); - - CHECK( -2 == chunk.front() ); - CHECK( 2 == chunk.back() ); - } // THEN - - THEN( "an exception is thrown when using an index that is too large or " - "too small" ) { - - CHECK_NOTHROW( chunk.at( 0 ) ); - CHECK_NOTHROW( chunk.at( 4 ) ); - CHECK_THROWS( chunk.at( 5 ) ); - CHECK_THROWS( chunk.at( 10 ) ); - } // THEN - } // WHEN } // GIVEN GIVEN( "containers and views with values" ) { @@ -94,8 +59,8 @@ SCENARIO( "IteratorView" ) { std::vector< double > copy1 = container1; std::vector< double > copy2 = container2; - auto view1 = make_view( copy1 ); - auto view2 = make_view( copy2 ); + IteratorView< std::vector< double >::iterator > view1( copy1.begin(), copy1.end() ); + IteratorView< std::vector< double >::iterator > view2( copy2.begin(), copy2.end() ); WHEN( "when make comparison functions are used" ) { diff --git a/src/tools/ranges/TransformIterator.hpp b/src/tools/ranges/TransformIterator.hpp new file mode 100644 index 0000000..0c80996 --- /dev/null +++ b/src/tools/ranges/TransformIterator.hpp @@ -0,0 +1,157 @@ +#ifndef NJOY_UTILITY_TRANSFORMITERATOR +#define NJOY_UTILITY_TRANSFORMITERATOR + +// system includes +#include + +// other includes + +namespace njoy { +namespace tools { +namespace ranges { + +/** + * + * @brief An iterator class that applies a transformation when dereferencing + * the iterator + */ +template < typename Iterator, typename Transformation > +class TransformIterator { + + /* fields */ + Iterator iter_; + Transformation transform_; + +public: + + using iterator_category = typename Iterator::iterator_category; + using difference_type = typename Iterator::difference_type; + using value_type = decltype( std::declval< Transformation >()( std::declval< typename Iterator::value_type >() ) ); + using pointer = value_type*; + using reference = value_type; + + /* constructor */ + + constexpr TransformIterator() = delete; + + /** + * @brief Constructor + * + * @param[in] iter the iterator + * @param[in] transformation the transformation to be applied + */ + constexpr TransformIterator( Iterator iter, Transformation transformation ) : + iter_( std::move( iter ) ), transform_( std::move( transformation ) ) {} + + /* methods */ + + constexpr reference operator*() const { + + return this->transform_( *this->iter_ ); + } + + constexpr reference operator[]( difference_type i ) const { + + return this->transform_( this->iter_[i] ); + } + + constexpr TransformIterator& operator+=( difference_type i ) { + + this->iter_ += i; + return *this; + } + + constexpr TransformIterator& operator-=( difference_type i ) { + + this->iter_ -= i; + return *this; + } + + constexpr TransformIterator& operator++( void ) { + + ++this->iter_; + return *this; + } + + constexpr TransformIterator operator++( int ) { + + return TransformIterator( this->iter_++, transform_ ); + } + + constexpr TransformIterator& operator--( void ) { + + --this->iter_; + return *this; + } + + constexpr TransformIterator operator--( int ) { + + return TransformIterator( this->iter_--, this->transform_ ); + } + + friend constexpr TransformIterator operator+( difference_type i, + TransformIterator iter ) { + + return TransformIterator( iter.iter_ + i, iter.transform_ ); + } + + friend constexpr TransformIterator operator+( TransformIterator iter, + difference_type i ) { + + return TransformIterator( iter.iter_ + i, iter.transform_ ); + } + + friend constexpr TransformIterator operator-( TransformIterator iter, + difference_type i ) { + + return TransformIterator( iter.iter_ - i, iter.transform_ ); + } + + friend constexpr difference_type operator-( const TransformIterator& left, + const TransformIterator& right ) { + + return left.iter_ - right.iter_; + } + + friend constexpr bool operator==( const TransformIterator& left, + const TransformIterator& right ) { + + return left.iter_ == right.iter_; + }; + + friend constexpr bool operator!=( const TransformIterator& left, + const TransformIterator& right ) { + + return !( left == right ); + }; + + friend constexpr bool operator<( const TransformIterator& left, + const TransformIterator& right) { + + return left.iter_ < right.iter_; + } + + friend constexpr bool operator>( const TransformIterator& left, + const TransformIterator& right ) { + + return right < left; + } + + friend constexpr bool operator<=( const TransformIterator& left, + const TransformIterator& right ) { + + return !( right < left ); + } + + friend constexpr bool operator>=( const TransformIterator& left, + const TransformIterator& right ) { + + return !( left < right ); + } +}; + +} // ranges namespace +} // tools namespace +} // njoy namespace + +#endif diff --git a/src/tools/ranges/TransformIterator/test/CMakeLists.txt b/src/tools/ranges/TransformIterator/test/CMakeLists.txt new file mode 100644 index 0000000..dea3472 --- /dev/null +++ b/src/tools/ranges/TransformIterator/test/CMakeLists.txt @@ -0,0 +1 @@ +add_cpp_test( ranges.TransformIterator TransformIterator.test.cpp ) diff --git a/src/tools/ranges/TransformIterator/test/TransformIterator.test.cpp b/src/tools/ranges/TransformIterator/test/TransformIterator.test.cpp new file mode 100644 index 0000000..c7293f5 --- /dev/null +++ b/src/tools/ranges/TransformIterator/test/TransformIterator.test.cpp @@ -0,0 +1,85 @@ +// include Catch2 +#include + +// what we are testing +#include "tools/ranges/TransformIterator.hpp" + +// other includes + +// convenience typedefs +using namespace njoy::tools::ranges; + +SCENARIO( "TransformIterator" ) { + + GIVEN( "a container with values and a transformation operation" ) { + + std::vector< int > values = { -2, -1, 0, 1, 2 }; + auto transform = [] ( auto&& value ) { return 2 * value; }; + + WHEN( "when iterators are used" ) { + + TransformIterator iter( values.begin(), transform ); + TransformIterator begin( values.begin(), transform ); + TransformIterator end( values.end(), transform ); + + THEN( "TransformIterator behaves as an iterator" ) { + + CHECK( *iter == -4 ); + + CHECK( iter[0] == -4 ); + CHECK( iter[1] == -2 ); + CHECK( iter[2] == 0 ); + CHECK( iter[3] == 2 ); + CHECK( iter[4] == 4 ); + + CHECK( *( begin + 0 ) == -4 ); + CHECK( *( begin + 1 ) == -2 ); + CHECK( *( begin + 2 ) == 0 ); + CHECK( *( begin + 3 ) == 2 ); + CHECK( *( begin + 4 ) == 4 ); + + CHECK( *( 0 + begin ) == -4 ); + CHECK( *( 1 + begin ) == -2 ); + CHECK( *( 2 + begin ) == 0 ); + CHECK( *( 3 + begin ) == 2 ); + CHECK( *( 4 + begin ) == 4 ); + + CHECK( *( end - 1 ) == 4 ); + CHECK( *( end - 2 ) == 2 ); + CHECK( *( end - 3 ) == 0 ); + CHECK( *( end - 4 ) == -2 ); + CHECK( *( end - 5 ) == -4 ); + + CHECK( end - begin == 5 ); + + iter += 3; + CHECK( *iter == 2 ); + iter -= 3; + CHECK( *iter == -4 ); + + ++iter; + CHECK( *iter == -2 ); + --iter; + CHECK( *iter == -4 ); + + CHECK( *( iter++ ) == -4 ); + CHECK( *iter == -2 ); + CHECK( *( iter-- ) == -2 ); + CHECK( *iter == -4 ); + + CHECK( true == ( iter == begin ) ); + CHECK( false == ( iter == end ) ); + CHECK( true == ( iter != end ) ); + CHECK( false == ( iter != begin ) ); + CHECK( true == ( iter < end ) ); + CHECK( false == ( iter < begin ) ); + CHECK( true == ( iter <= begin ) ); + CHECK( false == ( end <= iter ) ); + CHECK( true == ( end > iter ) ); + CHECK( false == ( iter > begin ) ); + CHECK( true == ( end >= iter ) ); + CHECK( false == ( begin >= end ) ); + } // THEN + } // WHEN + } // GIVEN +} // SCENARIO diff --git a/src/tools/ranges/make_transform_view.hpp b/src/tools/ranges/make_transform_view.hpp new file mode 100644 index 0000000..29a3244 --- /dev/null +++ b/src/tools/ranges/make_transform_view.hpp @@ -0,0 +1,48 @@ +#ifndef NJOY_TOOLS_RANGES_MAKE_TRANSFORM_VIEW +#define NJOY_TOOLS_RANGES_MAKE_TRANSFORM_VIEW + +// system includes + +// other includes +#include "tools/ranges/IteratorView.hpp" +#include "tools/ranges/TransformIterator.hpp" + +namespace njoy { +namespace tools { +namespace ranges { + +/** + * @brief Make an IteratorView based on two iterators and a transformation + * + * @param[in] begin the iterator to the beginning of the view + * @param[in] end the iterator to the end of the view + * @param[in] transformation the transformation to be applied + */ +template < typename Iterator, typename Transformation > +constexpr auto make_transform_view( Iterator begin, Iterator end, + Transformation transformation ) { + + using Iter = TransformIterator< Iterator, Transformation >; + return IteratorView< Iter >{ Iter( std::move( begin ), transformation ), + Iter( std::move( end ), transformation ) }; +} + +/** + * @brief Make an IteratorView based on a container and a transformation + * + * @param[in] container the container + * @param[in] transformation the transformation to be applied + */ +template < typename Container, typename Transformation > +constexpr auto make_transform_view( Container&& container, + Transformation transformation ) { + + return make_transform_view( container.begin(), container.end(), + std::move( transformation ) ); +} + +} // ranges namespace +} // tools namespace +} // njoy namespace + +#endif diff --git a/src/tools/ranges/make_transform_view/test/CMakeLists.txt b/src/tools/ranges/make_transform_view/test/CMakeLists.txt new file mode 100644 index 0000000..b5bc1b1 --- /dev/null +++ b/src/tools/ranges/make_transform_view/test/CMakeLists.txt @@ -0,0 +1 @@ +add_cpp_test( ranges.make_transform_view make_transform_view.test.cpp ) diff --git a/src/tools/ranges/make_transform_view/test/make_transform_view.test.cpp b/src/tools/ranges/make_transform_view/test/make_transform_view.test.cpp new file mode 100644 index 0000000..2b787fc --- /dev/null +++ b/src/tools/ranges/make_transform_view/test/make_transform_view.test.cpp @@ -0,0 +1,71 @@ +// include Catch2 +#include + +// what we are testing +#include "tools/ranges/make_transform_view.hpp" + +// other includes + +// convenience typedefs +using namespace njoy::tools::ranges; + +SCENARIO( "make_view" ) { + + GIVEN( "a container with values and a transformation" ) { + + std::vector< int > values = { -2, -1, 0, 1, 2 }; + auto transform = [] ( auto&& value ) { return 2 * value; }; + + WHEN( "when using iterators and the transformation" ) { + + auto chunk = make_transform_view( values.begin(), values.end(), transform ); + + THEN( "an IteratorView can be constructed and members can be tested" ) { + + CHECK( 5 == chunk.size() ); + CHECK( false == chunk.empty() ); + + CHECK( -4 == chunk[0] ); + CHECK( -2 == chunk[1] ); + CHECK( 0 == chunk[2] ); + CHECK( 2 == chunk[3] ); + CHECK( 4 == chunk[4] ); + + CHECK( -4 == chunk.at( 0 ) ); + CHECK( -2 == chunk.at( 1 ) ); + CHECK( 0 == chunk.at( 2 ) ); + CHECK( 2 == chunk.at( 3 ) ); + CHECK( 4 == chunk.at( 4 ) ); + + CHECK( -4 == chunk.front() ); + CHECK( 4 == chunk.back() ); + } // THEN + } // WHEN + + WHEN( "when using the container and the transformation" ) { + + auto chunk = make_transform_view( values, transform ); + + THEN( "an IteratorView can be constructed and members can be tested" ) { + + CHECK( 5 == chunk.size() ); + CHECK( false == chunk.empty() ); + + CHECK( -4 == chunk[0] ); + CHECK( -2 == chunk[1] ); + CHECK( 0 == chunk[2] ); + CHECK( 2 == chunk[3] ); + CHECK( 4 == chunk[4] ); + + CHECK( -4 == chunk.at( 0 ) ); + CHECK( -2 == chunk.at( 1 ) ); + CHECK( 0 == chunk.at( 2 ) ); + CHECK( 2 == chunk.at( 3 ) ); + CHECK( 4 == chunk.at( 4 ) ); + + CHECK( -4 == chunk.front() ); + CHECK( 4 == chunk.back() ); + } // THEN + } // WHEN + } // GIVEN +} // SCENARIO diff --git a/src/tools/ranges/make_view.hpp b/src/tools/ranges/make_view.hpp new file mode 100644 index 0000000..966016a --- /dev/null +++ b/src/tools/ranges/make_view.hpp @@ -0,0 +1,40 @@ +#ifndef NJOY_TOOLS_RANGES_MAKE_VIEW +#define NJOY_TOOLS_RANGES_MAKE_VIEW + +// system includes + +// other includes +#include "tools/ranges/IteratorView.hpp" + +namespace njoy { +namespace tools { +namespace ranges { + +/** + * @brief Make an IteratorView based on two iterators + * + * @param[in] begin the iterator to the beginning of the view + * @param[in] end the iterator to the end of the view + */ +template < typename Iterator > +constexpr auto make_view( Iterator begin, Iterator end ) { + + return IteratorView< Iterator >{ std::move( begin ), std::move( end ) }; +} + +/** + * @brief Make an IteratorView based on a container + * + * @param[in] container the container + */ +template < typename Container > +constexpr auto make_view( Container&& container ) { + + return make_view( container.begin(), container.end() ); +} + +} // ranges namespace +} // tools namespace +} // njoy namespace + +#endif diff --git a/src/tools/ranges/make_view/test/CMakeLists.txt b/src/tools/ranges/make_view/test/CMakeLists.txt new file mode 100644 index 0000000..7269425 --- /dev/null +++ b/src/tools/ranges/make_view/test/CMakeLists.txt @@ -0,0 +1 @@ +add_cpp_test( ranges.make_view make_view.test.cpp ) diff --git a/src/tools/ranges/make_view/test/make_view.test.cpp b/src/tools/ranges/make_view/test/make_view.test.cpp new file mode 100644 index 0000000..715bf28 --- /dev/null +++ b/src/tools/ranges/make_view/test/make_view.test.cpp @@ -0,0 +1,70 @@ +// include Catch2 +#include + +// what we are testing +#include "tools/ranges/make_view.hpp" + +// other includes + +// convenience typedefs +using namespace njoy::tools::ranges; + +SCENARIO( "make_view" ) { + + GIVEN( "a container with values" ) { + + std::vector< int > values = { -2, -1, 0, 1, 2 }; + + WHEN( "when using iterators" ) { + + auto chunk = make_view( values.begin(), values.end() ); + + THEN( "an IteratorView can be constructed and members can be tested" ) { + + CHECK( 5 == chunk.size() ); + CHECK( false == chunk.empty() ); + + CHECK( -2 == chunk[0] ); + CHECK( -1 == chunk[1] ); + CHECK( 0 == chunk[2] ); + CHECK( 1 == chunk[3] ); + CHECK( 2 == chunk[4] ); + + CHECK( -2 == chunk.at( 0 ) ); + CHECK( -1 == chunk.at( 1 ) ); + CHECK( 0 == chunk.at( 2 ) ); + CHECK( 1 == chunk.at( 3 ) ); + CHECK( 2 == chunk.at( 4 ) ); + + CHECK( -2 == chunk.front() ); + CHECK( 2 == chunk.back() ); + } // THEN + } // WHEN + + WHEN( "when using the container" ) { + + auto chunk = make_view( values ); + + THEN( "an IteratorView can be constructed and members can be tested" ) { + + CHECK( 5 == chunk.size() ); + CHECK( false == chunk.empty() ); + + CHECK( -2 == chunk[0] ); + CHECK( -1 == chunk[1] ); + CHECK( 0 == chunk[2] ); + CHECK( 1 == chunk[3] ); + CHECK( 2 == chunk[4] ); + + CHECK( -2 == chunk.at( 0 ) ); + CHECK( -1 == chunk.at( 1 ) ); + CHECK( 0 == chunk.at( 2 ) ); + CHECK( 1 == chunk.at( 3 ) ); + CHECK( 2 == chunk.at( 4 ) ); + + CHECK( -2 == chunk.front() ); + CHECK( 2 == chunk.back() ); + } // THEN + } // WHEN + } // GIVEN +} // SCENARIO