From e00e1e124a61b5617d78d2452f5d432b5302a1b2 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Wed, 16 Feb 2022 20:54:33 +0100 Subject: [PATCH] added 3 manipulators (map, zip, filter) and related tests. Updated readme --- README.md | 5 +- inc/fpgen.hpp | 1 + inc/manipulators.hpp | 80 ++++++++++++++++++++++++ test/Makefile | 2 +- test/src/test_manip.cpp | 135 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 inc/manipulators.hpp create mode 100644 test/src/test_manip.cpp diff --git a/README.md b/README.md index d5f44b4..797dfe0 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,12 @@ Currently supported features: - Create generators from `std::` containers with a single type argument, with and without indexing. - Create generators from `std::` containers with two type arguments. - Create generators from incrementable types (using `operator++(void)`). - -Planned/In progress features: - Commonly used manipulators: - Lazy `map`ping over generators. - Lazy `zip`ping of generators. + - Lazy `filter`ing of generators. + +Planned/In progress features: - Commonly used aggregators: - Lazy `fold`ing of generators. - Lazy `sum`ming of generators. diff --git a/inc/fpgen.hpp b/inc/fpgen.hpp index 19e99cc..2aa2d20 100644 --- a/inc/fpgen.hpp +++ b/inc/fpgen.hpp @@ -2,6 +2,7 @@ #define _FPGEN_MAIN #include "generator.hpp" +#include "manipulators.hpp" #include "sources.hpp" #endif diff --git a/inc/manipulators.hpp b/inc/manipulators.hpp new file mode 100644 index 0000000..7f095c0 --- /dev/null +++ b/inc/manipulators.hpp @@ -0,0 +1,80 @@ +#ifndef _FPGEN_MANIP +#define _FPGEN_MANIP + +#include "generator.hpp" +#include +#include + +/** + * \brief The namespace containing all of fpgen's code. + */ +namespace fpgen { +/** + * \brief Maps a function over a generator. + * + * Creates a new generator whose values are the transformed values generated by + * applying the given mapping function on each value in the original generator. + * Using the provided generator after calling fpgen::map is undefined behaviour. + * + * \tparam TIn The type contained in the provided generator. + * \tparam Fun The function signature of the mapping function. + * \param[in,out] gen The generator to map over. Will be in unusable state + * afterwards. + * \param[in] func The function to map with. + * \returns A new generator whose contained type is the return type of the + * mapping function. + */ +template +auto map(generator &gen, Fun func) + -> generator::type> { + while (gen) { + co_yield func(gen()); + } +} + +/** + * \brief Combines two generators into a single generator. + * + * The result is a tuple of values, one taken from each generator. Once one of + * the generators runs out of values, the newly generator stops as well. Using + * either generator after calling this function is undefined behaviour. + * + * \tparam T1 The type contained in the first generator. + * \tparam T2 The type contained in the second generator. + * \param[in, out] gen1 The first generator to use. + * \param[in, out] gen2 The second generator to use. + * \returns A new generator containing tuples of values from both generators. + */ +template +generator> zip(generator &gen1, generator &gen2) { + while (gen1 && gen2) { + co_yield {gen1(), gen2()}; + } +} + +/** + * \brief Filters a generator, keeping only values matching a predicate. + * + * The result is a generator with as much or possibly fewer elements. Each + * element is an element in the original generator. Elements not matching the + * predicate are removed. + * + * \tparam T The type contained in the generator. + * \tparam Pred The function type of the predicate function. Should return a + * bool. + * \param gen The generator containing the original values. + * \param p The predicate. + * \returns A new generator which yields all values in the original generator + * except those not matching the predicate. + */ +template +generator filter(generator &gen, Pred p) { + while (gen) { + T val(gen()); + if (p(val)) + co_yield val; + } +} +} // namespace fpgen + +#endif diff --git a/test/Makefile b/test/Makefile index 010828e..348dd20 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,6 +1,6 @@ SOURCES=$(shell find $(SRCD) -name '*.cpp') DEPS=$(SOURCES:$(SRCD)/%.cpp=$(OBJD)/%.d) -TESTS=generator sources +TESTS=generator sources manip TESTOBJ=$(TESTS:%=$(OBJD)/test_%.o) CONAN_PKG_OVERRIDE=gtest diff --git a/test/src/test_manip.cpp b/test/src/test_manip.cpp new file mode 100644 index 0000000..aeb1521 --- /dev/null +++ b/test/src/test_manip.cpp @@ -0,0 +1,135 @@ +#include "generator.hpp" +#include "manipulators.hpp" +#include "sources.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include + +fpgen::generator manip_empty() { co_return 0; } + +fpgen::generator manip() { + size_t i = 1; + while (i < 1024) { + co_yield i; + i *= 2; + } + co_return i; +} + +fpgen::generator until12() { + for (int i = 0; i < 12; i++) { + co_yield i; + } + co_return 12; +} + +size_t mapper(size_t v) { return v * v; } +bool is_even(size_t v) { return (v % 2 == 0); } +bool over_100(size_t v) { return (v > 100); } + +TEST(manipulators, map_empty) { + auto gen = manip_empty(); + gen(); + + for (auto v : fpgen::map(gen, mapper)) { + FAIL() << "Should not return a value"; + } + SUCCEED(); +} + +TEST(manipulators, map) { + auto gen = manip(); + size_t i = 1; + for (auto v : fpgen::map(gen, mapper)) { + EXPECT_EQ(i * i, v); + EXPECT_TRUE(i <= 1024); + i *= 2; + } +} + +TEST(manipulators, zip_both_empty) { + auto gen = manip_empty(); + auto gen2 = manip_empty(); + gen(); + gen2(); + + for (auto v : fpgen::zip(gen, gen2)) { + FAIL() << "Should not return a value"; + } + SUCCEED(); +} + +TEST(manipulators, zip_first_empty) { + auto gen = manip_empty(); + auto gen2 = fpgen::inc((size_t)0); + gen(); + + for (auto v : fpgen::zip(gen, gen2)) { + FAIL() << "Should not return a value"; + } + SUCCEED(); +} + +TEST(manipulators, zip_second_empty) { + auto gen = fpgen::inc((size_t)0); + auto gen2 = manip_empty(); + gen2(); + + for (auto v : fpgen::zip(gen, gen2)) { + FAIL() << "Should not return a value"; + } + SUCCEED(); +} + +TEST(manipulators, zip_none_empty) { + auto gen = fpgen::inc((size_t)0); + auto gen2 = manip(); + + size_t i = 0; + size_t j = 1; + + for (auto v : fpgen::zip(gen, gen2)) { + EXPECT_EQ(std::get<0>(v), i); + EXPECT_EQ(std::get<1>(v), j); + EXPECT_TRUE(j <= 1024); + EXPECT_TRUE(i <= 10); + + i++; + j *= 2; + } +} + +TEST(manipulators, filter_empty) { + auto gen = manip_empty(); + gen(); + + size_t i = 0; + + for (auto v : fpgen::filter(gen, is_even)) { + FAIL(); + } + SUCCEED(); +} + +TEST(manipulators, filter_to_empty) { + auto gen = until12(); + + for (auto v : fpgen::filter(gen, over_100)) { + FAIL(); + } + SUCCEED(); +} + +TEST(manipulators, filter_normal) { + auto gen = until12(); + size_t i = 0; + for (auto v : fpgen::filter(gen, is_even)) { + EXPECT_EQ(v, i); + EXPECT_TRUE(i <= 12); + i += 2; + } +}