diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 00000000..f4abeaad --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +8.* diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..d9d9622a --- /dev/null +++ b/.clang-format @@ -0,0 +1,13 @@ +--- +Language: Cpp +BasedOnStyle: Google +AlignAfterOpenBracket: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +BreakBeforeBinaryOperators: NonAssignment +DerivePointerAlignment: false +NamespaceIndentation: All +FixNamespaceComments: false +IncludeBlocks: Preserve +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0d4fed27 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bazel-* +MODULE.bazel.lock diff --git a/.install-libcxx-travis.sh b/.install-libcxx-travis.sh new file mode 100644 index 00000000..98b18fb0 --- /dev/null +++ b/.install-libcxx-travis.sh @@ -0,0 +1,12 @@ +sudo apt-get install -y dpkg +wget -c http://launchpadlibrarian.net/360656578/libc++-helpers_6.0-2_all.deb +sudo dpkg -i libc++-helpers_6.0-2_all.deb +wget -c http://launchpadlibrarian.net/360656583/libc++abi1_6.0-2_amd64.deb +sudo dpkg -i libc++abi1_6.0-2_amd64.deb +wget -c http://launchpadlibrarian.net/360656580/libc++1_6.0-2_amd64.deb +sudo dpkg -i libc++1_6.0-2_amd64.deb +wget -c http://launchpadlibrarian.net/360656576/libc++-dev_6.0-2_amd64.deb +sudo dpkg -i libc++-dev_6.0-2_amd64.deb +wget -c http://launchpadlibrarian.net/360656581/libc++abi-dev_6.0-2_amd64.deb +sudo dpkg -i libc++abi-dev_6.0-2_amd64.deb +sudo apt-get install -f diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..349a41d2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,146 @@ +language: cpp +dist: xenial +sudo: require + +matrix: + include: + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial + packages: + - clang-5.0 + - clang++-5.0 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-5.0 + - USE_LIBCXX=1 + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial + packages: + - clang-6.0 + - clang++-6.0 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-6.0 + - USE_LIBCXX=1 + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-7 + packages: + - clang-7 + - clang++-7 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-7 + - USE_LIBCXX=1 + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-xenial-8 + packages: + - clang-8 + - clang++-8 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-8 + - USE_LIBCXX=1 + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + # travis complains about an unlisted source here if I do it with a plain + # llvm-toolchain-xenial-9 + - sourceline: 'deb https://apt.llvm.org/xenial/ llvm-toolchain-xenial-9 main' + key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' + packages: + - clang-9 + - clang++-9 + - valgrind + - gcc-8-base + - libc6 + - libgcc1 + - scons + env: + - COMPILER=clang++-9 + - USE_LIBCXX=1 + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-7 + - valgrind + - scons + env: + - COMPILER=g++-7 + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-8 + - valgrind + - scons + env: + - COMPILER=g++-8 + - os: linux + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-9 + - valgrind + - scons + env: + - COMPILER=g++-9 + +script: + - ${COMPILER} --version + - CXX="${COMPILER}" scons + - ./test_all + - valgrind ./test_all + + +before_script: + - if [ -n "${USE_LIBCXX}" ]; then + bash .install-libcxx-travis.sh; + fi + - cd test + - ./download_catch.sh diff --git a/BUILD b/BUILD new file mode 100644 index 00000000..f2fab7df --- /dev/null +++ b/BUILD @@ -0,0 +1,43 @@ +cc_library( + name = "cppitertools", + hdrs = [ + "cppitertools/accumulate.hpp", + "cppitertools/batched.hpp", + "cppitertools/chain.hpp", + "cppitertools/chunked.hpp", + "cppitertools/combinations.hpp", + "cppitertools/combinations_with_replacement.hpp", + "cppitertools/compress.hpp", + "cppitertools/count.hpp", + "cppitertools/cycle.hpp", + "cppitertools/dropwhile.hpp", + "cppitertools/enumerate.hpp", + "cppitertools/filter.hpp", + "cppitertools/filterfalse.hpp", + "cppitertools/groupby.hpp", + "cppitertools/imap.hpp", + "cppitertools/itertools.hpp", + "cppitertools/permutations.hpp", + "cppitertools/powerset.hpp", + "cppitertools/product.hpp", + "cppitertools/range.hpp", + "cppitertools/repeat.hpp", + "cppitertools/reversed.hpp", + "cppitertools/slice.hpp", + "cppitertools/sliding_window.hpp", + "cppitertools/sorted.hpp", + "cppitertools/starmap.hpp", + "cppitertools/takewhile.hpp", + "cppitertools/unique_everseen.hpp", + "cppitertools/unique_justseen.hpp", + "cppitertools/zip.hpp", + "cppitertools/zip_longest.hpp", + ], + srcs = [ + "cppitertools/internal/iter_tuples.hpp", + "cppitertools/internal/iterator_wrapper.hpp", + "cppitertools/internal/iteratoriterator.hpp", + "cppitertools/internal/iterbase.hpp", + ], + visibility = ["//visibility:public"], +) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..c7eb98ca --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.12) +project(cppitertools VERSION 2.0) + +# installation directories +set(cppitertools_INSTALL_INCLUDE_DIR "include" CACHE STRING "The installation include directory") +set(cppitertools_INSTALL_CMAKE_DIR "share" CACHE STRING "The installation cmake directory") + +# define a header-only library +add_library(cppitertools INTERFACE) +add_library(cppitertools::cppitertools ALIAS cppitertools) + +target_include_directories(cppitertools INTERFACE + $ + $) + +# require C++17 +target_compile_features(cppitertools INTERFACE cxx_std_17) + +# Make package findable +configure_file(cmake/dummy-config.cmake.in cppitertools-config.cmake @ONLY) + +# Enable version checks in find_package +include(CMakePackageConfigHelpers) +write_basic_package_version_file(cppitertools-config-version.cmake COMPATIBILITY SameMajorVersion) + +# install and export target +install( + TARGETS cppitertools + EXPORT cppitertools-targets) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cppitertools + DESTINATION ${cppitertools_INSTALL_INCLUDE_DIR}) + +install( + EXPORT cppitertools-targets + FILE cppitertools-config.cmake + NAMESPACE cppitertools:: + DESTINATION ${cppitertools_INSTALL_CMAKE_DIR}/cppitertools) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/cppitertools-config-version.cmake + DESTINATION ${cppitertools_INSTALL_CMAKE_DIR}/cppitertools) diff --git a/LICENSE.md b/LICENSE.md index 1d6598bb..3d3c6ef0 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) {{year}}, {{fullname}} +Copyright (c) 2013, Ryan Haining, Aaron Josephs, Google All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 00000000..b4a4568e --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,3 @@ +module( + name = "cppitertools" +) diff --git a/README.md b/README.md index 7c8222db..740dacbd 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,375 @@ 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. -range-based for loop add-ons inspired by the python builtins and itertools -library. +*Note*: Everything is inside the `iter` namespace. -##### Table of Contents +Follow [@cppitertools](https://twitter.com/cppitertools) for updates. + +#### Build and Test Status +Status | Compilers +---- | ---- +[![Travis Build Status](https://travis-ci.com/ryanhaining/cppitertools.svg?branch=master)](https://app.travis-ci.com/github/ryanhaining/cppitertools) | gcc-7 gcc-8 gcc-9 clang-5.0 clang-6.0 clang-7 clang-8 clang-9 +[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/ryanhaining/cppitertools?svg=true)](https://ci.appveyor.com/project/ryanhaining/cppitertools) | MSVC 2017 MSVC 2019 + +#### Table of Contents [range](#range)
+[enumerate](#enumerate)
+[zip](#zip)
+[zip\_longest](#zip)
+[imap](#imap)
[filter](#filter)
+[filterfalse](#filterfalse)
+[unique\_everseen](#unique_everseen)
+[unique\_justseen](#unique_justseen)
[takewhile](#takewhile)
[dropwhile](#dropwhile)
-[enumerate](#enumerate)
[cycle](#cycle)
-[zip](#zip)
+[repeat](#repeat)
+[count](#count)
+[groupby](#groupby)
+[starmap](#starmap)
+[accumulate](#accumulate)
+[compress](#compress)
+[sorted](#sorted)
[chain](#chain)
-[reverse](#reverse)
+[chain.from\_iterable](#chainfrom_iterable)
+[reversed](#reversed)
[slice](#slice)
+[sliding\_window](#sliding_window)
+[chunked](#chunked)
+[batched](#batched)
-##### Combinatoric fuctions +##### Combinatorial functions [product](#product)
[combinations](#combinations)
+[combinations\_with\_replacement](#combinations_with_replacement)
[permutations](#permutations)
[powerset](#powerset)
+#### 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 ` 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 ` 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 + +```sh +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 + +```sh +$ 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 + +```c++ +std::vector 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: + +```c++ +for (auto&& p : enumerate(std::vector{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` 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: + +```c++ +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 acheive the same effect of the python range -function. `range` can be used in three different ways: +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` -Only the stopping point is provided. Prints 0 1 2 3 4 5 6 7 8 9 ```c++ for (auto i : range(10)) { - cout << i << '\n'; + cout << i << '\n'; } ``` -The start and stop are both provided. Prints 10 11 12 13 14 +The start and stop are both provided. Prints `10 11 12 13 14` + ```c++ for (auto i : range(10, 15)) { - cout << i << '\n'; + cout << i << '\n'; } ``` -The start, stop, and step are all provided. Prints 20 22 24 26 28 +The start, stop, and step are all provided. Prints `20 22 24 26 28` + ```c++ for (auto i : range(20, 30, 2)) { - cout << i << '\n'; + cout << i << '\n'; } ``` -Negative values are allowed as well. Prints 2 1 0 -1 -2 +Negative values are allowed as well. Prints `2 1 0 -1 -2` + ```c++ for (auto i : range(2, -3, -1)) { - cout << i << '\n'; + cout << i << '\n'; +} +``` + +A step size of 0 results in an empty range (Python's raises an exception). +The following prints nothing + +```c++ +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` + +```c++ +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 the `n`th element in the range. + enumerate --------- - -Can be used with any class with an iterator. Continually "yields" containers -similar to pairs. They are basic structs with a .index and a .element. Usage -appears as: +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: ```c++ vector vec{2, 4, 6, 8}; -for (auto e : enumerate(vec)) { - cout << e.index - << ": " - << e.element - << '\n'; +for (auto&& [i, e] : enumerate(vec)) { + cout << i << ": " << e << '\n'; } ``` filter ------ -Called as `filter(predicate, iterable)`. The predicate can be any callable. +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 +Prints values greater than 4: `5 6 7 8` + ```c++ -vector vec{1, 5, 6, 7, 3, 2, 8, 3, 2, 1}; -for (auto i : filter([] (int i) { return i > 4; }, vec)) { - cout << i <<'\n'; +vector 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. + +```c++ +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` + +```c++ +vector 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 tested for truth. + +Prints only zero values. + +```c++ +for(auto&& i : filterfalse(vec)) { + cout << i << '\n'; +} +``` + +unique\_everseen +---------------- +*Additional Requirements*: Underlying values must be copy-constructible. + +This is a filter adaptor that only generates values that have never been seen +before. + +Prints `1 2 3 4 5 6 7 8 9` + +```c++ +vector v {1,2,3,4,3,2,1,5,6,7,7,8,9,8,9,6}; +for (auto&& i : unique_everseen(v)) { + cout << i << ' '; +} +``` + +`unique_everseen` uses an `undordered_set` so it needs hashable elements. For +types that don't work with `std::hash` or `std::equal_to`, `unique_everseen` +also provides an overload taking a hash callable and an equality callable. +This **does not** work with the pipe syntax. + +```c++ +vector v { /* ... */ }; +for (auto&& w : unique_everseen(v, WidgetHash{}, WidgetEq{})) { + cout << w.name() << ' '; +} +``` + +unique\_justseen +---------------- +Another filter adaptor that only omits consecutive duplicates. + +Prints `1 2 3 4 3 2 1` + +```c++ +vector v {1,1,1,2,2,3,3,3,4,3,2,1,1,1}; +for (auto&& i : unique_justseen(v)) { + cout << i << ' '; +} +``` + +If elements cannot be directly compared with equality, you can pass in a key +callable. + +```c++ +vector v { /* ... */ }; +for (auto&& p : unique_justseen(v, [] (const Person& p) { return p.name; })) { + cout << p.name() << ' ' << p.age() << '\n'; +} ``` takewhile @@ -92,101 +377,369 @@ takewhile Yields elements from an iterable until the first element that is false under the predicate is encountered. -Prints 1 2 3 4. (5 is false under the predicate) +Prints `1 2 3 4`. (5 is false under the predicate) + ```c++ vector ivec{1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1}; -for (auto i : takewhile([] (int i) {return i < 5;}, ivec)) { - cout << i << '\n'; +for (auto&& i : takewhile([] (int i) {return i < 5;}, ivec)) { + cout << i << '\n'; } ``` dropwhile --------- -Yields all elements after and including the first element that is false under +Yields all elements after and including the first element that is true under the predicate. -Prints 5 6 7 1 2 +Prints `5 6 7 1 2` + ```c++ vector ivec{1, 2, 3, 4, 5, 6, 7, 1, 2}; -for (auto i : dropwhile([] (int i) {return i < 5;}, ivec)) { - cout << i << '\n'; +for (auto&& i : dropwhile([] (int i) {return i < 5;}, ivec)) { + cout << i << '\n'; } ``` cycle ----- +*Additional Requirements*: Input must have a ForwardIterator -Repeatedly produce all values of an iterable. The loop will be infinite, so a -`break` is necessary to exit. +Repeatedly produces all values of an iterable. The loop will be infinite, so a +`break` or other control flow structure is necessary to exit. + +Prints `1 2 3` repeatedly until `some_condition` is true -Prints 1 2 3 repeatedly until `some_condition` is true ```c++ vector vec{1, 2, 3}; -for (auto i : cycle(vec)) { - cout << i << '\n'; - if (some_condition) { - break; - } +for (auto&& i : cycle(vec)) { + cout << i << '\n'; + if (some_condition) { + break; + } +} +``` + +repeat +------ +Repeatedly produces a single argument forever, or a given number of times. +`repeat` will bind a reference when passed an lvalue and move when given +an rvalue. It will then yield a reference to the same item until completion. + +The below prints `1` five times. + +```c++ +for (auto&& e : repeat(1, 5)) { + cout << e << '\n'; +} +``` + +The below prints `2` forever + +```c++ +for (auto&& e : repeat(2)) { + cout << e << '\n'; +} +``` + +count +----- +Effectively a `range` without a stopping point.
+`count()` with no arguments will start counting from 0 with a positive +step of 1.
+`count(i)` will start counting from `i` with a positive step of 1.
+`count(i, st)` will start counting from `i` with a step of `st`. + +*Technical limitations*: Unlike Python which can use its long integer +types when needed, `count()` would eventually exceed the +maximum possible value for its type (or minimum with a negative step). +`count` is actually implemented as a `range` with the stopping point +being the `std::numeric_limits::max()` for the integral type (`long` +by default) + +The below will print `0 1 2` ... etc + +```c++ +for (auto&& i : count()) { + cout << i << '\n'; +} +``` + +groupby +------- +*Additional Requirements*: If the Input's iterator's `operator*()` returns +a reference, the reference must remain valid after the iterator is incremented. +Roughly equivalent to requiring the Input have a ForwardIterator. + +Separate an iterable into groups sharing a common key. The following example +creates a new group whenever a string of a different length is encountered. + +```c++ +vector vec = { + "hi", "ab", "ho", + "abc", "def", + "abcde", "efghi" +}; + +for (auto&& gb : groupby(vec, [] (const string &s) {return s.length(); })) { + cout << "key: " << gb.first << '\n'; + cout << "content: "; + for (auto&& s : gb.second) { + cout << s << " "; + } + cout << '\n'; +} +``` + +*Note*: Just like Python's `itertools.groupby`, this doesn't do any sorting. +It just iterates through, making a new group each time there is a key change. +Thus, if the group is unsorted, the same key may appear multiple times. + +starmap +------- +Takes a sequence of tuple-like objects (anything that works with `std::get`) +and unpacks each object into individual arguments for each function call. +The below example takes a `vector` of `pairs` of ints, and passes them +to a function expecting two ints, with the elements of the `pair` being +the first and second arguments to the function. + +```c++ +vector> v = {{2, 3}, {5, 2}, {3, 4}}; // {base, exponent} +for (auto&& i : starmap([](int b, int e){return pow(b, e);}, v)) { + // ... } ``` +`starmap` can also work over a tuple-like object of tuple-like objects even +when the contained objects are different as long as the functor works with +multiple types of calls. For example, a `Callable` struct with overloads +for its `operator()` will work as long as all overloads have the same +return type + +```c++ +struct Callable { + int operator()(int i) const; + int operator()(int i, char c) const; + int operator()(double d, int i, char c) const; +}; +``` + +This will work with a tuple of mixed types + +```c++ +auto t = make_tuple( + make_tuple(5), // first form + make_pair(3, 'c'), // second + make_tuple(1.0, 1, '1')); // third +for (auto&& i : starmap(Callable{}, t)) { + // ... +} +``` + +accumulate +---------- +*Additional Requirements*: Type return from functor (with reference removed) +must be assignable. + +Differs from `std::accumulate` (which in my humble opinion should be named +`std::reduce` or `std::foldl`). It is similar to a functional reduce where one +can see all of the intermediate results. By default, it keeps a running sum. + +Prints: `1 3 6 10 15` + +```c++ +for (auto&& i : accumulate(range(1, 6))) { + cout << i << '\n'; +} +``` + +A second, optional argument may provide an alternative binary function +to compute results. The following example multiplies the numbers, rather +than adding them. + +Prints: `1 2 6 24 120` + +```c++ +for (auto&& i : accumulate(range(1, 6), std::multiplies{})) { + cout << i << '\n'; +} +``` + +Note: The intermediate result type must support default construction +and assignment. zip --- - -Takes an arbitrary number of ranges of different types and efficiently iterates over -them in parallel (so an iterator to each container is incremented simultaneously). -When you dereference an iterator to "zipped" range you get a tuple of iterators to -those containers. +Takes an arbitrary number of ranges of different types and efficiently iterates +over them in parallel (so an iterator to each container is incremented +simultaneously). When you dereference an iterator to "zipped" range you get a +tuple of the elements the iterators were holding. Example usage: + ```c++ -array i{{1,2,3,4}}; -vector f{1.2,1.4,12.3,4.5,9.9}; -vector s{"i","like","apples","alot","dude"}; -array d{{1.2,1.2,1.2,1.2,1.2}}; +array iseq{{1,2,3,4}}; +vector fseq{1.2,1.4,12.3,4.5,9.9}; +vector sseq{"i","like","apples","a lot","dude"}; +array dseq{{1.2,1.2,1.2,1.2,1.2}}; + +for (auto&& [i, f, s, d] : zip(iseq, fseq, sseq, dseq)) { + cout << i << ' ' << f << ' ' << s << ' ' << d << '\n'; + f = 2.2f; // modifies the underlying 'fseq' sequence +} +``` -for (auto e : zip(i,f,s,d)) { - cout << zip_get<0>(e) << ' ' - << zip_get<1>(e) << ' ' - << zip_get<2>(e) << ' ' - << zip_get<3>(e) << '\n'; - zip_get<1>(e)=2.2f; // modify the float array +zip\_longest +------------ +Terminates on the longest sequence instead of the shortest. +Repeatedly yields a tuple of `boost::optional`s where `T` is the type +yielded by the sequences' respective iterators. Because of its boost +dependency, `zip_longest` is not in `itertools.hpp` and must be included +separately. +The following loop prints either "Just \" or "Nothing" for each +element in each tuple yielded. + +```c++ +vector v1 = {0, 1, 2, 3}; +vector v2 = {10, 11}; +for (auto&& [x, y] : zip_longest(v1, v2)) { + cout << '{'; + if (x) { + cout << "Just " << *x; + } else { + cout << "Nothing"; + } + cout << ", "; + if (y) { + cout << "Just " << *y; + } else { + cout << "Nothing"; + } + cout << "}\n"; } ``` -`iter::zip_get` is used to readably dereference the iterators yielded +The output is: -a `zip_longest` also exists where the range terminates on the longest -range instead of the shortest. because of that you have to return a -`boost::optional` (`std::optional` when it is released) and cannot use -`zip_get` +``` +{Just 0, Just 10} +{Just 1, Just 11} +{Just 2, Nothing} +{Just 3, Nothing} +``` +imap +---- +Takes a function and one or more iterables. The number of iterables must +match the number of arguments to the function. Applies the function to +each element (or elements) in the iterable(s). Terminates on the shortest +sequence. + +Prints the squares of the numbers in vec: `1 4 9 16 25` +```c++ +vector vec{1, 2, 3, 4, 5}; +for (auto&& i : imap([] (int x) {return x * x;}, vec)) { + cout << i << '\n'; +} +``` + +With more than one sequence, the below adds corresponding elements from +each vector together, printing `11 23 35 47 59 71` + +```c++ +vector vec1{1, 3, 5, 7, 9, 11}; +vector vec2{10, 20, 30, 40, 50, 60}; +for (auto&& i : imap([] (int x, int y) { return x + y; }, vec1, vec2)) { + cout << i << '\n'; +} +``` + +*Note*: The name `imap` is chosen to prevent confusion/collision with +`std::map`, and because it is more related to `itertools.imap` than +the python builtin `map`. + +compress +-------- +Yields only the values corresponding to true in the selectors iterable. +Terminates on the shortest sequence. + +Prints `2 6` +```c++ +vector ivec{1, 2, 3, 4, 5, 6}; +vector bvec{false, true, false, false, false, true}; +for (auto&& i : compress(ivec, bvec) { + cout << i << '\n'; +} +``` + +sorted +------ +*Additional Requirements*: Input must have a ForwardIterator + +Allows iteration over a sequence in sorted order. `sorted` does +**not** produce a new sequence, copy elements, or modify the original +sequence. It only provides a way to iterate over existing elements. +`sorted` also takes an optional second +[comparator](http://en.cppreference.com/w/cpp/concept/Compare) +argument. If not provided, defaults to `std::less`.
+Iterables passed to sorted are required to have an iterator with +an `operator*() const` member. + +The below outputs `0 1 2 3 4`. + +```c++ +unordered_set nums{4, 0, 2, 1, 3}; +for (auto&& i : sorted(nums)) { + cout << i << '\n'; +} +``` chain ----- +*Additional Requirements*: The underlying iterators of all containers' +`operator*` must have the *exact* same type This can chain any set of ranges together as long as their iterators dereference to the same type. ```c++ -vector empty{}; -vector vec1{1,2,3,4,5,6}; -array arr1{{7,8,9,10}}; +vector empty{}; +vector vec1{1,2,3,4,5,6}; +array arr1{{7,8,9,10}}; -for (auto i : chain(empty,vec1,arr1)) { - cout << i << '\n'; +for (auto&& i : chain(empty,vec1,arr1)) { + cout << i << '\n'; } ``` -reverse -------- +chain.from\_iterable +-------------------- +Similar to chain, but rather than taking a variadic number of iterables, +it takes an iterable of iterables and chains the contained iterables together. +A simple example is shown below using a vector of vectors to represent +a 2d ragged array, and prints it in row-major order. + +```c++ +vector> matrix = { + {1, 2, 3}, + {4, 5}, + {6, 8, 9, 10, 11, 12} +}; + +for (auto&& i : chain.from_iterable(matrix)) { + cout << i << '\n'; +} +``` + +reversed +-------- +*Additional Requirements*: Input must be compatible with `std::rbegin()` and +`std::rend()` Iterates over elements of a sequence in reverse order. ```c++ -for (auto i : reverse(a)) { - cout << i << '\n'; +for (auto&& i : reversed(a)) { + cout << i << '\n'; } ``` @@ -197,75 +750,205 @@ Returns selected elements from a range, parameters are start, stop and step. the range returned is [start,stop) where you only take every step element This outputs `0 3 6 9 12` + ```c++ vector a{0,1,2,3,4,5,6,7,8,9,10,11,12,13}; -for (auto i : slice(a,0,15,3)) { - cout << i << '\n'; +for (auto&& i : slice(a,0,15,3)) { + cout << i << '\n'; +} +``` + +sliding\_window +--------------- +*Additional Requirements*: Input must have a ForwardIterator + +Takes a section from a range and increments the whole section. If the +window size is larger than the length of the input, the `sliding_window` will +yield nothing (begin == end). + +Example: +`[1, 2, 3, 4, 5, 6, 7, 8, 9]` + +take a section of size 4, output is: +``` +1 2 3 4 +2 3 4 5 +3 4 5 6 +4 5 6 7 +5 6 7 8 +6 7 8 9 +``` + +Example Usage: + +```c++ +vector v = {1,2,3,4,5,6,7,8,9}; +for (auto&& sec : sliding_window(v,4)) { + for (auto&& i : sec) { + cout << i << ' '; + i.get() = 90; + } + cout << '\n'; } ``` +chunked +------- +chunked will yield subsequent chunks of an iterable in blocks of a specified +size. The final chunk may be shorter than the rest if the chunk size given +does not evenly divide the length of the iterable. + +Example usage: + +```c++ +vector v {1,2,3,4,5,6,7,8,9}; +for (auto&& sec : chunked(v,4)) { + for (auto&& i : sec) { + cout << i << ' '; + } + cout << '\n'; +} +``` + +The above prints: + +``` +1 2 3 4 +5 6 7 8 +9 +``` + +batched +------- +batched will yield a given number N of batches containing subsequent elements from an iterable, +assuming the iterable contains at least N elements. +The size of each batch is immaterial, but the implementation guarantees that no two batches will +differ in size by more than 1. + +Example usage: + +```c++ +vector v {1,2,3,4,5,6,7,8,9}; +for (auto&& sec : batched(v,4)) { + for (auto&& i : sec) { + cout << i << ' '; + } + cout << '\n'; +} +``` + +The above prints: + +``` +1 2 3 +4 5 +6 7 +8 9 +``` + product ------- +------- +*Additional Requirements*: Input must have a ForwardIterator + +Generates the cartesian product of the given ranges put together. -Generates the cartesian project of the given ranges put together +Example usage: -Example usage: ```c++ -std::vector v1{1,2,3}; -std::vector v2{7,8}; -std::vector v3{"the","cat"}; -std::vector v4{"hi","what","up","dude"}; -for (auto t : product(v1,v2,v3,v4)) { - std::cout << std::get<0>(t) << ", " - << std::get<1>(t) << ", " - << std::get<2>(t) << ", " - << std::get<3>(t) << std::endl; -} +vector v1{1,2,3}; +vector v2{7,8}; +vector v3{"the","cat"}; +vector v4{"hi","what's","up","dude"}; +for (auto&& [a, b, c, d] : product(v1,v2,v3,v4)) { + cout << a << ", " << b << ", " << c << ", " << d << '\n'; +} +``` + +Product also accepts a "repeat" as a template argument. Currently this is the +only way to do repeats. **If you are reading this and need `product(seq, 3)` +instead of `product<3>(seq)` please open an issue**. + +Example usage: + +```c++ +std::string s = "abc"; +// equivalent of product(s, s, s); +for (auto&& t : product<3>(s)) { + // ... +} ``` combinations ------------ +------------ +*Additional Requirements*: Input must have a ForwardIterator -Generates n length unique sequences of the input range, there is also a -combinations_with_replacement +Generates n length unique sequences of the input range. Example usage: + ```c++ -std::vector v = {1,2,3,4,5}; -for (auto i : combinations(v,3)) { - //std::cout << i << std::endl; - for (auto j : i ) std::cout << j << " "; - std::cout< v = {1,2,3,4,5}; +for (auto&& i : combinations(v,3)) { + for (auto&& j : i ) cout << j << " "; + cout << '\n'; } ``` +combinations\_with\_replacement +------------------------------- +*Additional Requirements*: Input must have a ForwardIterator + +Like combinations, but with replacement of each element. The +below is printed by the loop that follows: + +``` +{A, A} +{A, B} +{A, C} +{B, B} +{B, C} +{C, C} +``` + +```c++ +for (auto&& v : combinations_with_replacement(s, 2)) { + cout << '{' << v[0] << ", " << v[1] << "}\n"; +} +``` permutations ------------ +------------ +*Additional Requirements*: Input must have a ForwardIterator. Iterator must +have an `operator*() const`. -Generates all the permutations of a range using `std::next_permutation` +Generates all the permutations of a range using `std::next_permutation`. Example usage: + ```c++ -std::vector v = {1,2,3,4,5}; -for (auto vec : permutations(v)) { - for (auto i : vec) { - std::cout << i << " "; - } - std::cout << std::endl; -} +vector v = {1,2,3,4,5}; +for (auto&& vec : permutations(v)) { + for (auto&& i : vec) { + cout << i << ' '; + } + cout << '\n'; +} ``` powerset -------- +-------- +*Additional Requirements*: Input must have a ForwardIterator -Generates every possible subset of a set, never run it since it runs in 𝚯(2^n). +Generates every possible subset of a set, runs in O(2^n). Example usage: + ```c++ -std::vector vec {1,2,3,4,5,6,7,8,9}; -for (auto v : powerset(vec)) { - for (auto i : v) std::cout << i << " "; - std::cout << std::endl; +vector vec {1,2,3,4,5,6,7,8,9}; +for (auto&& v : powerset(vec)) { + for (auto&& i : v) { + cout << i << " "; + } + cout << '\n'; } ``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..201281d4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +If you have discovered a security vulnerability in this project, please report it +privately. **Do not disclose it as a public issue.** This gives me time to work with you +to fix the issue before public exposure, reducing the chance that the exploit will be +used before a patch is released. + +You may submit the report in the following ways: + +- send an email to haining.cpp@gmail.com; and/or +- send me a [private vulnerability report](https://github.com/ryanhaining/cppitertools/security/advisories/new) + +Please provide the following information in your report: + +- A description of the vulnerability and its impact +- How to reproduce the issue + +This project is maintained by a single maintainer on a reasonable-effort basis. As such, +I ask that you give me 90 days to work on a fix before public exposure. diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 00000000..e69de29b diff --git a/chain.hpp b/chain.hpp deleted file mode 100644 index a825e1d5..00000000 --- a/chain.hpp +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef CHAIN_HPP -#define CHAIN_HPP - -#include "iterator_range.hpp" -#include - -namespace iter { - template - struct chain_iter; - template - struct chain_iter { - - private: - using Iterator = decltype(std::declval().begin()); - Iterator begin; - const Iterator end;//never really used but kept it for consistency - - public: - chain_iter(Container & container, bool is_end=false) : - begin(container.begin()),end(container.end()) { - if(is_end) begin = container.end(); - } - chain_iter & operator++() - { - ++begin; - return *this; - } - auto operator*()->decltype(*begin) - { - return *begin; - } - bool operator!=(const chain_iter & rhs) const{ - return this->begin != rhs.begin; - } - }; - template - struct chain_iter - { - private: - using Iterator = decltype(std::declval().begin()); - Iterator begin; - const Iterator end; - bool end_reached = false; - chain_iter next_iter; - - public: - chain_iter(Container & container, Containers& ... rest, bool is_end=false) : - begin(container.begin()), - end(container.end()), - next_iter(rest...,is_end) { - if(is_end) - begin = container.end(); - } - chain_iter & operator++() - { - if (begin == end) { - ++next_iter; - } - else { - ++begin; - } - return *this; - } - auto operator*()->decltype(*begin) - { - if (begin == end) { - return *next_iter; - } - else { - return *begin; - } - } - bool operator !=(const chain_iter & rhs) const { - if (begin == end) { - return this->next_iter != rhs.next_iter; - } - else - return this->begin != rhs.begin; - } - }; - template - iterator_range> chain(Containers& ... containers) - { - auto begin = - chain_iter(containers...); - auto end = - chain_iter(containers...,true); - return - iterator_range>(begin,end); - } -} - -#endif //CHAIN_HPP diff --git a/cmake/dummy-config.cmake.in b/cmake/dummy-config.cmake.in new file mode 100644 index 00000000..d8c788f1 --- /dev/null +++ b/cmake/dummy-config.cmake.in @@ -0,0 +1,5 @@ +# Dummy config file +# When a dependency is added with add_subdirectory, but searched with find_package + +# Redirect to the directory added with add_subdirectory +add_subdirectory(@PROJECT_SOURCE_DIR@ @PROJECT_BINARY_DIR@) \ No newline at end of file diff --git a/combinations.hpp b/combinations.hpp deleted file mode 100644 index fab96317..00000000 --- a/combinations.hpp +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef COMBINATIONS_HPP -#define COMBINATIONS_HPP - -#include "iterator_range.hpp" - -#include -#include -#include -#include -#include - -namespace iter { - //Could try having antoher template for container to return (right now it's - //just an std::vector) - template - struct combinations_iter; - - template - iterator_range> - combinations(const Container & container, size_t N) { - auto begin = combinations_iter(container,N); - auto end = combinations_iter(container,N); - return - iterator_range>(begin,end); - } - template - struct combinations_iter - { - private: - const Container & items; - std::vector indicies; - bool not_done = true; - - public: - //Holy shit look at this typedef - using item_t = typename - std::remove_const< - typename std::remove_reference::type>::type; - combinations_iter(const Container & i, size_t N) : - items(i),indicies(N) - { - if (N == 0) { - not_done = false; - return; - } - size_t inc = 0; - for (auto & iter : indicies) { - if (items.cbegin() + inc != items.cend()) { - iter = items.cbegin()+inc; - ++inc; - } - else { - not_done = false; - break; - } - } - } - std::vector operator*() const - { - std::vector values; - for (auto i : indicies) { - values.push_back(*i); - } - return values; - } - - - combinations_iter & - operator++() - { - for (auto iter = indicies.rbegin(); iter != indicies.rend(); ++iter) { - ++(*iter); - //what we have to check here is if the distance between the - //index and the end of indicies is >= the distance between - //the item and end of item - if ((*iter + std::distance(indicies.rbegin(),iter)) == - items.cend()) { - if ( (iter + 1) != indicies.rend()) { - size_t inc = 1; - for (auto down = iter; down != indicies.rbegin()-1;--down) { - (*down) = (*(iter + 1)) + 1 + inc; - /*if (*down == items.cend()) { - iter = iter + 1; - }*/ - ++inc; - } - } - else { - not_done = false; - break; - } - } - else break; - //we break because none of the rest of the items need to - //be incremented - } - return *this; - } - - bool operator !=(const combinations_iter &) - { - //because of the way this is done you have to start from the - //begining of the range and end at the end, you could break in - //the middle of the loop though, it's not different from the way - //that python's works - return not_done; - } - }; -} - -#endif //COMBINATIONS_HPP diff --git a/combinations_with_replacement.hpp b/combinations_with_replacement.hpp deleted file mode 100644 index 4196a491..00000000 --- a/combinations_with_replacement.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef COMBINATIONS_WITH_REPLACEMENT_HPP -#define COMBINATIONS_WITH_REPLACEMENT_HPP - -#include "iterator_range.hpp" - -#include -#include -#include - -namespace iter { - //if size isn't passed as template argument would have to switch to vectors - //for everything, generally I would say you don't need to decide the amount - //of items in your combination at runtime, but rather it is a way to view - //a list based on the problem your solving, that being said it would be easy - //to make it decided at runtime - template - struct combinations_with_replacement_iter; - - template - iterator_range> - combinations_with_replacement(const Container & container, size_t N) { - auto begin = combinations_with_replacement_iter(container, N); - auto end = combinations_with_replacement_iter(container, N); - return - iterator_range>(begin,end); - } - template - struct combinations_with_replacement_iter - { - private: - const Container & items; - std::vector indicies; - bool not_done = true; - - public: - //Holy shit look at this typedef - using item_t = typename - std::remove_const< - typename std::remove_reference::type>::type; - combinations_with_replacement_iter(const Container & i, size_t N) : - items(i), indicies(N) - { - if (N == 0) { - not_done = false; - return; - } - for (auto & iter : indicies) iter = items.cbegin(); - } - //technically should be a dynarray - std::vector operator*()const - { - std::vector values; - for (auto i : indicies) { - values.push_back(*i); - } - return values; - } - - - combinations_with_replacement_iter & - operator++() - { - for (auto iter = indicies.rbegin(); iter != indicies.rend(); ++iter) { - ++(*iter); - if (*iter == items.cend()) { - if ( (iter + 1) != indicies.rend()) { - for (auto down = iter; down != indicies.rbegin()-1;--down) { - (*down) = (*(iter + 1)) + 1; - } - } - else { - not_done = false; - break; - } - } - else break; - //we break because none of the rest of the items need to - //be incremented - } - return *this; - } - - bool operator !=(const combinations_with_replacement_iter &) - { - //because of the way this is done you have to start from the - //begining of the range and end at the end, you could break in - //the middle of the loop though, it's not different from the way - //that python's works - return not_done; - } - }; -} - -#endif //COMBINATIONS_WITH_REPLACEMENT_HPP diff --git a/compress.hpp b/compress.hpp deleted file mode 100644 index 92761729..00000000 --- a/compress.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef COMPRESS__H__ -#define COMPRESS__H__ - -#include - -// TODO everything in here - -namespace iter { - - //Forward declarations of Compressed and compress - template - class Compressed; - - template - Compressed compress(Container &, Selector &); - - - template - class Compressed { - // The only thing allowed to directly instantiate an Compressed is - // the compress function - friend Compressed compress( - Container &, Selector &); - - // Type of the Container::Iterator, but since the name of that - // iterator can be anything, we have to grab it with this - using contained_iter_type = - decltype(std::declval().begin()); - - // The type returned when dereferencing the Container::Iterator - using contained_iter_ret = - decltype(std::declval().operator*()); - - // Selector::Iterator type - using selector_iter_type = - decltype(std::declval().begin()); - - private: - Container & container; - Selector & selectors; - - // Value constructor for use only in the compress function - Compressed(Container & container, Selector & selectors) : - container(container), - selectors(selectors) - { } - Compressed () = delete; - Compressed & operator=(const Compressed &) = delete; - - public: - Compressed (const Compressed &) = default; - - class Iterator { - private: - contained_iter_type sub_iter; - const contained_iter_type sub_end; - - selector_iter_type selector_iter; - const selector_iter_type selector_end; - - void increment_iterators() { - ++this->sub_iter; - ++this->selector_iter; - } - - void skip_failures() { - while (this->sub_iter != this->sub_end && - !*this->selector_iter) { - this->increment_iterators(); - } - } - - public: - Iterator (contained_iter_type cont_iter, - contained_iter_type cont_end, - selector_iter_type sel_iter, - selector_iter_type sel_end) : - sub_iter(cont_iter), - sub_end(cont_end), - selector_iter(sel_iter), - selector_end(sel_end) - { - this->skip_failures(); - } - - contained_iter_ret operator*() const { - return *this->sub_iter; - } - - Iterator & operator++() { - this->increment_iterators(); - this->skip_failures(); - return *this; - } - - bool operator!=(const Iterator & other) const { - return this->sub_iter != other.sub_iter && - this->selector_iter != other.selector_iter; - } - }; - - Iterator begin() const { - return Iterator( - this->container.begin(), this->container.end(), - this->selectors.begin(), this->selectors.end()); - } - - Iterator end() const { - return Iterator( - this->container.end(), this->container.end(), - this->selectors.end(), this->selectors.end()); - } - - }; - - // Helper function to instantiate an Compressed - template - Compressed compress( - Container & container, Selector & selectors) { - return Compressed(container, selectors); - } - -} - -#endif //ifndef COMPRESS__H__ diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 00000000..114b620f --- /dev/null +++ b/conanfile.py @@ -0,0 +1,44 @@ +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps + + +class CppIterTools(ConanFile): + name = 'cppitertools' + version = '3.0' + author = 'Ryan Haining ' + homepage = 'https://github.com/ryanhaining/cppitertools' + url = homepage + topics = ('itertools', 'cppitertools') + license = "BSD 2-Clause 'Simplified' License" + description = '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.' + settings = 'build_type', 'compiler', 'os', 'arch' + exports = 'LICENSE.md' + + exports_sources = ( + 'cppitertools/*', + 'cppitertools/internal/*', + 'CMakeLists.txt', + 'cmake/dummy-config.cmake.in') + + def layout(self): + cmake_layout(self) + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + tc = CMakeToolchain(self) + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.bindirs = [] + self.cpp_info.libdirs = [] diff --git a/cppitertools/accumulate.hpp b/cppitertools/accumulate.hpp new file mode 100644 index 00000000..2c5bc54e --- /dev/null +++ b/cppitertools/accumulate.hpp @@ -0,0 +1,120 @@ +#ifndef ITER_ACCUMULATE_H_ +#define ITER_ACCUMULATE_H_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class Accumulator; + + using AccumulateFn = IterToolFnOptionalBindSecond>; + } + inline constexpr impl::AccumulateFn accumulate{}; +} + +template +class iter::impl::Accumulator { + private: + Container container_; + mutable AccumulateFunc accumulate_func_; + + friend AccumulateFn; + + using AccumVal = std::remove_cv_t< + std::remove_reference_t, iterator_deref>>>; + + Accumulator(Container&& container, AccumulateFunc accumulate_func) + : container_(std::forward(container)), + accumulate_func_(accumulate_func) {} + + public: + Accumulator(Accumulator&&) = default; + + template + class Iterator { + private: + template + friend class Iterator; + IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + AccumulateFunc* accumulate_func_; + std::optional acc_val_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = AccumVal; + using difference_type = std::ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end, AccumulateFunc& accumulate_fun) + : sub_iter_{std::move(sub_iter)}, + sub_end_{std::move(sub_end)}, + accumulate_func_(&accumulate_fun), + // only get first value if not an end iterator + acc_val_{!(sub_iter_ != sub_end_) + ? std::nullopt + : std::make_optional(*sub_iter_)} {} + + reference operator*() const { + return *acc_val_; + } + + pointer operator->() const { + return &*acc_val_; + } + + Iterator& operator++() { + ++sub_iter_; + if (sub_iter_ != sub_end_) { + *acc_val_ = std::invoke(*accumulate_func_, *acc_val_, *sub_iter_); + } + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return sub_iter_ != other.sub_iter_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_), accumulate_func_}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), accumulate_func_}; + } + Iterator> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_)), accumulate_func_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), accumulate_func_}; + } +}; + +#endif diff --git a/cppitertools/batched.hpp b/cppitertools/batched.hpp new file mode 100644 index 00000000..f3d00ece --- /dev/null +++ b/cppitertools/batched.hpp @@ -0,0 +1,163 @@ +#ifndef ITER_BATCHED_HPP_ +#define ITER_BATCHED_HPP_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iteratoriterator.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class Batcher; + + using BatchedFn = IterToolFnBindSizeTSecond; + } + inline constexpr impl::BatchedFn batched{}; +} + +template +class iter::impl::Batcher { + private: + Container container_; + std::size_t num_batches_; + + Batcher(Container&& container, std::size_t const num_batches) + : container_(std::forward(container)), num_batches_{num_batches} {} + + friend BatchedFn; + + template + using IndexVector = std::vector>; + template + using DerefVec = IterIterWrapper>; + + public: + Batcher(Batcher&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + std::shared_ptr> batch_ = + std::make_shared>(); + IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + std::size_t num_batches_; + std::size_t size_; + std::size_t count_; + + bool done() const { + return batch_->empty(); + } + + void refill_batch() { + batch_->get().clear(); + if (count_ < num_batches_) { + std::size_t const batch_size(size_ / num_batches_ + std::min(1, (size_ % num_batches_) / (count_ + 1))); + batch_->get().reserve(batch_size); + for (std::size_t i = 0; i < batch_size; ++i) { + batch_->get().push_back(sub_iter_); + ++sub_iter_; + } + ++count_; + } + } + + public: + using iterator_category = std::input_iterator_tag; + using value_type = DerefVec; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + template + struct distance_helper { + static constexpr difference_type distance(Iter1 it1, Iter2 it2) { + difference_type dist(0); + for (; it1 != it2; ++it1) + ++dist; + return dist; + } + }; + + template + struct distance_helper && std::is_arithmetic_v>> { + static constexpr difference_type distance(Iter1 it1, Iter2 it2) { + return std::distance(it1, it2); + } + }; + + template + difference_type distance(Iter1 it1, Iter2 it2) const { + return distance_helper::distance(it1, it2); + } + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end, std::size_t num_batches) + : sub_iter_{std::move(sub_iter)}, + sub_end_{std::move(sub_end)}, + num_batches_{num_batches}, + size_{static_cast(distance(sub_iter_, sub_end_))}, + count_{0} { + refill_batch(); + } + + Iterator& operator++() { + refill_batch(); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + template + bool operator==(const Iterator& other) const { + return done() == other.done() + && (done() || !(sub_iter_ != other.sub_iter_)); + } + + DerefVec& operator*() { + return *batch_; + } + + DerefVec* operator->() { + return batch_.get(); + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_), num_batches_}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), num_batches_}; + } + + Iterator> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_)), num_batches_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), num_batches_}; + } +}; + +#endif diff --git a/cppitertools/chain.hpp b/cppitertools/chain.hpp new file mode 100644 index 00000000..05557387 --- /dev/null +++ b/cppitertools/chain.hpp @@ -0,0 +1,349 @@ +#ifndef ITER_CHAIN_HPP_ +#define ITER_CHAIN_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "internal/iter_tuples.hpp" +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +namespace iter { + namespace impl { + template + class Chained; + + template + class ChainedFromIterable; + + using ChainFromIterableFn = IterToolFn; + + // rather than a chain function, use a callable object to support + // from_iterable + class ChainMaker; + + template + struct AsTupleOfConstImpl; + + template + struct AsTupleOfConstImpl> + : type_is...>> {}; + + template + using AsTupleOfConst = typename AsTupleOfConstImpl::type; + } +} + +template +class iter::impl::Chained { + private: + friend ChainMaker; + + template + class IteratorDataPair { + IteratorDataPair() = delete; + + public: + using IterTupTypeA = iterator_tuple_type; + using IterTupTypeB = iterator_tuple_type; + + template + static bool get_and_check_not_equal( + const IterTupTypeA& lhs, const IterTupTypeB& rhs) { + return std::get(lhs) != std::get(rhs); + } + + using NeqFunc = bool (*)(const IterTupTypeA&, const IterTupTypeB&); + + constexpr static std::array neq_comparers{ + {get_and_check_not_equal...}}; + }; + + template + class IteratorData { + IteratorData() = delete; + static_assert( + std::tuple_size>::value == sizeof...(Is), + "tuple size != sizeof Is"); + + static_assert( + are_same>...>::value, + "All chained iterables must have iterators that " + "dereference to the same type, including cv-qualifiers " + "and references."); + + public: + using IterTupType = iterator_tuple_type; + using DerefType = iterator_deref>; + using ArrowType = iterator_arrow>; + + template + static DerefType get_and_deref(IterTupType& iters) { + return *std::get(iters); + } + + template + static ArrowType get_and_arrow(IterTupType& iters) { + return apply_arrow(std::get(iters)); + } + + template + static void get_and_increment(IterTupType& iters) { + ++std::get(iters); + } + + using DerefFunc = DerefType (*)(IterTupType&); + using ArrowFunc = ArrowType (*)(IterTupType&); + using IncFunc = void (*)(IterTupType&); + + constexpr static std::array derefers{ + {get_and_deref...}}; + + constexpr static std::array arrowers{ + {get_and_arrow...}}; + + constexpr static std::array incrementers{ + {get_and_increment...}}; + + using TraitsValue = + iterator_traits_deref>; + }; + + Chained(TupType&& t) : tup_(std::move(t)) {} + TupType tup_; + + public: + Chained(Chained&&) = default; + + template + class Iterator { + private: + using IterData = IteratorData; + std::size_t index_; + typename IterData::IterTupType iters_; + typename IterData::IterTupType ends_; + + void check_for_end_and_adjust() { + while (index_ < sizeof...(Is) + && !(IteratorDataPair::neq_comparers[index_]( + iters_, ends_))) { + ++index_; + } + } + + public: + template + friend class Iterator; + + using iterator_category = std::input_iterator_tag; + using value_type = typename IteratorData::TraitsValue; + using difference_type = std::ptrdiff_t; + using pointer = typename IteratorData::ArrowType; + using reference = typename IteratorData::DerefType; + + Iterator(std::size_t i, typename IterData::IterTupType&& iters, + typename IterData::IterTupType&& ends) + : index_{i}, iters_(std::move(iters)), ends_(std::move(ends)) { + check_for_end_and_adjust(); + } + + decltype(auto) operator*() { + return IterData::derefers[index_](iters_); + } + + decltype(auto) operator->() { + return IterData::arrowers[index_](iters_); + } + + Iterator& operator++() { + IterData::incrementers[index_](iters_); + check_for_end_and_adjust(); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return index_ != other.index_ + || (index_ != sizeof...(Is) + && IteratorDataPair::neq_comparers[index_]( + iters_, other.iters_)); + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + }; + + Iterator begin() { + return {0, {get_begin(std::get(tup_))...}, + {get_end(std::get(tup_))...}}; + } + + Iterator end() { + return {sizeof...(Is), {get_end(std::get(tup_))...}, + {get_end(std::get(tup_))...}}; + } + + Iterator> begin() const { + return {0, {get_begin(std::as_const(std::get(tup_)))...}, + {get_end(std::as_const(std::get(tup_)))...}}; + } + + Iterator> end() const { + return {sizeof...(Is), {get_end(std::as_const(std::get(tup_)))...}, + {get_end(std::as_const(std::get(tup_)))...}}; + } +}; + +template +class iter::impl::ChainedFromIterable { + private: + friend ChainFromIterableFn; + Container container_; + ChainedFromIterable(Container&& container) + : container_(std::forward(container)) {} + + public: + ChainedFromIterable(ChainedFromIterable&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + using SubContainer = iterator_deref; + using SubIter = IteratorWrapper; + + IteratorWrapper top_level_iter_; + IteratorWrapper top_level_end_; + DerefHolder sub_iterable_; + std::optional sub_iter_p_; + std::optional sub_end_p_; + + void advance_while_empty_sub_iterable() { + while (top_level_iter_ != top_level_end_ && sub_iter_p_ == sub_end_p_) { + ++top_level_iter_; + update_sub_iterable(); + } + } + + void update_sub_iterable() { + if (top_level_iter_ != top_level_end_) { + sub_iterable_.reset(*top_level_iter_); + sub_iter_p_ = + std::make_optional(get_begin(sub_iterable_.get())); + sub_end_p_ = std::make_optional(get_end(sub_iterable_.get())); + } else { + sub_iter_p_.reset(); + sub_end_p_.reset(); + } + } + + void next_sub_iterable() { + update_sub_iterable(); + advance_while_empty_sub_iterable(); + } + + public: + using iterator_category = std::input_iterator_tag; + using value_type = iterator_traits_deref>; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(IteratorWrapper&& top_iter, + IteratorWrapper&& top_end) + : top_level_iter_{std::move(top_iter)}, + top_level_end_{std::move(top_end)} { + next_sub_iterable(); + } + + Iterator& operator++() { + ++*sub_iter_p_; + if (!(*sub_iter_p_ != *sub_end_p_)) { + ++top_level_iter_; + next_sub_iterable(); + } + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return top_level_iter_ != other.top_level_iter_ + || sub_iter_p_ != other.sub_iter_p_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + + iterator_deref> operator*() { + return **sub_iter_p_; + } + + iterator_arrow> operator->() { + return apply_arrow(*sub_iter_p_); + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_)}; + } + + Iterator end() { + return {get_end(container_), get_end(container_)}; + } + + Iterator> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_))}; + } + + Iterator> end() const { + return { + get_end(std::as_const(container_)), get_end(std::as_const(container_))}; + } +}; + +class iter::impl::ChainMaker { + private: + template + Chained chain_impl( + TupleType&& containers, std::index_sequence) const { + return {std::move(containers)}; + } + + public: + // expose regular call operator to provide usual chain() + template + auto operator()(Containers&&... cs) const { + return chain_impl( + std::tuple{std::forward(cs)...}, + std::index_sequence_for{}); + } + + ChainFromIterableFn from_iterable; +}; + +namespace iter { + inline constexpr auto chain = iter::impl::ChainMaker{}; +} + +#endif diff --git a/cppitertools/chunked.hpp b/cppitertools/chunked.hpp new file mode 100644 index 00000000..13fa652f --- /dev/null +++ b/cppitertools/chunked.hpp @@ -0,0 +1,135 @@ +#ifndef ITER_CHUNKED_HPP_ +#define ITER_CHUNKED_HPP_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iteratoriterator.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class Chunker; + + using ChunkedFn = IterToolFnBindSizeTSecond; + } + inline constexpr impl::ChunkedFn chunked{}; +} + +template +class iter::impl::Chunker { + private: + Container container_; + std::size_t chunk_size_; + + Chunker(Container&& container, std::size_t sz) + : container_(std::forward(container)), chunk_size_{sz} {} + + friend ChunkedFn; + + template + using IndexVector = std::vector>; + template + using DerefVec = IterIterWrapper>; + + public: + Chunker(Chunker&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + std::shared_ptr> chunk_ = + std::make_shared>(); + IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + std::size_t chunk_size_ = 0; + + bool done() const { + return chunk_->empty(); + } + + void refill_chunk() { + chunk_->get().clear(); + std::size_t i{0}; + while (i < chunk_size_ && sub_iter_ != sub_end_) { + chunk_->get().push_back(sub_iter_); + ++sub_iter_; + ++i; + } + } + + public: + using iterator_category = std::input_iterator_tag; + using value_type = DerefVec; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end, std::size_t s) + : sub_iter_{std::move(sub_iter)}, + sub_end_{std::move(sub_end)}, + chunk_size_{s} { + chunk_->get().reserve(chunk_size_); + refill_chunk(); + } + + Iterator& operator++() { + refill_chunk(); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + template + bool operator==(const Iterator& other) const { + return done() == other.done() + && (done() || !(sub_iter_ != other.sub_iter_)); + } + + DerefVec& operator*() { + return *chunk_; + } + + DerefVec* operator->() { + return chunk_.get(); + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_), chunk_size_}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), chunk_size_}; + } + + Iterator> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_)), chunk_size_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), chunk_size_}; + } +}; + +#endif diff --git a/cppitertools/combinations.hpp b/cppitertools/combinations.hpp new file mode 100644 index 00000000..cdee9cdd --- /dev/null +++ b/cppitertools/combinations.hpp @@ -0,0 +1,169 @@ +#ifndef ITER_COMBINATIONS_HPP_ +#define ITER_COMBINATIONS_HPP_ + +#include "internal/iteratoriterator.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include + +namespace iter { + namespace impl { + template + class Combinator; + + using CombinationsFn = IterToolFnBindSizeTSecond; + } + inline constexpr impl::CombinationsFn combinations{}; +} + +template +class iter::impl::Combinator { + private: + Container container_; + std::size_t length_; + + friend CombinationsFn; + + Combinator(Container&& container, std::size_t length) + : container_(std::forward(container)), length_{length} {} + + template + using IndexVector = std::vector>; + template + using CombIteratorDeref = IterIterWrapper>; + + public: + Combinator(Combinator&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + constexpr static const int COMPLETE = -1; + std::remove_reference_t* container_p_; + CombIteratorDeref indices_; + int steps_{}; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = CombIteratorDeref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(ContainerT& container, std::size_t n) + : container_p_{&container}, indices_{n} { + if (n == 0) { + steps_ = COMPLETE; + return; + } + size_t inc = 0; + for (auto& iter : indices_.get()) { + auto it = get_begin(*container_p_); + dumb_advance(it, get_end(*container_p_), inc); + if (it != get_end(*container_p_)) { + iter = it; + ++inc; + } else { + steps_ = COMPLETE; + break; + } + } + } + + static Iterator zero_length_end(ContainerT& container) { + Iterator it{container, 0}; + it.steps_ = 0; + return it; + } + + CombIteratorDeref& operator*() { + return indices_; + } + + CombIteratorDeref* operator->() { + return &indices_; + } + + Iterator& operator++() { + if (indices_.get().empty()) { + // zero-length case. + ++steps_; + return *this; + } + for (auto iter = indices_.get().rbegin(); iter != indices_.get().rend(); + ++iter) { + ++(*iter); + + // what we have to check here is if the distance between + // the index and the end of indices_ is >= the distance + // between the item and end of item + auto dist = std::distance(indices_.get().rbegin(), iter); + + if (!(dumb_next(*iter, dist) != get_end(*container_p_))) { + if ((iter + 1) != indices_.get().rend()) { + size_t inc = 1; + for (auto down = iter;; --down) { + (*down) = dumb_next(*(iter + 1), 1 + inc); + ++inc; + if (down == indices_.get().rbegin()) break; + } + } else { + steps_ = COMPLETE; + break; + } + } else { + break; + } + // we break because none of the rest of the items need + // to be incremented + } + if (steps_ != COMPLETE) { + ++steps_; + } + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + template + bool operator==(const Iterator& other) const { + return steps_ == other.steps_; + } + }; + + Iterator begin() { + return {container_, length_}; + } + + Iterator end() { + if (length_ == 0) { + return Iterator::zero_length_end(container_); + } + return {container_, 0}; + } + + Iterator> begin() const { + return {std::as_const(container_), length_}; + } + + Iterator> end() const { + if (length_ == 0) { + return Iterator>::zero_length_end(container_); + } + return {std::as_const(container_), 0}; + } +}; + +#endif diff --git a/cppitertools/combinations_with_replacement.hpp b/cppitertools/combinations_with_replacement.hpp new file mode 100644 index 00000000..39db37bd --- /dev/null +++ b/cppitertools/combinations_with_replacement.hpp @@ -0,0 +1,148 @@ +#ifndef ITER_COMBINATIONS_WITH_REPLACEMENT_HPP_ +#define ITER_COMBINATIONS_WITH_REPLACEMENT_HPP_ + +#include "internal/iteratoriterator.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include + +namespace iter { + namespace impl { + template + class CombinatorWithReplacement; + using CombinationsWithReplacementFn = + IterToolFnBindSizeTSecond; + } + inline constexpr impl::CombinationsWithReplacementFn combinations_with_replacement{}; +} + +template +class iter::impl::CombinatorWithReplacement { + private: + Container container_; + std::size_t length_; + + friend CombinationsWithReplacementFn; + + CombinatorWithReplacement(Container&& container, std::size_t n) + : container_(std::forward(container)), length_{n} {} + + template + using IndexVector = std::vector>; + template + using CombIteratorDeref = IterIterWrapper>; + + public: + CombinatorWithReplacement(CombinatorWithReplacement&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + constexpr static const int COMPLETE = -1; + std::remove_reference_t* container_p_; + CombIteratorDeref indices_; + int steps_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = CombIteratorDeref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(ContainerT& in_container, std::size_t n) + : container_p_{&in_container}, + indices_(n, get_begin(in_container)), + steps_{(get_begin(in_container) != get_end(in_container) && n) + ? 0 + : COMPLETE} {} + + static Iterator zero_length_end(ContainerT& container) { + Iterator it{container, 0}; + it.steps_ = 0; + return it; + } + + CombIteratorDeref& operator*() { + return indices_; + } + + CombIteratorDeref* operator->() { + return &indices_; + } + + Iterator& operator++() { + if (indices_.get().empty()) { + // zero-length case. + ++steps_; + return *this; + } + for (auto iter = indices_.get().rbegin(); iter != indices_.get().rend(); + ++iter) { + ++(*iter); + if (!(*iter != get_end(*container_p_))) { + if ((iter + 1) != indices_.get().rend()) { + for (auto down = iter;; --down) { + (*down) = dumb_next(*(iter + 1)); + if (down == indices_.get().rbegin()) break; + } + } else { + steps_ = COMPLETE; + break; + } + } else { + // we break because none of the rest of the items + // need to be incremented + break; + } + } + if (steps_ != COMPLETE) { + ++steps_; + } + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + template + bool operator==(const Iterator& other) const { + return steps_ == other.steps_; + } + }; + + Iterator begin() { + return {container_, length_}; + } + + Iterator end() { + if (length_ == 0) { + return Iterator::zero_length_end(container_); + } + return {container_, 0}; + } + + Iterator> begin() const { + return {std::as_const(container_), length_}; + } + + Iterator> end() const { + if (length_ == 0) { + return Iterator>::zero_length_end(container_); + } + return {std::as_const(container_), 0}; + } +}; + +#endif diff --git a/cppitertools/compress.hpp b/cppitertools/compress.hpp new file mode 100644 index 00000000..f4606ef8 --- /dev/null +++ b/cppitertools/compress.hpp @@ -0,0 +1,140 @@ +#ifndef ITER_COMPRESS_H_ +#define ITER_COMPRESS_H_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include + +namespace iter { + namespace impl { + template + class Compressed; + } + + template + impl::Compressed compress(Container&&, Selector&&); +} + +template +class iter::impl::Compressed { + private: + Container container_; + Selector selectors_; + + friend Compressed iter::compress( + Container&&, Selector&&); + + Compressed(Container&& in_container, Selector&& in_selectors) + : container_(std::forward(in_container)), + selectors_(std::forward(in_selectors)) {} + + public: + Compressed(Compressed&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + + IteratorWrapper selector_iter_; + IteratorWrapper selector_end_; + + void increment_iterators() { + ++sub_iter_; + ++selector_iter_; + } + + void skip_failures() { + while (sub_iter_ != sub_end_ && selector_iter_ != selector_end_ + && !*selector_iter_) { + increment_iterators(); + } + } + + public: + using iterator_category = std::input_iterator_tag; + using value_type = iterator_traits_deref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(IteratorWrapper&& cont_iter, + IteratorWrapper&& cont_end, + IteratorWrapper&& sel_iter, + IteratorWrapper&& sel_end) + : sub_iter_{std::move(cont_iter)}, + sub_end_{std::move(cont_end)}, + selector_iter_{std::move(sel_iter)}, + selector_end_{std::move(sel_end)} { + skip_failures(); + } + + iterator_deref operator*() { + return *sub_iter_; + } + + iterator_arrow operator->() { + return apply_arrow(sub_iter_); + } + + Iterator& operator++() { + increment_iterators(); + skip_failures(); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return sub_iter_ != other.sub_iter_ + && selector_iter_ != other.selector_iter_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_), get_begin(selectors_), + get_end(selectors_)}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), get_end(selectors_), + get_end(selectors_)}; + } + + Iterator, AsConst> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_)), + get_begin(std::as_const(selectors_)), + get_end(std::as_const(selectors_))}; + } + + Iterator, AsConst> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), + get_end(std::as_const(selectors_)), + get_end(std::as_const(selectors_))}; + } +}; + +template +iter::impl::Compressed iter::compress( + Container&& container_, Selector&& selectors_) { + return { + std::forward(container_), std::forward(selectors_)}; +} + +#endif diff --git a/cppitertools/count.hpp b/cppitertools/count.hpp new file mode 100644 index 00000000..327596bf --- /dev/null +++ b/cppitertools/count.hpp @@ -0,0 +1,22 @@ +#ifndef ITER_COUNT_H_ +#define ITER_COUNT_H_ + +#include "range.hpp" + +#include + +namespace iter { + template + constexpr auto count(T start, T step) noexcept { + T stop = step < T(0) ? std::numeric_limits::lowest() + : std::numeric_limits::max(); + return range(start, stop, step); + } + + template + constexpr auto count(T start = T(0)) noexcept { + return count(start, T(1)); + } +} + +#endif diff --git a/cppitertools/cycle.hpp b/cppitertools/cycle.hpp new file mode 100644 index 00000000..fb3ca9c7 --- /dev/null +++ b/cppitertools/cycle.hpp @@ -0,0 +1,108 @@ +#ifndef ITER_CYCLE_H_ +#define ITER_CYCLE_H_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include + +namespace iter { + namespace impl { + template + class Cycler; + + using CycleFn = IterToolFn; + } + inline constexpr impl::CycleFn cycle{}; +} + +template +class iter::impl::Cycler { + private: + friend CycleFn; + + Container container_; + + Cycler(Container&& container) + : container_(std::forward(container)) {} + + public: + Cycler(Cycler&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + IteratorWrapper sub_iter_; + IteratorWrapper sub_begin_; + IteratorWrapper sub_end_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = iterator_traits_deref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end) + : sub_iter_{sub_iter}, + sub_begin_{sub_iter}, + sub_end_{std::move(sub_end)} {} + + iterator_deref operator*() { + return *sub_iter_; + } + + iterator_arrow operator->() { + return apply_arrow(sub_iter_); + } + + Iterator& operator++() { + ++sub_iter_; + // reset to beginning upon reaching the sub_end_ + if (!(sub_iter_ != sub_end_)) { + sub_iter_ = sub_begin_; + } + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return sub_iter_ != other.sub_iter_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_)}; + } + + Iterator end() { + return {get_end(container_), get_end(container_)}; + } + + Iterator> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_))}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_))}; + } +}; + +#endif diff --git a/cppitertools/dropwhile.hpp b/cppitertools/dropwhile.hpp new file mode 100644 index 00000000..86942826 --- /dev/null +++ b/cppitertools/dropwhile.hpp @@ -0,0 +1,136 @@ +#ifndef ITER_DROPWHILE_H_ +#define ITER_DROPWHILE_H_ + +#include "filter.hpp" +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include + +namespace iter { + namespace impl { + template + class Dropper; + + using DropWhileFn = IterToolFnOptionalBindFirst; + } + inline constexpr impl::DropWhileFn dropwhile{}; +} + +template +class iter::impl::Dropper { + private: + Container container_; + mutable FilterFunc filter_func_; + + friend DropWhileFn; + + Dropper(FilterFunc filter_func, Container&& container) + : container_(std::forward(container)), + filter_func_(std::move(filter_func)) {} + + public: + Dropper(Dropper&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + using Holder = DerefHolder>; + mutable IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + mutable Holder item_; + FilterFunc* filter_func_; + + // see comments from filter about mutability + void inc_sub_iter() const { + ++sub_iter_; + if (sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + } + } + + // skip all values for which the predicate is true + void skip_passes() const { + while (sub_iter_ != sub_end_ && std::invoke(*filter_func_, item_.get())) { + inc_sub_iter(); + } + } + + void init_if_first_use() const { + if (!item_ && sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + skip_passes(); + } + } + + public: + using iterator_category = std::input_iterator_tag; + using value_type = iterator_traits_deref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end, FilterFunc& filter_func) + : sub_iter_{std::move(sub_iter)}, + sub_end_{std::move(sub_end)}, + filter_func_(&filter_func) {} + + typename Holder::reference operator*() { + init_if_first_use(); + return item_.get(); + } + + typename Holder::pointer operator->() { + init_if_first_use(); + return item_.get_ptr(); + } + + Iterator& operator++() { + init_if_first_use(); + inc_sub_iter(); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + init_if_first_use(); + other.init_if_first_use(); + return sub_iter_ != other.sub_iter_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_), filter_func_}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), filter_func_}; + } + + Iterator> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_)), filter_func_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), filter_func_}; + } +}; + +#endif diff --git a/cppitertools/enumerate.hpp b/cppitertools/enumerate.hpp new file mode 100644 index 00000000..5d59feab --- /dev/null +++ b/cppitertools/enumerate.hpp @@ -0,0 +1,135 @@ +#ifndef ITER_ENUMERATE_H_ +#define ITER_ENUMERATE_H_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + using EnumBasePair = std::pair; + + // "yielded" by the Enumerable::Iterator. Has a .index, and a + // .element referencing the value yielded by the subiterator + template + class EnumIterYield : public EnumBasePair { + using BasePair = EnumBasePair; + using BasePair::BasePair; + + public: + typename BasePair::first_type index = BasePair::first; + typename BasePair::second_type element = BasePair::second; + }; + + template + class Enumerable; + + using EnumerateFn = IterToolFnOptionalBindSecond; + } + inline constexpr impl::EnumerateFn enumerate{}; +} + +namespace std { + template + struct tuple_size> + : public tuple_size> {}; + + template + struct tuple_element> + : public tuple_element> {}; +} + +template +class iter::impl::Enumerable { + private: + Container container_; + const Index start_; + + friend EnumerateFn; + + // Value constructor for use only in the enumerate function + Enumerable(Container&& container, Index start) + : container_(std::forward(container)), start_{start} {} + + public: + Enumerable(Enumerable&&) = default; + + template + using IterYield = EnumIterYield>; + + // Holds an iterator of the contained type and an Index for the + // index_. Each call to ++ increments both of these data members. + // Each dereference returns an IterYield. + template + class Iterator { + private: + template + friend class Iterator; + IteratorWrapper sub_iter_; + Index index_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = IterYield; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(IteratorWrapper&& sub_iter, Index start) + : sub_iter_{std::move(sub_iter)}, index_{start} {} + + IterYield operator*() { + return {index_, *sub_iter_}; + } + + ArrowProxy> operator->() { + return {**this}; + } + + Iterator& operator++() { + ++sub_iter_; + ++index_; + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return sub_iter_ != other.sub_iter_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + }; + + Iterator begin() { + return {get_begin(container_), start_}; + } + + Iterator end() { + return {get_end(container_), start_}; + } + + Iterator> begin() const { + return {get_begin(std::as_const(container_)), start_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), start_}; + } +}; +#endif diff --git a/cppitertools/filter.hpp b/cppitertools/filter.hpp new file mode 100644 index 00000000..d743d8b1 --- /dev/null +++ b/cppitertools/filter.hpp @@ -0,0 +1,153 @@ +#ifndef ITER_FILTER_H_ +#define ITER_FILTER_H_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class Filtered; + + struct BoolTester { + template + constexpr bool operator()(const T& item_) const { + return bool(item_); + } + }; + + using FilterFn = IterToolFnOptionalBindFirst; + } + + inline constexpr impl::FilterFn filter{}; +} + +template +class iter::impl::Filtered { + static_assert(!std::is_reference_v); + + private: + Container container_; + mutable FilterFunc filter_func_; + + friend FilterFn; + + protected: + // Value constructor for use only in the filter function + Filtered(FilterFunc filter_func, Container&& container) + : container_(std::forward(container)), + filter_func_(std::move(filter_func)) {} + + public: + Filtered(Filtered&&) = default; + + template + class Iterator { + private: + template + friend class Iterator; + using Holder = DerefHolder>; + mutable IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + mutable Holder item_; + FilterFunc* filter_func_; + + // All of these are marked const because the sub_iter_ is lazily + // initialized. The morality of this is questionable. + void inc_sub_iter() const { + ++sub_iter_; + if (sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + } + } + + // increment until the iterator points to is true on the + // predicate. Called by constructor and operator++ + void skip_failures() const { + while ( + sub_iter_ != sub_end_ && !std::invoke(*filter_func_, item_.get())) { + inc_sub_iter(); + } + } + + void init_if_first_use() const { + if (!item_ && sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + skip_failures(); + } + } + + public: + using iterator_category = std::input_iterator_tag; + using value_type = iterator_traits_deref; + using difference_type = std::ptrdiff_t; + using pointer = typename Holder::pointer; + using reference = typename Holder::reference; + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end, FilterFunc& filter_func) + : sub_iter_{std::move(sub_iter)}, + sub_end_{std::move(sub_end)}, + filter_func_(&filter_func) {} + + typename Holder::reference operator*() { + init_if_first_use(); + return item_.get(); + } + + typename Holder::pointer operator->() { + init_if_first_use(); + return item_.get_ptr(); + } + + Iterator& operator++() { + init_if_first_use(); + inc_sub_iter(); + skip_failures(); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + init_if_first_use(); + other.init_if_first_use(); + return sub_iter_ != other.sub_iter_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_), filter_func_}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), filter_func_}; + } + + Iterator> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_)), filter_func_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), filter_func_}; + } +}; + +#endif diff --git a/cppitertools/filterfalse.hpp b/cppitertools/filterfalse.hpp new file mode 100644 index 00000000..269724dd --- /dev/null +++ b/cppitertools/filterfalse.hpp @@ -0,0 +1,55 @@ +#ifndef ITER_FILTER_FALSE_HPP_ +#define ITER_FILTER_FALSE_HPP_ + +#include "filter.hpp" +#include "internal/iterbase.hpp" + +#include +#include + +namespace iter { + namespace impl { + // Callable object that reverses the boolean result of another + // callable, taking the object in a Container's iterator + template + class PredicateFlipper { + private: + FilterFunc filter_func_; + + public: + PredicateFlipper(FilterFunc filter_func) + : filter_func_(std::move(filter_func)) {} + + // Calls the filter_func_ + template + bool operator()(const T& item) const { + return !bool(std::invoke(filter_func_, item)); + } + + // with non-const incase FilterFunc::operator() is non-const + template + bool operator()(const T& item) { + return !bool(std::invoke(filter_func_, item)); + } + }; + + template + class FilterFalsed; + + using FilterFalseFn = IterToolFnOptionalBindFirst; + } + inline constexpr impl::FilterFalseFn filterfalse{}; +} + +// Delegates to Filtered with PredicateFlipper +template +class iter::impl::FilterFalsed + : public Filtered, Container> { + friend FilterFalseFn; + FilterFalsed(FilterFunc in_filter_func, Container&& in_container) + : Filtered, Container>( + {std::move(in_filter_func)}, + std::forward(in_container)) {} +}; + +#endif diff --git a/cppitertools/groupby.hpp b/cppitertools/groupby.hpp new file mode 100644 index 00000000..1c73f9f6 --- /dev/null +++ b/cppitertools/groupby.hpp @@ -0,0 +1,296 @@ +#ifndef ITER_GROUP_BY_HPP_ +#define ITER_GROUP_BY_HPP_ + +// this is easily the most functionally complex itertool + +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class GroupProducer; + + using GroupByFn = IterToolFnOptionalBindSecond; + } + inline constexpr impl::GroupByFn groupby{}; +} + +template +class iter::impl::GroupProducer { + private: + Container container_; + mutable KeyFunc key_func_; + + friend GroupByFn; + + template + using key_func_ret = std::invoke_result_t>; + + GroupProducer(Container&& container, KeyFunc key_func) + : container_(std::forward(container)), + key_func_(std::move(key_func)) {} + + public: + GroupProducer(GroupProducer&&) = default; + + template + class Iterator; + template + class Group; + + private: + template + using KeyGroupPair = std::pair, Group>; + template + using Holder = DerefHolder>; + + public: + template + class Iterator { + private: + template + friend class Iterator; + IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + Holder item_; + KeyFunc* key_func_; + std::optional> current_key_group_pair_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = KeyGroupPair; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end, KeyFunc& key_func) + : sub_iter_{std::move(sub_iter)}, + sub_end_{std::move(sub_end)}, + key_func_(&key_func) { + if (sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + } + } + + Iterator(const Iterator& other) + : sub_iter_{other.sub_iter_}, + sub_end_{other.sub_end_}, + item_{other.item_}, + key_func_{other.key_func_} {} + + Iterator& operator=(const Iterator& other) { + if (this == &other) { + return *this; + } + sub_iter_ = other.sub_iter_; + sub_end_ = other.sub_end_; + item_ = other.item_; + key_func_ = other.key_func_; + current_key_group_pair_.reset(); + return *this; + } + + ~Iterator() = default; + + // NOTE the implicitly generated move constructor would + // be wrong + + KeyGroupPair& operator*() { + set_key_group_pair(); + return *current_key_group_pair_; + } + + KeyGroupPair* operator->() { + set_key_group_pair(); + return &*current_key_group_pair_; + } + + Iterator& operator++() { + if (!current_key_group_pair_) { + set_key_group_pair(); + } + current_key_group_pair_.reset(); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return sub_iter_ != other.sub_iter_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + + void increment_iterator() { + if (sub_iter_ != sub_end_) { + ++sub_iter_; + if (sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + } + } + } + + bool exhausted() const { + return !(sub_iter_ != sub_end_); + } + + typename Holder::reference get() { + return item_.get(); + } + + typename Holder::pointer get_ptr() { + return item_.get_ptr(); + } + + key_func_ret next_key() { + return std::invoke(*key_func_, item_.get()); + } + + void set_key_group_pair() { + if (!current_key_group_pair_) { + current_key_group_pair_.emplace(std::invoke(*key_func_, item_.get()), + Group{*this, next_key()}); + } + } + }; + + template + class Group { + private: + template + friend class Iterator; + friend class GroupIterator; + Iterator& owner_; + // The key function may return a reference, so we need to call forward, not + // move, when going for efficiency. + key_func_ret key_; + + // completed is set if a Group is iterated through + // completely. It is checked in the destructor, and + // if the Group has not been completed, the destructor + // exhausts it. This ensures that the next Group starts + // at the correct position when the user short-circuits + // iteration over a Group. + // The move constructor sets the rvalue's completed + // attribute to true, so its destructor doesn't do anything + // when called. + bool completed = false; + + Group(Iterator& owner, key_func_ret key) + : owner_(owner), key_(std::forward>(key)) {} + + public: + ~Group() { + if (!completed) { + for (auto iter = begin(), end_it = end(); iter != end_it; ++iter) { + } + } + } + + // move-constructible, non-copy-constructible, non-assignable + Group(Group&& other) noexcept + : owner_(other.owner_), + key_{std::forward>(other.key_)}, + completed{other.completed} { + other.completed = true; + } + + class GroupIterator { + private: + std::remove_reference_t>* key_; + Group* group_p_; + + bool not_at_end() { + return !group_p_->owner_.exhausted() + && group_p_->owner_.next_key() == *key_; + } + + public: + using iterator_category = std::input_iterator_tag; + using value_type = iterator_traits_deref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + // TODO template this? idk if it's relevant here + GroupIterator(Group* group_p, key_func_ret& key) + : key_{&key}, group_p_{group_p} {} + + bool operator!=(const GroupIterator& other) const { + return !(*this == other); + } + + bool operator==(const GroupIterator& other) const { + return group_p_ == other.group_p_; + } + + GroupIterator& operator++() { + group_p_->owner_.increment_iterator(); + if (!not_at_end()) { + group_p_->completed = true; + group_p_ = nullptr; + } + return *this; + } + + GroupIterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + iterator_deref operator*() { + return group_p_->owner_.get(); + } + + typename Holder::pointer operator->() { + return group_p_->owner_.get_ptr(); + } + }; + + GroupIterator begin() { + return {this, key_}; + } + + GroupIterator end() { + return {nullptr, key_}; + } + }; + + Iterator begin() { + return {get_begin(container_), get_end(container_), key_func_}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), key_func_}; + } + + Iterator> begin() const { + return {get_begin(std::as_const(container_)), + get_end(std::as_const(container_)), key_func_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), key_func_}; + } +}; + +#endif diff --git a/cppitertools/imap.hpp b/cppitertools/imap.hpp new file mode 100644 index 00000000..21ca2e81 --- /dev/null +++ b/cppitertools/imap.hpp @@ -0,0 +1,28 @@ +#ifndef ITER_IMAP_H_ +#define ITER_IMAP_H_ + +#include + +#include "starmap.hpp" +#include "zip.hpp" + +namespace iter { + namespace impl { + struct IMapFn : PipeableAndBindFirst { + template + auto operator()(MapFunc map_func, Containers&&... containers) const + // explicitly specifying type here to allow more expressions that only + // care about the type, and don't need a valid implementation. + // See #66 + -> StarMapper(containers)...))> { + return starmap( + std::move(map_func), zip(std::forward(containers)...)); + } + using PipeableAndBindFirst::operator(); + }; + } + inline constexpr impl::IMapFn imap{}; +} + +#endif diff --git a/cppitertools/internal/iter_tuples.hpp b/cppitertools/internal/iter_tuples.hpp new file mode 100644 index 00000000..c71f8dee --- /dev/null +++ b/cppitertools/internal/iter_tuples.hpp @@ -0,0 +1,57 @@ +#ifndef ITERTOOLS_ITER_TUPLES_HPP_ +#define ITERTOOLS_ITER_TUPLES_HPP_ + +#include "iterator_wrapper.hpp" +#include "iterbase.hpp" + +#include + +namespace iter { + namespace impl { + namespace detail { + template + std::tuple...> iterator_tuple_deref_helper( + const std::tuple&); + + template + std::tuple...> iterator_tuple_type_helper( + const std::tuple&); + + template + std::tuple>...> + const_iterator_tuple_deref_helper(const std::tuple&); + + template + std::tuple>...> + const_iterator_tuple_type_helper(const std::tuple&); + } + // Given a tuple template argument, evaluates to a tuple of iterators + // for the template argument's contained types. + template + using iterator_tuple_type = + decltype(detail::iterator_tuple_type_helper(std::declval())); + + template + using const_iterator_tuple_type = decltype( + detail::const_iterator_tuple_type_helper(std::declval())); + + // Given a tuple template argument, evaluates to a tuple of + // what the iterators for the template argument's contained types + // dereference to + template + using iterator_deref_tuple = decltype( + detail::iterator_tuple_deref_helper(std::declval())); + + template + using const_iterator_deref_tuple = decltype( + detail::const_iterator_tuple_deref_helper(std::declval())); + + // function absorbing all arguments passed to it. used when + // applying a function to a parameter pack but not passing the evaluated + // results anywhere + template + void absorb(Ts&&...) {} + } +} + +#endif diff --git a/cppitertools/internal/iterator_wrapper.hpp b/cppitertools/internal/iterator_wrapper.hpp new file mode 100644 index 00000000..f068da97 --- /dev/null +++ b/cppitertools/internal/iterator_wrapper.hpp @@ -0,0 +1,103 @@ +#ifndef ITERTOOLS_ITERATOR_WRAPPER_HPP_ +#define ITERTOOLS_ITERATOR_WRAPPER_HPP_ + +#include +#include +#include +#include "iterbase.hpp" + +namespace iter { + namespace impl { + // iterator_end_type is the type of C's end iterator + template + using iterator_end_type = decltype(get_end(std::declval())); + + template + class IteratorWrapperImpl; + + // If begin and end return the same type, type will be + // iterator_type + // If begin and end return different types, type will be IteratorWrapperImpl + template + struct IteratorWrapperImplType; + + template + struct IteratorWrapperImplType + : type_is> {}; + + template + struct IteratorWrapperImplType + : type_is, + iterator_end_type>> {}; + + template + using IteratorWrapper = typename IteratorWrapperImplType, + impl::iterator_end_type>>::type; + } +} + +template +class iter::impl::IteratorWrapperImpl { + private: + static_assert(!std::is_same_v); + SubIter& sub_iter() { + auto* sub = std::get_if(&sub_iter_or_end_); + assert(sub); + return *sub; + } + + const SubIter& sub_iter() const { + auto* sub = std::get_if(&sub_iter_or_end_); + assert(sub); + return *sub; + } + + std::variant sub_iter_or_end_; + + public: + IteratorWrapperImpl() : IteratorWrapperImpl(SubIter{}) {} + + IteratorWrapperImpl(SubIter&& it) : sub_iter_or_end_{std::move(it)} {} + + IteratorWrapperImpl(SubEnd&& it) : sub_iter_or_end_(std::move(it)) {} + + IteratorWrapperImpl& operator++() { + ++sub_iter(); + return *this; + } + + decltype(auto) operator*() { + return *sub_iter(); + } + + decltype(auto) operator*() const { + return *sub_iter(); + } + + decltype(auto) operator-> () { + return apply_arrow(sub_iter()); + } + + decltype(auto) operator-> () const { + return apply_arrow(sub_iter()); + } + + bool operator!=(const IteratorWrapperImpl& other) const { + constexpr static struct : std::not_equal_to { + // specially compare Ends because rangev3 sentinels are not equality + // comparable + bool operator()(const SubEnd&, const SubEnd&) const { + return false; + } + using std::not_equal_to::operator(); + } not_equal; + return std::visit(not_equal, sub_iter_or_end_, other.sub_iter_or_end_); + } + + bool operator==(const IteratorWrapperImpl& other) const { + return !(*this != other); + } +}; + +#endif diff --git a/cppitertools/internal/iteratoriterator.hpp b/cppitertools/internal/iteratoriterator.hpp new file mode 100644 index 00000000..0ab23bad --- /dev/null +++ b/cppitertools/internal/iteratoriterator.hpp @@ -0,0 +1,279 @@ +#ifndef ITERATOR_ITERATOR_HPP_ +#define ITERATOR_ITERATOR_HPP_ + +#include +#include +#include +#include "iterbase.hpp" + +// IterIterWrapper and IteratorIterator provide a means to have a container +// of iterators act like a container of the pointed to objects. This is useful +// for combinatorics and similar itertools which need to keep track of +// more than one element at a time. +// an IterIterWrapper::iterator>> +// behave like some_collection when iterated over or indexed + +namespace iter { + namespace impl { + template + struct HasConstDeref : std::false_type {}; + + template + struct HasConstDeref())>> + : std::true_type {}; + + template + class IteratorIterator { + template + friend class IteratorIterator; + using Diff = std::ptrdiff_t; + static_assert( + std::is_same< + typename std::iterator_traits::iterator_category, + std::random_access_iterator_tag>::value, + "IteratorIterator only works with random access iterators"); + + private: + TopIter sub_iter; + + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = std::remove_cv_t< + std::remove_reference_t())>>; + using difference_type = std::ptrdiff_t; + using pointer = + std::remove_reference_t())>*; + using reference = std::add_lvalue_reference_t< + std::remove_reference_t())>>; + + IteratorIterator() = default; + IteratorIterator(const TopIter& it) : sub_iter{it} {} + + const TopIter& get() const { + return sub_iter; + } + + template + bool operator==(const IteratorIterator& other) const { + return !(*this != other); + } + + template + bool operator!=(const IteratorIterator& other) const { + return this->sub_iter != other.sub_iter; + } + + IteratorIterator& operator++() { + ++this->sub_iter; + return *this; + } + + IteratorIterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + IteratorIterator& operator--() { + --this->sub_iter; + return *this; + } + + IteratorIterator operator--(int) { + auto ret = *this; + --*this; + return ret; + } + + auto operator*() const -> decltype(**sub_iter) { + return **this->sub_iter; + } + + auto operator->() const -> decltype(*sub_iter) { + return *this->sub_iter; + } + + IteratorIterator& operator+=(Diff n) { + this->sub_iter += n; + return *this; + } + + IteratorIterator operator+(Diff n) const { + auto it = *this; + it += n; + return it; + } + + friend IteratorIterator operator+(Diff n, IteratorIterator it) { + it += n; + return it; + } + + IteratorIterator& operator-=(Diff n) { + this->sub_iter -= n; + return *this; + } + + IteratorIterator operator-(Diff n) const { + auto it = *this; + it -= n; + return it; + } + + Diff operator-(const IteratorIterator& rhs) const { + return this->sub_iter - rhs.sub_iter; + } + + auto operator[](Diff idx) const -> decltype(*sub_iter[idx]) { + return *sub_iter[idx]; + } + + bool operator<(const IteratorIterator& rhs) const { + return this->sub_iter < rhs.sub_iter; + } + + bool operator>(const IteratorIterator& rhs) const { + return this->sub_iter > rhs.sub_iter; + } + + bool operator<=(const IteratorIterator& rhs) const { + return this->sub_iter <= rhs.sub_iter; + } + + bool operator>=(const IteratorIterator& rhs) const { + return this->sub_iter >= rhs.sub_iter; + } + }; + + template + class IterIterWrapper { + private: + Container container; + + using contained_iter = typename Container::value_type; + using size_type = typename Container::size_type; + using iterator = IteratorIterator; + using const_iterator = + IteratorIterator; + using reverse_iterator = + IteratorIterator; + using const_reverse_iterator = + IteratorIterator; + + template + struct ConstAtTypeOrVoid : type_is {}; + + template + struct ConstAtTypeOrVoid().at(0))>> + : type_is().at(0))> {}; + + using const_at_type_or_void_t = typename ConstAtTypeOrVoid<>::type; + + template + struct ConstIndexTypeOrVoid : type_is {}; + + template + struct ConstIndexTypeOrVoid()[0])>> + : type_is()[0])> {}; + + using const_index_type_or_void_t = typename ConstIndexTypeOrVoid<>::type; + + public: + IterIterWrapper() = default; + + explicit IterIterWrapper(size_type sz) : container(sz) {} + + IterIterWrapper(size_type sz, const contained_iter& val) + : container(sz, val) {} + + auto at(size_type pos) -> decltype(*container.at(pos)) { + return *container.at(pos); + } + + auto at(size_type pos) const -> const_at_type_or_void_t { + return *container.at(pos); + } + + auto operator[](size_type pos) noexcept(noexcept(*container[pos])) + -> decltype(*container[pos]) { + return *container[pos]; + } + + auto operator[](size_type pos) const noexcept(noexcept(*container[pos])) + -> const_index_type_or_void_t { + return *container[pos]; + } + + bool empty() const noexcept { + return container.empty(); + } + + size_type size() const noexcept { + return container.size(); + } + + iterator begin() noexcept { + return {container.begin()}; + } + + iterator end() noexcept { + return {container.end()}; + } + + const_iterator begin() const noexcept { + return {container.begin()}; + } + + const_iterator end() const noexcept { + return {container.end()}; + } + + const_iterator cbegin() const noexcept { + return {container.cbegin()}; + } + + const_iterator cend() const noexcept { + return {container.cend()}; + } + + reverse_iterator rbegin() noexcept { + return {container.rbegin()}; + } + + reverse_iterator rend() noexcept { + return {container.rend()}; + } + + const_reverse_iterator rbegin() const noexcept { + return {container.rbegin()}; + } + + const_reverse_iterator rend() const noexcept { + return {container.rend()}; + } + + const_reverse_iterator crbegin() const noexcept { + return {container.rbegin()}; + } + + const_reverse_iterator crend() const noexcept { + return {container.rend()}; + } + + // get() exposes the underlying container. this allows the + // itertools to manipulate the iterators in the container + // and should not be depended on anywhere else. + Container& get() noexcept { + return container; + } + + const Container& get() const noexcept { + return container; + } + }; + } +} + +#endif diff --git a/cppitertools/internal/iterbase.hpp b/cppitertools/internal/iterbase.hpp new file mode 100644 index 00000000..e9627079 --- /dev/null +++ b/cppitertools/internal/iterbase.hpp @@ -0,0 +1,533 @@ +#ifndef ITERBASE_HPP_ +#define ITERBASE_HPP_ + +// This file consists of utilities used for the generic nature of the +// iterable wrapper classes. As such, the contents of this file should be +// considered UNDOCUMENTED and is subject to change without warning. This +// also applies to the name of the file. No user code should include +// this file directly. + +#include +#include +#include +#include +#include +#include +#include +#include + +// see gcc bug 87651 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87651 +#ifdef __GNUC__ +#define NO_GCC_FRIEND_ERROR __GNUC__ < 8 +#else +#define NO_GCC_FRIEND_ERROR 1 +#endif + +namespace iter { + namespace impl { + namespace get_iters { + // begin() for C arrays + template + T* get_begin_impl(T (&array)[N], int) { + return array; + } + + // Prefer member begin(). + template ().begin())> + I get_begin_impl(T& r, int) { + return r.begin(); + } + + // Use ADL otherwises. + template ()))> + I get_begin_impl(T& r, long) { + return begin(r); + } + + template + auto get_begin(T& t) -> decltype(get_begin_impl(std::declval(), 42)) { + return get_begin_impl(t, 42); + } + + // end() for C arrays + template + T* get_end_impl(T (&array)[N], int) { + return array + N; + } + + // Prefer member end(). + template ().end())> + I get_end_impl(T& r, int) { + return r.end(); + } + + // Use ADL otherwise. + template ()))> + I get_end_impl(T& r, long) { + return end(r); + } + + template + auto get_end(T& t) -> decltype(get_end_impl(std::declval(), 42)) { + return get_end_impl(t, 42); + } + } + using get_iters::get_begin; + using get_iters::get_end; + + template + struct type_is { + using type = T; + }; + + template + using AsConst = decltype(std::as_const(std::declval())); + + // iterator_type is the type of C's iterator + // TODO: See bug + // https://developercommunity.visualstudio.com/content/problem/252157/sfinae-error-depends-on-name-of-template-parameter.html + // for why we use T instead of Container. Should be + // changed back to Container when that bug is fixed in + // MSVC. + template + using iterator_type = decltype(get_begin(std::declval())); + + // iterator_type is the type of C's iterator + template + using const_iterator_type = decltype(get_begin( + std::declval&>())); + + // iterator_deref is the type obtained by dereferencing an iterator + // to an object of type C + template + using iterator_deref = decltype(*std::declval&>()); + + // const_iteator_deref is the type obtained through dereferencing + // a const iterator& (note: not a const_iterator). ie: the result + // of Container::iterator::operator*() const + template + using const_iterator_deref = + decltype(*std::declval&>()); + + // the type of dereferencing a const_iterator + template + using const_iterator_type_deref = + decltype(*std::declval&>()); + + template + using iterator_traits_deref = + std::remove_cv_t>>; + + template + struct IsIterable : std::false_type {}; + + // Assuming that if a type works with begin, it is an iterable. + template + struct IsIterable>> : std::true_type {}; + + template + constexpr bool is_iterable = IsIterable::value; + + struct Identity { + template + const T& operator()(const T& t) const { + return t; + } + }; + + namespace detail { + template + struct ArrowHelper { + using type = void; + void operator()(T&) const noexcept {} + }; + + template + struct ArrowHelper { + using type = T*; + constexpr type operator()(T* t) const noexcept { + return t; + } + }; + + template + struct ArrowHelper().operator->())>> { + using type = decltype(std::declval().operator->()); + type operator()(T& t) const { + return t.operator->(); + } + }; + + template + using arrow = typename detail::ArrowHelper::type; + } + + // type of C::iterator::operator->, also works with pointers + // void if the iterator has no operator-> + template + using iterator_arrow = detail::arrow>; + + // applys the -> operator to an object, if the object is a pointer, + // it returns the pointer + template + detail::arrow apply_arrow(T& t) { + return detail::ArrowHelper{}(t); + } + + // For iterators that have an operator* which returns a value + // they can return this type from their operator-> instead, which will + // wrap an object and allow it to be used with arrow + template + class ArrowProxy { + private: + using TPlain = typename std::remove_reference::type; + T obj; + + public: + constexpr ArrowProxy(T&& in_obj) : obj(std::forward(in_obj)) {} + + TPlain* operator->() { + return &obj; + } + }; + + template + struct is_random_access_iter : std::false_type {}; + + template + struct is_random_access_iter::iterator_category, + std::random_access_iterator_tag>::value>> : std::true_type {}; + + template + using has_random_access_iter = is_random_access_iter>; + // because std::advance assumes a lot and is actually smart, I need a dumb + + // version that will work with most things + template + void dumb_advance_unsafe(InputIt& iter, Distance distance) { + for (Distance i(0); i < distance; ++i) { + ++iter; + } + } + + // iter will not be incremented past end + template + void dumb_advance(Iter& iter, const EndIter& end, Distance distance) { + if constexpr (is_random_access_iter{}) { + if (static_cast(end - iter) < distance) { + iter = end; + } else { + iter += distance; + } + } else { + for (Distance i(0); i < distance && iter != end; ++i) { + ++iter; + } + } + } + + template + ForwardIt dumb_next(ForwardIt it, Distance distance = 1) { + dumb_advance_unsafe(it, distance); + return it; + } + + template + ForwardIt dumb_next( + ForwardIt it, const ForwardIt& end, Distance distance = 1) { + dumb_advance(it, end, distance); + return it; + } + + template + Distance dumb_size(Container&& container) { + Distance d{0}; + auto end_it = get_end(container); + for (auto it = get_begin(container); it != end_it; ++it) { + ++d; + } + return d; + } + + template + struct are_same : std::true_type {}; + + template + struct are_same + : std::integral_constant::value && are_same::value> {}; + + // DerefHolder holds the value gotten from an iterator dereference + // if the iterator dereferences to an lvalue references, a pointer to the + // element is stored. if it does not, a value is stored instead + // get() returns a reference to the held item + // get_ptr() returns a pointer to the held item + // reset() replaces the currently held item + template + class DerefHolder { + private: + static_assert(!std::is_lvalue_reference::value, + "Non-lvalue-ref specialization used for lvalue ref type"); + // it could still be an rvalue reference + using TPlain = std::remove_cv_t>; + + std::optional item_p_; + + public: + using reference = TPlain&; + using pointer = TPlain*; + + static constexpr bool stores_value = true; + + DerefHolder() = default; + + reference get() { + assert(item_p_.has_value()); + return *item_p_; + } + + pointer get_ptr() { + assert(item_p_.has_value()); + return &item_p_.value(); + } + + void reset(T&& item) { + item_p_.emplace(std::move(item)); + } + + explicit operator bool() const { + return static_cast(item_p_); + } + }; + + // Specialization for when T is an lvalue ref + template + class DerefHolder { + public: + using reference = T&; + using pointer = T*; + + private: + pointer item_p_{}; + + public: + static constexpr bool stores_value = false; + + DerefHolder() = default; + + reference get() { + assert(item_p_); + return *item_p_; + } + + pointer get_ptr() { + assert(item_p_); + return item_p_; + } + + void reset(reference item) { + item_p_ = &item; + } + + explicit operator bool() const { + return item_p_ != nullptr; + } + }; + + // allows f(x) to be 'called' as x | f + // let the record show I dislike adding yet another syntactical mess to + // this clown car of a language. + template + struct Pipeable { + template +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 14 + [[gnu::no_dangling]] +#endif + friend decltype(auto) operator|(T&& x, Pipeable&& p) { + return static_cast(p)(std::forward(x)); + } + + template + friend decltype(auto) operator|(T&& x, const Pipeable& p) { + return static_cast(p)(std::forward(x)); + } + }; + + // Pipeable Callable generator, where ItImpl is templated on the first + // argument to the call. + template