diff --git a/inc/aggregators.hpp b/inc/aggregators.hpp new file mode 100644 index 0000000..898d2c1 --- /dev/null +++ b/inc/aggregators.hpp @@ -0,0 +1,205 @@ +#ifndef _FPGEN_AGGREGATORS +#define _FPGEN_AGGREGATORS + +#include "generator.hpp" +#include +#include +#include + +/** + * \brief The namespace containing all of fpgen's code. + */ +namespace fpgen { +/** + * \brief Aggregates all data in the generator to a dataset. + * + * The supplied container should support `push_back` (like most of the `std::` + * containers). Each element is extracted from the generator and inserted into + * the container. The container is not cleared before inserting. Elements + * already extracted (due to previous calls to the generator, ...) cannot be + * reconstructed. + * + * \tparam T The type contained in the container and generator. + * \tparam Args Other parameters to be passed to the container. + * \tparam Container The container type to output to. + * \param[in, out] gen The generator to extract from. + * \param[out] out The container to output to. + * \returns A reference to the modified container. + */ +template typename Container> +Container &aggregate_to(generator &gen, + Container &out) { + while (gen) { + out.push_back(gen()); + } + return out; +} + +/** + * \brief Aggregates all data in the generator into an associative container. + * + * The supplied container should support `operator[]` (by reference, like + * `std::map`). Duplicate key values will be be overwritten. The container is + * not cleared before writing. Elements that have already been extracted from + * the generator cannot be reconstructed. + * + * \tparam TKey The key type for the container. + * \tparam TValue The value type for the container. + * \tparam Args Other parameters to be passed to the container. + * \tparam Container The container to be used. + * \param[in, out] gen The generator to extract from. + * \param[out] out The container to insert to. + * \returns A reference to the modified container. + */ +template typename Container> +Container & +tup_aggregate_to(generator> &gen, + Container &out) { + while (gen) { + std::tuple tup = gen(); + out[std::get<0>(tup)] = std::get<1>(tup); + } + return out; +} + +/** + * \brief Counts the amount of elements in the generator. + * + * Each element is extracted from the generator. These values are not + * recoverable. Only values left in the generator are counted. Afterwards, the + * generator will be empty. + * + * \tparam T The type of values contained in the generator. + * \param[in,out] gen The generator to iterate over. + * \returns The amount of elements in the generator. + */ +template size_t count(generator &gen) { + size_t cnt = 0; + while (gen) { + gen(); + cnt++; + } + return cnt; +} + +/** + * \brief Accumulates each value in the generator using the provided function. + * + * For each element in the generator, the provided function (which should have + * (TOut, TIn) -> TOut) as signature is called. The result is stored in the + * accumulator, which is passed down to the next value in the generator. Once + * all values are extracted, the resulting accumulator is returned. The + * accumulator is initialized using `TOut value = {};`. + * + * \tparam TOut The output type (accumulator type). + * \tparam TIn The input type (type contained in the generator). + * \tparam Fun The function type (should have the signature (TOut, TIn) -> + * TOut). + * \param[in,out] gen The generator to fold. + * \param[in] folder The folding function. + * \returns The final accumulator value. + */ +template +TOut fold(generator &gen, Fun folder) { + TOut value = {}; + while (gen) { + value = folder(value, gen()); + } + return value; +} + +/** + * \brief Accumulates each value in the generator using the provided function. + * + * For each element in the generator, the provided function (which should have + * (TOut, TIn) -> TOut) as signature is called. The result is stored in the + * accumulator, which is passed down to the next value in the generator. Once + * all values are extracted, the resulting accumulator is returned. The + * accumulator is initialized using `TOut value(initial);`. + * + * \tparam TOut The output type (accumulator type). + * \tparam TIn The input type (type contained in the generator). + * \tparam Fun The function type (should have the signature (TOut, TIn) -> + * TOut). + * \param[in,out] gen The generator to fold. + * \param[in] folder The folding function. + * \param[in] initial The initial value for the accumulator (is copied). + * \returns The final accumulator value. + */ +template +TOut fold(generator &gen, Fun folder, TOut initial) { + TOut value(initial); + while (gen) { + value = folder(value, gen()); + } + return value; +} + +/** + * \brief Accumulates each value in the generator using the provided function. + * + * For each element in the generator, the provided function (which should have + * (TOut, TIn) -> TOut) as signature is called. The result is stored in the + * provided accumulator, which is passed down to the next value in the + * generator. Once all values are extracted, the resulting accumulator is + * returned. Each step modifies the accumulator. + * + * \tparam TOut The output type (accumulator type). + * \tparam TIn The input type (type contained in the generator). + * \tparam Fun The function type (should have the signature (TOut, TIn) -> + * TOut or (TOut &, TIn) -> TOut). + * \param[in,out] gen The generator to fold. + * \param[in] folder The folding function. + * \param[out] initial The initial value for the accumulator (is overwritten). + * \returns A reference to the value which was passed as initial value and is + * now the output value. + */ +template +TOut &fold_ref(generator &gen, Fun folder, TOut &initial) { + while (gen) { + initial = folder(initial, gen()); + } + return initial; +} + +/** + * \brief Sums each value in the generator. + * + * The type contained in the generator should support `operator+`. An initial + * accumulator is constructed using `T accum = {}`. Each next value is added to + * the accumulator, which is returned afterwards. + * + * \tparam T The type contained in the generator, should support `operator+`. + * \param[in,out] gen The generator to sum over. + * \returns The sum of all elements. + */ +template T sum(generator &gen) { + T accum = {}; + while (gen) { + accum = accum + gen(); + } + return accum; +} + +/** + * \brief Loops over the generator, calling a function on each element in it. + * + * Each element is extracted from the generator and used only as function + * argument. All other aggregators can be written as a combination of this + * function and a lambda function. Afterwards, the generator will be empty. + * + * \tparam T The type of values contained in the generator. + * \tparam Fun The function type of the callback. + * \param[in,out] gen The generator to iterate over. + * \param[in] func The function to use. + */ +template void foreach (generator &gen, Fun func) { + while (gen) { + func(gen()); + } +} +} // namespace fpgen + +#endif diff --git a/test/Makefile b/test/Makefile index 348dd20..1e76973 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 manip +TESTS=generator sources manip aggreg TESTOBJ=$(TESTS:%=$(OBJD)/test_%.o) CONAN_PKG_OVERRIDE=gtest diff --git a/test/src/test_aggreg.cpp b/test/src/test_aggreg.cpp new file mode 100644 index 0000000..7ea4304 --- /dev/null +++ b/test/src/test_aggreg.cpp @@ -0,0 +1,163 @@ +#include "aggregators.hpp" +#include "generator.hpp" +#include "manipulators.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include + +fpgen::generator a_empty() { co_return 0; } + +fpgen::generator values() { + size_t i = 0; + size_t j = 1; + co_yield 0; + co_yield 1; + + while (j < 21) { + size_t tmp = i + j; + co_yield tmp; + i = j; + j = tmp; + } + + co_return i + j; +} + +size_t calc_sum() { + size_t i = 0; + size_t j = 1; + size_t sum = 1; + while (j <= 21) { + size_t tmp = i + j; + sum += tmp; + i = j; + j = tmp; + } + return sum; +} + +size_t sum(size_t old, size_t in) { return old + in; } +size_t sum_ref(size_t &old, size_t in) { + old = old + in; + return old; +} + +TEST(aggregate, empty) { + auto gen = a_empty(); + gen(); + std::vector res; + EXPECT_EQ(0, fpgen::aggregate_to(gen, res).size()); +} + +TEST(aggregate, vector) { + auto gen = values(); + std::vector res; + fpgen::aggregate_to(gen, res); + + EXPECT_EQ(0, res[0]); + EXPECT_EQ(1, res[1]); + EXPECT_EQ(1, res[2]); + EXPECT_EQ(2, res[3]); + EXPECT_EQ(3, res[4]); + EXPECT_EQ(5, res[5]); + EXPECT_EQ(8, res[6]); + EXPECT_EQ(13, res[7]); + EXPECT_EQ(21, res[8]); + EXPECT_EQ(34, res[9]); + EXPECT_EQ(res.size(), 10); +} + +TEST(aggregate, map) { + fpgen::generator sources[2] = {values(), values()}; + auto gen = fpgen::zip(sources[0], sources[1]); + std::map res; + fpgen::tup_aggregate_to(gen, res); + + EXPECT_EQ(0, res[0]); + EXPECT_EQ(1, res[1]); + EXPECT_EQ(2, res[2]); + EXPECT_EQ(3, res[3]); + EXPECT_EQ(5, res[5]); + EXPECT_EQ(8, res[8]); + EXPECT_EQ(13, res[13]); + EXPECT_EQ(21, res[21]); + EXPECT_EQ(34, res[34]); + EXPECT_EQ(res.size(), 9); +} + +TEST(aggregate, count_empty) { + auto gen = a_empty(); + gen(); + EXPECT_EQ(0, fpgen::count(gen)); +} + +TEST(aggregate, count) { + auto gen = values(); + EXPECT_EQ(10, fpgen::count(gen)); +} + +TEST(fold, fold_noin_empty) { + auto gen = a_empty(); + gen(); + EXPECT_EQ(0, fpgen::fold(gen, sum)); +} + +TEST(fold, fold_noin) { + auto gen = values(); + EXPECT_EQ(calc_sum(), fpgen::fold(gen, sum)); +} + +TEST(fold, fold_in_noref_empty) { + auto gen = a_empty(); + gen(); + EXPECT_EQ(7, fpgen::fold(gen, sum, 7)); +} + +TEST(fold, fold_in_noref) { + auto gen = values(); + EXPECT_EQ(calc_sum() + 7, fpgen::fold(gen, sum, 7)); +} + +TEST(fold, fold_in_ref_empty) { + auto gen = a_empty(); + gen(); + size_t res = 7; + EXPECT_EQ(7, fpgen::fold_ref(gen, sum, res)); + EXPECT_EQ(7, res); +} + +TEST(fold, fold_in_ref) { + auto gen = values(); + size_t res = 7; + EXPECT_EQ(calc_sum() + 7, fpgen::fold_ref(gen, sum, res)); + EXPECT_EQ(calc_sum() + 7, res); +} + +TEST(sum, empty) { + auto gen = a_empty(); + gen(); + EXPECT_EQ(0, fpgen::sum(gen)); +} + +TEST(sum, normal) { + auto gen = values(); + EXPECT_EQ(calc_sum(), fpgen::sum(gen)); +} + +TEST(foreach, empty) { + auto gen = a_empty(); + gen(); + size_t res = 0; + fpgen::foreach (gen, [&res](size_t val) { res += val; }); + EXPECT_EQ(res, 0); +} + +TEST(foreach, normal) { + auto gen = values(); + auto gen2 = values(); + size_t res = 0; + fpgen::foreach (gen, [&res](size_t val) { res += val; }); + EXPECT_EQ(res, fpgen::sum(gen2)); +}