diff --git a/include/rusty_iterators/interface.hpp b/include/rusty_iterators/interface.hpp index 81adc8d..d8f3c79 100644 --- a/include/rusty_iterators/interface.hpp +++ b/include/rusty_iterators/interface.hpp @@ -10,6 +10,7 @@ #include "map.hpp" #include "moving_window.hpp" #include "skip.hpp" +#include "step_by.hpp" #include "take.hpp" #include "zip.hpp" @@ -47,6 +48,7 @@ using iterator::Inspect; using iterator::Map; using iterator::MovingWindow; using iterator::Skip; +using iterator::StepBy; using iterator::Take; using iterator::Zip; @@ -152,6 +154,7 @@ class IterInterface [[nodiscard]] auto reduce(Functor&& f) -> std::optional; [[nodiscard]] auto skip(size_t n) -> Skip; + [[nodiscard]] auto stepBy(size_t step) -> StepBy; template requires Summable @@ -452,6 +455,13 @@ auto rusty_iterators::interface::IterInterface::skip(size_t n) -> Sk return Skip{std::forward(self()), n}; } +template +auto rusty_iterators::interface::IterInterface::stepBy(size_t step) + -> StepBy +{ + return StepBy{std::forward(self()), step}; +} + template template requires rusty_iterators::concepts::Summable diff --git a/include/rusty_iterators/step_by.hpp b/include/rusty_iterators/step_by.hpp new file mode 100644 index 0000000..479bc58 --- /dev/null +++ b/include/rusty_iterators/step_by.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "interface.fwd.hpp" + +#include +#include + +namespace rusty_iterators::iterator +{ +using interface::IterInterface; + +template +class StepBy : public IterInterface> +{ + public: + explicit StepBy(Other&& it, size_t step) : it(std::forward(it)) + { + if (step == 0) + throw std::length_error{"Step has to be greater than zero."}; + + stepMinusOne = step - 1; + } + + auto next() -> std::optional; + [[nodiscard]] auto sizeHint() const -> std::optional; + + private: + Other it; + size_t stepMinusOne; + bool firstTake = true; + + [[nodiscard]] inline auto originalStep() const -> size_t { return stepMinusOne + 1; } + + [[nodiscard]] inline auto firstSize(size_t underlyingSize) const -> size_t + { + auto step = originalStep(); + return underlyingSize == 0 ? 0 : 1 + (underlyingSize - 1) / step; + } + + [[nodiscard]] inline auto otherSize(size_t underlyingSize) const -> size_t + { + auto step = originalStep(); + return underlyingSize / step; + } +}; +} // namespace rusty_iterators::iterator + +template +auto rusty_iterators::iterator::StepBy::next() -> std::optional +{ + auto stepSize = firstTake ? 0 : stepMinusOne; + firstTake = false; + + return std::move(it.nth(stepSize)); +} + +template +auto rusty_iterators::iterator::StepBy::sizeHint() const -> std::optional +{ + auto underlyingSize = it.sizeHint(); + + if (!underlyingSize.has_value()) + return std::nullopt; + + if (firstTake) + return firstSize(underlyingSize.value()); + + return otherSize(underlyingSize.value()); +} diff --git a/tests/step_by.test.cpp b/tests/step_by.test.cpp new file mode 100644 index 0000000..8c50b04 --- /dev/null +++ b/tests/step_by.test.cpp @@ -0,0 +1,69 @@ +#include +#include + +#include +#include + +using ::rusty_iterators::iterator::LazyIterator; +using ::testing::ElementsAreArray; + +TEST(TestStepByIterator, ThrowWhenSizeIsZero) +{ + auto vec = std::vector{1, 2, 3}; + + EXPECT_THROW(auto _ = LazyIterator{vec}.stepBy(0), std::length_error); +} + +TEST(TestStepByIterator, NextReturnsFirstElement) +{ + auto vec = std::vector{1, 2, 3, 4, 5}; + auto it = LazyIterator{vec}.stepBy(2); + + ASSERT_EQ(it.next(), 1); + ASSERT_EQ(it.next(), 3); + ASSERT_EQ(it.next(), 5); + ASSERT_EQ(it.next(), std::nullopt); +} + +TEST(TestStepByIterator, NextWhenStepLargerThanIterator) +{ + auto vec = std::vector{1, 2, 3}; + auto it = LazyIterator{vec}.stepBy(10); + + ASSERT_EQ(it.next(), 1); + ASSERT_EQ(it.next(), std::nullopt); +} + +TEST(TestStepByIterator, TestCollectIterator) +{ + auto vec = std::vector{1, 2, 3, 4, 5}; + auto it = LazyIterator{vec}.stepBy(2); + + EXPECT_THAT(it.collect(), ElementsAreArray({1, 3, 5})); +} + +TEST(TestStepByIterator, TestSizeHintWhenUnderlyingInfinite) +{ + auto vec = std::vector{1, 2}; + auto it = LazyIterator{vec}.cycle().stepBy(3); + + ASSERT_EQ(it.sizeHint(), std::nullopt); +} + +TEST(TestStepByIterator, TestSizeHintFirstTake) +{ + auto vec = std::vector{1, 2, 3, 4, 5}; + auto it = LazyIterator{vec}.stepBy(2); + + ASSERT_EQ(it.sizeHint(), 3); +} + +TEST(TestStepByIterator, TestSizeHintOtherTake) +{ + auto vec = std::vector{1, 2, 3, 4, 5}; + auto it = LazyIterator{vec}.stepBy(2); + + it.next(); + + ASSERT_EQ(it.sizeHint(), 2); +}