Skip to content

Commit ea3f3e7

Browse files
feat: add filterMap to iterator interface (#90)
1 parent f1977d0 commit ea3f3e7

File tree

6 files changed

+146
-4
lines changed

6 files changed

+146
-4
lines changed

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,23 @@ All of the benchmarks measure the performance in release mode and run similar sc
129129

130130
All of the measurements were taken on MacBook M3 Pro 18GB.
131131

132-
#### Apply filter and map on 10 000 000 ints
132+
#### Filter and map
133+
134+
Apply filter and map on 1'000'000 integers
135+
136+
| **Benchmark** | **Time [ns]** | **CPU [ns]** |
137+
|:------------------------------:|:-------------:|:------------:|
138+
| benchmarkRustyIterFilterAndMap | 559108 | 559065 |
139+
| benchmarkRustyIterFilterMap | 556122 | 556120 |
140+
| benchmarkRangesFilterTransform | 539868 | 539786 |
141+
142+
Apply filter and map on 10'000'000 integers
133143

134144
| **Benchmark** | **Time [ns]** | **CPU [ns]** |
135145
|:------------------------------:|:-------------:|:------------:|
136-
| benchmarkRustyIterFilterMap | 5565001 | 5564975 |
137-
| benchmarkRangesFilterTransform | 5837398 | 5837397 |
146+
| benchmarkRustyIterFilterAndMap | 5644164 | 5643854 |
147+
| benchmarkRustyIterFilterMap | 5599177 | 5599185 |
148+
| benchmarkRangesFilterTransform | 5988982 | 5988701 |
138149

139150
## Authors
140151

benchmarks/iterator.benchmark.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ auto initializeIncrementalVector() -> std::vector<int>
1717
return std::move(data);
1818
}
1919

20-
auto benchmarkRustyIterFilterMap(benchmark::State& state) -> void
20+
auto benchmarkRustyIterFilterAndMap(benchmark::State& state) -> void
2121
{
2222
auto data = initializeIncrementalVector();
2323

@@ -30,6 +30,21 @@ auto benchmarkRustyIterFilterMap(benchmark::State& state) -> void
3030
}
3131
}
3232

33+
auto benchmarkRustyIterFilterMap(benchmark::State& state) -> void
34+
{
35+
auto data = initializeIncrementalVector();
36+
37+
for (auto _ : state)
38+
{
39+
auto f = [](int x) -> std::optional<int> {
40+
if (x % 2 == 0)
41+
return std::make_optional(x * 2);
42+
return std::nullopt;
43+
};
44+
auto result = LazyIterator{data}.filterMap(std::move(f)).collect();
45+
}
46+
}
47+
3348
auto benchmarkRangesFilterTransform(benchmark::State& state) -> void
3449
{
3550
auto data = initializeIncrementalVector();
@@ -64,6 +79,7 @@ auto benchmarkRustyIterCacheCycle(benchmark::State& state) -> void
6479
}
6580
}
6681

82+
BENCHMARK(benchmarkRustyIterFilterAndMap);
6783
BENCHMARK(benchmarkRustyIterFilterMap);
6884
BENCHMARK(benchmarkRangesFilterTransform);
6985
BENCHMARK(benchmarkRustyIterCopyCycle);

include/rusty_iterators/concepts.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <optional>
34
#include <tuple>
45

56
namespace rusty_iterators::concepts
@@ -26,6 +27,11 @@ concept EqFunctor = requires(Functor f, std::tuple<T, T> t) {
2627
template <class T, class Functor>
2728
concept FilterFunctor = AllFunctor<T, Functor>;
2829

30+
template <class Tin, class Tout, class Functor>
31+
concept FilterMapFunctor = requires(Functor f, Tin t) {
32+
{ f(t) } -> std::same_as<std::optional<Tout>>;
33+
};
34+
2935
template <class B, class T, class Functor>
3036
concept FoldFunctor = requires(Functor f, B first, T second) {
3137
{ f(first, second) } -> std::same_as<B>;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#pragma once
2+
3+
#include "concepts.hpp"
4+
#include "interface.fwd.hpp"
5+
6+
#include <optional>
7+
8+
namespace rusty_iterators::iterator
9+
{
10+
using concepts::FilterMapFunctor;
11+
using interface::IterInterface;
12+
13+
template <class Tin, class Tout, class Functor, class Other>
14+
requires FilterMapFunctor<Tin, Tout, Functor>
15+
class FilterMap : public IterInterface<Tout, FilterMap<Tin, Tout, Functor, Other>>
16+
{
17+
public:
18+
explicit FilterMap(Other&& it, Functor&& f)
19+
: it(std::forward<Other>(it)), func(std::forward<Functor>(f))
20+
{}
21+
22+
auto next() -> std::optional<Tout>;
23+
[[nodiscard]] auto sizeHint() const -> std::optional<size_t>;
24+
25+
private:
26+
Other it;
27+
Functor func;
28+
};
29+
} // namespace rusty_iterators::iterator
30+
31+
template <class Tin, class Tout, class Functor, class Other>
32+
requires rusty_iterators::concepts::FilterMapFunctor<Tin, Tout, Functor>
33+
auto rusty_iterators::iterator::FilterMap<Tin, Tout, Functor, Other>::next() -> std::optional<Tout>
34+
{
35+
auto nextItem = it.next();
36+
37+
while (nextItem.has_value())
38+
{
39+
auto result = func(nextItem.value());
40+
41+
if (result.has_value())
42+
{
43+
return std::make_optional(std::move(result.value()));
44+
}
45+
nextItem = it.next();
46+
}
47+
return std::nullopt;
48+
}
49+
50+
template <class Tin, class Tout, class Functor, class Other>
51+
requires rusty_iterators::concepts::FilterMapFunctor<Tin, Tout, Functor>
52+
auto rusty_iterators::iterator::FilterMap<Tin, Tout, Functor, Other>::sizeHint() const
53+
-> std::optional<size_t>
54+
{
55+
return it.sizeHint();
56+
}

include/rusty_iterators/interface.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
#include "concepts.hpp"
55
#include "cycle.hpp"
66
#include "filter.hpp"
7+
#include "filter_map.hpp"
78
#include "inspect.hpp"
89
#include "map.hpp"
910
#include "moving_window.hpp"
1011
#include "take.hpp"
1112
#include "zip.hpp"
1213

1314
#include <stdexcept>
15+
#include <type_traits>
1416
#include <vector>
1517

1618
namespace rusty_iterators::interface
@@ -20,6 +22,7 @@ using concepts::AnyFunctor;
2022
using concepts::Comparable;
2123
using concepts::EqFunctor;
2224
using concepts::FilterFunctor;
25+
using concepts::FilterMapFunctor;
2326
using concepts::FoldFunctor;
2427
using concepts::ForEachFunctor;
2528
using concepts::Indexable;
@@ -33,6 +36,7 @@ using iterator::Chain;
3336
using iterator::CopyCycle;
3437
using iterator::CycleType;
3538
using iterator::Filter;
39+
using iterator::FilterMap;
3640
using iterator::Inspect;
3741
using iterator::Map;
3842
using iterator::MovingWindow;
@@ -88,6 +92,11 @@ class IterInterface
8892
requires FilterFunctor<T, Functor>
8993
[[nodiscard]] auto filter(Functor&& f) -> Filter<T, Functor, Derived>;
9094

95+
template <class Functor>
96+
requires FilterMapFunctor<T, typename std::invoke_result_t<Functor, T>::value_type, Functor>
97+
[[nodiscard]] auto filterMap(Functor&& f)
98+
-> FilterMap<T, typename std::invoke_result_t<Functor, T>::value_type, Functor, Derived>;
99+
91100
template <class B, class Functor>
92101
requires FoldFunctor<B, T, Functor>
93102
[[nodiscard]] auto fold(B&& init, Functor&& f) -> B;
@@ -277,6 +286,17 @@ auto rusty_iterators::interface::IterInterface<T, Derived>::filter(Functor&& f)
277286
return Filter<T, Functor, Derived>{std::forward<Derived>(self()), std::forward<Functor>(f)};
278287
}
279288

289+
template <class T, class Derived>
290+
template <class Functor>
291+
requires rusty_iterators::concepts::FilterMapFunctor<
292+
T, typename std::invoke_result_t<Functor, T>::value_type, Functor>
293+
auto rusty_iterators::interface::IterInterface<T, Derived>::filterMap(Functor&& f)
294+
-> FilterMap<T, typename std::invoke_result_t<Functor, T>::value_type, Functor, Derived>
295+
{
296+
return FilterMap<T, typename std::invoke_result_t<Functor, T>::value_type, Functor, Derived>{
297+
std::forward<Derived>(self()), std::forward<Functor>(f)};
298+
}
299+
280300
template <class T, class Derived>
281301
template <class B, class Functor>
282302
requires rusty_iterators::concepts::FoldFunctor<B, T, Functor>

tests/filter_map.test.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
8+
TEST(TestFilterMapIterator, NextChecksIfValid)
9+
{
10+
auto vec = std::vector{1, 2, 3};
11+
auto f = [](auto x) -> std::optional<float> {
12+
if (x % 2 == 0)
13+
return std::make_optional(static_cast<float>(x));
14+
return std::nullopt;
15+
};
16+
auto it = LazyIterator{vec}.filterMap(std::move(f));
17+
18+
ASSERT_EQ(it.next(), 2.0);
19+
ASSERT_EQ(it.next(), std::nullopt);
20+
}
21+
22+
TEST(TestFilterMapIterator, SizeHintReturnsValueOfUnderlyingIterator)
23+
{
24+
auto vec = std::vector{1, 2, 3};
25+
auto f = [](auto x) -> std::optional<float> {
26+
if (x % 2 == 0)
27+
return std::make_optional(static_cast<float>(x));
28+
return std::nullopt;
29+
};
30+
auto it = LazyIterator{vec}.filterMap(std::move(f));
31+
32+
ASSERT_EQ(it.sizeHint(), 3.0);
33+
}

0 commit comments

Comments
 (0)