The main part of this library is a wide collection of transducers implemented using the techniques described in the Design section. Many of them borrow the names of their Clojure equivalents, but there are additional ones inspired by Python itertools and other sequence manipulation libraries.
zug::
cat
= comp(cat_t{})¶Flattens the sequence one level. If multiple inputs are received, it flattens all inputs in parallel.
auto v = std::vector<std::vector<int>>{{1, 2}, {3}, {4, 5, 6}};
auto r = into_vector(cat, v);
CHECK(r == (std::vector<int>{1, 2, 3, 4, 5, 6}));
auto v1 = std::vector<std::vector<int>>{{1, 2}, {3, 4}};
auto v2 = std::vector<std::vector<char>>{{'a'}, {'c', 'd'}};
auto r = into_vector(cat, v1, v2);
using t = std::vector<std::tuple<int, char>>;
CHECK(r == (t{{1, 'a'}, {3, 'c'}, {4, 'd'}}));
zug::
chain
(InputRangeTs&&... rs)¶Alias for chainr
.
zug::
chainr
(InputRangeTs... rs)¶Transducer produces the sequence passed as parameter after all other input has finished.
auto v1 = std::vector<int>{1, 2};
auto v2 = std::vector<int>{13, 42, 5};
auto res = into_vector(chain(v2), v1);
CHECK(res == (std::vector<int>{1, 2, 13, 42, 5}));
auto v1 = std::vector<int>{1, 2};
auto v2 = std::vector<int>{13, 42, 5};
auto res = into_vector(chain(v2, v1), v1);
CHECK(res == (std::vector<int>{1, 2, 13, 42, 5, 1, 2}));
// This is a limitation of the pure stateful transducer design
auto v1 = std::vector<int>{};
auto v2 = std::vector<int>{13, 42, 5};
auto res = into_vector(chain(v2), v1);
CHECK(res == (std::vector<int>{}));
zug::
chainl
(InputRangeTs... rs)¶Transducer produces the sequence passed as parameter before processing the first input.
auto v1 = std::vector<int>{1, 2};
auto v2 = std::vector<int>{13, 42, 5};
auto res = into_vector(chainl(v2), v1);
CHECK(res == (std::vector<int>{13, 42, 5, 1, 2}));
auto v1 = std::vector<int>{1, 2};
auto v2 = std::vector<int>{13, 42, 5};
auto res = into_vector(chainl(v2, v1), v1);
CHECK(res == (std::vector<int>{1, 2, 13, 42, 5, 1, 2}));
// This is a limitation of the pure stateful transducer design
auto v1 = std::vector<int>{};
auto v2 = std::vector<int>{13, 42, 5};
auto res = into_vector(chainl(v2), v1);
CHECK(res == (std::vector<int>{}));
zug::
count
(InitT init = InitT{0}, DeltaT delta = DeltaT{1})¶Generator transducer produces a sequence:
$$init, init+step, …, init+step*n$$
It is inspired by Python’s itertools.count
generator.
auto v = std::vector<int>{13, 42, 5};
auto r = into_vector(count(), v);
using t = std::vector<std::tuple<int, std::size_t>>;
CHECK(r == (t{{13, 0u}, {42, 1u}, {5, 2u}}));
auto r = into_vector(count(1.0, 0.5) | take(5));
CHECK(r == (std::vector<double>{{1.0, 1.5, 2.0, 2.5, 3.0}}));
zug::
cycle
(InputRangeTs&&... rs)¶Generator transducer produces the sequence passed as parameter, by cycling over it.
auto v1 = std::vector<int>{13, 42, 5, 6, 15};
auto v2 = std::vector<int>{0, 1};
auto r = into_vector(cycle(v2), v1);
using t = std::vector<std::tuple<int, int>>;
CHECK(r == (t{{13, 0}, {42, 1}, {5, 0}, {6, 1}, {15, 0}}));
auto v1 = std::vector<int>{0, 1};
auto v2 = std::vector<std::string>{"one", "two", "three"};
auto r = into_vector(cycle(v1, v2) | take(5));
using t = std::vector<std::tuple<int, std::string>>;
CHECK(r == (t{{0, "one"}, //
{1, "two"},
{0, "three"},
{1, "one"},
{0, "two"}}));
zug::
dedupe
= comp(dedupe_t{})¶Transducer that removes consecutive duplicates from a sequence.
auto v = std::vector<int>{1, 1, 2, 1, 1, 3, 2, 2, 2, 1};
auto res = into_vector(dedupe, v);
CHECK(res == (decltype(res){1, 2, 1, 3, 2, 1}));
zug::
distinct
= comp(distinct_t{})¶Transducer that removes duplicates from a sequence.
Note that this transducer uses \( O(n) \) space (with \( n \) being the size of the resulting sequence) since it needs to remember every element in the sequence to mark whether it’s been seen.
auto v = std::vector<int>{1, 2, 1, 3, 2, 1};
auto res = into_vector(distinct, v);
CHECK(res == (decltype(res){1, 2, 3}));
zug::
drop
(IntegralT n)¶Removes the first n
elements from the sequence.
auto v = std::vector<int>{1, 2, 3, 4, 5};
auto res = into_vector(drop(2), v);
CHECK(res == (decltype(res){3, 4, 5}));
zug::
drop_while
(PredicateT &&predicate)¶Skips every element that passes the predicate, until one element passes it it. From then on it lets everything go through.
auto v = std::vector<int>{1, 2, 3, 4, 3, 2, 1};
auto res = into_vector(drop_while([](int x) { return x < 4; }), v);
CHECK(res == (decltype(res){4, 3, 2, 1}));
zug::
each
(ActionT &&action)¶Transducer that evaluates the function action
on each input, forwarding the input down the original inputs down the transducer chain. The provided function is used only for side-effects.
auto v = std::vector<int>{1, 2, 3, 6};
auto r1 = std::vector<int>{};
auto r2 = into_vector(each([&](int x) { r1.push_back(x); }), v);
CHECK(v == r1);
CHECK(v == r2);
zug::
eager
(Algo algo)¶Transducer that processes the whole sequence with the eager algorithm algo
. algo
is a function that takes a vector as an argument a returns a range. It used to implement other transducers, like sorted
and reversed
.
Note that this transducer produces no output until completion and uses \( O(n) \) space.
zug::
sorted
= eager(sorted_t{})¶Eager transducer that sorts the input sequence.
auto v = std::vector<int>{6, 2, 1, 12, 3};
auto r = into_vector(sorted, v);
CHECK(r == (decltype(r){1, 2, 3, 6, 12}));
zug::
reversed
= eager(reversed_t{})¶Eager transducer that reverses the input sequence.
auto v = std::vector<int>{1, 2, 3, 4, 5};
auto r = into_vector(reversed, v);
CHECK(r == (decltype(r){5, 4, 3, 2, 1}));
zug::
enumerate
= enumerate_from(std::size_t{})¶Enumerates elements in the stream, by prepending the index at in which they appear, starting from initial
.
auto s = std::string{"hello"};
auto r = into_vector(enumerate, s);
CHECK(r == decltype(r){{0, 'h'}, {1, 'e'}, {2, 'l'}, {3, 'l'}, {4, 'o'}});
zug::
enumerate_from
(T &&initial)¶Enumerates elements in the stream, by prepending the index at in which they appear, starting from initial
.
auto s = std::string{"hello"};
auto r = into_vector(enumerate_from(2), s);
CHECK(r == decltype(r){{2, 'h'}, {3, 'e'}, {4, 'l'}, {5, 'l'}, {6, 'o'}});
zug::
filter
(PredicateT &&predicate)¶Transducer that removes all inputs that do not pass the predicate
.
auto v = std::vector<int>{1, 2, 3, 4, 5, 6};
auto res = into_vector(filter([](int x) { return x % 2; }), v);
CHECK(res == decltype(v){1, 3, 5});
zug::
interleave
= comp(interleave_t{})¶Feed every argument of each element individually into the stream.
auto v = std::vector<int>{1, 2, 3, 4, 5};
auto r = into_vector(interleave, v, v);
CHECK(r == decltype(r){1, 1, 2, 2, 3, 3, 4, 4, 5, 5});
zug::
interpose
(ValueTs&&... xs)¶Feeds the values passed as arguments in between the elements of the stream.
auto v = std::vector<int>{1, 2, 3, 4, 5};
auto res = into_vector(interpose(42), v);
CHECK(res == (decltype(res){1, 42, 2, 42, 3, 42, 4, 42, 5}));
zug::
iter
(InputRangeT &&r, InputRangeTs&&... rs)¶Generator transducer produces the sequence passed as parameter, by iterating over it.
auto v = std::vector<int>{13, 42, 5};
auto res = into_vector(iter(v));
CHECK(res == (std::vector<int>{13, 42, 5}));
auto v1 = std::vector<int>{13, 42, 5, 6, 7};
auto v2 = std::vector<std::string>{"one", "two", "three"};
auto res = into_vector(iter(v1), v2);
CHECK(res == (decltype(res){{"one", 13}, {"two", 42}, {"three", 5}}));
zug::
mapcat
(MappingT &&mapping)¶Equivalent to cat | map(mapping)
.
auto v = std::vector<std::vector<int>>{{1, 2}, {3}, {4, 5, 6}};
auto res = into_vector(mapcat([](int x) { return x * 2; }), v);
CHECK(res == (std::vector<int>{2, 4, 6, 8, 10, 12}));
zug::
map
(MappingT &&mapping)¶Transforms every input using the mapping
function.
auto v = std::vector<int>{1, 2, 3, 6};
auto r = into_vector(map([](int x) { return x * 2; }), v);
CHECK(r == decltype(r){2, 4, 6, 12});
zug::
map_indexed
(MappingT &&mapping)¶Equivalent to count() | map(mapping)
.
auto v = std::vector<std::string>{"1", "2", "3", "6"};
auto timesi = [](std::string x, std::size_t index) {
return std::stoi(x) * static_cast<int>(index);
};
CHECK(transduce(map_indexed(timesi), std::plus<int>{}, 1, v) == 27);
zug::
partition
(SizeT size)¶Groups elements of the sequence in vectors of size
elements.
auto v = std::vector<int>{1, 2, 3, 4, 5, 6, 7};
auto r = into_vector(partition(2u), v);
using t = std::vector<std::vector<int>>;
CHECK(r == t{{1, 2}, {3, 4}, {5, 6}, {7}});
zug::
partition_by
(MappingT &&mapping)¶Groups consecutive elements for which mapping(x) == mapping(y)
in vectors.
auto v = std::vector<int>{1, 1, 2, 4, 2, 3};
auto res = into_vector(partition_by([](int x) { return x % 2; }), v);
using t = std::vector<std::vector<int>>;
CHECK(res == t{{1, 1}, {2, 4, 2}, {3}});
auto v = std::vector<int>{1, 1, 2, 2, 2, 3};
auto res = into_vector(partition_by(identity), v);
CHECK(res == decltype(res){{1, 1}, {2, 2, 2}, {3}});
zug::
product
(InputRangeT1 &&r1, InputRangeTs&&... rs)¶Transducer combines every element that passes by with every element in the range that it takes as argument. If multiple ranges are passed, it generates all combinations of elements from all of them.
auto v1 = std::vector<int>{1, 2};
auto v2 = std::vector<int>{3, 4};
auto res = into_vector(product(v2), v1);
using t = std::vector<std::tuple<int, int>>;
CHECK(res == t{{1, 3}, {1, 4}, {2, 3}, {2, 4}});
auto v1 = std::vector<int>{1, 2};
auto v2 = std::vector<int>{3, 4};
auto v3 = std::vector<char>{'a', 'b'};
auto res = into_vector(product(v2, v3), v1);
using t = std::vector<std::tuple<int, int, char>>;
CHECK(res == t{{1, 3, 'a'},
{1, 3, 'b'},
{1, 4, 'a'},
{1, 4, 'b'},
{2, 3, 'a'},
{2, 3, 'b'},
{2, 4, 'a'},
{2, 4, 'b'}});
zug::
random_sample
(ProbabilityT prob, GeneratorT gen = GeneratorT{})¶Given a random number generator gen
and a probability prob
let’s elements through when gen() <= prob
.
Similar to clojure.core/random-sample.
CHECK(transduce(range(20) | random_sample(0.5),
std::plus<>{},
std::size_t{}) == 100);
CHECK(transduce(range(20) | random_sample(1),
std::plus<>{},
std::size_t{}) == 190);
CHECK(transduce(range(20) | random_sample(0), //
std::plus<>{},
std::size_t{}) == 0);
zug::
range
(StopT &&stop)¶zug::
range
(StartT &&start, StopT &&stop)¶zug::
range
(StartT &&start, StopT &&stop, StepT &&step)¶Produces numbers in the range \( [start, stop) \) in steps of size \( step \). start
and step
are optional.
auto res = into_vector(range(3));
CHECK(res == std::vector<std::size_t>{{0, 1, 2}});
auto res = into_vector(range(2, 6));
CHECK(res == std::vector<int>{2, 3, 4, 5});
auto res = into_vector(range(2, 6, 2));
CHECK(res == std::vector<int>{2, 4});
auto s = std::string{"hello"};
auto res = into_vector(range(2, 6, 2), s);
CHECK(res == std::vector<std::tuple<char, int>>{{'h', 2}, {'e', 4}});
zug::
readbuf
(InputStreamT &stream)¶Generator transducer that reads buffers of size N
from , and passes them into the sequence. It passes them into the sequence as an range with char*
iterators.
auto as_str = map([](auto buf) {
return std::string{buf.begin(), buf.end()};
});
auto stream = std::stringstream{"12345"};
auto res = into_vector(readbuf<3>(stream) | as_str);
CHECK(res == std::vector<std::string>{"123", "45"});
zug::
readbuf
(InputStreamT &stream, std::size_t n)¶Like readbuf(stream)
but with runtime specified size.
auto as_str = map([](auto buf) {
return std::string{buf.begin(), buf.end()};
});
auto stream = std::stringstream{"12345"};
auto res = into_vector(readbuf(stream, 3) | as_str);
CHECK(res == std::vector<std::string>{"123", "45"});
zug::
read
(InputStreamT &stream)¶Generator transducer that produces a sequence of values of type T
read from the given stream
using the operator >>
. If more than one type is passed, it reads multiple values producing a T0, T1, ..., Tn
on each step.
auto stream = std::stringstream{"1 2 3 4 5 6"};
auto res = into_vector(read<int>(stream));
CHECK(res == std::vector<int>{1, 2, 3, 4, 5, 6});
auto stream = std::stringstream{"1 hello 2 my 3 friend 4 !!"};
auto res = into_vector(read<int, std::string>(stream));
CHECK(res == std::vector<std::tuple<int, std::string>>{
{1, "hello"}, {2, "my"}, {3, "friend"}, {4, "!!"}});
zug::
remove
(PredicateT &&pred)¶Removes elements that pass the predicate.
auto v = std::vector<int>{1, 2, 3, 6};
auto times2 = [](int x) { return x * 2; };
auto odd = [](int x) { return x % 2 == 0; };
auto res = transduce(remove(odd) | map(times2), std::plus<int>{}, 1, v);
CHECK(res == 9);
zug::
repeat
(ValueT &&r, ValueTs&&... rs)¶Generator transducer produces the values passed as parameter infinitely.
auto res = into_vector(repeat('a') | take(3));
CHECK(res == (std::vector<char>{{'a', 'a', 'a'}}));
auto v = std::vector<int>{1, 2, 3};
auto res = into_vector(repeat('a'), v);
CHECK(res == decltype(res){{1, 'a'}, {2, 'a'}, {3, 'a'}});
zug::
repeatn
(IntegralT &&n, ValueTs&&... rs)¶Generator transducer produces the values passed as parameter up to n
times.
auto res = into_vector(repeatn(3, 'a'));
CHECK(res == (std::vector<char>{{'a', 'a', 'a'}}));
zug::
replace
(TableT &&table)¶Transducer that replaces elements by those in the table
, a mapping providing a find()
method returning an iterator comparable with end()
. If an input is not found, it is left untouched in the sequence.
Similar to clojure.core/replace.
auto table = std::map<std::string, std::string>{{"hola", "adios"}};
auto v = std::vector<std::string>{"hola", " ", "amigo"};
CHECK(transduce(replace(table), std::plus<>{}, std::string{}, v) ==
"adios amigo");
zug::
replace_all
(TableT table)¶Transducer that replaces all elements by table[tuplify(inputs)]
.
auto table = std::map<std::string, int>{{"hola", 1}, {"amigo", 2}};
auto v = std::vector<std::string>{"hola", " ", "amigo"};
CHECK(transduce(replace_all(table), std::plus<>{}, int{}, v) == 3);
zug::
replace_all_safe
(TableT &&table)¶Transducer that replaces all elements by table.at(tuplify(inputs))
auto table = std::map<std::string, int>{{"hola", 1}, {"amigo", 2}};
auto v = std::vector<std::string>{"hola", "amigo"};
CHECK(transduce(replace_all_safe(table), std::plus<>{}, int{}, v) == 3);
auto table = std::map<std::string, int>{{"hola", 12}, {"amigo", 42}};
auto v = std::vector<std::string>{"hola", "oops", "amigo"};
CHECK_THROWS_AS(transduce(replace_all_safe(table), std::plus<>{}, int{}, v),
std::out_of_range);
zug::
sink
(ActionT &&action)¶Transducer that evaluates action
on each input. The input is forwarded into the action and discarded. The next transducer is excited for every input, but with no arguments.
auto v = std::vector<int>{1, 2, 3, 6};
auto r = std::vector<int>{};
run(sink([&](int x) { r.push_back(x); }), v);
CHECK(v == r);
auto v = std::vector<int>{1, 42, 13, 6};
auto r = into_vector(sink([&](int x) {}) | enumerate, v);
CHECK(r == std::vector<std::size_t>{0, 1, 2, 3});
each
, but can perform better since the arguments can be moved into the action. If we are not interested in the inputs after the action, this one should be preferred.zug::
scan
(State init, ReducingFunction rf)¶For each input use the provided reducing function to combine items into a value that will be fed into the sequence. Inspired by rxcpp::operators::scan.
The reducing function may use the state interface that this library provide, allowing it to be combined with stateful transducers. The state is passed unwrapped into the sequence, so the state of the transducers is not visible into the outter stream. However, when the inner reduction completes, it completes the outter reduction as well.
auto v = std::vector<int>{1, 2, 3, 4, 5, 6};
auto r = into_vector(scan(0, std::plus<>{}), v);
CHECK(r == (std::vector<int>{1, 3, 6, 10, 15, 21}));
auto r = into_vector(count(1) | scan(0, take(6)(std::plus<>{})));
CHECK(r == std::vector<int>{1, 3, 6, 10, 15, 21});
zug::
take
(IntegralT n)¶Lets n
elements through and finishes.
auto v = std::vector<int>{1, 2, 3, 4, 5};
auto res = into_vector(take(3), v);
CHECK(res == std::vector<int>{1, 2, 3});
zug::
take_nth
(IntegralT nth)¶Lets every nth elements of the sequence through.
Similar to clojure.core/take-nth.
auto v = std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto res = into_vector(take_nth(3), v);
CHECK(res == std::vector<int>{1, 4, 7, 10});
zug::
take_while
(PredicateT predicate)¶Lets items through until the predicate does not pass, then stops.
auto v = std::vector<int>{1, 2, 3, 4, 5};
auto pred = std::bind(std::less<>{}, _1, 4);
auto res = into_vector(take_while(pred), v);
CHECK(res == decltype(res){1, 2, 3});
zug::
unzip
= comp(unzip_t{})¶Transducer that expands all unzipable inputs into the sequence. Unzipable inputs are std::tuple
, std::pair
and std::array
.
auto v = std::vector<std::tuple<int, int>>{{1, 10}, {2, 20}, {3, 30}};
auto r = into_vector(unzip | map(std::plus<>{}), v);
CHECK(r == std::vector<int>{11, 22, 33});
zug::
writebuf
(OutputStreamT &stream)¶Generator transducer that writes buffers into the stream and passes them into the sequence. A buffer shall be a range with char*
iterators. Lets n
elements through and finishes.
auto v = std::vector<std::string>{"123", "4", "56"};
auto stream = std::stringstream{};
run(writebuf(stream), v);
CHECK(stream.str() == "123456");
zug::
write
(OutputStreamT &stream, InSeparatorT in_sep, ArgSeparatorT arg_sep)¶Transducer that writes the into a given stream
using the operator <<
. It also forwards the values for further processing.
Note that in_sep
and arg_sep
are optional separators. The former is used to separate inputs, and the latter is used to spearate arguments of one single input. If arg_sep
is not specified but in_sep
is, the latter is used also to separate arguments. By default, no separator is used.
auto v = std::vector<int>{1, 2, 3, 4};
auto stream = std::stringstream{};
run(write(stream), v);
CHECK(stream.str() == "1234");
auto v = std::vector<int>{1, 2, 3, 4};
auto stream = std::stringstream{};
run(write(stream, ' '), v);
CHECK(stream.str() == "1 2 3 4");
auto v1 = std::vector<int>{1, 2, 3, 4};
auto v2 = std::vector<char>{'y', 'e', 'a', 'h'};
auto stream = std::stringstream{};
run(write(stream, ' '), v1, v2);
CHECK(stream.str() == "1 y 2 e 3 a 4 h");
auto v1 = std::vector<int>{1, 2, 3, 4};
auto v2 = std::vector<char>{'y', 'e', 'a', 'h'};
auto stream = std::stringstream{};
run(write(stream, ' ', ','), v1, v2);
CHECK(stream.str() == "1,y 2,e 3,a 4,h");
zug::
zip
= map(tuplify)¶Transducer that, if more than one argument is passed, forwards all of them as a single tuple.
auto v1 = std::vector<int>{13, 42, 5};
auto v2 = std::vector<double>{1.1, 2.2, 3.3};
auto res = transduce(
zip,
[](double x, std::tuple<int, double> t) {
return x + std::get<0>(t) + std::get<1>(t);
},
1.0,
v1,
v2);
CHECK_THAT(res, Catch::Matchers::WithinAbs(67.6, 0.001));