diff --git a/README.md b/README.md index 499acd2..a404a40 100644 --- a/README.md +++ b/README.md @@ -129,12 +129,23 @@ All of the benchmarks measure the performance in release mode and run similar sc All of the measurements were taken on MacBook M3 Pro 18GB. -#### Apply filter and map on 10 000 000 ints +#### Filter and map + +Apply filter and map on 1'000'000 integers + +| **Benchmark** | **Time [ns]** | **CPU [ns]** | +|:------------------------------:|:-------------:|:------------:| +| benchmarkRustyIterFilterAndMap | 559108 | 559065 | +| benchmarkRustyIterFilterMap | 556122 | 556120 | +| benchmarkRangesFilterTransform | 539868 | 539786 | + +Apply filter and map on 10'000'000 integers | **Benchmark** | **Time [ns]** | **CPU [ns]** | |:------------------------------:|:-------------:|:------------:| -| benchmarkRustyIterFilterMap | 5565001 | 5564975 | -| benchmarkRangesFilterTransform | 5837398 | 5837397 | +| benchmarkRustyIterFilterAndMap | 5644164 | 5643854 | +| benchmarkRustyIterFilterMap | 5599177 | 5599185 | +| benchmarkRangesFilterTransform | 5988982 | 5988701 | ## Authors diff --git a/benchmarks/iterator.benchmark.cpp b/benchmarks/iterator.benchmark.cpp index 2f3edb8..825e949 100644 --- a/benchmarks/iterator.benchmark.cpp +++ b/benchmarks/iterator.benchmark.cpp @@ -17,7 +17,7 @@ auto initializeIncrementalVector() -> std::vector return std::move(data); } -auto benchmarkRustyIterFilterMap(benchmark::State& state) -> void +auto benchmarkRustyIterFilterAndMap(benchmark::State& state) -> void { auto data = initializeIncrementalVector(); @@ -30,6 +30,21 @@ auto benchmarkRustyIterFilterMap(benchmark::State& state) -> void } } +auto benchmarkRustyIterFilterMap(benchmark::State& state) -> void +{ + auto data = initializeIncrementalVector(); + + for (auto _ : state) + { + auto f = [](int x) -> std::optional { + if (x % 2 == 0) + return std::make_optional(x * 2); + return std::nullopt; + }; + auto result = LazyIterator{data}.filterMap(std::move(f)).collect(); + } +} + auto benchmarkRangesFilterTransform(benchmark::State& state) -> void { auto data = initializeIncrementalVector(); @@ -64,6 +79,7 @@ auto benchmarkRustyIterCacheCycle(benchmark::State& state) -> void } } +BENCHMARK(benchmarkRustyIterFilterAndMap); BENCHMARK(benchmarkRustyIterFilterMap); BENCHMARK(benchmarkRangesFilterTransform); BENCHMARK(benchmarkRustyIterCopyCycle); diff --git a/include/rusty_iterators/concepts.hpp b/include/rusty_iterators/concepts.hpp index 2a58897..5f8a7a8 100644 --- a/include/rusty_iterators/concepts.hpp +++ b/include/rusty_iterators/concepts.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include namespace rusty_iterators::concepts @@ -26,6 +27,11 @@ concept EqFunctor = requires(Functor f, std::tuple t) { template concept FilterFunctor = AllFunctor; +template +concept FilterMapFunctor = requires(Functor f, Tin t) { + { f(t) } -> std::same_as>; +}; + template concept FoldFunctor = requires(Functor f, B first, T second) { { f(first, second) } -> std::same_as; diff --git a/include/rusty_iterators/filter_map.hpp b/include/rusty_iterators/filter_map.hpp new file mode 100644 index 0000000..fc3b28f --- /dev/null +++ b/include/rusty_iterators/filter_map.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "concepts.hpp" +#include "interface.fwd.hpp" + +#include + +namespace rusty_iterators::iterator +{ +using concepts::FilterMapFunctor; +using interface::IterInterface; + +template + requires FilterMapFunctor +class FilterMap : public IterInterface> +{ + public: + explicit FilterMap(Other&& it, Functor&& f) + : it(std::forward(it)), func(std::forward(f)) + {} + + auto next() -> std::optional; + [[nodiscard]] auto sizeHint() const -> std::optional; + + private: + Other it; + Functor func; +}; +} // namespace rusty_iterators::iterator + +template + requires rusty_iterators::concepts::FilterMapFunctor +auto rusty_iterators::iterator::FilterMap::next() -> std::optional +{ + auto nextItem = it.next(); + + while (nextItem.has_value()) + { + auto result = func(nextItem.value()); + + if (result.has_value()) + { + return std::make_optional(std::move(result.value())); + } + nextItem = it.next(); + } + return std::nullopt; +} + +template + requires rusty_iterators::concepts::FilterMapFunctor +auto rusty_iterators::iterator::FilterMap::sizeHint() const + -> std::optional +{ + return it.sizeHint(); +} diff --git a/include/rusty_iterators/interface.hpp b/include/rusty_iterators/interface.hpp index 7254ee7..aabba69 100644 --- a/include/rusty_iterators/interface.hpp +++ b/include/rusty_iterators/interface.hpp @@ -4,6 +4,7 @@ #include "concepts.hpp" #include "cycle.hpp" #include "filter.hpp" +#include "filter_map.hpp" #include "inspect.hpp" #include "map.hpp" #include "moving_window.hpp" @@ -11,6 +12,7 @@ #include "zip.hpp" #include +#include #include namespace rusty_iterators::interface @@ -20,6 +22,7 @@ using concepts::AnyFunctor; using concepts::Comparable; using concepts::EqFunctor; using concepts::FilterFunctor; +using concepts::FilterMapFunctor; using concepts::FoldFunctor; using concepts::ForEachFunctor; using concepts::Indexable; @@ -33,6 +36,7 @@ using iterator::Chain; using iterator::CopyCycle; using iterator::CycleType; using iterator::Filter; +using iterator::FilterMap; using iterator::Inspect; using iterator::Map; using iterator::MovingWindow; @@ -88,6 +92,11 @@ class IterInterface requires FilterFunctor [[nodiscard]] auto filter(Functor&& f) -> Filter; + template + requires FilterMapFunctor::value_type, Functor> + [[nodiscard]] auto filterMap(Functor&& f) + -> FilterMap::value_type, Functor, Derived>; + template requires FoldFunctor [[nodiscard]] auto fold(B&& init, Functor&& f) -> B; @@ -277,6 +286,17 @@ auto rusty_iterators::interface::IterInterface::filter(Functor&& f) return Filter{std::forward(self()), std::forward(f)}; } +template +template + requires rusty_iterators::concepts::FilterMapFunctor< + T, typename std::invoke_result_t::value_type, Functor> +auto rusty_iterators::interface::IterInterface::filterMap(Functor&& f) + -> FilterMap::value_type, Functor, Derived> +{ + return FilterMap::value_type, Functor, Derived>{ + std::forward(self()), std::forward(f)}; +} + template template requires rusty_iterators::concepts::FoldFunctor diff --git a/tests/filter_map.test.cpp b/tests/filter_map.test.cpp new file mode 100644 index 0000000..f00d2c9 --- /dev/null +++ b/tests/filter_map.test.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include + +using ::rusty_iterators::iterator::LazyIterator; + +TEST(TestFilterMapIterator, NextChecksIfValid) +{ + auto vec = std::vector{1, 2, 3}; + auto f = [](auto x) -> std::optional { + if (x % 2 == 0) + return std::make_optional(static_cast(x)); + return std::nullopt; + }; + auto it = LazyIterator{vec}.filterMap(std::move(f)); + + ASSERT_EQ(it.next(), 2.0); + ASSERT_EQ(it.next(), std::nullopt); +} + +TEST(TestFilterMapIterator, SizeHintReturnsValueOfUnderlyingIterator) +{ + auto vec = std::vector{1, 2, 3}; + auto f = [](auto x) -> std::optional { + if (x % 2 == 0) + return std::make_optional(static_cast(x)); + return std::nullopt; + }; + auto it = LazyIterator{vec}.filterMap(std::move(f)); + + ASSERT_EQ(it.sizeHint(), 3.0); +}