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 index b5f272f2..d9d9622a 100644 --- a/.clang-format +++ b/.clang-format @@ -7,5 +7,7 @@ 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/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 b293c690..740dacbd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ 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 +library. Like itertools and the Python3 builtins, this library uses lazy evaluation wherever possible. -*Note*: Everthing is inside the `iter` namespace. +*Note*: Everything is inside the `iter` namespace. + +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)
@@ -23,6 +30,7 @@ evaluation wherever possible. [repeat](#repeat)
[count](#count)
[groupby](#groupby)
+[starmap](#starmap)
[accumulate](#accumulate)
[compress](#compress)
[sorted](#sorted)
@@ -32,8 +40,9 @@ evaluation wherever possible. [slice](#slice)
[sliding\_window](#sliding_window)
[chunked](#chunked)
+[batched](#batched)
-##### Combinatoric fuctions +##### Combinatorial functions [product](#product)
[combinations](#combinations)
[combinations\_with\_replacement](#combinations_with_replacement)
@@ -44,13 +53,37 @@ evaluation wherever possible. 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 +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 +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, @@ -60,7 +93,7 @@ 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 +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. @@ -69,12 +102,12 @@ as InputIterators and behave as such. 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 +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 @@ -89,69 +122,119 @@ 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 +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 +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 +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 achieve the same effect of the python range -function. `range` can be used in three different ways: +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'; + cout << i << '\n'; } ``` @@ -159,9 +242,10 @@ 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'; + cout << i << '\n'; } ``` @@ -171,42 +255,46 @@ 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 --------- - -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, 4, 0, 6, 7, 3, 0, 2, 8, 3, 2, 1}; for (auto&& i : filter([] (int i) { return i > 4; }, vec)) { - cout << i <<'\n'; + 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'; + cout << i << '\n'; } ``` @@ -214,49 +302,73 @@ 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 ` +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'; + 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'; + 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. For this to work your object must be specialized for `std::hash`. +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 << ' '; + 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` -Example Usage: + ```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 << ' '; + 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'; } ``` @@ -265,11 +377,12 @@ 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'; + cout << i << '\n'; } ``` @@ -279,10 +392,11 @@ Yields all elements after and including the first element that is true under the predicate. 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'; + cout << i << '\n'; } ``` @@ -290,18 +404,18 @@ cycle ----- *Additional Requirements*: Input must have a ForwardIterator - -Repeatedly produces all values of an iterable. The loop will be infinite, so a +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 + ```c++ vector vec{1, 2, 3}; for (auto&& i : cycle(vec)) { - cout << i << '\n'; - if (some_condition) { - break; - } + cout << i << '\n'; + if (some_condition) { + break; + } } ``` @@ -309,19 +423,21 @@ 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. +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'; + cout << e << '\n'; } ``` The below prints `2` forever + ```c++ for (auto&& e : repeat(2)) { - cout << e << '\n'; + cout << e << '\n'; } ``` @@ -334,16 +450,17 @@ 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() will eventually exceed the +types when needed, `count()` would eventually exceed the maximum possible value for its type (or minimum with a negative step). -When using a signed type it is up to the API user to ensure this does -not happen. If the limit is exceeded for signed types, the result is -undefined (as per the C++ standard). +`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'; + cout << i << '\n'; } ``` @@ -353,50 +470,97 @@ groupby 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 +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" + "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'; + 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. -accumulate +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. +`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'; + cout << i << '\n'; } ``` + A second, optional argument may provide an alternative binary function -to compute results. The following example multiplies the numbers, rather +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'; + cout << i << '\n'; } ``` @@ -407,56 +571,55 @@ 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 +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&& e : zip(i,f,s,d)) { - cout << std::get<0>(e) << ' ' - << std::get<1>(e) << ' ' - << std::get<2>(e) << ' ' - << std::get<3>(e) << '\n'; - std::get<1>(e)=2.2f; // modifies the underlying 'f' array +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 } ``` 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 +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 +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&& t : zip_longest(v1, v2)) { - cout << '{'; - if (std::get<0>(t)) { - cout << "Just " << *std::get<0>(t); - } else { - cout << "Nothing"; - } - cout << ", "; - if (std::get<1>(t)) { - cout << "Just " << *std::get<1>(t); - } else { - cout << "Nothing"; - } - cout << "}\n"; +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"; } ``` The output is: + ``` {Just 0, Just 10} {Just 1, Just 11} @@ -466,27 +629,27 @@ The output is: 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 +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'; + 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'; + cout << i << '\n'; } ``` @@ -494,10 +657,8 @@ for (auto&& i : imap([] (int x, int y) { return x + y; }, vec1, vec2)) { `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. @@ -506,7 +667,7 @@ Prints `2 6` 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'; + cout << i << '\n'; } ``` @@ -516,10 +677,10 @@ sorted 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. +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`.
+argument. If not provided, defaults to `std::less`.
Iterables passed to sorted are required to have an iterator with an `operator*() const` member. @@ -528,7 +689,7 @@ 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'; + cout << i << '\n'; } ``` @@ -546,39 +707,39 @@ vector vec1{1,2,3,4,5,6}; array arr1{{7,8,9,10}}; for (auto&& i : chain(empty,vec1,arr1)) { - cout << i << '\n'; + cout << i << '\n'; } ``` 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} + {1, 2, 3}, + {4, 5}, + {6, 8, 9, 10, 11, 12} }; for (auto&& i : chain.from_iterable(matrix)) { - cout << i << '\n'; + cout << i << '\n'; } ``` reversed -------- -*Additional Requirements*: Input must have `.rbegin()` and `.rend()`, or be -a plain C array. +-------- +*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 : reversed(a)) { - cout << i << '\n'; + cout << i << '\n'; } ``` @@ -589,18 +750,19 @@ 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'; + 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 +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). @@ -618,82 +780,127 @@ take a section of size 4, output is: ``` 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'; + for (auto&& i : sec) { + cout << i << ' '; + i.get() = 90; + } + cout << '\n'; } ``` -chunked ------- -chunked will yield subsequent chunkes of an iterable in blocks of a specified +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 +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'; + 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 project of the given ranges put together +Generates the cartesian product of the given ranges put together. Example usage: + ```c++ vector v1{1,2,3}; vector v2{7,8}; vector v3{"the","cat"}; -vector v4{"hi","what","up","dude"}; -for (auto&& t : product(v1,v2,v3,v4)) { - cout << std::get<0>(t) << ", " - << std::get<1>(t) << ", " - << std::get<2>(t) << ", " - << std::get<3>(t) << '\n'; +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. Example usage: + ```c++ vector v = {1,2,3,4,5}; for (auto&& i : combinations(v,3)) { - for (auto&& j : i ) cout << j << " "; - cout << '\n'; + 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 +Like combinations, but with replacement of each element. The below is printed by the loop that follows: + ``` {A, A} {A, B} @@ -702,43 +909,46 @@ below is printed by the loop that follows: {B, C} {C, C} ``` + ```c++ for (auto&& v : combinations_with_replacement(s, 2)) { - cout << '{' << v[0] << ", " << v[1] << "}\n"; + cout << '{' << v[0] << ", " << v[1] << "}\n"; } ``` permutations ------------ -*Additional Requirements*: Input must have a ForwardIterator. Iterator must +------------ +*Additional Requirements*: Input must have a ForwardIterator. Iterator must have an `operator*() const`. Generates all the permutations of a range using `std::next_permutation`. Example usage: + ```c++ vector v = {1,2,3,4,5}; for (auto&& vec : permutations(v)) { - for (auto&& i : vec) { - cout << i << ' '; - } - cout << '\n'; + for (auto&& i : vec) { + cout << i << ' '; + } + cout << '\n'; } ``` powerset -------- +-------- *Additional Requirements*: Input must have a ForwardIterator Generates every possible subset of a set, runs in O(2^n). Example usage: + ```c++ vector vec {1,2,3,4,5,6,7,8,9}; for (auto&& v : powerset(vec)) { - for (auto&& i : v) { - cout << i << " "; - } - cout << '\n'; + 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/accumulate.hpp b/accumulate.hpp deleted file mode 100644 index 6d336e2c..00000000 --- a/accumulate.hpp +++ /dev/null @@ -1,161 +0,0 @@ -#ifndef ITER_ACCUMULATE_H_ -#define ITER_ACCUMULATE_H_ - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Accumulator; - } - - template - impl::Accumulator accumulate( - Container&&, AccumulateFunc); - - template - impl::Accumulator, AccumulateFunc> accumulate( - std::initializer_list, AccumulateFunc); -} - -template -class iter::impl::Accumulator { - private: - Container container; - AccumulateFunc accumulate_func; - - friend Accumulator iter::accumulate( - Container&&, AccumulateFunc); - - template - friend Accumulator, AF> iter::accumulate( - std::initializer_list, AF); - - // AccumVal must be default constructible - using AccumVal = - typename std::remove_reference, iterator_deref)>::type>::type; - - Accumulator(Container&& in_container, AccumulateFunc in_accumulate_func) - : container(std::forward(in_container)), - accumulate_func(in_accumulate_func) {} - - public: - Accumulator(Accumulator&&) = default; - - class Iterator : public std::iterator { - private: - iterator_type sub_iter; - iterator_type sub_end; - AccumulateFunc* accumulate_func; - std::unique_ptr acc_val; - - public: - Iterator(iterator_type&& iter, iterator_type&& end, - AccumulateFunc& in_accumulate_fun) - : sub_iter{std::move(iter)}, - sub_end{std::move(end)}, - accumulate_func(&in_accumulate_fun), - // only get first value if not an end iterator - acc_val{!(iter != end) ? nullptr : new AccumVal(*iter)} {} - - Iterator(const Iterator& other) - : sub_iter{other.sub_iter}, - sub_end{other.sub_end}, - accumulate_func{other.accumulate_func}, - acc_val{other.acc_val ? new AccumVal(*other.acc_val) : nullptr} {} - - Iterator& operator=(const Iterator& other) { - if (this == &other) { return *this; } - this->sub_iter = other.sub_iter; - this->sub_end = other.sub_end; - this->accumulate_func = other.accumulate_func; - this->acc_val.reset( - other.acc_val ? new AccumVal(*other.acc_val) : nullptr); - return *this; - } - - Iterator(Iterator&&) = default; - Iterator& operator=(Iterator&&) = default; - - const AccumVal& operator*() const { - return *this->acc_val; - } - - const AccumVal* operator->() const { - return this->acc_val.get(); - } - - Iterator& operator++() { - ++this->sub_iter; - if (this->sub_iter != this->sub_end) { - *this->acc_val = (*accumulate_func)(*this->acc_val, *this->sub_iter); - } - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return this->sub_iter != other.sub_iter; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - }; - - Iterator begin() { - return {std::begin(this->container), std::end(this->container), - this->accumulate_func}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container), - this->accumulate_func}; - } -}; - -template -iter::impl::Accumulator iter::accumulate( - Container&& container, AccumulateFunc accumulate_func) { - return {std::forward(container), accumulate_func}; -} - -template -iter::impl::Accumulator, AccumulateFunc> -iter::accumulate(std::initializer_list il, AccumulateFunc accumulate_func) { - return {std::move(il), accumulate_func}; -} - -namespace iter { - template - auto accumulate(Container&& container) -> decltype(accumulate( - std::forward(container), - std::plus>::type>{})) { - return accumulate( - std::forward(container), - std::plus>::type>{}); - } - - template - auto accumulate(std::initializer_list il) - -> decltype(accumulate(std::move(il), std::plus{})) { - return accumulate(std::move(il), std::plus{}); - } -} - -#endif diff --git a/chain.hpp b/chain.hpp deleted file mode 100644 index fe30255d..00000000 --- a/chain.hpp +++ /dev/null @@ -1,317 +0,0 @@ -#ifndef ITER_CHAIN_HPP_ -#define ITER_CHAIN_HPP_ - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Chained; - - template - class Chained; - - template - class ChainedFromIterable; - - // rather than a chain function, use a callable object to support - // .from_iterable() - class ChainMaker; - } -} - -template -class iter::impl::Chained { - static_assert(are_same, - iterator_deref...>::value, - "All chained iterables must have iterators that " - "dereference to the same type, including cv-qualifiers " - "and references."); - - friend class ChainMaker; - template - friend class Chained; - - private: - Container container; - Chained rest_chained; - Chained(Container&& in_container, RestContainers&&... rest) - : container(std::forward(in_container)), - rest_chained{std::forward(rest)...} {} - - public: - Chained(Chained&&) = default; - class Iterator : public std::iterator> { - private: - using RestIter = typename Chained::Iterator; - iterator_type sub_iter; - iterator_type sub_end; - RestIter rest_iter; - bool at_end; - - public: - Iterator(iterator_type&& s_begin, - iterator_type&& s_end, RestIter&& in_rest_iter) - : sub_iter{std::move(s_begin)}, - sub_end{std::move(s_end)}, - rest_iter{std::move(in_rest_iter)}, - at_end{!(sub_iter != sub_end)} {} - - Iterator& operator++() { - if (this->at_end) { - ++this->rest_iter; - } else { - ++this->sub_iter; - if (!(this->sub_iter != this->sub_end)) { - this->at_end = true; - } - } - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return this->sub_iter != other.sub_iter - || this->rest_iter != other.rest_iter; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - - iterator_deref operator*() { - return this->at_end ? *this->rest_iter : *this->sub_iter; - } - - iterator_arrow operator->() { - return this->at_end ? apply_arrow(this->rest_iter) - : apply_arrow(this->sub_iter); - } - }; - - Iterator begin() { - return {std::begin(this->container), std::end(this->container), - std::begin(this->rest_chained)}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container), - std::end(this->rest_chained)}; - } -}; -template -class iter::impl::Chained { - friend class ChainMaker; - template - friend class Chained; - - private: - Container container; - Chained(Container&& in_container) - : container(std::forward(in_container)) {} - - public: - Chained(Chained&&) = default; - class Iterator : public std::iterator> { - private: - iterator_type sub_iter; - iterator_type sub_end; - - public: - Iterator(const iterator_type& s_begin, - const iterator_type& s_end) - : sub_iter{s_begin}, sub_end{s_end} {} - - Iterator& operator++() { - ++this->sub_iter; - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return this->sub_iter != other.sub_iter; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - - iterator_deref operator*() { - return *this->sub_iter; - } - - iterator_arrow operator->() { - return apply_arrow(this->sub_iter); - } - }; - - Iterator begin() { - return {std::begin(this->container), std::end(this->container)}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container)}; - } -}; - -template -class iter::impl::ChainedFromIterable { - private: - Container container; - friend class ChainMaker; - ChainedFromIterable(Container&& in_container) - : container(std::forward(in_container)) {} - - public: - ChainedFromIterable(ChainedFromIterable&&) = default; - class Iterator : public std::iterator>> { - private: - using SubContainer = iterator_deref; - using SubIter = iterator_type; - - iterator_type top_level_iter; - iterator_type top_level_end; - std::unique_ptr sub_iter_p; - std::unique_ptr sub_end_p; - - static std::unique_ptr clone_sub_pointer(const SubIter* sub_iter) { - return std::unique_ptr{ - sub_iter ? new SubIter{*sub_iter} : nullptr}; - } - - bool sub_iters_differ(const Iterator& other) const { - if (this->sub_iter_p == other.sub_iter_p) { - return false; - } - if (this->sub_iter_p == nullptr || other.sub_iter_p == nullptr) { - // since the first check tests if they're the same, - // this will return if only one is nullptr - return true; - } - return *this->sub_iter_p != *other.sub_iter_p; - } - - public: - Iterator( - iterator_type&& top_iter, iterator_type&& top_end) - : top_level_iter{std::move(top_iter)}, - top_level_end{std::move(top_end)}, - sub_iter_p{!(top_iter != top_end) - ? // iter == end ? - nullptr - : new SubIter{std::begin(*top_iter)}}, - sub_end_p{!(top_iter != top_end) ? // iter == end ? - nullptr - : new SubIter{std::end(*top_iter)}} { - } - - Iterator(const Iterator& other) - : top_level_iter{other.top_level_iter}, - top_level_end{other.top_level_end}, - sub_iter_p{clone_sub_pointer(other.sub_iter_p.get())}, - sub_end_p{clone_sub_pointer(other.sub_end_p.get())} {} - - Iterator& operator=(const Iterator& other) { - if (this == &other) { return *this; } - - this->top_level_iter = other.top_level_iter; - this->top_level_end = other.top_level_end; - this->sub_iter_p = clone_sub_pointer(other.sub_iter_p.get()); - this->sub_end_p = clone_sub_pointer(other.sub_end_p.get()); - - return *this; - } - - Iterator(Iterator&&) = default; - Iterator& operator=(Iterator&&) = default; - ~Iterator() = default; - - Iterator& operator++() { - ++*this->sub_iter_p; - if (!(*this->sub_iter_p != *this->sub_end_p)) { - ++this->top_level_iter; - if (this->top_level_iter != this->top_level_end) { - sub_iter_p.reset(new SubIter{std::begin(*this->top_level_iter)}); - sub_end_p.reset(new SubIter{std::end(*this->top_level_iter)}); - } else { - sub_iter_p.reset(nullptr); - sub_end_p.reset(nullptr); - } - } - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return this->top_level_iter != other.top_level_iter - || this->sub_iters_differ(other); - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - - iterator_deref> operator*() { - return **this->sub_iter_p; - } - - iterator_arrow> operator->() { - return apply_arrow(*this->sub_iter_p); - } - }; - - Iterator begin() { - return {std::begin(this->container), std::end(this->container)}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container)}; - } -}; - -class iter::impl::ChainMaker { - public: - // expose regular call operator to provide usual chain() - template - Chained operator()(Containers&&... cs) const { - return {std::forward(cs)...}; - } - - // chain.from_iterable() - template - ChainedFromIterable from_iterable(Container&& container) const { - return {std::forward(container)}; - } -}; - -namespace iter { - namespace { - constexpr auto chain = impl::ChainMaker{}; - } -} - -#endif diff --git a/chunked.hpp b/chunked.hpp deleted file mode 100644 index 3ce976f4..00000000 --- a/chunked.hpp +++ /dev/null @@ -1,129 +0,0 @@ -#ifndef ITER_CHUNKED_HPP_ -#define ITER_CHUNKED_HPP_ - -#include "internal/iterbase.hpp" -#include "internal/iteratoriterator.hpp" - -#include -#include -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Chunker; - } - - template - impl::Chunker chunked(Container&&, std::size_t); - - template - impl::Chunker> chunked( - std::initializer_list, std::size_t); -} - -template -class iter::impl::Chunker { - private: - Container container; - std::size_t chunk_size; - - Chunker(Container&& c, std::size_t sz) - : container(std::forward(c)), chunk_size{sz} {} - - friend Chunker iter::chunked(Container&&, std::size_t); - template - friend Chunker> iter::chunked( - std::initializer_list, std::size_t); - - using IndexVector = std::vector>; - using DerefVec = IterIterWrapper; - - public: - Chunker(Chunker&&) = default; - class Iterator : public std::iterator { - private: - iterator_type sub_iter; - iterator_type sub_end; - DerefVec chunk; - std::size_t chunk_size = 0; - - bool done() const { - return this->chunk.empty(); - } - - void refill_chunk() { - this->chunk.get().clear(); - std::size_t i{0}; - while (i < chunk_size && this->sub_iter != this->sub_end) { - chunk.get().push_back(this->sub_iter); - ++this->sub_iter; - ++i; - } - } - - public: - Iterator(iterator_type&& in_iter, - iterator_type&& in_end, std::size_t s) - : sub_iter{std::move(in_iter)}, - sub_end{std::move(in_end)}, - chunk_size{s} { - this->chunk.get().reserve(this->chunk_size); - this->refill_chunk(); - } - - Iterator& operator++() { - this->refill_chunk(); - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return !(*this == other); - } - - bool operator==(const Iterator& other) const { - return this->done() == other.done() - && (this->done() || !(this->sub_iter != other.sub_iter)); - } - - DerefVec& operator*() { - return this->chunk; - } - - DerefVec* operator->() { - return &this->chunk; - } - }; - - Iterator begin() { - return {std::begin(this->container), std::end(this->container), chunk_size}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container), chunk_size}; - } -}; - -template -iter::impl::Chunker iter::chunked( - Container&& container, std::size_t chunk_size) { - return {std::forward(container), chunk_size}; -} - -template -iter::impl::Chunker> iter::chunked( - std::initializer_list il, std::size_t chunk_size) { - return {std::move(il), chunk_size}; -} - -#endif 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 098fc9d0..00000000 --- a/combinations.hpp +++ /dev/null @@ -1,151 +0,0 @@ -#ifndef ITER_COMBINATIONS_HPP_ -#define ITER_COMBINATIONS_HPP_ - -#include "internal/iterbase.hpp" -#include "internal/iteratoriterator.hpp" - -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Combinator; - } - - template - impl::Combinator combinations(Container&&, std::size_t); - - template - impl::Combinator> combinations( - std::initializer_list, std::size_t); -} - -template -class iter::impl::Combinator { - private: - Container container; - std::size_t length; - - friend Combinator iter::combinations(Container&&, std::size_t); - template - friend Combinator> iter::combinations( - std::initializer_list, std::size_t); - - Combinator(Container&& in_container, std::size_t in_length) - : container(std::forward(in_container)), length{in_length} {} - - using IndexVector = std::vector>; - using CombIteratorDeref = IterIterWrapper; - - public: - Combinator(Combinator&&) = default; - class Iterator - : public std::iterator { - private: - constexpr static const int COMPLETE = -1; - typename std::remove_reference::type* container_p; - CombIteratorDeref indices; - int steps{}; - - public: - Iterator(Container& in_container, std::size_t n) - : container_p{&in_container}, indices{n} { - if (n == 0) { - this->steps = COMPLETE; - return; - } - size_t inc = 0; - for (auto& iter : this->indices.get()) { - auto it = std::begin(*this->container_p); - dumb_advance(it, std::end(*this->container_p), inc); - if (it != std::end(*this->container_p)) { - iter = it; - ++inc; - } else { - this->steps = COMPLETE; - break; - } - } - } - - CombIteratorDeref& operator*() { - return this->indices; - } - - CombIteratorDeref* operator->() { - return &this->indices; - } - - Iterator& operator++() { - 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(this->indices.get().rbegin(), iter); - - if (!(dumb_next(*iter, dist) != std::end(*this->container_p))) { - if ((iter + 1) != indices.get().rend()) { - size_t inc = 1; - for (auto down = iter; down != indices.get().rbegin() - 1; --down) { - (*down) = dumb_next(*(iter + 1), 1 + inc); - ++inc; - } - } else { - this->steps = COMPLETE; - break; - } - } else { - break; - } - // we break because none of the rest of the items need - // to be incremented - } - if (this->steps != COMPLETE) { - ++this->steps; - } - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return !(*this == other); - } - - bool operator==(const Iterator& other) const { - return this->steps == other.steps; - } - }; - - Iterator begin() { - return {this->container, this->length}; - } - - Iterator end() { - return {this->container, 0}; - } -}; - -template -iter::impl::Combinator iter::combinations( - Container&& container, std::size_t length) { - return {std::forward(container), length}; -} - -template -iter::impl::Combinator> iter::combinations( - std::initializer_list il, std::size_t length) { - return {std::move(il), length}; -} - -#endif diff --git a/combinations_with_replacement.hpp b/combinations_with_replacement.hpp deleted file mode 100644 index adc41665..00000000 --- a/combinations_with_replacement.hpp +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef ITER_COMBINATIONS_WITH_REPLACEMENT_HPP_ -#define ITER_COMBINATIONS_WITH_REPLACEMENT_HPP_ - -#include "internal/iterbase.hpp" -#include "internal/iteratoriterator.hpp" - -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class CombinatorWithReplacement; - } - - template - impl::CombinatorWithReplacement combinations_with_replacement( - Container&&, std::size_t); - - template - impl::CombinatorWithReplacement> - combinations_with_replacement(std::initializer_list, std::size_t); -} - -template -class iter::impl::CombinatorWithReplacement { - private: - Container container; - std::size_t length; - - friend CombinatorWithReplacement - iter::combinations_with_replacement(Container&&, std::size_t); - template - friend CombinatorWithReplacement> - iter::combinations_with_replacement( - std::initializer_list, std::size_t); - - CombinatorWithReplacement(Container&& in_container, std::size_t n) - : container(std::forward(in_container)), length{n} {} - - using IndexVector = std::vector>; - using CombIteratorDeref = IterIterWrapper; - - public: - CombinatorWithReplacement(CombinatorWithReplacement&&) = default; - class Iterator - : public std::iterator { - private: - constexpr static const int COMPLETE = -1; - typename std::remove_reference::type* container_p; - CombIteratorDeref indices; - int steps; - - public: - Iterator(Container& in_container, std::size_t n) - : container_p{&in_container}, - indices(n, std::begin(in_container)), - steps{(std::begin(in_container) != std::end(in_container) && n) - ? 0 - : COMPLETE} {} - - CombIteratorDeref& operator*() { - return this->indices; - } - - CombIteratorDeref* operator->() { - return &this->indices; - } - - Iterator& operator++() { - for (auto iter = indices.get().rbegin(); iter != indices.get().rend(); - ++iter) { - ++(*iter); - if (!(*iter != std::end(*this->container_p))) { - if ((iter + 1) != indices.get().rend()) { - for (auto down = iter; down != indices.get().rbegin() - 1; --down) { - (*down) = dumb_next(*(iter + 1)); - } - } else { - this->steps = COMPLETE; - break; - } - } else { - // we break because none of the rest of the items - // need to be incremented - break; - } - } - if (this->steps != COMPLETE) { - ++this->steps; - } - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return !(*this == other); - } - - bool operator==(const Iterator& other) const { - return this->steps == other.steps; - } - }; - - Iterator begin() { - return {this->container, this->length}; - } - - Iterator end() { - return {this->container, 0}; - } -}; - -template -iter::impl::CombinatorWithReplacement -iter::combinations_with_replacement(Container&& container, std::size_t length) { - return {std::forward(container), length}; -} - -template -iter::impl::CombinatorWithReplacement> -iter::combinations_with_replacement( - std::initializer_list il, std::size_t length) { - return {std::move(il), length}; -} - -#endif diff --git a/compress.hpp b/compress.hpp deleted file mode 100644 index dd92c203..00000000 --- a/compress.hpp +++ /dev/null @@ -1,162 +0,0 @@ -#ifndef ITER_COMPRESS_H_ -#define ITER_COMPRESS_H_ - -#include "internal/iterbase.hpp" - -#include -#include -#include - -namespace iter { - namespace impl { - template - class Compressed; - } - - template - impl::Compressed compress(Container&&, Selector&&); - - template - impl::Compressed, Selector> compress( - std::initializer_list, Selector&&); - - template - impl::Compressed> compress( - Container&&, std::initializer_list); - - template - impl::Compressed, std::initializer_list> compress( - std::initializer_list, std::initializer_list); -} - -template -class iter::impl::Compressed { - private: - Container container; - Selector selectors; - - friend Compressed iter::compress( - Container&&, Selector&&); - - template - friend Compressed, Sel> iter::compress( - std::initializer_list, Sel&&); - - template - friend Compressed> iter::compress( - Con&&, std::initializer_list); - - template - friend Compressed, std::initializer_list> - iter::compress(std::initializer_list, std::initializer_list); - - // Selector::Iterator type - using selector_iter_type = decltype(std::begin(selectors)); - - Compressed(Container&& in_container, Selector&& in_selectors) - : container(std::forward(in_container)), - selectors(std::forward(in_selectors)) {} - - public: - Compressed(Compressed&&) = default; - class Iterator : public std::iterator> { - private: - iterator_type sub_iter; - iterator_type sub_end; - - selector_iter_type selector_iter; - 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->selector_end - && !*this->selector_iter) { - this->increment_iterators(); - } - } - - public: - Iterator(iterator_type&& cont_iter, - iterator_type&& cont_end, selector_iter_type&& sel_iter, - selector_iter_type&& 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)} { - this->skip_failures(); - } - - iterator_deref operator*() { - return *this->sub_iter; - } - - iterator_arrow operator->() { - return apply_arrow(this->sub_iter); - } - - Iterator& operator++() { - this->increment_iterators(); - this->skip_failures(); - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return this->sub_iter != other.sub_iter - && this->selector_iter != other.selector_iter; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - }; - - Iterator begin() { - return {std::begin(this->container), std::end(this->container), - std::begin(this->selectors), std::end(this->selectors)}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container), - std::end(this->selectors), std::end(this->selectors)}; - } -}; - -template -iter::impl::Compressed iter::compress( - Container&& container, Selector&& selectors) { - return { - std::forward(container), std::forward(selectors)}; -} - -template -iter::impl::Compressed, Selector> iter::compress( - std::initializer_list data, Selector&& selectors) { - return {std::move(data), std::forward(selectors)}; -} - -template -iter::impl::Compressed> iter::compress( - Container&& container, std::initializer_list selectors) { - return {std::forward(container), std::move(selectors)}; -} - -template -iter::impl::Compressed, std::initializer_list> -iter::compress( - std::initializer_list data, std::initializer_list selectors) { - return {std::move(data), std::move(selectors)}; -} - -#endif 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/count.hpp b/count.hpp deleted file mode 100644 index 0a67d971..00000000 --- a/count.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef ITER_COUNT_H_ -#define ITER_COUNT_H_ - -#include "range.hpp" - -#include - -namespace iter { - - template - constexpr auto count(T start, T step) noexcept - -> decltype(range(start, start, start)) { - // if step is < 0, stop is numeric min, otherwise numeric max - return range(start, step < T(0) ? std::numeric_limits::min() - : std::numeric_limits::max(), - step); - } - - template - constexpr auto count(T start = T(0)) noexcept - -> decltype(range(start, start)) { - return count(start, T(1)); - } -} - -#endif 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/internal/iteratoriterator.hpp b/cppitertools/internal/iteratoriterator.hpp similarity index 83% rename from internal/iteratoriterator.hpp rename to cppitertools/internal/iteratoriterator.hpp index a49b4b06..0ab23bad 100644 --- a/internal/iteratoriterator.hpp +++ b/cppitertools/internal/iteratoriterator.hpp @@ -1,10 +1,10 @@ #ifndef ITERATOR_ITERATOR_HPP_ #define ITERATOR_ITERATOR_HPP_ -#include "iterbase.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 @@ -19,17 +19,17 @@ namespace iter { struct HasConstDeref : std::false_type {}; template - struct HasConstDeref())>> + struct HasConstDeref())>> : std::true_type {}; template - class IteratorIterator - : public std::iterator())>::type> { + class IteratorIterator { + template + friend class IteratorIterator; using Diff = std::ptrdiff_t; static_assert( - std::is_same::iterator_category, + std::is_same< + typename std::iterator_traits::iterator_category, std::random_access_iterator_tag>::value, "IteratorIterator only works with random access iterators"); @@ -37,14 +37,29 @@ namespace iter { 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} {} - bool operator==(const IteratorIterator& other) const { + const TopIter& get() const { + return sub_iter; + } + + template + bool operator==(const IteratorIterator& other) const { return !(*this != other); } - bool operator!=(const IteratorIterator& other) const { + template + bool operator!=(const IteratorIterator& other) const { return this->sub_iter != other.sub_iter; } @@ -70,11 +85,11 @@ namespace iter { return ret; } - auto operator*() -> decltype(**sub_iter) { + auto operator*() const -> decltype(**sub_iter) { return **this->sub_iter; } - auto operator -> () -> decltype(*sub_iter) { + auto operator->() const -> decltype(*sub_iter) { return *this->sub_iter; } @@ -105,16 +120,11 @@ namespace iter { return it; } - friend IteratorIterator operator-(Diff n, IteratorIterator it) { - it -= n; - return it; - } - Diff operator-(const IteratorIterator& rhs) const { return this->sub_iter - rhs.sub_iter; } - auto operator[](Diff idx) -> decltype(*sub_iter[idx]) { + auto operator[](Diff idx) const -> decltype(*sub_iter[idx]) { return *sub_iter[idx]; } @@ -155,7 +165,7 @@ namespace iter { template struct ConstAtTypeOrVoid().at(0))>> + std::void_t().at(0))>> : type_is().at(0))> {}; using const_at_type_or_void_t = typename ConstAtTypeOrVoid<>::type; @@ -165,7 +175,7 @@ namespace iter { template struct ConstIndexTypeOrVoid()[0])>> + std::void_t()[0])>> : type_is()[0])> {}; using const_index_type_or_void_t = typename ConstIndexTypeOrVoid<>::type; 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