Skip to content

Commit c5314c5

Browse files
authored
Merge pull request #7 from jay-tux/feat/manipulators
Manipulators
2 parents fe02a62 + e00e1e1 commit c5314c5

File tree

5 files changed

+220
-3
lines changed

5 files changed

+220
-3
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ Currently supported features:
1313
- Create generators from `std::` containers with a single type argument, with and without indexing.
1414
- Create generators from `std::` containers with two type arguments.
1515
- Create generators from incrementable types (using `operator++(void)`).
16-
17-
Planned/In progress features:
1816
- Commonly used manipulators:
1917
- Lazy `map`ping over generators.
2018
- Lazy `zip`ping of generators.
19+
- Lazy `filter`ing of generators.
20+
21+
Planned/In progress features:
2122
- Commonly used aggregators:
2223
- Lazy `fold`ing of generators.
2324
- Lazy `sum`ming of generators.

inc/fpgen.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define _FPGEN_MAIN
33

44
#include "generator.hpp"
5+
#include "manipulators.hpp"
56
#include "sources.hpp"
67

78
#endif

inc/manipulators.hpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#ifndef _FPGEN_MANIP
2+
#define _FPGEN_MANIP
3+
4+
#include "generator.hpp"
5+
#include <array>
6+
#include <type_traits>
7+
8+
/**
9+
* \brief The namespace containing all of fpgen's code.
10+
*/
11+
namespace fpgen {
12+
/**
13+
* \brief Maps a function over a generator.
14+
*
15+
* Creates a new generator whose values are the transformed values generated by
16+
* applying the given mapping function on each value in the original generator.
17+
* Using the provided generator after calling fpgen::map is undefined behaviour.
18+
*
19+
* \tparam TIn The type contained in the provided generator.
20+
* \tparam Fun The function signature of the mapping function.
21+
* \param[in,out] gen The generator to map over. Will be in unusable state
22+
* afterwards.
23+
* \param[in] func The function to map with.
24+
* \returns A new generator whose contained type is the return type of the
25+
* mapping function.
26+
*/
27+
template <typename TIn, typename Fun>
28+
auto map(generator<TIn> &gen, Fun func)
29+
-> generator<typename std::invoke_result<Fun, TIn>::type> {
30+
while (gen) {
31+
co_yield func(gen());
32+
}
33+
}
34+
35+
/**
36+
* \brief Combines two generators into a single generator.
37+
*
38+
* The result is a tuple of values, one taken from each generator. Once one of
39+
* the generators runs out of values, the newly generator stops as well. Using
40+
* either generator after calling this function is undefined behaviour.
41+
*
42+
* \tparam T1 The type contained in the first generator.
43+
* \tparam T2 The type contained in the second generator.
44+
* \param[in, out] gen1 The first generator to use.
45+
* \param[in, out] gen2 The second generator to use.
46+
* \returns A new generator containing tuples of values from both generators.
47+
*/
48+
template <typename T1, typename T2>
49+
generator<std::tuple<T1, T2>> zip(generator<T1> &gen1, generator<T2> &gen2) {
50+
while (gen1 && gen2) {
51+
co_yield {gen1(), gen2()};
52+
}
53+
}
54+
55+
/**
56+
* \brief Filters a generator, keeping only values matching a predicate.
57+
*
58+
* The result is a generator with as much or possibly fewer elements. Each
59+
* element is an element in the original generator. Elements not matching the
60+
* predicate are removed.
61+
*
62+
* \tparam T The type contained in the generator.
63+
* \tparam Pred The function type of the predicate function. Should return a
64+
* bool.
65+
* \param gen The generator containing the original values.
66+
* \param p The predicate.
67+
* \returns A new generator which yields all values in the original generator
68+
* except those not matching the predicate.
69+
*/
70+
template <typename T, typename Pred>
71+
generator<T> filter(generator<T> &gen, Pred p) {
72+
while (gen) {
73+
T val(gen());
74+
if (p(val))
75+
co_yield val;
76+
}
77+
}
78+
} // namespace fpgen
79+
80+
#endif

test/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
SOURCES=$(shell find $(SRCD) -name '*.cpp')
22
DEPS=$(SOURCES:$(SRCD)/%.cpp=$(OBJD)/%.d)
3-
TESTS=generator sources
3+
TESTS=generator sources manip
44
TESTOBJ=$(TESTS:%=$(OBJD)/test_%.o)
55

66
CONAN_PKG_OVERRIDE=gtest

test/src/test_manip.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#include "generator.hpp"
2+
#include "manipulators.hpp"
3+
#include "sources.hpp"
4+
#include "gmock/gmock.h"
5+
#include "gtest/gtest.h"
6+
7+
#include <map>
8+
#include <set>
9+
#include <string>
10+
#include <vector>
11+
12+
fpgen::generator<size_t> manip_empty() { co_return 0; }
13+
14+
fpgen::generator<size_t> manip() {
15+
size_t i = 1;
16+
while (i < 1024) {
17+
co_yield i;
18+
i *= 2;
19+
}
20+
co_return i;
21+
}
22+
23+
fpgen::generator<size_t> until12() {
24+
for (int i = 0; i < 12; i++) {
25+
co_yield i;
26+
}
27+
co_return 12;
28+
}
29+
30+
size_t mapper(size_t v) { return v * v; }
31+
bool is_even(size_t v) { return (v % 2 == 0); }
32+
bool over_100(size_t v) { return (v > 100); }
33+
34+
TEST(manipulators, map_empty) {
35+
auto gen = manip_empty();
36+
gen();
37+
38+
for (auto v : fpgen::map(gen, mapper)) {
39+
FAIL() << "Should not return a value";
40+
}
41+
SUCCEED();
42+
}
43+
44+
TEST(manipulators, map) {
45+
auto gen = manip();
46+
size_t i = 1;
47+
for (auto v : fpgen::map(gen, mapper)) {
48+
EXPECT_EQ(i * i, v);
49+
EXPECT_TRUE(i <= 1024);
50+
i *= 2;
51+
}
52+
}
53+
54+
TEST(manipulators, zip_both_empty) {
55+
auto gen = manip_empty();
56+
auto gen2 = manip_empty();
57+
gen();
58+
gen2();
59+
60+
for (auto v : fpgen::zip(gen, gen2)) {
61+
FAIL() << "Should not return a value";
62+
}
63+
SUCCEED();
64+
}
65+
66+
TEST(manipulators, zip_first_empty) {
67+
auto gen = manip_empty();
68+
auto gen2 = fpgen::inc((size_t)0);
69+
gen();
70+
71+
for (auto v : fpgen::zip(gen, gen2)) {
72+
FAIL() << "Should not return a value";
73+
}
74+
SUCCEED();
75+
}
76+
77+
TEST(manipulators, zip_second_empty) {
78+
auto gen = fpgen::inc((size_t)0);
79+
auto gen2 = manip_empty();
80+
gen2();
81+
82+
for (auto v : fpgen::zip(gen, gen2)) {
83+
FAIL() << "Should not return a value";
84+
}
85+
SUCCEED();
86+
}
87+
88+
TEST(manipulators, zip_none_empty) {
89+
auto gen = fpgen::inc((size_t)0);
90+
auto gen2 = manip();
91+
92+
size_t i = 0;
93+
size_t j = 1;
94+
95+
for (auto v : fpgen::zip(gen, gen2)) {
96+
EXPECT_EQ(std::get<0>(v), i);
97+
EXPECT_EQ(std::get<1>(v), j);
98+
EXPECT_TRUE(j <= 1024);
99+
EXPECT_TRUE(i <= 10);
100+
101+
i++;
102+
j *= 2;
103+
}
104+
}
105+
106+
TEST(manipulators, filter_empty) {
107+
auto gen = manip_empty();
108+
gen();
109+
110+
size_t i = 0;
111+
112+
for (auto v : fpgen::filter(gen, is_even)) {
113+
FAIL();
114+
}
115+
SUCCEED();
116+
}
117+
118+
TEST(manipulators, filter_to_empty) {
119+
auto gen = until12();
120+
121+
for (auto v : fpgen::filter(gen, over_100)) {
122+
FAIL();
123+
}
124+
SUCCEED();
125+
}
126+
127+
TEST(manipulators, filter_normal) {
128+
auto gen = until12();
129+
size_t i = 0;
130+
for (auto v : fpgen::filter(gen, is_even)) {
131+
EXPECT_EQ(v, i);
132+
EXPECT_TRUE(i <= 12);
133+
i += 2;
134+
}
135+
}

0 commit comments

Comments
 (0)