Skip to content

Commit 79af0c4

Browse files
authored
Merge pull request #8 from jay-tux/feat/aggregators
Feat/aggregators
2 parents c5314c5 + 0d879ed commit 79af0c4

File tree

3 files changed

+369
-1
lines changed

3 files changed

+369
-1
lines changed

inc/aggregators.hpp

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#ifndef _FPGEN_AGGREGATORS
2+
#define _FPGEN_AGGREGATORS
3+
4+
#include "generator.hpp"
5+
#include <forward_list>
6+
#include <tuple>
7+
#include <type_traits>
8+
9+
/**
10+
* \brief The namespace containing all of fpgen's code.
11+
*/
12+
namespace fpgen {
13+
/**
14+
* \brief Aggregates all data in the generator to a dataset.
15+
*
16+
* The supplied container should support `push_back` (like most of the `std::`
17+
* containers). Each element is extracted from the generator and inserted into
18+
* the container. The container is not cleared before inserting. Elements
19+
* already extracted (due to previous calls to the generator, ...) cannot be
20+
* reconstructed.
21+
*
22+
* \tparam T The type contained in the container and generator.
23+
* \tparam Args Other parameters to be passed to the container.
24+
* \tparam Container The container type to output to.
25+
* \param[in, out] gen The generator to extract from.
26+
* \param[out] out The container to output to.
27+
* \returns A reference to the modified container.
28+
*/
29+
template <typename T, typename... Args,
30+
template <typename...> typename Container>
31+
Container<T, Args...> &aggregate_to(generator<T> &gen,
32+
Container<T, Args...> &out) {
33+
while (gen) {
34+
out.push_back(gen());
35+
}
36+
return out;
37+
}
38+
39+
/**
40+
* \brief Aggregates all data in the generator into an associative container.
41+
*
42+
* The supplied container should support `operator[]` (by reference, like
43+
* `std::map`). Duplicate key values will be be overwritten. The container is
44+
* not cleared before writing. Elements that have already been extracted from
45+
* the generator cannot be reconstructed.
46+
*
47+
* \tparam TKey The key type for the container.
48+
* \tparam TValue The value type for the container.
49+
* \tparam Args Other parameters to be passed to the container.
50+
* \tparam Container The container to be used.
51+
* \param[in, out] gen The generator to extract from.
52+
* \param[out] out The container to insert to.
53+
* \returns A reference to the modified container.
54+
*/
55+
template <typename TKey, typename TVal, typename... Args,
56+
template <typename...> typename Container>
57+
Container<TKey, TVal, Args...> &
58+
tup_aggregate_to(generator<std::tuple<TKey, TVal>> &gen,
59+
Container<TKey, TVal, Args...> &out) {
60+
while (gen) {
61+
std::tuple<TKey, TVal> tup = gen();
62+
out[std::get<0>(tup)] = std::get<1>(tup);
63+
}
64+
return out;
65+
}
66+
67+
/**
68+
* \brief Counts the amount of elements in the generator.
69+
*
70+
* Each element is extracted from the generator. These values are not
71+
* recoverable. Only values left in the generator are counted. Afterwards, the
72+
* generator will be empty.
73+
*
74+
* \tparam T The type of values contained in the generator.
75+
* \param[in,out] gen The generator to iterate over.
76+
* \returns The amount of elements in the generator.
77+
*/
78+
template <typename T> size_t count(generator<T> &gen) {
79+
size_t cnt = 0;
80+
while (gen) {
81+
gen();
82+
cnt++;
83+
}
84+
return cnt;
85+
}
86+
87+
/**
88+
* \brief Accumulates each value in the generator using the provided function.
89+
*
90+
* For each element in the generator, the provided function (which should have
91+
* (TOut, TIn) -> TOut) as signature is called. The result is stored in the
92+
* accumulator, which is passed down to the next value in the generator. Once
93+
* all values are extracted, the resulting accumulator is returned. The
94+
* accumulator is initialized using `TOut value = {};`.
95+
*
96+
* \tparam TOut The output type (accumulator type).
97+
* \tparam TIn The input type (type contained in the generator).
98+
* \tparam Fun The function type (should have the signature (TOut, TIn) ->
99+
* TOut).
100+
* \param[in,out] gen The generator to fold.
101+
* \param[in] folder The folding function.
102+
* \returns The final accumulator value.
103+
*/
104+
template <typename TOut, typename TIn, typename Fun>
105+
TOut fold(generator<TIn> &gen, Fun folder) {
106+
TOut value = {};
107+
while (gen) {
108+
value = folder(value, gen());
109+
}
110+
return value;
111+
}
112+
113+
/**
114+
* \brief Accumulates each value in the generator using the provided function.
115+
*
116+
* For each element in the generator, the provided function (which should have
117+
* (TOut, TIn) -> TOut) as signature is called. The result is stored in the
118+
* accumulator, which is passed down to the next value in the generator. Once
119+
* all values are extracted, the resulting accumulator is returned. The
120+
* accumulator is initialized using `TOut value(initial);`.
121+
*
122+
* \tparam TOut The output type (accumulator type).
123+
* \tparam TIn The input type (type contained in the generator).
124+
* \tparam Fun The function type (should have the signature (TOut, TIn) ->
125+
* TOut).
126+
* \param[in,out] gen The generator to fold.
127+
* \param[in] folder The folding function.
128+
* \param[in] initial The initial value for the accumulator (is copied).
129+
* \returns The final accumulator value.
130+
*/
131+
template <typename TOut, typename TIn, typename Fun>
132+
TOut fold(generator<TIn> &gen, Fun folder, TOut initial) {
133+
TOut value(initial);
134+
while (gen) {
135+
value = folder(value, gen());
136+
}
137+
return value;
138+
}
139+
140+
/**
141+
* \brief Accumulates each value in the generator using the provided function.
142+
*
143+
* For each element in the generator, the provided function (which should have
144+
* (TOut, TIn) -> TOut) as signature is called. The result is stored in the
145+
* provided accumulator, which is passed down to the next value in the
146+
* generator. Once all values are extracted, the resulting accumulator is
147+
* returned. Each step modifies the accumulator.
148+
*
149+
* \tparam TOut The output type (accumulator type).
150+
* \tparam TIn The input type (type contained in the generator).
151+
* \tparam Fun The function type (should have the signature (TOut, TIn) ->
152+
* TOut or (TOut &, TIn) -> TOut).
153+
* \param[in,out] gen The generator to fold.
154+
* \param[in] folder The folding function.
155+
* \param[out] initial The initial value for the accumulator (is overwritten).
156+
* \returns A reference to the value which was passed as initial value and is
157+
* now the output value.
158+
*/
159+
template <typename TOut, typename TIn, typename Fun>
160+
TOut &fold_ref(generator<TIn> &gen, Fun folder, TOut &initial) {
161+
while (gen) {
162+
initial = folder(initial, gen());
163+
}
164+
return initial;
165+
}
166+
167+
/**
168+
* \brief Sums each value in the generator.
169+
*
170+
* The type contained in the generator should support `operator+`. An initial
171+
* accumulator is constructed using `T accum = {}`. Each next value is added to
172+
* the accumulator, which is returned afterwards.
173+
*
174+
* \tparam T The type contained in the generator, should support `operator+`.
175+
* \param[in,out] gen The generator to sum over.
176+
* \returns The sum of all elements.
177+
*/
178+
template <typename T> T sum(generator<T> &gen) {
179+
T accum = {};
180+
while (gen) {
181+
accum = accum + gen();
182+
}
183+
return accum;
184+
}
185+
186+
/**
187+
* \brief Loops over the generator, calling a function on each element in it.
188+
*
189+
* Each element is extracted from the generator and used only as function
190+
* argument. All other aggregators can be written as a combination of this
191+
* function and a lambda function. Afterwards, the generator will be empty.
192+
*
193+
* \tparam T The type of values contained in the generator.
194+
* \tparam Fun The function type of the callback.
195+
* \param[in,out] gen The generator to iterate over.
196+
* \param[in] func The function to use.
197+
*/
198+
template <typename T, typename Fun> void foreach (generator<T> &gen, Fun func) {
199+
while (gen) {
200+
func(gen());
201+
}
202+
}
203+
} // namespace fpgen
204+
205+
#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 manip
3+
TESTS=generator sources manip aggreg
44
TESTOBJ=$(TESTS:%=$(OBJD)/test_%.o)
55

66
CONAN_PKG_OVERRIDE=gtest

test/src/test_aggreg.cpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#include "aggregators.hpp"
2+
#include "generator.hpp"
3+
#include "manipulators.hpp"
4+
#include "gmock/gmock.h"
5+
#include "gtest/gtest.h"
6+
7+
#include <map>
8+
#include <vector>
9+
10+
fpgen::generator<size_t> a_empty() { co_return 0; }
11+
12+
fpgen::generator<size_t> values() {
13+
size_t i = 0;
14+
size_t j = 1;
15+
co_yield 0;
16+
co_yield 1;
17+
18+
while (j < 21) {
19+
size_t tmp = i + j;
20+
co_yield tmp;
21+
i = j;
22+
j = tmp;
23+
}
24+
25+
co_return i + j;
26+
}
27+
28+
size_t calc_sum() {
29+
size_t i = 0;
30+
size_t j = 1;
31+
size_t sum = 1;
32+
while (j <= 21) {
33+
size_t tmp = i + j;
34+
sum += tmp;
35+
i = j;
36+
j = tmp;
37+
}
38+
return sum;
39+
}
40+
41+
size_t sum(size_t old, size_t in) { return old + in; }
42+
size_t sum_ref(size_t &old, size_t in) {
43+
old = old + in;
44+
return old;
45+
}
46+
47+
TEST(aggregate, empty) {
48+
auto gen = a_empty();
49+
gen();
50+
std::vector<size_t> res;
51+
EXPECT_EQ(0, fpgen::aggregate_to(gen, res).size());
52+
}
53+
54+
TEST(aggregate, vector) {
55+
auto gen = values();
56+
std::vector<size_t> res;
57+
fpgen::aggregate_to(gen, res);
58+
59+
EXPECT_EQ(0, res[0]);
60+
EXPECT_EQ(1, res[1]);
61+
EXPECT_EQ(1, res[2]);
62+
EXPECT_EQ(2, res[3]);
63+
EXPECT_EQ(3, res[4]);
64+
EXPECT_EQ(5, res[5]);
65+
EXPECT_EQ(8, res[6]);
66+
EXPECT_EQ(13, res[7]);
67+
EXPECT_EQ(21, res[8]);
68+
EXPECT_EQ(34, res[9]);
69+
EXPECT_EQ(res.size(), 10);
70+
}
71+
72+
TEST(aggregate, map) {
73+
fpgen::generator<size_t> sources[2] = {values(), values()};
74+
auto gen = fpgen::zip(sources[0], sources[1]);
75+
std::map<size_t, size_t> res;
76+
fpgen::tup_aggregate_to(gen, res);
77+
78+
EXPECT_EQ(0, res[0]);
79+
EXPECT_EQ(1, res[1]);
80+
EXPECT_EQ(2, res[2]);
81+
EXPECT_EQ(3, res[3]);
82+
EXPECT_EQ(5, res[5]);
83+
EXPECT_EQ(8, res[8]);
84+
EXPECT_EQ(13, res[13]);
85+
EXPECT_EQ(21, res[21]);
86+
EXPECT_EQ(34, res[34]);
87+
EXPECT_EQ(res.size(), 9);
88+
}
89+
90+
TEST(aggregate, count_empty) {
91+
auto gen = a_empty();
92+
gen();
93+
EXPECT_EQ(0, fpgen::count(gen));
94+
}
95+
96+
TEST(aggregate, count) {
97+
auto gen = values();
98+
EXPECT_EQ(10, fpgen::count(gen));
99+
}
100+
101+
TEST(fold, fold_noin_empty) {
102+
auto gen = a_empty();
103+
gen();
104+
EXPECT_EQ(0, fpgen::fold<size_t>(gen, sum));
105+
}
106+
107+
TEST(fold, fold_noin) {
108+
auto gen = values();
109+
EXPECT_EQ(calc_sum(), fpgen::fold<size_t>(gen, sum));
110+
}
111+
112+
TEST(fold, fold_in_noref_empty) {
113+
auto gen = a_empty();
114+
gen();
115+
EXPECT_EQ(7, fpgen::fold<size_t>(gen, sum, 7));
116+
}
117+
118+
TEST(fold, fold_in_noref) {
119+
auto gen = values();
120+
EXPECT_EQ(calc_sum() + 7, fpgen::fold<size_t>(gen, sum, 7));
121+
}
122+
123+
TEST(fold, fold_in_ref_empty) {
124+
auto gen = a_empty();
125+
gen();
126+
size_t res = 7;
127+
EXPECT_EQ(7, fpgen::fold_ref<size_t>(gen, sum, res));
128+
EXPECT_EQ(7, res);
129+
}
130+
131+
TEST(fold, fold_in_ref) {
132+
auto gen = values();
133+
size_t res = 7;
134+
EXPECT_EQ(calc_sum() + 7, fpgen::fold_ref<size_t>(gen, sum, res));
135+
EXPECT_EQ(calc_sum() + 7, res);
136+
}
137+
138+
TEST(sum, empty) {
139+
auto gen = a_empty();
140+
gen();
141+
EXPECT_EQ(0, fpgen::sum(gen));
142+
}
143+
144+
TEST(sum, normal) {
145+
auto gen = values();
146+
EXPECT_EQ(calc_sum(), fpgen::sum(gen));
147+
}
148+
149+
TEST(foreach, empty) {
150+
auto gen = a_empty();
151+
gen();
152+
size_t res = 0;
153+
fpgen::foreach (gen, [&res](size_t val) { res += val; });
154+
EXPECT_EQ(res, 0);
155+
}
156+
157+
TEST(foreach, normal) {
158+
auto gen = values();
159+
auto gen2 = values();
160+
size_t res = 0;
161+
fpgen::foreach (gen, [&res](size_t val) { res += val; });
162+
EXPECT_EQ(res, fpgen::sum(gen2));
163+
}

0 commit comments

Comments
 (0)