Cppitertools
Implementation of python itertools and builtin iteration functions for C++17
Install / Use
/learn @ryanhaining/CppitertoolsREADME
CPPItertools
Range-based for loop add-ons inspired by the Python builtins and itertools library. Like itertools and the Python3 builtins, this library uses lazy evaluation wherever possible.
Note: Everything is inside the iter namespace.
Follow @cppitertools for updates.
Build and Test Status
Status | Compilers
---- | ----
| gcc-7 gcc-8 gcc-9 clang-5.0 clang-6.0 clang-7 clang-8 clang-9
| MSVC 2017 MSVC 2019
Table of Contents
range<br /> enumerate<br /> zip<br /> zip_longest<br /> imap<br /> filter<br /> filterfalse<br /> unique_everseen<br /> unique_justseen<br /> takewhile<br /> dropwhile<br /> cycle<br /> repeat<br /> count<br /> groupby<br /> starmap<br /> accumulate<br /> compress<br /> sorted<br /> chain<br /> chain.from_iterable<br /> reversed<br /> slice<br /> sliding_window<br /> chunked<br /> batched<br />
Combinatorial functions
product<br /> combinations<br /> combinations_with_replacement<br /> permutations<br /> powerset<br />
Requirements
This library is header-only and relies only on the C++ standard
library. The only exception is zip_longest which uses boost::optional.
#include <cppitertools/itertools.hpp> will include all of the provided
tools except for zip_longest which must be included separately. You may
also include individual pieces with the relevant header
(#include <cppitertools/enumerate.hpp> for example).
Running tests
You may use either scons or bazel to build the tests. scons seems
to work better with viewing the test output, but the same bazel command
can be run from any directory.
To run tests with scons you must be within the test directory
test$ # build and run all tests
test$ scons
test$ ./test_all
test$ # build and run a specific test
test$ scons test_enumerate
test$ ./test_enumerate
test$ valgrind ./test_enumerate
bazel absolute commands can be run from any directory inside the project
$ bazel test //test:all # runs all tests
$ bazel test //test:test_enumerate # runs a specific test
Requirements of passed objects
Most itertools will work with iterables using InputIterators and not copy
or move any underlying elements. The itertools that need ForwardIterators or
have additional requirements are noted in this document. However, the cases
should be fairly obvious: any time an element needs to appear multiple times
(as in combinations or cycle) or be looked at more than once (specifically,
sorted).
This library takes every effort to rely on as little as possible from the
underlying iterables, but if anything noteworthy is needed it is described
in this document.
Guarantees of implementations
By implementations, I mean the objects returned by the API's functions. All of the implementation classes are move-constructible, not copy-constructible, not assignable. All iterators that work over another iterable are tagged as InputIterators and behave as such.
Feedback
If you find anything not working as you expect, not compiling when you believe it should, a divergence from the python itertools behavior, or any sort of error, please let me know. The preferable means would be to open an issue on GitHub. If you want to talk about an issue that you don't feel would be appropriate as a GitHub issue (or you just don't want to open one), you can email me directly with whatever code you have that describes the problem; I've been pretty responsive in the past. If I believe you are "misusing" the library, I'll try to put the blame on myself for being unclear in this document and take the steps to clarify it. So please, contact me with any concerns, I'm open to feedback.
How (not) to use this library
The library functions create and return objects that are properly templated on the iterable they are passed. These exact names of these types or precisely how they are templated is unspecified, you should rely on the functions described in this document. If you plan to use these functions in very simple, straight forward means as in the examples on this page, then you will be fine. If you feel like you need to open the header files, then I've probably under-described something, let me know.
Handling of rvalues vs lvalues
The rules are pretty simple, and the library can be largely used without knowledge of them. Let's take an example
std::vector<int> vec{2,4,6,8};
for (auto&& p : enumerate(vec)) { /* ... */ }
In this case, enumerate will return an object that has bound a reference to
vec. No copies are produced here, neither of vec nor of the elements it
holds.
If an rvalue was passed to enumerate, binding a reference would be unsafe. Consider:
for (auto&& p : enumerate(std::vector<int>{2,4,6,8})) { /* ... */ }
Instead, enumerate will return an object that has the temporary moved into
it. That is, the returned object will contain a std::vector<int> rather than
just a reference to one. This may seem like a contrived example, but it matters
when enumerate is passed the result of a function call like enumerate(f()),
or, more obviously, something like enumerate(zip(a, b)). The object returned
from zip must be moved into the enumerate object. As a more specific
result, itertools can be mixed and nested.
Pipe syntax
Wherever it makes sense, I've implemented the "pipe" operator that has become common in similar libraries. When the syntax is available, it is done by pulling out the iterable from the call and placing it before the tool. For example:
filter(pred, seq); // regular call
seq | filter(pred); // pipe-style
enumerate(seq); // regular call
seq | enumerate; // pipe-style.
The following tools support pipe. The remaining I left out because although some of them have multiple reasonable versions, it wasn't obvious to me how I would expect them to behave:
- accumulate
- chain.from_iterable
- chunked
- batched
- combinations
- combinations_with_replacement
- cycle
- dropwhile
- enumerate
- filter
- filterfalse
- groupby
- imap
- permutations
- powerset
- reversed
- slice
- sliding_window
- sorted
- starmap
- takewhile
- unique_everseen (*only without custom hash and equality callables)
- unique_justseen
I don't personally care for the piping style, but it seemed to be desired by the users.
range
Uses an underlying iterator to achieve the same effect of the python range
function. range can be used in three different ways:
Only the stopping point is provided. Prints 0 1 2 3 4 5 6 7 8 9
for (auto i : range(10)) {
cout << i << '\n';
}
The start and stop are both provided. Prints 10 11 12 13 14
for (auto i : range(10, 15)) {
cout << i << '\n';
}
The start, stop, and step are all provided. Prints 20 22 24 26 28
for (auto i : range(20, 30, 2)) {
cout << i << '\n';
}
Negative values are allowed as well. Prints 2 1 0 -1 -2
for (auto i : range(2, -3, -1)) {
cout << i << '\n';
}
A step size of 0 results in an empty range (Python's raises an exception). The following prints nothing
for (auto i : range(0, 10, 0)) {
cout << i << '\n';
}
In addition to normal integer range operations, doubles and other numeric types are supported through the template
Prints: 5.0 5.5 6.0 ... 9.5
for(auto i : range(5.0, 10.0, 0.5)) {
cout << i << '\n';
}
Implementation Note: Typical ranges have their current value incremented by
the step size repeatedly (value += step). Floating point range value are
recomputed at each step to avoid accumulating floating point inaccuracies
(value = start + (step * steps_taken). The result of the latter is a bit
slower but more accurate.
range also supports the following operations:
.size()to get the number of elements in the range (not enabled for floating point ranges).- Accessors for
.start(),.stop(), and.step(). - Indexing. Given a range
r,r[n]is thenth element in the range.
enumerate
Continually "yields" containers similar to pairs. They are structs with the
index in .first, and the element in .second, and also work with structured
binding declarations. Usage appears as:
vector<int> vec{2, 4, 6, 8};
for (auto&& [i, e] : enumerate(vec)) {
cout << i << ": " << e << '\n';
}
filter
Called as filter(predicate, iterable). The predicate can be any callable.
filter will only yield values that are true under the predicate.
Prints values greater than 4: 5 6 7 8
vector<int> vec{1, 5, 4, 0, 6, 7, 3, 0, 2, 8, 3, 2, 1};
for (auto&& i : filter([] (int i) { return i > 4; }, vec)) {
cout << i <<'\n';
}
If no predicate is passed, the elements themselves are tested for truth
Prints only non-zero values.
for(auto&& i : filter(vec)) {
cout << i << '\n';
}
filterfalse
Similar to filter, but only prints values that are false under the predicate.
Prints values not greater than 4: 1 4 3 2 3 2 1
vector<int> vec{1, 5, 4, 0, 6, 7, 3, 0, 2, 8, 3, 2, 1};
for (auto&& i : filterfalse([] (int i) { return i > 4; }, vec)) {
cout << i <<'\n';
}
If no predicate is passed, the elements themselves are te
