Skip to content

Commit 3554463

Browse files
feat: add flatten to iterator interface (#112)
1 parent f415445 commit 3554463

File tree

4 files changed

+163
-2
lines changed

4 files changed

+163
-2
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#pragma once
2+
3+
#include "concepts.hpp"
4+
#include "interface.fwd.hpp"
5+
#include "peekable.hpp"
6+
7+
#include <optional>
8+
9+
namespace rusty_iterators::iterator
10+
{
11+
using concepts::Indexable;
12+
using interface::IterInterface;
13+
14+
template <class T, class Other>
15+
requires Indexable<T>
16+
class Flatten : public IterInterface<typename T::value_type, Flatten<T, Other>>
17+
{
18+
public:
19+
explicit Flatten(Other&& it) : it(std::forward<Peekable<T, Other>>(it.peekable())) {}
20+
21+
auto next() -> std::optional<typename T::value_type>;
22+
[[nodiscard]] auto sizeHint() -> std::optional<size_t>;
23+
24+
private:
25+
Peekable<T, Other> it;
26+
size_t ptr = 0;
27+
std::optional<T> cache = std::nullopt;
28+
};
29+
} // namespace rusty_iterators::iterator
30+
31+
template <class T, class Other>
32+
requires rusty_iterators::concepts::Indexable<T>
33+
auto rusty_iterators::iterator::Flatten<T, Other>::next() -> std::optional<typename T::value_type>
34+
{
35+
if (cache.has_value() && ptr < cache.value().size())
36+
{
37+
auto item = cache.value().at(ptr);
38+
ptr += 1;
39+
return std::move(item);
40+
}
41+
auto indexableItem = it.next();
42+
43+
if (!indexableItem.has_value())
44+
return std::nullopt;
45+
46+
ptr = 0;
47+
cache = std::move(indexableItem);
48+
49+
return next();
50+
}
51+
52+
template <class T, class Other>
53+
requires rusty_iterators::concepts::Indexable<T>
54+
auto rusty_iterators::iterator::Flatten<T, Other>::sizeHint() -> std::optional<size_t>
55+
{
56+
auto unfoldedSize = it.sizeHint();
57+
58+
if (!unfoldedSize.has_value())
59+
return std::move(unfoldedSize);
60+
61+
auto peekedItem = it.peek();
62+
63+
if (!peekedItem.has_value())
64+
return 0;
65+
66+
return unfoldedSize.value() * peekedItem.value().size();
67+
}

include/rusty_iterators/interface.hpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "enumerate.hpp"
77
#include "filter.hpp"
88
#include "filter_map.hpp"
9+
#include "flatten.hpp"
910
#include "inspect.hpp"
1011
#include "interperse.hpp"
1112
#include "map.hpp"
@@ -47,6 +48,7 @@ using iterator::CycleType;
4748
using iterator::Enumerate;
4849
using iterator::Filter;
4950
using iterator::FilterMap;
51+
using iterator::Flatten;
5052
using iterator::Inspect;
5153
using iterator::Interperse;
5254
using iterator::Map;
@@ -113,6 +115,10 @@ class IterInterface
113115
[[nodiscard]] auto filterMap(Functor&& f)
114116
-> FilterMap<T, typename std::invoke_result_t<Functor, T>::value_type, Functor, Derived>;
115117

118+
template <class R = T>
119+
requires Indexable<R>
120+
[[nodiscard]] auto flatten() -> Flatten<R, Derived>;
121+
116122
template <class B, class Functor>
117123
requires FoldFunctor<B, T, Functor>
118124
[[nodiscard]] auto fold(B&& init, Functor&& f) -> B;
@@ -241,7 +247,7 @@ auto rusty_iterators::interface::IterInterface<T, Derived>::collect() -> std::ve
241247
auto size = sizeHintChecked();
242248

243249
collection.reserve(size);
244-
self().forEach([&collection](auto&& x) { collection.push_back(std::move(x)); });
250+
self().forEach([&collection](auto x) { collection.push_back(x); });
245251

246252
return std::move(collection);
247253
}
@@ -322,6 +328,14 @@ auto rusty_iterators::interface::IterInterface<T, Derived>::filterMap(Functor&&
322328
std::forward<Derived>(self()), std::forward<Functor>(f)};
323329
}
324330

331+
template <class T, class Derived>
332+
template <class R>
333+
requires rusty_iterators::concepts::Indexable<R>
334+
auto rusty_iterators::interface::IterInterface<T, Derived>::flatten() -> Flatten<R, Derived>
335+
{
336+
return Flatten<R, Derived>{std::forward<Derived>(self())};
337+
}
338+
325339
template <class T, class Derived>
326340
template <class B, class Functor>
327341
requires rusty_iterators::concepts::FoldFunctor<B, T, Functor>
@@ -353,7 +367,7 @@ auto rusty_iterators::interface::IterInterface<T, Derived>::forEach(Functor&& f)
353367

354368
[[likely]] while (nextItem.has_value())
355369
{
356-
func(std::move(nextItem.value()));
370+
func(nextItem.value());
357371
nextItem = self().next();
358372
}
359373
}

include/rusty_iterators/iterator.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ using Item = std::reference_wrapper<const typename Container::value_type>;
1616
namespace rusty_iterators::iterator
1717
{
1818
using concepts::FoldFunctor;
19+
using concepts::Indexable;
1920
using concepts::Multiplyable;
2021
using concepts::Summable;
2122

@@ -30,6 +31,10 @@ class LazyIterator : public interface::IterInterface<Item<Container>, LazyIterat
3031
public:
3132
explicit LazyIterator(Container& it) : ptr(it.begin()), end(it.end()) {}
3233

34+
template <class R = RawT>
35+
requires Indexable<R>
36+
[[nodiscard]] auto flatten() -> decltype(auto);
37+
3338
auto next() -> std::optional<T>;
3439
[[nodiscard]] auto sizeHint() const -> std::optional<size_t>;
3540

@@ -47,6 +52,15 @@ class LazyIterator : public interface::IterInterface<Item<Container>, LazyIterat
4752
};
4853
} // namespace rusty_iterators::iterator
4954

55+
template <class Container>
56+
requires std::ranges::range<Container>
57+
template <class R>
58+
requires rusty_iterators::concepts::Indexable<R>
59+
auto rusty_iterators::iterator::LazyIterator<Container>::flatten() -> decltype(auto)
60+
{
61+
return this->map([](auto x) { return x.get(); }).flatten();
62+
}
63+
5064
template <class Container>
5165
requires std::ranges::range<Container>
5266
auto rusty_iterators::iterator::LazyIterator<Container>::next() -> std::optional<T>

tests/flatten.test.cpp

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

0 commit comments

Comments
 (0)