diff --git a/include/rusty_iterators/flatten.hpp b/include/rusty_iterators/flatten.hpp new file mode 100644 index 0000000..b6a6eea --- /dev/null +++ b/include/rusty_iterators/flatten.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "concepts.hpp" +#include "interface.fwd.hpp" +#include "peekable.hpp" + +#include + +namespace rusty_iterators::iterator +{ +using concepts::Indexable; +using interface::IterInterface; + +template + requires Indexable +class Flatten : public IterInterface> +{ + public: + explicit Flatten(Other&& it) : it(std::forward>(it.peekable())) {} + + auto next() -> std::optional; + [[nodiscard]] auto sizeHint() -> std::optional; + + private: + Peekable it; + size_t ptr = 0; + std::optional cache = std::nullopt; +}; +} // namespace rusty_iterators::iterator + +template + requires rusty_iterators::concepts::Indexable +auto rusty_iterators::iterator::Flatten::next() -> std::optional +{ + if (cache.has_value() && ptr < cache.value().size()) + { + auto item = cache.value().at(ptr); + ptr += 1; + return std::move(item); + } + auto indexableItem = it.next(); + + if (!indexableItem.has_value()) + return std::nullopt; + + ptr = 0; + cache = std::move(indexableItem); + + return next(); +} + +template + requires rusty_iterators::concepts::Indexable +auto rusty_iterators::iterator::Flatten::sizeHint() -> std::optional +{ + auto unfoldedSize = it.sizeHint(); + + if (!unfoldedSize.has_value()) + return std::move(unfoldedSize); + + auto peekedItem = it.peek(); + + if (!peekedItem.has_value()) + return 0; + + return unfoldedSize.value() * peekedItem.value().size(); +} diff --git a/include/rusty_iterators/interface.hpp b/include/rusty_iterators/interface.hpp index 757ca4c..ded0dbd 100644 --- a/include/rusty_iterators/interface.hpp +++ b/include/rusty_iterators/interface.hpp @@ -6,6 +6,7 @@ #include "enumerate.hpp" #include "filter.hpp" #include "filter_map.hpp" +#include "flatten.hpp" #include "inspect.hpp" #include "interperse.hpp" #include "map.hpp" @@ -47,6 +48,7 @@ using iterator::CycleType; using iterator::Enumerate; using iterator::Filter; using iterator::FilterMap; +using iterator::Flatten; using iterator::Inspect; using iterator::Interperse; using iterator::Map; @@ -113,6 +115,10 @@ class IterInterface [[nodiscard]] auto filterMap(Functor&& f) -> FilterMap::value_type, Functor, Derived>; + template + requires Indexable + [[nodiscard]] auto flatten() -> Flatten; + template requires FoldFunctor [[nodiscard]] auto fold(B&& init, Functor&& f) -> B; @@ -241,7 +247,7 @@ auto rusty_iterators::interface::IterInterface::collect() -> std::ve auto size = sizeHintChecked(); collection.reserve(size); - self().forEach([&collection](auto&& x) { collection.push_back(std::move(x)); }); + self().forEach([&collection](auto x) { collection.push_back(x); }); return std::move(collection); } @@ -322,6 +328,14 @@ auto rusty_iterators::interface::IterInterface::filterMap(Functor&& std::forward(self()), std::forward(f)}; } +template +template + requires rusty_iterators::concepts::Indexable +auto rusty_iterators::interface::IterInterface::flatten() -> Flatten +{ + return Flatten{std::forward(self())}; +} + template template requires rusty_iterators::concepts::FoldFunctor @@ -353,7 +367,7 @@ auto rusty_iterators::interface::IterInterface::forEach(Functor&& f) [[likely]] while (nextItem.has_value()) { - func(std::move(nextItem.value())); + func(nextItem.value()); nextItem = self().next(); } } diff --git a/include/rusty_iterators/iterator.hpp b/include/rusty_iterators/iterator.hpp index dfc4c33..b63be1b 100644 --- a/include/rusty_iterators/iterator.hpp +++ b/include/rusty_iterators/iterator.hpp @@ -16,6 +16,7 @@ using Item = std::reference_wrapper; namespace rusty_iterators::iterator { using concepts::FoldFunctor; +using concepts::Indexable; using concepts::Multiplyable; using concepts::Summable; @@ -30,6 +31,10 @@ class LazyIterator : public interface::IterInterface, LazyIterat public: explicit LazyIterator(Container& it) : ptr(it.begin()), end(it.end()) {} + template + requires Indexable + [[nodiscard]] auto flatten() -> decltype(auto); + auto next() -> std::optional; [[nodiscard]] auto sizeHint() const -> std::optional; @@ -47,6 +52,15 @@ class LazyIterator : public interface::IterInterface, LazyIterat }; } // namespace rusty_iterators::iterator +template + requires std::ranges::range +template + requires rusty_iterators::concepts::Indexable +auto rusty_iterators::iterator::LazyIterator::flatten() -> decltype(auto) +{ + return this->map([](auto x) { return x.get(); }).flatten(); +} + template requires std::ranges::range auto rusty_iterators::iterator::LazyIterator::next() -> std::optional diff --git a/tests/flatten.test.cpp b/tests/flatten.test.cpp new file mode 100644 index 0000000..39f9748 --- /dev/null +++ b/tests/flatten.test.cpp @@ -0,0 +1,66 @@ +#include +#include + +#include + +using ::rusty_iterators::iterator::LazyIterator; +using ::testing::ElementsAreArray; + +TEST(TestFlattenIterator, TestNextReturnsUnfolded) +{ + auto vec = std::vector>{ + {1, 2}, + {3, 4} + }; + auto it = LazyIterator{vec}.flatten(); + + ASSERT_EQ(it.next(), 1); + ASSERT_EQ(it.next(), 2); + ASSERT_EQ(it.next(), 3); + ASSERT_EQ(it.next(), 4); + ASSERT_EQ(it.next(), std::nullopt); +} + +TEST(TestFlattenIterator, TestCollectFlattened) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.movingWindow(2).flatten(); + + EXPECT_THAT(it.collect(), ElementsAreArray({1, 2, 2, 3})); +} + +TEST(TestFlattenIterator, TestDealingWithReferenceWrapper) +{ + auto vec = std::vector>{ + {1, 2}, + {3, 4} + }; + auto it = LazyIterator{vec}.flatten(); + + EXPECT_THAT(it.collect(), ElementsAreArray({1, 2, 3, 4})); +} + +TEST(TestFlattenIterator, TestSizeHintOnInfiniteIterator) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.movingWindow(2).cycle().flatten(); + + ASSERT_EQ(it.sizeHint(), std::nullopt); +} + +TEST(TestFlattenIterator, TestSizeHintOnEmptyIterator) +{ + auto vec = std::vector{}; + auto it = LazyIterator{vec}.movingWindow(2).flatten(); + + ASSERT_EQ(it.sizeHint(), 0); +} + +TEST(TestFlattenIterator, TestSizeHintOnNonEmptyIterator) +{ + auto vec = std::vector{1, 2, 3, 4}; + auto it = LazyIterator{vec}.movingWindow(2).flatten(); + + ASSERT_EQ(it.sizeHint(), 6); + ASSERT_EQ(it.count(), 6); +}