diff --git a/include/rusty_iterators/interface.hpp b/include/rusty_iterators/interface.hpp index 3377977..eb88e9d 100644 --- a/include/rusty_iterators/interface.hpp +++ b/include/rusty_iterators/interface.hpp @@ -10,6 +10,7 @@ #include "interperse.hpp" #include "map.hpp" #include "moving_window.hpp" +#include "peekable.hpp" #include "skip.hpp" #include "step_by.hpp" #include "take.hpp" @@ -50,6 +51,7 @@ using iterator::Inspect; using iterator::Interperse; using iterator::Map; using iterator::MovingWindow; +using iterator::Peekable; using iterator::Skip; using iterator::StepBy; using iterator::Take; @@ -148,6 +150,7 @@ class IterInterface [[nodiscard]] auto neBy(Other&& it, Functor&& f) -> bool; [[nodiscard]] auto nth(size_t element) -> std::optional; + [[nodiscard]] auto peekable() -> Peekable; template requires PositionFunctor @@ -426,6 +429,12 @@ auto rusty_iterators::interface::IterInterface::nth(size_t n) -> std return self().next(); } +template +auto rusty_iterators::interface::IterInterface::peekable() -> Peekable +{ + return Peekable{std::forward(self())}; +} + template template requires rusty_iterators::concepts::PositionFunctor diff --git a/include/rusty_iterators/peekable.hpp b/include/rusty_iterators/peekable.hpp new file mode 100644 index 0000000..de12d7f --- /dev/null +++ b/include/rusty_iterators/peekable.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "interface.fwd.hpp" + +#include + +namespace rusty_iterators::iterator +{ +using interface::IterInterface; + +template +class Peekable : public IterInterface> +{ + public: + explicit Peekable(Other&& it) : it(std::forward(it)) {} + + auto next() -> std::optional; + auto peek() -> std::optional; + [[nodiscard]] auto sizeHint() const -> std::optional; + + private: + Other it; + std::optional> peeked = std::nullopt; +}; +} // namespace rusty_iterators::iterator + +template +auto rusty_iterators::iterator::Peekable::next() -> std::optional +{ + if (peeked.has_value()) + { + auto item = peeked.value(); + peeked = std::nullopt; + return std::move(item); + } + return std::move(it.next()); +} + +template +auto rusty_iterators::iterator::Peekable::peek() -> std::optional +{ + if (peeked.has_value()) + return peeked.value(); + + peeked = std::make_optional(it.next()); + return peeked.value(); +} + +template +auto rusty_iterators::iterator::Peekable::sizeHint() const -> std::optional +{ + auto underlyingSize = it.sizeHint(); + + if (!underlyingSize.has_value()) + return std::move(underlyingSize); + + auto fullSize = underlyingSize.value(); + + if (peeked.has_value() && peeked.value().has_value()) + fullSize += 1; + + return fullSize; +} diff --git a/tests/peekable.test.cpp b/tests/peekable.test.cpp new file mode 100644 index 0000000..ab6de12 --- /dev/null +++ b/tests/peekable.test.cpp @@ -0,0 +1,78 @@ +#include +#include + +#include + +using ::rusty_iterators::iterator::LazyIterator; +using ::testing::ElementsAreArray; + +TEST(TestPeekableIterator, TestNextReturnsNextItem) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.peekable(); + + ASSERT_EQ(it.next(), 1); + ASSERT_EQ(it.next(), 2); + ASSERT_EQ(it.next(), 3); + ASSERT_EQ(it.next(), std::nullopt); +} + +TEST(TestPeekableIterator, TestPeekGivesNextItem) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.peekable(); + + ASSERT_EQ(it.peek(), 1); + ASSERT_EQ(it.next(), 1); + ASSERT_EQ(it.peek(), 2); + ASSERT_EQ(it.next(), 2); +} + +TEST(TestPeekableIterator, TestPeekMultipleTimes) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.peekable(); + + ASSERT_EQ(it.peek(), 1); + ASSERT_EQ(it.peek(), 1); + ASSERT_EQ(it.peek(), 1); + ASSERT_EQ(it.next(), 1); +} + +TEST(TestPeekableIterator, TestCollectPeekedIterator) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.peekable(); + + it.peek(); + + EXPECT_THAT(it.collect(), ElementsAreArray({1, 2, 3})); +} + +TEST(TestPeekableIterator, TestSizeHintOnNotPeeked) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.peekable(); + + ASSERT_EQ(it.sizeHint(), 3); +} + +TEST(TestPeekableIterator, TestSizeHinttPeeked) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.peekable(); + + it.peek(); + + ASSERT_EQ(it.sizeHint(), 3); +} + +TEST(TestPeekableIterator, TestSizeHintOnPeekedInfinite) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.cycle().peekable(); + + it.peek(); + + ASSERT_EQ(it.sizeHint(), std::nullopt); +}