Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions inc/fpgen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define _FPGEN_MAIN

#include "generator.hpp"
#include "manipulators.hpp"
#include "sources.hpp"

#endif
80 changes: 80 additions & 0 deletions inc/manipulators.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#ifndef _FPGEN_MANIP
#define _FPGEN_MANIP

#include "generator.hpp"
#include <array>
#include <type_traits>

/**
* \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 <typename TIn, typename Fun>
auto map(generator<TIn> &gen, Fun func)
-> generator<typename std::invoke_result<Fun, TIn>::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 <typename T1, typename T2>
generator<std::tuple<T1, T2>> zip(generator<T1> &gen1, generator<T2> &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 <typename T, typename Pred>
generator<T> filter(generator<T> &gen, Pred p) {
while (gen) {
T val(gen());
if (p(val))
co_yield val;
}
}
} // namespace fpgen

#endif
2 changes: 1 addition & 1 deletion test/Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
135 changes: 135 additions & 0 deletions test/src/test_manip.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include "generator.hpp"
#include "manipulators.hpp"
#include "sources.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include <map>
#include <set>
#include <string>
#include <vector>

fpgen::generator<size_t> manip_empty() { co_return 0; }

fpgen::generator<size_t> manip() {
size_t i = 1;
while (i < 1024) {
co_yield i;
i *= 2;
}
co_return i;
}

fpgen::generator<size_t> 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;
}
}