Skip to content

Commit 2738276

Browse files
feat: add stepBy to iterator interface
1 parent f52c06e commit 2738276

File tree

3 files changed

+148
-0
lines changed

3 files changed

+148
-0
lines changed

include/rusty_iterators/interface.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "map.hpp"
1111
#include "moving_window.hpp"
1212
#include "skip.hpp"
13+
#include "step_by.hpp"
1314
#include "take.hpp"
1415
#include "zip.hpp"
1516

@@ -47,6 +48,7 @@ using iterator::Inspect;
4748
using iterator::Map;
4849
using iterator::MovingWindow;
4950
using iterator::Skip;
51+
using iterator::StepBy;
5052
using iterator::Take;
5153
using iterator::Zip;
5254

@@ -152,6 +154,7 @@ class IterInterface
152154
[[nodiscard]] auto reduce(Functor&& f) -> std::optional<T>;
153155

154156
[[nodiscard]] auto skip(size_t n) -> Skip<T, Derived>;
157+
[[nodiscard]] auto stepBy(size_t step) -> StepBy<T, Derived>;
155158

156159
template <class R = T>
157160
requires Summable<R>
@@ -452,6 +455,13 @@ auto rusty_iterators::interface::IterInterface<T, Derived>::skip(size_t n) -> Sk
452455
return Skip<T, Derived>{std::forward<Derived>(self()), n};
453456
}
454457

458+
template <class T, class Derived>
459+
auto rusty_iterators::interface::IterInterface<T, Derived>::stepBy(size_t step)
460+
-> StepBy<T, Derived>
461+
{
462+
return StepBy<T, Derived>{std::forward<Derived>(self()), step};
463+
}
464+
455465
template <class T, class Derived>
456466
template <class R>
457467
requires rusty_iterators::concepts::Summable<R>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#pragma once
2+
3+
#include "interface.fwd.hpp"
4+
5+
#include <optional>
6+
#include <stdexcept>
7+
8+
namespace rusty_iterators::iterator
9+
{
10+
using interface::IterInterface;
11+
12+
template <class T, class Other>
13+
class StepBy : public IterInterface<T, StepBy<T, Other>>
14+
{
15+
public:
16+
explicit StepBy(Other&& it, size_t step) : it(std::forward<Other>(it))
17+
{
18+
if (step == 0)
19+
throw std::length_error{"Step has to be greater than zero."};
20+
21+
stepMinusOne = step - 1;
22+
}
23+
24+
auto next() -> std::optional<T>;
25+
[[nodiscard]] auto sizeHint() const -> std::optional<size_t>;
26+
27+
private:
28+
Other it;
29+
size_t stepMinusOne;
30+
bool firstTake = true;
31+
32+
[[nodiscard]] inline auto originalStep() const -> size_t { return stepMinusOne + 1; }
33+
34+
[[nodiscard]] inline auto firstSize(size_t underlyingSize) const -> size_t
35+
{
36+
auto step = originalStep();
37+
return underlyingSize == 0 ? 0 : 1 + (underlyingSize - 1) / step;
38+
}
39+
40+
[[nodiscard]] inline auto otherSize(size_t underlyingSize) const -> size_t
41+
{
42+
auto step = originalStep();
43+
return underlyingSize / step;
44+
}
45+
};
46+
} // namespace rusty_iterators::iterator
47+
48+
template <class T, class Other>
49+
auto rusty_iterators::iterator::StepBy<T, Other>::next() -> std::optional<T>
50+
{
51+
auto stepSize = firstTake ? 0 : stepMinusOne;
52+
firstTake = false;
53+
54+
return std::move(it.nth(stepSize));
55+
}
56+
57+
template <class T, class Other>
58+
auto rusty_iterators::iterator::StepBy<T, Other>::sizeHint() const -> std::optional<size_t>
59+
{
60+
auto underlyingSize = it.sizeHint();
61+
62+
if (!underlyingSize.has_value())
63+
return std::nullopt;
64+
65+
if (firstTake)
66+
return firstSize(underlyingSize.value());
67+
68+
return otherSize(underlyingSize.value());
69+
}

tests/step_by.test.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#include <gmock/gmock.h>
2+
#include <gtest/gtest.h>
3+
4+
#include <rusty_iterators/iterator.hpp>
5+
#include <stdexcept>
6+
7+
using ::rusty_iterators::iterator::LazyIterator;
8+
using ::testing::ElementsAreArray;
9+
10+
TEST(TestStepByIterator, ThrowWhenSizeIsZero)
11+
{
12+
auto vec = std::vector{1, 2, 3};
13+
14+
EXPECT_THROW(auto _ = LazyIterator{vec}.stepBy(0), std::length_error);
15+
}
16+
17+
TEST(TestStepByIterator, NextReturnsFirstElement)
18+
{
19+
auto vec = std::vector{1, 2, 3, 4, 5};
20+
auto it = LazyIterator{vec}.stepBy(2);
21+
22+
ASSERT_EQ(it.next(), 1);
23+
ASSERT_EQ(it.next(), 3);
24+
ASSERT_EQ(it.next(), 5);
25+
ASSERT_EQ(it.next(), std::nullopt);
26+
}
27+
28+
TEST(TestStepByIterator, NextWhenStepLargerThanIterator)
29+
{
30+
auto vec = std::vector{1, 2, 3};
31+
auto it = LazyIterator{vec}.stepBy(10);
32+
33+
ASSERT_EQ(it.next(), 1);
34+
ASSERT_EQ(it.next(), std::nullopt);
35+
}
36+
37+
TEST(TestStepByIterator, TestCollectIterator)
38+
{
39+
auto vec = std::vector{1, 2, 3, 4, 5};
40+
auto it = LazyIterator{vec}.stepBy(2);
41+
42+
EXPECT_THAT(it.collect(), ElementsAreArray({1, 3, 5}));
43+
}
44+
45+
TEST(TestStepByIterator, TestSizeHintWhenUnderlyingInfinite)
46+
{
47+
auto vec = std::vector{1, 2};
48+
auto it = LazyIterator{vec}.cycle().stepBy(3);
49+
50+
ASSERT_EQ(it.sizeHint(), std::nullopt);
51+
}
52+
53+
TEST(TestStepByIterator, TestSizeHintFirstTake)
54+
{
55+
auto vec = std::vector{1, 2, 3, 4, 5};
56+
auto it = LazyIterator{vec}.stepBy(2);
57+
58+
ASSERT_EQ(it.sizeHint(), 3);
59+
}
60+
61+
TEST(TestStepByIterator, TestSizeHintOtherTake)
62+
{
63+
auto vec = std::vector{1, 2, 3, 4, 5};
64+
auto it = LazyIterator{vec}.stepBy(2);
65+
66+
it.next();
67+
68+
ASSERT_EQ(it.sizeHint(), 2);
69+
}

0 commit comments

Comments
 (0)