diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4d8fdcfe..e3e08e6f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -22,6 +22,7 @@ set(examples FunctionTools Generate GroupBy + InclusiveScan Join JoinWhere Map diff --git a/examples/InclusiveScan.cpp b/examples/InclusiveScan.cpp new file mode 100644 index 00000000..b5819649 --- /dev/null +++ b/examples/InclusiveScan.cpp @@ -0,0 +1,23 @@ +#include + +#include + +int main() { + int array[] = {3, 5, 2, 3, 4, 2, 3}; + // start the scan from arr[0] (3) + auto scan = lz::iScan(array); + + for (const int& i : scan) { + fmt::print("{} ", i); + } + // prints 3 8 10 13 17 19 22 + + // essentially it's: + // 3 (3) + // 3 + 5 (8) + // 8 + 2 (10) + // 10 + 3 (13) + // 13 + 4 (17) + // 17 + 2 (19) + // 19 + 3 (22) +} \ No newline at end of file diff --git a/include/Lz/ExclusiveScan.hpp b/include/Lz/ExclusiveScan.hpp index 8282b05d..93572560 100644 --- a/include/Lz/ExclusiveScan.hpp +++ b/include/Lz/ExclusiveScan.hpp @@ -1,7 +1,7 @@ #pragma once -#ifndef LZ_SCAN_HPP -# define LZ_SCAN_HPP +#ifndef LZ_EXCLUSIVE_SCAN_HPP +# define LZ_EXCLUSIVE_SCAN_HPP # include "detail/BasicIteratorView.hpp" # include "detail/ExclusiveScanIterator.hpp" @@ -51,9 +51,10 @@ class ExclusiveScan final : public internal::BasicIteratorView)> +template, + class BinaryOp = MAKE_BIN_OP(std::plus, internal::Decay)> LZ_NODISCARD LZ_CONSTEXPR_CXX_14 ExclusiveScan, internal::Decay> -eScan(Iterator first, Iterator last, T&& init, BinaryOp&& binOp = {}) { +eScan(Iterator first, Iterator last, T&& init = {}, BinaryOp&& binOp = {}) { return { std::move(first), std::move(last), std::move(init), std::forward(binOp) }; } @@ -80,10 +81,10 @@ eScan(Iterator first, Iterator last, T&& init, BinaryOp&& binOp = {}) { * @return An exclusive scan view object. */ // clang-format off -template)> +template, class BinaryOp = MAKE_BIN_OP(std::plus, internal::Decay)> LZ_NODISCARD LZ_CONSTEXPR_CXX_14 ExclusiveScan, internal::Decay, internal::Decay> -eScan(Iterable&& iterable, T&& init, BinaryOp&& binOp = {}) { +eScan(Iterable&& iterable, T&& init = {}, BinaryOp&& binOp = {}) { return eScan(internal::begin(std::forward(iterable)), internal::end(std::forward(iterable)), std::forward(init), std::forward(binOp)); } diff --git a/include/Lz/InclusiveScan.hpp b/include/Lz/InclusiveScan.hpp new file mode 100644 index 00000000..af5c3531 --- /dev/null +++ b/include/Lz/InclusiveScan.hpp @@ -0,0 +1,162 @@ +#pragma once + +#ifndef LZ_INCLUSIVE_SCAN_HPP +# define LZ_INCLUSIVE_SCAN_HPP + +# include "detail/BasicIteratorView.hpp" +# include "detail/InclusiveScanIterator.hpp" + +namespace lz { +LZ_MODULE_EXPORT_SCOPE_BEGIN + +template +class InclusiveScan final : public internal::BasicIteratorView> { +public: + using iterator = internal::InclusiveScanIterator; + using const_iterator = iterator; + + constexpr InclusiveScan() = default; + + LZ_CONSTEXPR_CXX_14 InclusiveScan(Iterator first, Iterator last, T init, BinaryOp binaryOp) : + internal::BasicIteratorView(iterator(std::move(first), init, binaryOp), + iterator(std::move(last), init, binaryOp)) { + } +}; + +/** + * @addtogroup ItFns + * @{ + */ + +/** + * @brief Returns an inclusive scan iterator. + * @details Returns an inclusive scan iterator. This iterator begins by returning `binOp(std::move(init), *first)`. It then + * proceeds to the next one, which is essentially the previously calculated value + the current iterator element being handled. + * Example: + * @example + * ```cpp + * int array[] = {3, 5, 2, 3, 4, 2, 3}; + * // start the scan from 3 + init (1) = 4 + * auto scan = lz::iScanFrom(std::begin(array), std::end(array), 1); + * + * for (const int& i : scan) { + * fmt::print("{} ", i); + * } + * // prints 4 9 11 14 18 20 23 + * ``` + * + * @param first The iterator denoting the beginning + * @param last The iterator denoting the end + * @param init The value to start with + * @param binOp The fold function. Essentially, it is executed as (`init = binOp(std::move(init), *iterator);`) + * @return An inclusive scan view object. + */ +template)> +LZ_NODISCARD LZ_CONSTEXPR_CXX_14 InclusiveScan, internal::Decay> +iScanFrom(Iterator first, Iterator last, T&& init, BinaryOp&& binOp = {}) { + auto tmp = binOp(std::forward(init), *first); + return { std::move(first), std::move(last), std::move(tmp), std::forward(binOp) }; +} + +/** + * @brief Returns an inclusive scan iterator. + * @details Returns an inclusive scan iterator. This iterator begins by returning `binOp(std::move(init), *first)`. It then + * proceeds to the next one, which is essentially the previously calculated value + the current iterator element being handled. + * Example: + * @example + * ```cpp + * int array[] = {3, 5, 2, 3, 4, 2, 3}; + * // start the scan from 3 + init (1) = 4 + * auto scan = lz::iScanFrom(array, 1); + * + * for (const int& i : scan) { + * fmt::print("{} ", i); + * } + * // prints 4 9 11 14 18 20 23 + * ``` + * + * @param iterable The iterable to perform the inclusive scan over + * @param init The value to start with + * @param binOp The fold function. Essentially, it is executed as (`init = binOp(std::move(init), *iterator);`) + * @return An inclusive scan view object. + */ +// clang-format off +template)> +LZ_NODISCARD LZ_CONSTEXPR_CXX_14 +InclusiveScan, internal::Decay, internal::Decay> +iScanFrom(Iterable&& iterable, T&& init, BinaryOp&& binOp = {}) { + return iScanFrom(internal::begin(std::forward(iterable)), internal::end(std::forward(iterable)), + std::forward(init), std::forward(binOp)); +} +// clang-format on + +/** + * @brief Returns an inclusive scan iterator. + * @details Returns an inclusive scan iterator. This iterator begins by returning `*first`. It then + * proceeds to the next one, which is essentially the previously calculated value (calculated by `binOp(std::move(current), *it)` + * + the current iterator element being handled. Example: + * @example + * ```cpp + * int array[] = {3, 5, 2, 3, 4, 2, 3}; + * // start the scan from 3 + * auto scan = lz::iScan(array); + * + * for (const int& i : scan) { + * fmt::print("{} ", i); + * } + * // prints 3 8 10 13 17 19 22 + * ``` + * + * @param first The iterator denoting the beginning + * @param last The iterator denoting the end + * @param init The value to start with + * @param binOp The fold function. Essentially, it is executed as (`init = binOp(std::move(init), *iterator);`) + * @return An inclusive scan view object. + */ +template)> +LZ_NODISCARD LZ_CONSTEXPR_CXX_14 InclusiveScan, internal::Decay> +iScan(Iterator first, Iterator last, BinaryOp&& binOp = {}) { + auto tmp = *first; + return { std::move(first), std::move(last), std::move(tmp), std::forward(binOp) }; +} + +/** + * @brief Returns an inclusive scan iterator. + * @details Returns an inclusive scan iterator. This iterator begins by returning `*std::begin(iterable)`. It then + * proceeds to the next one, which is essentially the previously calculated value (calculated by `binOp(std::move(current), *it)` + * + the current iterator element being handled. Example: + * @example + * ```cpp + * int array[] = {3, 5, 2, 3, 4, 2, 3}; + * // start the scan from 3 + * auto scan = lz::iScan(array); + * + * for (const int& i : scan) { + * fmt::print("{} ", i); + * } + * // prints 3 8 10 13 17 19 22 + * ``` + * + * @param iterable The iterable to perform the inclusive scan over + * @param init The value to start with + * @param binOp The fold function. Essentially, it is executed as (`init = binOp(std::move(init), *iterator);`) + * @return An inclusive scan view object. + */ +// clang-format off +template)> +LZ_NODISCARD LZ_CONSTEXPR_CXX_14 +InclusiveScan, internal::ValueTypeIterable, internal::Decay> +iScan(Iterable&& iterable, BinaryOp&& binOp = {}) { + return iScan(internal::begin(std::forward(iterable)), internal::end(std::forward(iterable)), std::forward(binOp)); +} +// clang-format on + +// End of group +/** + * @} + */ + +LZ_MODULE_EXPORT_SCOPE_END +} // namespace lz + +#endif \ No newline at end of file diff --git a/include/Lz/Lz.hpp b/include/Lz/Lz.hpp index 7e08962c..f4937acf 100644 --- a/include/Lz/Lz.hpp +++ b/include/Lz/Lz.hpp @@ -10,10 +10,12 @@ # include "Lz/Enumerate.hpp" # include "Lz/Except.hpp" # include "Lz/Exclude.hpp" +# include "Lz/ExclusiveScan.hpp" # include "Lz/Flatten.hpp" # include "Lz/FunctionTools.hpp" # include "Lz/Generate.hpp" # include "Lz/GroupBy.hpp" +# include "Lz/InclusiveScan.hpp" # include "Lz/JoinWhere.hpp" # include "Lz/Loop.hpp" # include "Lz/Random.hpp" @@ -210,7 +212,6 @@ class IterView final : public internal::BasicIteratorView { } //! See Loop.hpp for documentation - template LZ_NODISCARD LZ_CONSTEXPR_CXX_20 IterView> loop() const { return chain(lz::loop(*this)); } @@ -220,11 +221,28 @@ class IterView final : public internal::BasicIteratorView { exclude(const difference_type from, const difference_type to) const { return chain(lz::exclude(*this, from, to)); } + + // clang-format off + //! See InclusiveScan.hpp for documentation. + template)> + LZ_NODISCARD + LZ_CONSTEXPR_CXX_20 IterView, internal::Decay>> + iScan(T&& init = {}, BinaryOp&& binaryOp = {}) const { + return chain(lz::iScan(*this, std::forward(init), std::forward(binaryOp))); + } + + //! See ExclusiveScan.hpp for documentation. + template)> + LZ_NODISCARD + LZ_CONSTEXPR_CXX_20 IterView, internal::Decay>> + eScan(T&& init = {}, BinaryOp&& binaryOp = {}) const { + return chain(lz::eScan(*this, std::forward(init), std::forward(binaryOp))); + } + // clang-format on - template LZ_NODISCARD LZ_CONSTEXPR_CXX_20 IterView> - rotate(const internal::DiffType start) const { - return chain(lz::rotate(*this, start)); + rotate(iterator start) const { + return chain(lz::rotate(std::move(start), this->begin(), this->end())); } //! See FunctionTools.hpp `hasOne` for documentation. diff --git a/include/Lz/detail/ExclusiveScanIterator.hpp b/include/Lz/detail/ExclusiveScanIterator.hpp index 410d29a5..2ec6f900 100644 --- a/include/Lz/detail/ExclusiveScanIterator.hpp +++ b/include/Lz/detail/ExclusiveScanIterator.hpp @@ -1,7 +1,7 @@ #pragma once -#ifndef LZ_SCAN_ITERATOR -# define LZ_SCAN_ITERATOR +#ifndef LZ_EXCLUSIVE_SCAN_ITERATOR_HPP +# define LZ_EXCLUSIVE_SCAN_ITERATOR_HPP # include "FunctionContainer.hpp" diff --git a/include/Lz/detail/InclusiveScanIterator.hpp b/include/Lz/detail/InclusiveScanIterator.hpp new file mode 100644 index 00000000..1ae18acf --- /dev/null +++ b/include/Lz/detail/InclusiveScanIterator.hpp @@ -0,0 +1,64 @@ +#pragma once + +#ifndef LZ_INCLUSIVE_SCAN_ITERATOR_HPP +# define LZ_INCLUSIVE_SCAN_ITERATOR_HPP + +# include "FunctionContainer.hpp" + +namespace lz { +namespace internal { +template +class InclusiveScanIterator { + T _reducer{}; + FunctionContainer _binaryOp{}; + Iterator _iterator{}; + + using IterTraits = std::iterator_traits; + +public: + using reference = T&; + using value_type = Decay; + using pointer = FakePointerProxy; + using difference_type = typename IterTraits::difference_type; + using iterator_category = std::forward_iterator_tag; + + constexpr InclusiveScanIterator() = default; + + LZ_CONSTEXPR_CXX_14 InclusiveScanIterator(Iterator iterator, T init, BinaryOp binaryOp) : + _reducer(std::move(init)), + _binaryOp(std::move(binaryOp)), + _iterator(std::move(iterator)) { + } + + LZ_NODISCARD LZ_CONSTEXPR_CXX_14 typename std::remove_reference::type const& operator*() const { + return _reducer; + } + + LZ_NODISCARD LZ_CONSTEXPR_CXX_14 reference operator*() { + return _reducer; + } + + LZ_CONSTEXPR_CXX_20 InclusiveScanIterator& operator++() { + ++_iterator; + _reducer = _binaryOp(std::move(_reducer), *_iterator); + return *this; + } + + LZ_CONSTEXPR_CXX_20 InclusiveScanIterator operator++(int) { + InclusiveScanIterator tmp(*this); + ++*this; + return tmp; + } + + LZ_NODISCARD constexpr friend bool operator!=(const InclusiveScanIterator& a, const InclusiveScanIterator& b) { + return a._iterator != b._iterator; + } + + LZ_NODISCARD constexpr friend bool operator==(const InclusiveScanIterator& a, const InclusiveScanIterator& b) { + return !(a != b); // NOLINT + } +}; +} // namespace internal +} // namespace lz + +#endif \ No newline at end of file diff --git a/include/Lz/detail/LzTools.hpp b/include/Lz/detail/LzTools.hpp index 69b1ae00..a36d88b6 100644 --- a/include/Lz/detail/LzTools.hpp +++ b/include/Lz/detail/LzTools.hpp @@ -276,7 +276,7 @@ template using FunctionReturnType = decltype(std::declval()(std::declval()...)); template -using ValueTypeIterable = typename Decay::value_type; +using ValueTypeIterable = typename std::iterator_traits>::value_type; template using DiffTypeIterable = typename std::iterator_traits>::difference_type; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e7367f1d..70a1ad2b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(cpp-lazy-tests generate-tests.cpp generate-while-tests.cpp group-by-tests.cpp + inclusive-scan-tests.cpp init-tests.cpp join-tests.cpp join-where-tests.cpp diff --git a/tests/exclusive-scan-tests.cpp b/tests/exclusive-scan-tests.cpp index 36c11d9a..77c5a91c 100644 --- a/tests/exclusive-scan-tests.cpp +++ b/tests/exclusive-scan-tests.cpp @@ -8,7 +8,7 @@ TEST_CASE("ExclusiveScan basic functionality", "[ExclusiveScan][Basic functionality]") { int arr[] = { 3, 1, 4, 1, 5, 9, 2, 6 }; - auto scan = lz::eScan(arr, 0); + auto scan = lz::eScan(arr); CHECK(*scan.begin() == 0); CHECK(std::distance(std::begin(scan), std::end(scan)) == std::distance(std::begin(arr), std::end(arr))); diff --git a/tests/inclusive-scan-tests.cpp b/tests/inclusive-scan-tests.cpp new file mode 100644 index 00000000..a6c1d72e --- /dev/null +++ b/tests/inclusive-scan-tests.cpp @@ -0,0 +1,88 @@ +#include + +#include + +#include +#include +#include + +TEST_CASE("Inclusive scan changing and creating elements", "[InclusiveScan][Basic functionality]") { + int arr[] = { 3, 1, 4, 1, 5, 9, 2, 6 }; + auto scan = lz::iScan(arr); + CHECK(*scan.begin() == 3); + + REQUIRE(std::distance(scan.begin(), scan.end()) == std::distance(std::begin(arr), std::end(arr))); + + int expected[] = { 3, 4, 8, 9, 14, 23, 25, 31 }; + CHECK(std::equal(std::begin(expected), std::end(expected), std::begin(scan))); + + scan = lz::iScanFrom(arr, 2); + REQUIRE(std::distance(scan.begin(), scan.end()) == std::distance(std::begin(arr), std::end(arr))); + int expected2[] = { 5, 6, 10, 11, 16, 25, 27, 33 }; + CHECK(std::equal(std::begin(expected2), std::end(expected2), std::begin(scan))); +} + +TEST_CASE("Inclusive scan splitter binary operations", "[InclusiveScan][Binary ops]") { + int arr[] = { 1, 2, 3, 4, 5 }; + auto scan = lz::iScan(arr); + + SECTION("Operator++") { + auto it = scan.begin(); + CHECK(*it == 1); + ++it; + CHECK(*it == 1 + 2); + ++it; + CHECK(*it == 1 + 2 + 3); + } + + SECTION("Operator== & operator!=") { + CHECK(scan.begin() != scan.end()); + auto begin = scan.begin(); + begin = scan.end(); + CHECK(begin == scan.end()); + begin = scan.begin(); + ++begin; + CHECK(begin != scan.begin()); + CHECK(begin != scan.end()); + } +} + +TEST_CASE("Inclusive scan splitter to containers", "[InclusiveScan][To container]") { + int toScan[] = { 2, 5, 6, 4, 87, 8, 45, 7 }; + auto scanner = lz::iScan(toScan); + + SECTION("To array") { + std::array expected = { 2, 7, 13, 17, 104, 112, 157, 164 }; + auto actual = scanner.toArray(); + CHECK(actual == expected); + } + + SECTION("To vector") { + std::vector expected = { 2, 7, 13, 17, 104, 112, 157, 164 }; + auto actual = scanner.toVector(); + CHECK(expected == actual); + } + + SECTION("To other container using to<>()") { + std::list expected = { 2, 7, 13, 17, 104, 112, 157, 164 }; + auto actual = scanner.to(); + CHECK(expected == actual); + } + + SECTION("To map") { + std::map expected = { { 4, 2 }, { 14, 7 }, { 26, 13 }, { 34, 17 }, + { 208, 104 }, { 224, 112 }, { 314, 157 }, { 328, 164 } }; + auto actual = scanner.toMap([](int i) { return i + i; }); + for (auto&& p : actual) { + UNSCOPED_INFO(fmt::format("({}, {})", p.first, p.second)); + } + CHECK(expected == actual); + } + + SECTION("To unordered map") { + std::unordered_map expected = { { 4, 2 }, { 14, 7 }, { 26, 13 }, { 34, 17 }, + { 208, 104 }, { 224, 112 }, { 314, 157 }, { 328, 164 } }; + auto actual = scanner.toUnorderedMap([](int i) { return i + i; }); + CHECK(expected == actual); + } +}