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 0f7d2d0a..740dacbd 100644 --- a/README.md +++ b/README.md @@ -1,10 +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)
@@ -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,41 +122,42 @@ 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 +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. +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 @@ -133,6 +167,7 @@ would expect them to behave: - accumulate - chain.from\_iterable - chunked +- batched - combinations - combinations\_with\_replacement - cycle @@ -150,52 +185,56 @@ would expect them to behave: - sorted - starmap - takewhile -- unique\_everseen +- 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'; } ``` @@ -203,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'; } ``` @@ -215,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'; } ``` @@ -258,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'; } ``` @@ -309,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'; } ``` @@ -323,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'; } ``` @@ -334,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; + } } ``` @@ -353,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'; } ``` @@ -378,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'; } ``` @@ -397,31 +470,32 @@ 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. 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 @@ -431,21 +505,21 @@ 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 +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; + int operator()(int i) const; + int operator()(int i, char c) const; + int operator()(double d, int i, char c) const; }; ``` @@ -453,36 +527,40 @@ 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 + 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'; } ``` @@ -493,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} @@ -552,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'; } ``` @@ -580,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. @@ -592,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'; } ``` @@ -602,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. @@ -614,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'; } ``` @@ -632,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'; } ``` @@ -675,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). @@ -704,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} @@ -788,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 3df0fd29..00000000 --- a/accumulate.hpp +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef ITER_ACCUMULATE_H_ -#define ITER_ACCUMULATE_H_ - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Accumulator; - - using AccumulateFn = IterToolFnOptionalBindSecond>; - } - constexpr impl::AccumulateFn accumulate{}; -} - -template -class iter::impl::Accumulator { - private: - Container container; - AccumulateFunc accumulate_func; - - friend AccumulateFn; - - using AccumVal = std::remove_reference_t, iterator_deref)>>; - - 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}; - } -}; - -#endif diff --git a/chain.hpp b/chain.hpp deleted file mode 100644 index 38e7eb69..00000000 --- a/chain.hpp +++ /dev/null @@ -1,321 +0,0 @@ -#ifndef ITER_CHAIN_HPP_ -#define ITER_CHAIN_HPP_ - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include -#include -#include - -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 -class iter::impl::Chained { - private: - friend ChainMaker; - - 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."); - - 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); - } - - template - static bool get_and_check_not_equal( - const IterTupType& lhs, const IterTupType& rhs) { - return std::get(lhs) != std::get(rhs); - } - - using DerefFunc = DerefType (*)(IterTupType&); - using ArrowFunc = ArrowType (*)(IterTupType&); - using IncFunc = void (*)(IterTupType&); - using NeqFunc = bool (*)(const IterTupType&, const 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...}}; - - constexpr static std::array neq_comparers{ - {get_and_check_not_equal...}}; - - using TraitsValue = iterator_traits_deref>; - - private: - Chained(TupType&& t) : tup(std::move(t)) {} - TupType tup; - - public: - Chained(Chained&&) = default; - - class Iterator : public std::iterator { - private: - std::size_t index; - IterTupType iters; - IterTupType ends; - - void check_for_end_and_adjust() { - while (this->index < sizeof...(Is) - && !(neq_comparers[this->index](this->iters, this->ends))) { - ++this->index; - } - } - - public: - Iterator(std::size_t i, IterTupType&& in_iters, IterTupType&& in_ends) - : index{i}, iters(in_iters), ends(in_ends) { - this->check_for_end_and_adjust(); - } - - decltype(auto) operator*() { - return derefers[this->index](this->iters); - } - - decltype(auto) operator-> () { - return arrowers[this->index](this->iters); - } - - Iterator& operator++() { - incrementers[this->index](this->iters); - this->check_for_end_and_adjust(); - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return this->index != other.index - || (this->index != sizeof...(Is) - && neq_comparers[this->index](this->iters, other.iters)); - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - }; - - Iterator begin() { - return {0, IterTupType{std::begin(std::get(this->tup))...}, - IterTupType{std::end(std::get(this->tup))...}}; - } - - Iterator end() { - return {sizeof...(Is), IterTupType{std::end(std::get(this->tup))...}, - IterTupType{std::end(std::get(this->tup))...}}; - } -}; - -template -constexpr std::array::DerefFunc, - sizeof...(Is)> iter::impl::Chained::derefers; - -template -constexpr std::array::ArrowFunc, - sizeof...(Is)> iter::impl::Chained::arrowers; - -template -constexpr std::array::IncFunc, - sizeof...(Is)> iter::impl::Chained::incrementers; - -template -constexpr std::array::NeqFunc, - sizeof...(Is)> iter::impl::Chained::neq_comparers; - -template -class iter::impl::ChainedFromIterable { - private: - friend ChainFromIterableFn; - Container container; - 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 sub_iter ? std::make_unique(*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 - : std::make_unique(std::begin(*top_iter))}, - sub_end_p{!(top_iter != top_end) - ? // iter == end ? - nullptr - : std::make_unique(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 = - std::make_unique(std::begin(*this->top_level_iter)); - sub_end_p = - std::make_unique(std::end(*this->top_level_iter)); - } else { - sub_iter_p.reset(); - sub_end_p.reset(); - } - } - 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 { - private: - template - Chained chain_impl( - TupleType&& in_containers, std::index_sequence) const { - return {std::move(in_containers)}; - } - - public: - // expose regular call operator to provide usual chain() - template - auto operator()(Containers&&... cs) const { - return this->chain_impl( - std::tuple{std::forward(cs)...}, - std::index_sequence_for{}); - } - - ChainFromIterableFn from_iterable; -}; - -namespace iter { - namespace { - constexpr auto chain = iter::impl::ChainMaker{}; - } -} - -#endif diff --git a/chunked.hpp b/chunked.hpp deleted file mode 100644 index d5676568..00000000 --- a/chunked.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef ITER_CHUNKED_HPP_ -#define ITER_CHUNKED_HPP_ - -#include "internal/iterbase.hpp" -#include "internal/iteratoriterator.hpp" - -#include -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Chunker; - - using ChunkedFn = IterToolFnBindSizeTSecond; - } - constexpr impl::ChunkedFn chunked{}; -} - -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 ChunkedFn; - - 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}; - } -}; - -#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 d90e21c6..00000000 --- a/combinations.hpp +++ /dev/null @@ -1,131 +0,0 @@ -#ifndef ITER_COMBINATIONS_HPP_ -#define ITER_COMBINATIONS_HPP_ - -#include "internal/iterbase.hpp" -#include "internal/iteratoriterator.hpp" - -#include -#include -#include - -namespace iter { - namespace impl { - template - class Combinator; - - using CombinationsFn = IterToolFnBindSizeTSecond; - } - constexpr impl::CombinationsFn combinations{}; -} - -template -class iter::impl::Combinator { - private: - Container container; - std::size_t length; - - friend CombinationsFn; - - 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; - std::remove_reference_t* 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}; - } -}; - -#endif diff --git a/combinations_with_replacement.hpp b/combinations_with_replacement.hpp deleted file mode 100644 index cfc04957..00000000 --- a/combinations_with_replacement.hpp +++ /dev/null @@ -1,110 +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 - -namespace iter { - namespace impl { - template - class CombinatorWithReplacement; - using CombinationsWithReplacementFn = - IterToolFnBindSizeTSecond; - } - constexpr impl::CombinationsWithReplacementFn combinations_with_replacement{}; -} - -template -class iter::impl::CombinatorWithReplacement { - private: - Container container; - std::size_t length; - - friend CombinationsWithReplacementFn; - - 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; - std::remove_reference_t* 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}; - } -}; - -#endif diff --git a/compress.hpp b/compress.hpp deleted file mode 100644 index 22d8cded..00000000 --- a/compress.hpp +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef ITER_COMPRESS_H_ -#define ITER_COMPRESS_H_ - -#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&&); - - // 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)}; -} - -#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/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/count.hpp b/cppitertools/count.hpp similarity index 75% rename from count.hpp rename to cppitertools/count.hpp index 1694589c..327596bf 100644 --- a/count.hpp +++ b/cppitertools/count.hpp @@ -8,8 +8,7 @@ namespace iter { template constexpr auto count(T start, T step) noexcept { - // if step is < 0, set the stop to numeric min, otherwise numeric max - T stop = step < T(0) ? std::numeric_limits::min() + T stop = step < T(0) ? std::numeric_limits::lowest() : std::numeric_limits::max(); return range(start, stop, step); } 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/filterfalse.hpp b/cppitertools/filterfalse.hpp similarity index 72% rename from filterfalse.hpp rename to cppitertools/filterfalse.hpp index 513962f7..269724dd 100644 --- a/filterfalse.hpp +++ b/cppitertools/filterfalse.hpp @@ -1,9 +1,10 @@ #ifndef ITER_FILTER_FALSE_HPP_ #define ITER_FILTER_FALSE_HPP_ -#include "internal/iterbase.hpp" #include "filter.hpp" +#include "internal/iterbase.hpp" +#include #include namespace iter { @@ -13,22 +14,22 @@ namespace iter { template class PredicateFlipper { private: - FilterFunc filter_func; + FilterFunc filter_func_; public: - PredicateFlipper(FilterFunc in_filter_func) - : filter_func(std::move(in_filter_func)) {} + PredicateFlipper(FilterFunc filter_func) + : filter_func_(std::move(filter_func)) {} - // Calls the filter_func + // Calls the filter_func_ template bool operator()(const T& item) const { - return !bool(filter_func(item)); + return !bool(std::invoke(filter_func_, item)); } // with non-const incase FilterFunc::operator() is non-const template bool operator()(const T& item) { - return !bool(filter_func(item)); + return !bool(std::invoke(filter_func_, item)); } }; @@ -37,7 +38,7 @@ namespace iter { using FilterFalseFn = IterToolFnOptionalBindFirst; } - constexpr impl::FilterFalseFn filterfalse{}; + inline constexpr impl::FilterFalseFn filterfalse{}; } // Delegates to Filtered with PredicateFlipper @@ -47,7 +48,8 @@ class iter::impl::FilterFalsed friend FilterFalseFn; FilterFalsed(FilterFunc in_filter_func, Container&& in_container) : Filtered, Container>( - {in_filter_func}, std::forward(in_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/internal/iterbase.hpp b/cppitertools/internal/iterbase.hpp similarity index 59% rename from internal/iterbase.hpp rename to cppitertools/internal/iterbase.hpp index aa6d6a2c..e9627079 100644 --- a/internal/iterbase.hpp +++ b/cppitertools/internal/iterbase.hpp @@ -7,32 +7,96 @@ // also applies to the name of the file. No user code should include // this file directly. -#include -#include -#include +#include +#include #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; }; - // gcc CWG 1558 - template - struct void_t_help { - using type = void; - }; - template - using void_t = typename void_t_help::type; + 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 iterator_type = decltype(std::begin(std::declval())); + 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 @@ -46,20 +110,32 @@ namespace iter { 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_reference_t>; + std::remove_cv_t>>; template struct IsIterable : std::false_type {}; - // Assuming that if a type works with std::begin, it is an iterable. + // Assuming that if a type works with begin, it is an iterable. template - struct IsIterable>> : std::true_type {}; + 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 { @@ -76,7 +152,8 @@ namespace iter { }; template - struct ArrowHelper().operator->())>> { + struct ArrowHelper().operator->())>> { using type = decltype(std::declval().operator->()); type operator()(T& t) const { return t.operator->(); @@ -121,9 +198,9 @@ namespace iter { template struct is_random_access_iter::iterator_category, - std::random_access_iterator_tag>::value>> : std::true_type {}; + std::enable_if_t< + std::is_same::iterator_category, + std::random_access_iterator_tag>::value>> : std::true_type {}; template using has_random_access_iter = is_random_access_iter>; @@ -131,39 +208,31 @@ namespace iter { // version that will work with most things template - void dumb_advance(InputIt& iter, Distance distance = 1) { + void dumb_advance_unsafe(InputIt& iter, Distance distance) { for (Distance i(0); i < distance; ++i) { ++iter; } } - template - void dumb_advance_impl( - Iter& iter, const Iter& end, Distance distance, std::false_type) { - for (Distance i(0); i < distance && iter != end; ++i) { - ++iter; - } - } - - template - void dumb_advance_impl( - Iter& iter, const Iter& end, Distance distance, std::true_type) { - if (static_cast(end - iter) < distance) { - iter = end; + // 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 { - iter += distance; + for (Distance i(0); i < distance && iter != end; ++i) { + ++iter; + } } } - // iter will not be incremented past end - template - void dumb_advance(Iter& iter, const Iter& end, Distance distance = 1) { - dumb_advance_impl(iter, end, distance, is_random_access_iter{}); - } - template ForwardIt dumb_next(ForwardIt it, Distance distance = 1) { - dumb_advance(it, distance); + dumb_advance_unsafe(it, distance); return it; } @@ -177,8 +246,8 @@ namespace iter { template Distance dumb_size(Container&& container) { Distance d{0}; - for (auto it = std::begin(container), end = std::end(container); - it != end; ++it) { + auto end_it = get_end(container); + for (auto it = get_begin(container); it != end_it; ++it) { ++d; } return d; @@ -192,59 +261,9 @@ namespace iter { : std::integral_constant::value && are_same::value> {}; - namespace detail { - template - std::tuple...> iterator_tuple_deref_helper( - const std::tuple&); - } - - namespace detail { - template - std::tuple...> 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())); - - // 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())); - - // ---- Tuple utilities ---- // - - // 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&&...) {} - - namespace detail { - template - decltype(auto) call_with_tuple_impl( - Func&& mf, TupleType&& tup, std::index_sequence) { - return mf(std::forward>>(std::get(tup))...); - } - } - - // expand a TupleType into individual arguments when calling a Func - template - decltype(auto) call_with_tuple(Func&& mf, TupleType&& tup) { - constexpr auto TUP_SIZE = std::tuple_size>::value; - return detail::call_with_tuple_impl(std::forward(mf), - std::forward(tup), std::make_index_sequence{}); - } - // DerefHolder holds the value gotten from an iterator dereference - // if the iterate dereferences to an lvalue references, a pointer to the - // element is stored - // if it does not, a value is stored instead + // 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 @@ -254,44 +273,34 @@ namespace iter { 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_reference_t; + using TPlain = std::remove_cv_t>; - std::unique_ptr item_p; + std::optional item_p_; public: using reference = TPlain&; using pointer = TPlain*; - DerefHolder() = default; - - DerefHolder(const DerefHolder& other) - : item_p{other.item_p ? std::make_unique(*other.item_p) - : nullptr} {} - - DerefHolder& operator=(const DerefHolder& other) { - this->item_p = - other.item_p ? std::make_unique(*other.item_p) : nullptr; - return *this; - } + static constexpr bool stores_value = true; - DerefHolder(DerefHolder&&) = default; - DerefHolder& operator=(DerefHolder&&) = default; - ~DerefHolder() = default; + DerefHolder() = default; reference get() { - return *this->item_p; + assert(item_p_.has_value()); + return *item_p_; } pointer get_ptr() { - return this->item_p.get(); + assert(item_p_.has_value()); + return &item_p_.value(); } void reset(T&& item) { - item_p = std::make_unique(std::move(item)); + item_p_.emplace(std::move(item)); } explicit operator bool() const { - return static_cast(this->item_p); + return static_cast(item_p_); } }; @@ -303,25 +312,29 @@ namespace iter { using pointer = T*; private: - pointer item_p{}; + pointer item_p_{}; public: + static constexpr bool stores_value = false; + DerefHolder() = default; reference get() { - return *this->item_p; + assert(item_p_); + return *item_p_; } pointer get_ptr() { - return this->item_p; + assert(item_p_); + return item_p_; } void reset(reference item) { - this->item_p = &item; + item_p_ = &item; } explicit operator bool() const { - return this->item_p != nullptr; + return item_p_ != nullptr; } }; @@ -331,6 +344,14 @@ namespace iter { 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)); } @@ -353,11 +374,17 @@ namespace iter { protected: template struct FnPartial : Pipeable> { + static_assert(!std::is_reference_v); mutable T stored_arg; - constexpr FnPartial(T in_t) : stored_arg(in_t) {} + constexpr FnPartial(T in_t) : stored_arg(std::move(in_t)) {} template - auto operator()(Container&& container) const { + auto operator()(Container&& container) && { + return F{}(std::move(stored_arg), std::forward(container)); + } + + template + auto operator()(Container&& container) const& { return F{}(stored_arg, std::forward(container)); } }; @@ -369,6 +396,42 @@ namespace iter { } }; + // Pipeable callable which allows binding of the second argument + // f(a, b) is the same as a | f(b) + // f(a) with an iterable is the same as f(a, DefaultT{}) + template + struct PipeableAndBindOptionalSecond : Pipeable { + protected: + template + struct FnPartial : Pipeable> { + mutable T stored_arg; + constexpr FnPartial(T in_t) : stored_arg(std::move(in_t)) {} + + template + auto operator()(Container&& container) && { + return F{}(std::forward(container), std::move(stored_arg)); + } + + template + auto operator()(Container&& container) const& { + return F{}(std::forward(container), stored_arg); + } + }; + + public: + template >> + FnPartial> operator()(T&& t) const { + return {std::forward(t)}; + } + + template >> + auto operator()(Container&& container) const { + return static_cast(*this)( + std::forward(container), DefaultT{}); + } + }; + // This is a complicated class to generate a callable that can work: // (1) with just a single (iterable) passed, and DefaultT substituted // (2) with an iterable and a callable @@ -380,22 +443,14 @@ namespace iter { using Base = PipeableAndBindFirst>; - protected: - template - auto operator()(Container&& container, std::false_type) const { - return static_cast (*this)( - std::forward(container)); - } - - template - auto operator()(Container&& container, std::true_type) const { - return (*this)(DefaultT{}, std::forward(container)); - } - public: template auto operator()(T&& t) const { - return (*this)(std::forward(t), IsIterable{}); + if constexpr (IsIterable{}) { + return (*this)(DefaultT{}, std::forward(t)); + } else { + return static_cast(*this)(std::forward(t)); + } } template struct FnPartial : Pipeable> { mutable T stored_arg; - constexpr FnPartial(T in_t) : stored_arg(in_t) {} + constexpr FnPartial(T in_t) : stored_arg(std::move(in_t)) {} template - auto operator()(Container&& container) const { + auto operator()(Container&& container) && { + return IterToolFnOptionalBindSecond{}( + std::forward(container), std::move(stored_arg)); + } + + template + auto operator()(Container&& container) const& { return IterToolFnOptionalBindSecond{}( std::forward(container), stored_arg); } diff --git a/itertools.hpp b/cppitertools/itertools.hpp similarity index 97% rename from itertools.hpp rename to cppitertools/itertools.hpp index 939d6919..bf824196 100644 --- a/itertools.hpp +++ b/cppitertools/itertools.hpp @@ -2,7 +2,9 @@ #define ITERTOOLS_ALL_HPP_ #include "accumulate.hpp" +#include "batched.hpp" #include "chain.hpp" +#include "chunked.hpp" #include "combinations.hpp" #include "combinations_with_replacement.hpp" #include "compress.hpp" @@ -13,9 +15,7 @@ #include "filter.hpp" #include "filterfalse.hpp" #include "groupby.hpp" -#include "chunked.hpp" #include "imap.hpp" -#include "sliding_window.hpp" #include "permutations.hpp" #include "powerset.hpp" #include "product.hpp" @@ -23,6 +23,7 @@ #include "repeat.hpp" #include "reversed.hpp" #include "slice.hpp" +#include "sliding_window.hpp" #include "sorted.hpp" #include "starmap.hpp" #include "takewhile.hpp" diff --git a/cppitertools/permutations.hpp b/cppitertools/permutations.hpp new file mode 100644 index 00000000..f567bc68 --- /dev/null +++ b/cppitertools/permutations.hpp @@ -0,0 +1,128 @@ +#ifndef ITER_PERMUTATIONS_HPP_ +#define ITER_PERMUTATIONS_HPP_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iteratoriterator.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class Permuter; + using PermutationsFn = IterToolFn; + } + inline constexpr impl::PermutationsFn permutations{}; +} + +template +class iter::impl::Permuter { + private: + friend PermutationsFn; + Container container_; + + template + using IndexVector = std::vector>; + template + using Permutable = IterIterWrapper>; + + Permuter(Container&& container) + : container_(std::forward(container)) {} + + public: + Permuter(Permuter&&) = default; + + template + class Iterator { + private: + template + friend class Iterator; + static constexpr const int COMPLETE = -1; + static bool cmp_iters(IteratorWrapper lhs, + IteratorWrapper rhs) noexcept { + return *lhs < *rhs; + } + + Permutable working_set_; + int steps_{}; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = Permutable; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end) + : steps_{sub_iter != sub_end ? 0 : COMPLETE} { + // done like this instead of using vector ctor with + // two iterators because that causes a substitution + // failure when the iterator is minimal + while (sub_iter != sub_end) { + working_set_.get().push_back(sub_iter); + ++sub_iter; + } + std::sort(get_begin(working_set_.get()), get_end(working_set_.get()), + cmp_iters); + } + + Permutable& operator*() { + return working_set_; + } + + Permutable* operator->() { + return &working_set_; + } + + Iterator& operator++() { + ++steps_; + if (!std::next_permutation(get_begin(working_set_.get()), + get_end(working_set_.get()), cmp_iters)) { + steps_ = COMPLETE; + } + 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 {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/powerset.hpp b/cppitertools/powerset.hpp new file mode 100644 index 00000000..d6131ca8 --- /dev/null +++ b/cppitertools/powerset.hpp @@ -0,0 +1,131 @@ +#ifndef ITER_POWERSET_HPP_ +#define ITER_POWERSET_HPP_ + +#include "combinations.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class Powersetter; + + using PowersetFn = IterToolFn; + } + inline constexpr impl::PowersetFn powerset{}; +} + +template +class iter::impl::Powersetter { + private: + Container container_; + template + using CombinatorType = decltype(combinations(std::declval(), 0)); + + friend PowersetFn; + + Powersetter(Container&& container) + : container_(std::forward(container)) {} + + public: + Powersetter(Powersetter&&) = default; + + template + class Iterator { + private: +#if 0 + template friend class Iterator; +#endif + std::remove_reference_t* container_p_; + std::size_t set_size_{}; + std::shared_ptr> comb_; + iterator_type> comb_iter_; + iterator_type> comb_end_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = CombinatorType; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(ContainerT& container, std::size_t sz) + : container_p_{&container}, + set_size_{sz}, + comb_{std::make_shared>( + combinations(container, sz))}, + comb_iter_{get_begin(*comb_)}, + comb_end_{get_end(*comb_)} {} + + Iterator& operator++() { + ++comb_iter_; + if (comb_iter_ == comb_end_) { + ++set_size_; + comb_ = std::make_shared>( + combinations(*container_p_, set_size_)); + + comb_iter_ = get_begin(*comb_); + comb_end_ = get_end(*comb_); + } + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + iterator_deref> operator*() { + return *comb_iter_; + } + + iterator_arrow> operator->() { + apply_arrow(comb_iter_); + } + + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + bool operator==(const Iterator& other) const { + return set_size_ == other.set_size_ && comb_iter_ == other.comb_iter_; + } +#if 0 + template + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + template + bool operator==(const Iterator& other) const { + return set_size_ == other.set_size_ && comb_iter_ == other.comb_iter_; + } +#endif + }; + + Iterator begin() { + return {container_, 0}; + } + + Iterator end() { + return {container_, dumb_size(container_) + 1}; + } + + Iterator> begin() const { + return {std::as_const(container_), 0}; + } + + Iterator> end() const { + return { + std::as_const(container_), dumb_size(std::as_const(container_)) + 1}; + } +}; + +#endif diff --git a/cppitertools/product.hpp b/cppitertools/product.hpp new file mode 100644 index 00000000..7de5727a --- /dev/null +++ b/cppitertools/product.hpp @@ -0,0 +1,233 @@ +#ifndef ITER_PRODUCT_HPP_ +#define ITER_PRODUCT_HPP_ + +#include "internal/iter_tuples.hpp" +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class Productor; + + template + Productor product_impl( + TupleType&& containers, std::index_sequence); + } +} + +template +class iter::impl::Productor { + friend Productor iter::impl::product_impl( + TupleType&&, std::index_sequence); + + private: + TupleType containers_; + + Productor(TupleType&& containers) : containers_(std::move(containers)) {} + + public: + Productor(Productor&&) = default; + + private: + template + class IteratorData { + IteratorData() = delete; + static_assert( + std::tuple_size>::value == sizeof...(Is), + "tuple size != sizeof Is"); + + public: + template + static bool equal(const IterTupType& lhs, const IterTupType& rhs) { + return !(std::get(lhs) != std::get(rhs)); + } + + // returns true if incremented, false if wrapped around + template + static bool get_and_increment_with_wraparound(IterTupType& iters, + const IterTupType& begin_iters, const IterTupType& end_iters) { + // if already at the end, we're looking at an empty container + if (equal(iters, end_iters)) { + return false; + } + + ++std::get(iters); + + if (equal(iters, end_iters)) { + std::get(iters) = std::get(begin_iters); + return false; + } + + return true; + } + using IncFunc = bool (*)( + IterTupType&, const IterTupType&, const IterTupType&); + + constexpr static std::array incrementers{ + {get_and_increment_with_wraparound...}}; + }; + + // template templates here because I need to defer evaluation in the const + // iteration case for types that don't have non-const begin() and end(). If I + // passed in the actual types of the tuples of iterators and the type for + // deref they'd need to be known in the function declarations below. + template class IteratorTuple, + template class TupleDeref> + class IteratorTempl { +#if NO_GCC_FRIEND_ERROR + private: + template class, template class> + friend class IteratorTempl; +#else + public: +#endif + + using IterTupType = IteratorTuple; + IterTupType iters_; + IterTupType begin_iters_; + IterTupType end_iters_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = TupleDeref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type; + + IteratorTempl(IteratorTuple&& iters, + IteratorTuple&& end_iters) + : iters_(std::move(iters)), + begin_iters_(iters_), + end_iters_(std::move(end_iters)) {} + + IteratorTempl& operator++() { + static constexpr int NUM_ELEMENTS = sizeof...(Is); + bool performed_increment = false; + for (int i = NUM_ELEMENTS - 1; i >= 0; --i) { + if (IteratorData::incrementers[i]( + iters_, begin_iters_, end_iters_)) { + performed_increment = true; + break; + } + } + if (!performed_increment) { + iters_ = end_iters_; + } + return *this; + } + + IteratorTempl operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template class IT, + template class TD> + bool operator!=(const IteratorTempl& other) const { + if constexpr (sizeof...(Is) == 0) { + return false; + } else { + return (... && (std::get(iters_) != std::get(other.iters_))); + } + } + + template class IT, + template class TD> + bool operator==(const IteratorTempl& other) const { + return !(*this != other); + } + + TupleDeref operator*() { + return {(*std::get(iters_))...}; + } + + auto operator->() -> ArrowProxy { + return {**this}; + } + }; + + using Iterator = + IteratorTempl; + using ConstIterator = IteratorTempl, + const_iterator_tuple_type, const_iterator_deref_tuple>; + + public: + Iterator begin() { + return {{get_begin(std::get(containers_))...}, + {get_end(std::get(containers_))...}}; + } + + Iterator end() { + return {{get_end(std::get(containers_))...}, + {get_end(std::get(containers_))...}}; + } + + ConstIterator begin() const { + return {{get_begin(std::as_const(std::get(containers_)))...}, + {get_end(std::as_const(std::get(containers_)))...}}; + } + + ConstIterator end() const { + return {{get_end(std::as_const(std::get(containers_)))...}, + {get_end(std::as_const(std::get(containers_)))...}}; + } +}; + +namespace iter::impl { + template + Productor product_impl( + TupleType&& containers, std::index_sequence) { + return {std::move(containers)}; + } +} + +namespace iter { + template + decltype(auto) product(Containers&&... containers) { + return impl::product_impl( + std::tuple(std::forward(containers)...), + std::index_sequence_for{}); + } + + constexpr std::array, 1> product() { + return {{}}; + } +} + +namespace iter::impl { + // rvalue must be copied, lvalue and const lvalue references can be bound + template + decltype(auto) product_repeat( + std::index_sequence, Container&& container) { + return product(((void)Is, Container(container))...); + } + + template + decltype(auto) product_repeat( + std::index_sequence, Container& container) { + return product(((void)Is, container)...); + } + + template + decltype(auto) product_repeat( + std::index_sequence, const Container& container) { + return product(((void)Is, container)...); + } +} + +namespace iter { + template + decltype(auto) product(Container&& container) { + return impl::product_repeat( + std::make_index_sequence{}, std::forward(container)); + } +} + +#endif diff --git a/range.hpp b/cppitertools/range.hpp similarity index 57% rename from range.hpp rename to cppitertools/range.hpp index 67a0c267..2efa7396 100644 --- a/range.hpp +++ b/cppitertools/range.hpp @@ -3,10 +3,10 @@ #include "internal/iterbase.hpp" +#include #include -#include #include -#include +#include namespace iter { namespace impl { @@ -37,23 +37,22 @@ namespace iter { public: constexpr RangeIterData() noexcept = default; constexpr RangeIterData(T in_value, T in_step) noexcept - : value_{in_value}, - step_{in_step} {} + : value_{in_value}, step_{in_step} {} constexpr T value() const noexcept { - return this->value_; + return value_; } constexpr T step() const noexcept { - return this->step_; + return step_; } void inc() noexcept { - this->value_ += step_; + value_ += step_; } constexpr bool operator==(const RangeIterData& other) const noexcept { - return this->value_ == other.value_; + return value_ == other.value_; } constexpr bool operator!=(const RangeIterData& other) const noexcept { @@ -68,34 +67,32 @@ namespace iter { T start_{}; T value_{}; T step_{}; - std::size_t steps_taken{}; + std::size_t steps_taken_{}; public: constexpr RangeIterData() noexcept = default; constexpr RangeIterData(T in_start, T in_step) noexcept - : start_{in_start}, - value_{in_start}, - step_{in_step} {} + : start_{in_start}, value_{in_start}, step_{in_step} {} constexpr T value() const noexcept { - return this->value_; + return value_; } constexpr T step() const noexcept { - return this->step_; + return step_; } void inc() noexcept { - ++this->steps_taken; - value_ = this->start_ + (this->step_ * this->steps_taken); + ++steps_taken_; + value_ = start_ + (step_ * steps_taken_); } constexpr bool operator==(const RangeIterData& other) const noexcept { // if the difference between the two values is less than the - // step size, they are considered equal - return (this->value_ < other.value_ ? other.value_ - this->value_ - : this->value_ - other.value_) - < this->step_; + // step_ size, they are considered equal + return (value_ < other.value_ ? other.value_ - value_ + : value_ - other.value_) + < step_; } constexpr bool operator!=(const RangeIterData& other) const noexcept { @@ -117,64 +114,101 @@ class iter::impl::Range { friend constexpr Range iter::range(U, U, U) noexcept; private: - const T start; - const T stop; - const T step; + const T start_; + const T stop_; + const T step_; + + constexpr Range(T stop) noexcept : start_{0}, stop_{stop}, step_{1} {} + + constexpr Range(T start, T stop, T step = 1) noexcept + : start_{start}, stop_{stop}, step_{step} {} + + // if val is "before" the stopping point. + static constexpr bool is_within_range( + T val, T stop_val, [[maybe_unused]] T step_val) { + if constexpr (std::is_unsigned{}) { + return val < stop_val; + } else { + return !(step_val > 0 && val >= stop_val) + && !(step_val < 0 && val <= stop_val); + } + } - constexpr Range(T in_stop) noexcept : start{0}, stop{in_stop}, step{1} {} + public: + constexpr T start() const noexcept { + return start_; + } - constexpr Range(T in_start, T in_stop, T in_step = 1) noexcept - : start{in_start}, - stop{in_stop}, - step{in_step} {} + constexpr T stop() const noexcept { + return stop_; + } + + constexpr T step() const noexcept { + return step_; + } + + constexpr T operator[](std::size_t index) const noexcept { + return start() + (step() * index); + } + + constexpr std::size_t size() const noexcept { + static_assert(!std::is_floating_point_v, + "range size() not supperted with floating point types"); + if (!is_within_range(start(), stop(), step())) { + return 0; + } + + auto diff = stop() - start(); + auto res = diff / step(); + assert(res >= 0); + auto result = static_cast(res); + if (diff % step()) { + ++result; + } + return result; + } - public: // the reference type here is T, which doesn't strictly follow all // of the rules, but std::vector::iterator::reference isn't // a reference type either, this isn't any worse - class Iterator : public std::iterator { + class Iterator { private: iter::detail::RangeIterData data; - bool is_end; - - // compare unsigned values - static bool not_equal_to_impl(const Iterator& iter, - const Iterator& end_iter, std::true_type) noexcept { - assert(!iter.is_end); - assert(end_iter.is_end); - return iter.data.value() < end_iter.data.value(); - } + bool is_end{}; - // compare signed values - static bool not_equal_to_impl(const Iterator& iter, - const Iterator& end_iter, std::false_type) noexcept { - assert(!iter.is_end); - assert(end_iter.is_end); - return !(iter.data.step() > 0 - && iter.data.value() >= end_iter.data.value()) - && !(iter.data.step() < 0 - && iter.data.value() <= end_iter.data.value()); + // first argument must be regular iterator + // second argument must be end iterator + static bool not_equal_to_impl( + const Iterator& lhs, const Iterator& rhs) noexcept { + assert(!lhs.is_end); + assert(rhs.is_end); + return is_within_range( + lhs.data.value(), rhs.data.value(), lhs.data.step()); } static bool not_equal_to_end( const Iterator& lhs, const Iterator& rhs) noexcept { if (rhs.is_end) { - return not_equal_to_impl(lhs, rhs, std::is_unsigned{}); + return not_equal_to_impl(lhs, rhs); } - return not_equal_to_impl(rhs, lhs, std::is_unsigned{}); + return not_equal_to_impl(rhs, lhs); } public: + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type; + constexpr Iterator() noexcept = default; constexpr Iterator(T in_value, T in_step, bool in_is_end) noexcept - : data(in_value, in_step), - is_end{in_is_end} {} + : data(in_value, in_step), is_end{in_is_end} {} constexpr T operator*() const noexcept { - return this->data.value(); + return data.value(); } constexpr ArrowProxy operator->() const noexcept { @@ -182,7 +216,7 @@ class iter::impl::Range { } Iterator& operator++() noexcept { - this->data.inc(); + data.inc(); return *this; } @@ -202,9 +236,9 @@ class iter::impl::Range { // infinitely (theoretically). If this occurs, the Range // will instead effectively be empty // - // 2) (stop - start) % step != 0. For + // 2) (stop_ - start_) % step_ != 0. For // example Range(1, 10, 2). The iterator will never be - // exactly equal to the stop value. + // exactly equal to the stop_ value. // // Another way to think about it is that the "end" // iterator represents the range of values that are invalid @@ -214,12 +248,12 @@ class iter::impl::Range { // // Two non-end iterators will compare by their stored values bool operator!=(const Iterator& other) const noexcept { - if (this->is_end && other.is_end) { + if (is_end && other.is_end) { return false; } - if (!this->is_end && !other.is_end) { - return this->data != other.data; + if (!is_end && !other.is_end) { + return data != other.data; } return not_equal_to_end(*this, other); } @@ -230,27 +264,29 @@ class iter::impl::Range { }; constexpr Iterator begin() const noexcept { - return {start, step, false}; + return {start_, step_, false}; } constexpr Iterator end() const noexcept { - return {stop, step, true}; + return {stop_, step_, true}; } }; template -constexpr iter::impl::Range iter::range(T stop) noexcept { - return {stop}; +constexpr iter::impl::Range iter::range(T stop_) noexcept { + return {stop_}; } template -constexpr iter::impl::Range iter::range(T start, T stop) noexcept { - return {start, stop}; +constexpr iter::impl::Range iter::range(T start_, T stop_) noexcept { + return {start_, stop_}; } template -constexpr iter::impl::Range iter::range(T start, T stop, T step) noexcept { - return step == T(0) ? impl::Range{0} : impl::Range{start, stop, step}; +constexpr iter::impl::Range iter::range( + T start_, T stop_, T step_) noexcept { + return step_ == T(0) ? impl::Range{0} + : impl::Range{start_, stop_, step_}; } #endif diff --git a/repeat.hpp b/cppitertools/repeat.hpp similarity index 65% rename from repeat.hpp rename to cppitertools/repeat.hpp index c33d40c5..327c21b0 100644 --- a/repeat.hpp +++ b/cppitertools/repeat.hpp @@ -23,27 +23,33 @@ class iter::impl::RepeaterWithCount { friend constexpr RepeaterWithCount iter::repeat(U&&, int); private: - T elem; - int count; + T elem_; + int count_; constexpr RepeaterWithCount(T e, int c) - : elem(std::forward(e)), count{c} {} + : elem_(std::forward(e)), count_{c} {} using TPlain = typename std::remove_reference::type; public: RepeaterWithCount(RepeaterWithCount&&) = default; - class Iterator : public std::iterator { + class Iterator { private: - const TPlain* elem; - int count; + const TPlain* elem_; + int count_; public: - constexpr Iterator(const TPlain* e, int c) : elem{e}, count{c} {} + using iterator_category = std::input_iterator_tag; + using value_type = const TPlain; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + constexpr Iterator(const TPlain* e, int c) : elem_{e}, count_{c} {} Iterator& operator++() { - --this->count; + --this->count_; return *this; } @@ -58,30 +64,38 @@ class iter::impl::RepeaterWithCount { } constexpr bool operator==(const Iterator& other) const { - return this->count == other.count; + return this->count_ == other.count_; } constexpr const TPlain& operator*() const { - return *this->elem; + return *this->elem_; } constexpr const TPlain* operator->() const { - return this->elem; + return this->elem_; } }; constexpr Iterator begin() const { - return {&this->elem, this->count}; + return {&this->elem_, this->count_}; } constexpr Iterator end() const { - return {&this->elem, 0}; + return {&this->elem_, 0}; + } + + constexpr Iterator rbegin() const { + return begin(); + } + + constexpr Iterator rend() const { + return end(); } }; template -constexpr iter::impl::RepeaterWithCount iter::repeat(T&& e, int count) { - return {std::forward(e), count < 0 ? 0 : count}; +constexpr iter::impl::RepeaterWithCount iter::repeat(T&& e, int count_) { + return {std::forward(e), count_ < 0 ? 0 : count_}; } namespace iter { @@ -101,19 +115,25 @@ class iter::impl::Repeater { private: using TPlain = typename std::remove_reference::type; - T elem; + T elem_; - constexpr Repeater(T e) : elem(std::forward(e)) {} + constexpr Repeater(T e) : elem_(std::forward(e)) {} public: Repeater(Repeater&&) = default; - class Iterator : public std::iterator { + class Iterator { private: - const TPlain* elem; + const TPlain* elem_; public: - constexpr Iterator(const TPlain* e) : elem{e} {} + using iterator_category = std::input_iterator_tag; + using value_type = const TPlain; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + constexpr Iterator(const TPlain* e) : elem_{e} {} constexpr const Iterator& operator++() const { return *this; @@ -132,21 +152,29 @@ class iter::impl::Repeater { } constexpr const TPlain& operator*() const { - return *this->elem; + return *this->elem_; } constexpr const TPlain* operator->() const { - return this->elem; + return this->elem_; } }; constexpr Iterator begin() const { - return {&this->elem}; + return {&this->elem_}; } constexpr Iterator end() const { return {nullptr}; } + + constexpr Iterator rbegin() const { + return begin(); + } + + constexpr Iterator rend() const { + return end(); + } }; template diff --git a/cppitertools/reversed.hpp b/cppitertools/reversed.hpp new file mode 100644 index 00000000..022088f6 --- /dev/null +++ b/cppitertools/reversed.hpp @@ -0,0 +1,137 @@ +#ifndef ITER_REVERSE_HPP_ +#define ITER_REVERSE_HPP_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include + +namespace iter { + namespace impl { + template + using reverse_iterator_type = + decltype(std::rbegin(std::declval())); + template + using reverse_iterator_end_type = + decltype(std::rend(std::declval())); + + // If rbegin and rend return the same type, type will be + // reverse_iterator_type + // If rbegin and rend return different types, type will be + // IteratorWrapperImpl + template + struct ReverseIteratorWrapperImplType; + + template + struct ReverseIteratorWrapperImplType + : type_is> {}; + + template + struct ReverseIteratorWrapperImplType + : type_is, + reverse_iterator_end_type>> {}; + + template + using ReverseIteratorWrapper = + typename ReverseIteratorWrapperImplType, + impl:: + reverse_iterator_end_type>>:: + type; + + template + class Reverser; + + using ReversedFn = IterToolFn; + } + inline constexpr impl::ReversedFn reversed{}; +} + +template +class iter::impl::Reverser { + private: + Container container_; + friend ReversedFn; + + Reverser(Container&& container) + : container_(std::forward(container)) {} + + template + using reverse_iterator_deref = + decltype(*std::declval&>()); + + template + using reverse_iterator_traits_deref = + std::remove_reference_t>; + + template + using reverse_iterator_arrow = detail::arrow>; + + public: + Reverser(Reverser&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + ReverseIteratorWrapper sub_iter_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = reverse_iterator_traits_deref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(ReverseIteratorWrapper&& sub_iter) + : sub_iter_{std::move(sub_iter)} {} + + reverse_iterator_deref operator*() { + return *sub_iter_; + } + + reverse_iterator_arrow operator->() { + return apply_arrow(sub_iter_); + } + + Iterator& operator++() { + ++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 {std::rbegin(container_)}; + } + + Iterator end() { + return {std::rend(container_)}; + } + + Iterator> begin() const { + return {std::rbegin(std::as_const(container_))}; + } + + Iterator> end() const { + return {std::rend(std::as_const(container_))}; + } +}; + +#endif diff --git a/cppitertools/slice.hpp b/cppitertools/slice.hpp new file mode 100644 index 00000000..38557016 --- /dev/null +++ b/cppitertools/slice.hpp @@ -0,0 +1,178 @@ +#ifndef ITER_SLICE_HPP_ +#define ITER_SLICE_HPP_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include + +namespace iter { + namespace impl { + template + class Sliced; + + struct SliceFn; + } +} + +template +class iter::impl::Sliced { + private: + Container container_; + DifferenceType start_; + DifferenceType stop_; + DifferenceType step_; + + friend SliceFn; + + Sliced(Container&& container, DifferenceType start, DifferenceType stop, + DifferenceType step) + : container_(std::forward(container)), + start_{start < stop && step > 0 ? start : stop}, + stop_{stop}, + step_{step} {} + + public: + Sliced(Sliced&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + DifferenceType current_; + DifferenceType stop_; + DifferenceType step_; + + 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 = iterator_deref; + + Iterator(IteratorWrapper&& sub_iter, + IteratorWrapper&& sub_end, DifferenceType start, + DifferenceType stop, DifferenceType step) + : sub_iter_{std::move(sub_iter)}, + sub_end_{std::move(sub_end)}, + current_{start}, + stop_{stop}, + step_{step} {} + + iterator_deref operator*() { + return *sub_iter_; + } + + iterator_arrow operator->() { + return apply_arrow(sub_iter_); + } + + Iterator& operator++() { + dumb_advance(sub_iter_, sub_end_, step_); + current_ += step_; + if (stop_ < current_) { + current_ = stop_; + } + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return sub_iter_ != other.sub_iter_ && current_ != other.current_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + }; + + Iterator begin() { + auto it = get_begin(container_); + dumb_advance(it, get_end(container_), start_); + return {std::move(it), get_end(container_), start_, stop_, step_}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), stop_, stop_, step_}; + } + + Iterator> begin() const { + auto it = get_begin(std::as_const(container_)); + dumb_advance(it, get_end(std::as_const(container_)), start_); + return {std::move(it), get_end(std::as_const(container_)), start_, stop_, + step_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), stop_, stop_, step_}; + } +}; + +struct iter::impl::SliceFn { + private: + template + class FnPartial : public Pipeable> { + public: + template + Sliced operator()(Container&& container) const { + return {std::forward(container), start_, stop_, step_}; + } + + private: + friend SliceFn; + constexpr FnPartial( + DifferenceType start, DifferenceType stop, DifferenceType step) noexcept + : start_{start}, stop_{stop}, step_{step} {} + DifferenceType start_; + DifferenceType stop_; + DifferenceType step_; + }; + + public: + template >> + Sliced operator()(Container&& container, + DifferenceType start, DifferenceType stop, + DifferenceType step = 1) const { + return {std::forward(container), start, stop, step}; + } + + // only given the end, assume step_ is 1 and begin is 0 + template >> + iter::impl::Sliced operator()( + Container&& container, DifferenceType stop) const { + return {std::forward(container), 0, stop, 1}; + } + + template >> + constexpr FnPartial operator()( + DifferenceType stop) const noexcept { + return {0, stop, 1}; + } + + template >> + constexpr FnPartial operator()(DifferenceType start, + DifferenceType stop, DifferenceType step = 1) const noexcept { + return {start, stop, step}; + } +}; + +namespace iter { + inline constexpr impl::SliceFn slice{}; +} + +#endif diff --git a/cppitertools/sliding_window.hpp b/cppitertools/sliding_window.hpp new file mode 100644 index 00000000..01c348ba --- /dev/null +++ b/cppitertools/sliding_window.hpp @@ -0,0 +1,126 @@ +#ifndef ITER_SLIDING_WINDOW_HPP_ +#define ITER_SLIDING_WINDOW_HPP_ + +#include "internal/iterator_wrapper.hpp" +#include "internal/iteratoriterator.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class WindowSlider; + using SlidingWindowFn = IterToolFnBindSizeTSecond; + } + inline constexpr impl::SlidingWindowFn sliding_window{}; +} + +template +class iter::impl::WindowSlider { + private: + Container container_; + std::size_t window_size_; + + friend SlidingWindowFn; + + WindowSlider(Container&& container, std::size_t win_sz) + : container_(std::forward(container)), window_size_{win_sz} {} + + template + using IndexVector = std::deque>; + template + using DerefVec = IterIterWrapper>; + + public: + WindowSlider(WindowSlider&&) = default; + template + class Iterator { + private: + template + friend class Iterator; + std::shared_ptr> window_ = + std::make_shared>(); + IteratorWrapper sub_iter_; + + 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 window_sz) + : sub_iter_(std::move(sub_iter)) { + std::size_t i{0}; + while (i < window_sz && sub_iter_ != sub_end) { + window_->get().push_back(sub_iter_); + ++i; + if (i != window_sz) { + ++sub_iter_; + } + } + } + + template + bool operator!=(const Iterator& other) const { + return sub_iter_ != other.sub_iter_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + + DerefVec& operator*() { + return *window_; + } + + DerefVec* operator->() { + return window_.get(); + } + + Iterator& operator++() { + ++sub_iter_; + window_->get().pop_front(); + window_->get().push_back(sub_iter_); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + }; + + Iterator begin() { + return { + (window_size_ != 0 ? IteratorWrapper{get_begin(container_)} + : IteratorWrapper{get_end(container_)}), + get_end(container_), window_size_}; + } + + Iterator end() { + return {get_end(container_), get_end(container_), window_size_}; + } + + Iterator> begin() const { + return {(window_size_ != 0 ? IteratorWrapper>{get_begin( + std::as_const(container_))} + : IteratorWrapper>{get_end( + std::as_const(container_))}), + get_end(std::as_const(container_)), window_size_}; + } + + Iterator> end() const { + return {get_end(std::as_const(container_)), + get_end(std::as_const(container_)), window_size_}; + } +}; + +#endif diff --git a/cppitertools/sorted.hpp b/cppitertools/sorted.hpp new file mode 100644 index 00000000..a704af3f --- /dev/null +++ b/cppitertools/sorted.hpp @@ -0,0 +1,173 @@ +#ifndef ITER_SORTED_HPP_ +#define ITER_SORTED_HPP_ + +#include "internal/iteratoriterator.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class SortedView; + using SortedFn = IterToolFnOptionalBindSecond>; + } + inline constexpr impl::SortedFn sorted{}; +} + +template +class iter::impl::SortedView { + private: + template + class SortedItersHolder { + public: + using IterIterWrap = + IterIterWrapper>>; + using ItIt = iterator_type; + using ConstItIt = void; + + private: + ContainerT container_; + IterIterWrap sorted_iters_; + + public: + SortedItersHolder(ContainerT&& container, CompareFunc compare_func) + : container_(std::forward(container)) { + // Fill the sorted_iters_ vector with an iterator to each + // element in the container_ + for (auto iter = get_begin(container_); iter != get_end(container_); + ++iter) { + sorted_iters_.get().push_back(iter); + } + + // sort by comparing the elements that the iterators point to + std::sort(get_begin(sorted_iters_.get()), get_end(sorted_iters_.get()), + [compare_func]( + iterator_type it1, iterator_type it2) { + return std::invoke(compare_func, *it1, *it2); + }); + } + + ItIt begin() { + return sorted_iters_.begin(); + } + + ItIt end() { + return sorted_iters_.end(); + } + }; + + template + class SortedItersHolder&>()))>> { + public: + using IterIterWrap = + IterIterWrapper>>; + using ItIt = iterator_type; + + using ConstIterIterWrap = + IterIterWrapper>>>; + using ConstItIt = iterator_type; + + private: + ContainerT container_; + mutable CompareFunc compare_func_; + IterIterWrap sorted_iters_; + mutable ConstIterIterWrap const_sorted_iters_; + + void populate_sorted_iters() const = delete; + void populate_sorted_iters() { + if (!sorted_iters_.empty()) { + return; + } + // Fill the sorted_iters_ vector with an iterator to each + // element in the container_ + for (auto iter = get_begin(container_); iter != get_end(container_); + ++iter) { + sorted_iters_.get().push_back(iter); + } + + // sort by comparing the elements that the iterators point to + std::sort(get_begin(sorted_iters_.get()), get_end(sorted_iters_.get()), + [this](iterator_type it1, iterator_type it2) { + return std::invoke(compare_func_, *it1, *it2); + }); + } + + void populate_const_sorted_iters() = delete; + void populate_const_sorted_iters() const { + if (!const_sorted_iters_.empty()) { + return; + } + for (auto iter = get_begin(std::as_const(container_)); + iter != get_end(std::as_const(container_)); ++iter) { + const_sorted_iters_.get().push_back(iter); + } + + // sort by comparing the elements that the iterators point to + std::sort(get_begin(const_sorted_iters_.get()), + get_end(const_sorted_iters_.get()), + [this](iterator_type> it1, + iterator_type> it2) { + return compare_func_(*it1, *it2); + }); + } + + public: + SortedItersHolder(ContainerT&& container, CompareFunc compare_func) + : container_(std::forward(container)), + compare_func_(std::move(compare_func)) {} + + ItIt begin() { + populate_sorted_iters(); + return sorted_iters_.begin(); + } + + ItIt end() { + populate_sorted_iters(); + return sorted_iters_.end(); + } + + ConstItIt begin() const { + populate_const_sorted_iters(); + return const_sorted_iters_.begin(); + } + + ConstItIt end() const { + populate_const_sorted_iters(); + return const_sorted_iters_.end(); + } + }; + + friend SortedFn; + + SortedItersHolder sorted_iters_holder_; + + SortedView(Container&& container, CompareFunc compare_func) + : sorted_iters_holder_{ + std::forward(container), std::move(compare_func)} {} + + public: + SortedView(SortedView&&) = default; + + typename SortedItersHolder::ItIt begin() { + return sorted_iters_holder_.begin(); + } + + typename SortedItersHolder::ItIt end() { + return sorted_iters_holder_.end(); + } + + typename SortedItersHolder::ConstItIt begin() const { + return sorted_iters_holder_.begin(); + } + + typename SortedItersHolder::ConstItIt end() const { + return sorted_iters_holder_.end(); + } +}; + +#endif diff --git a/cppitertools/starmap.hpp b/cppitertools/starmap.hpp new file mode 100644 index 00000000..f9c2d260 --- /dev/null +++ b/cppitertools/starmap.hpp @@ -0,0 +1,258 @@ +#ifndef ITER_STARMAP_H_ +#define ITER_STARMAP_H_ + +#include "internal/iter_tuples.hpp" +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class StarMapper; + + template + class TupleStarMapper; + + struct StarMapFn; + } +} + +// NOTE I don't know why, but clang gets very confused by having in the +// Iterators' member functions for these classes + +// starmap with a container_ where T is one of tuple, pair, array +template +class iter::impl::StarMapper { + private: + mutable Func func_; + Container container_; + + using StarIterDeref = + decltype(std::apply(func_, std::declval>())); + using StarIterDerefValue = + std::remove_cv_t>; + + StarMapper(Func f, Container&& c) + : func_(std::move(f)), container_(std::forward(c)) {} + + friend StarMapFn; + + public: + template + class Iterator { + private: + template + friend class Iterator; + Func* func_; + IteratorWrapper sub_iter_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = StarIterDerefValue; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = StarIterDeref; + + Iterator(Func& f, IteratorWrapper&& sub_iter) + : func_(&f), sub_iter_(std::move(sub_iter)) {} + + template + bool operator!=(const Iterator& other) const { + return sub_iter_ != other.sub_iter_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + + Iterator& operator++() { + ++sub_iter_; + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + decltype(auto) operator*() { + return std::apply(*func_, *sub_iter_); + } + + auto operator->() -> ArrowProxy { + return {**this}; + } + }; + + Iterator begin() { + return {func_, get_begin(container_)}; + } + + Iterator end() { + return {func_, get_end(container_)}; + } + + Iterator> begin() const { + return {func_, get_begin(std::as_const(container_))}; + } + + Iterator> end() const { + return {func_, get_end(std::as_const(container_))}; + } +}; + +// starmap for a tuple or pair of tuples or pairs +template +class iter::impl::TupleStarMapper { + private: + mutable Func func_; + TupType tup_; + + private: + static_assert(sizeof...(Is) == std::tuple_size>::value, + "tuple size doesn't match size of Is"); + + friend StarMapFn; + + TupleStarMapper(Func f, TupType t) + : func_(std::move(f)), tup_(std::forward(t)) {} + + // this is a wrapper class to hold the aliases and functions needed for the + // Iterator. + template + class IteratorData { + public: + template + static auto get_and_call_with_tuple(Func& f, TupTypeT& t) + -> decltype(std::apply(f, + std::get(t))) { // TODO: Remove duplicated expression in + // decltype, using decltype(auto) as return + // type, when all compilers correctly deduce + // type (i.e. MSVC cl 19.15 does not do it). + return std::apply(f, std::get(t)); + } + + using ResultType = decltype(get_and_call_with_tuple<0>(func_, tup_)); + using CallerFunc = ResultType (*)(Func&, TupTypeT&); + + constexpr static std::array callers{ + {get_and_call_with_tuple...}}; + + using TraitsValue = std::remove_reference_t; + + IteratorData() = delete; + }; + + public: + template + class Iterator { + private: + template + friend class Iterator; + Func* func_; + std::remove_reference_t* tup_; + std::size_t index_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = typename IteratorData::TraitsValue; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(Func& f, TupTypeT& t, std::size_t i) + : func_{&f}, tup_{&t}, index_{i} {} + + decltype(auto) operator*() { + return IteratorData::callers[index_](*func_, *tup_); + } + + auto operator->() { + return ArrowProxy{**this}; + } + + Iterator& operator++() { + ++index_; + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template + bool operator!=(const Iterator& other) const { + return index_ != other.index_; + } + + template + bool operator==(const Iterator& other) const { + return !(*this != other); + } + }; + + Iterator begin() { + return {func_, tup_, 0}; + } + + Iterator end() { + return {func_, tup_, sizeof...(Is)}; + } + + Iterator> begin() const { + return {func_, std::as_const(tup_), 0}; + } + + Iterator> end() const { + return {func_, std::as_const(tup_), sizeof...(Is)}; + } +}; + +struct iter::impl::StarMapFn : PipeableAndBindFirst { + private: + template + TupleStarMapper helper_with_tuples( + Func func, TupType&& tup, std::index_sequence) const { + return {std::move(func), std::forward(tup)}; + } + + template + struct is_tuple_like : std::false_type {}; + + template + struct is_tuple_like>::value)>> + : std::true_type {}; + + public: + template + auto operator()(Func func, Seq&& sequence) const { + if constexpr (is_tuple_like{}) { + return helper_with_tuples(std::move(func), std::forward(sequence), + std::make_index_sequence< + std::tuple_size>::value>{}); + } else { + return StarMapper{ + std::move(func), std::forward(sequence)}; + } + } + + using PipeableAndBindFirst::operator(); +}; + +namespace iter { + inline constexpr impl::StarMapFn starmap{}; +} + +#endif diff --git a/cppitertools/takewhile.hpp b/cppitertools/takewhile.hpp new file mode 100644 index 00000000..3a351d0c --- /dev/null +++ b/cppitertools/takewhile.hpp @@ -0,0 +1,139 @@ +#ifndef ITER_TAKEWHILE_H_ +#define ITER_TAKEWHILE_H_ + +#include "filter.hpp" +#include "internal/iterator_wrapper.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include + +namespace iter { + namespace impl { + template + class Taker; + + using TakeWhileFn = IterToolFnOptionalBindFirst; + } + inline constexpr impl::TakeWhileFn takewhile{}; +} + +template +class iter::impl::Taker { + private: + Container container_; + mutable FilterFunc filter_func_; + + friend TakeWhileFn; + + Taker(FilterFunc filter_func, Container&& container) + : container_(std::forward(container)), + filter_func_(std::move(filter_func)) {} + + public: + Taker(Taker&&) = default; + + template + class Iterator { + private: + template + friend class Iterator; + using Holder = DerefHolder>; + // I want this mutable so I can use operator* reliably in the const + // context of init_if_first_use + mutable IteratorWrapper sub_iter_; + IteratorWrapper sub_end_; + mutable Holder item_; + FilterFunc* filter_func_; + + // see comments from filter about mutability + void inc_sub_iter() { + ++sub_iter_; + if (sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + } + } + + void check_current() const { + if (sub_iter_ != sub_end_ && !std::invoke(*filter_func_, item_.get())) { + sub_iter_ = sub_end_; + } + } + + void init_if_first_use() const { + if (!item_ && sub_iter_ != sub_end_) { + item_.reset(*sub_iter_); + check_current(); + } + } + + 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(); + check_current(); + 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/unique_everseen.hpp b/cppitertools/unique_everseen.hpp new file mode 100644 index 00000000..2195a4bd --- /dev/null +++ b/cppitertools/unique_everseen.hpp @@ -0,0 +1,51 @@ +#ifndef ITER_UNIQUE_EVERSEEN_HPP_ +#define ITER_UNIQUE_EVERSEEN_HPP_ + +#include "filter.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include +#include + +namespace iter { + namespace impl { + struct UniqueEverseenFn : Pipeable { + private: + template + using Key = std::decay_t>; + + public: + template + auto operator()(Container&& container, const Hash& hash, + const KeyEqual& key_equal) const { + // You can't pass a hash function or an equality function without + // passing a bucket_count as well. We get the default bucket count here + // the first time this function runs. + static auto default_bucket_count = + std::unordered_set{}.bucket_count(); + using elem_type = iterator_deref; + auto func = + [elem_seen = + std::unordered_set, Hash, KeyEqual>( + default_bucket_count, hash, key_equal)]( + const std::remove_reference_t& e) mutable { + return elem_seen.insert(e).second; + }; + return filter(func, std::forward(container)); + } + + template + auto operator()(Container&& container) const { + return (*this)(std::forward(container), + std::hash>{}, std::equal_to>{}); + } + }; + } + + inline constexpr impl::UniqueEverseenFn unique_everseen{}; +} + +#endif diff --git a/cppitertools/unique_justseen.hpp b/cppitertools/unique_justseen.hpp new file mode 100644 index 00000000..e86d20ba --- /dev/null +++ b/cppitertools/unique_justseen.hpp @@ -0,0 +1,31 @@ +#ifndef ITER_UNIQUE_JUSTSEEN_HPP +#define ITER_UNIQUE_JUSTSEEN_HPP + +#include "groupby.hpp" +#include "imap.hpp" + +#include +#include + +namespace iter { + namespace impl { + struct UniqueJustseenFn + : PipeableAndBindOptionalSecond { + public: + using PipeableAndBindOptionalSecond:: + operator(); + template + auto operator()(Container&& container, KeyFunc key_fn) const { + // decltype(auto) return type in lambda so reference types are preserved + return imap( + [](auto&& group) -> decltype(auto) { + return *get_begin(group.second); + }, + groupby(std::forward(container), std::move(key_fn))); + } + }; + } + inline constexpr impl::UniqueJustseenFn unique_justseen{}; +} + +#endif diff --git a/cppitertools/zip.hpp b/cppitertools/zip.hpp new file mode 100644 index 00000000..98bfd3c4 --- /dev/null +++ b/cppitertools/zip.hpp @@ -0,0 +1,133 @@ +#ifndef ITER_ZIP_HPP_ +#define ITER_ZIP_HPP_ + +#include "internal/iter_tuples.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class Zipped; + + template + Zipped zip_impl(TupleType&&, std::index_sequence); + } + + template + auto zip(Containers&&... containers); +} + +template +class iter::impl::Zipped { + private: + TupleType containers_; + friend Zipped iter::impl::zip_impl( + TupleType&&, std::index_sequence); + + Zipped(TupleType&& containers) : containers_(std::move(containers)) {} + + public: + Zipped(Zipped&&) = default; + + // template templates here because I need to defer evaluation in the const + // iteration case for types that don't have non-const begin() and end(). If I + // passed in the actual types of the tuples of iterators and the type for + // deref they'd need to be known in the function declarations below. + template class IteratorTuple, + template class TupleDeref> + class Iterator { + // see gcc bug 87651 +#if NO_GCC_FRIEND_ERROR + private: + template class, template class> + friend class Iterator; +#else + public: +#endif + IteratorTuple iters_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = TupleDeref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type; + + Iterator(IteratorTuple&& iters) : iters_(std::move(iters)) {} + + Iterator& operator++() { + absorb(++std::get(iters_)...); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template class IT, + template class TD> + bool operator!=(const Iterator& other) const { + if constexpr (sizeof...(Is) == 0) { + return false; + } else { + return (... && (std::get(iters_) != std::get(other.iters_))); + } + } + + template class IT, + template class TD> + bool operator==(const Iterator& other) const { + return !(*this != other); + } + + TupleDeref operator*() { + return {(*std::get(iters_))...}; + } + + auto operator->() -> ArrowProxy { + return {**this}; + } + }; + + Iterator begin() { + return {{get_begin(std::get(containers_))...}}; + } + + Iterator end() { + return {{get_end(std::get(containers_))...}}; + } + + Iterator, const_iterator_tuple_type, + const_iterator_deref_tuple> + begin() const { + return {{get_begin(std::as_const(std::get(containers_)))...}}; + } + + Iterator, const_iterator_tuple_type, + const_iterator_deref_tuple> + end() const { + return {{get_end(std::as_const(std::get(containers_)))...}}; + } +}; + +template +iter::impl::Zipped iter::impl::zip_impl( + TupleType&& containers, std::index_sequence) { + return {std::move(containers)}; +} + +template +auto iter::zip(Containers&&... containers) { + return impl::zip_impl( + std::tuple{std::forward(containers)...}, + std::index_sequence_for{}); +} + +#endif diff --git a/cppitertools/zip_longest.hpp b/cppitertools/zip_longest.hpp new file mode 100644 index 00000000..238ec569 --- /dev/null +++ b/cppitertools/zip_longest.hpp @@ -0,0 +1,147 @@ +#ifndef ITER_ZIP_LONGEST_HPP_ +#define ITER_ZIP_LONGEST_HPP_ + +#include "internal/iter_tuples.hpp" +#include "internal/iterbase.hpp" + +#include +#include +#include +#include + +namespace iter { + namespace impl { + template + class ZippedLongest; + + template + ZippedLongest zip_longest_impl( + TupleType&&, std::index_sequence); + } + + template + auto zip_longest(Containers&&... containers); +} + +template +class iter::impl::ZippedLongest { + private: + TupleType containers_; + friend ZippedLongest zip_longest_impl( + TupleType&&, std::index_sequence); + + template + using OptType = boost::optional>>>; + + template + using ConstOptType = boost::optional>>>; + + template class OptTempl> + using ZipIterDeref = std::tuple...>; + + ZippedLongest(TupleType&& containers) : containers_(std::move(containers)) {} + + public: + ZippedLongest(ZippedLongest&&) = default; + template class IterTuple, + template class OptTempl> + class Iterator { +#if NO_GCC_FRIEND_ERROR + private: + template class, + template class> + friend class Iterator; +#else + public: +#endif + IterTuple iters_; + IterTuple ends_; + + public: + using iterator_category = std::input_iterator_tag; + using value_type = ZipIterDeref; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + + Iterator(IterTuple&& iters, IterTuple&& ends) + : iters_(std::move(iters)), ends_(std::move(ends)) {} + + Iterator& operator++() { + // increment every iterator that's not already at + // the end + absorb(((std::get(iters_) != std::get(ends_)) + ? (++std::get(iters_), 0) + : 0)...); + return *this; + } + + Iterator operator++(int) { + auto ret = *this; + ++*this; + return ret; + } + + template class TT, + template class TU> + bool operator!=(const Iterator& other) const { + return (... || (std::get(iters_) != std::get(other.iters_))); + } + + template class TT, + template class TU> + bool operator==(const Iterator& other) const { + return !(*this != other); + } + + ZipIterDeref operator*() { + return {((std::get(iters_) != std::get(ends_)) + ? OptTempl{*std::get(iters_)} + : OptTempl{})...}; + } + + auto operator-> () -> ArrowProxy { + return {**this}; + } + }; + + Iterator begin() { + return {{get_begin(std::get(containers_))...}, + {get_end(std::get(containers_))...}}; + } + + Iterator end() { + return {{get_end(std::get(containers_))...}, + {get_end(std::get(containers_))...}}; + } + + Iterator, const_iterator_tuple_type, ConstOptType> begin() + const { + return {{get_begin(std::as_const(std::get(containers_)))...}, + {get_end(std::as_const(std::get(containers_)))...}}; + } + + Iterator, const_iterator_tuple_type, ConstOptType> end() + const { + return {{get_end(std::as_const(std::get(containers_)))...}, + {get_end(std::as_const(std::get(containers_)))...}}; + } +}; + +template +iter::impl::ZippedLongest iter::impl::zip_longest_impl( + TupleType&& containers, std::index_sequence) { + return {std::move(containers)}; +} + +template +auto iter::zip_longest(Containers&&... containers) { + return impl::zip_longest_impl( + std::tuple{std::forward(containers)...}, + std::index_sequence_for{}); +} + +#endif diff --git a/cycle.hpp b/cycle.hpp deleted file mode 100644 index 64b3ee84..00000000 --- a/cycle.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef ITER_CYCLE_H_ -#define ITER_CYCLE_H_ - -#include "internal/iterbase.hpp" - -#include -#include -#include - -namespace iter { - namespace impl { - template - class Cycler; - - using CycleFn = IterToolFn; - } - constexpr impl::CycleFn cycle{}; -} - -template -class iter::impl::Cycler { - private: - friend CycleFn; - - Container container; - - Cycler(Container&& in_container) - : container(std::forward(in_container)) {} - - public: - Cycler(Cycler&&) = default; - class Iterator : public std::iterator> { - private: - iterator_type sub_iter; - iterator_type begin; - iterator_type end; - - public: - Iterator( - const iterator_type& iter, iterator_type&& in_end) - : sub_iter{iter}, begin{iter}, end{std::move(in_end)} {} - - iterator_deref operator*() { - return *this->sub_iter; - } - - iterator_arrow operator->() { - return apply_arrow(this->sub_iter); - } - - Iterator& operator++() { - ++this->sub_iter; - // reset to beginning upon reaching the end - if (!(this->sub_iter != this->end)) { - this->sub_iter = this->begin; - } - 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)}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container)}; - } -}; - -#endif diff --git a/dropwhile.hpp b/dropwhile.hpp deleted file mode 100644 index 869e0070..00000000 --- a/dropwhile.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef ITER_DROPWHILE_H_ -#define ITER_DROPWHILE_H_ - -#include "internal/iterbase.hpp" -#include "filter.hpp" - -#include -#include - -namespace iter { - namespace impl { - template - class Dropper; - - using DropWhileFn = IterToolFnOptionalBindFirst; - } - constexpr impl::DropWhileFn dropwhile{}; -} - -template -class iter::impl::Dropper { - private: - Container container; - FilterFunc filter_func; - - friend DropWhileFn; - - Dropper(FilterFunc in_filter_func, Container&& in_container) - : container(std::forward(in_container)), - filter_func(in_filter_func) {} - - public: - Dropper(Dropper&&) = default; - class Iterator : public std::iterator> { - private: - using Holder = DerefHolder>; - iterator_type sub_iter; - iterator_type sub_end; - Holder item; - FilterFunc* filter_func; - - void inc_sub_iter() { - ++this->sub_iter; - if (this->sub_iter != this->sub_end) { - this->item.reset(*this->sub_iter); - } - } - - // skip all values for which the predicate is true - void skip_passes() { - while (this->sub_iter != this->sub_end - && (*this->filter_func)(this->item.get())) { - this->inc_sub_iter(); - } - } - - public: - Iterator(iterator_type&& iter, iterator_type&& end, - FilterFunc& in_filter_func) - : sub_iter{std::move(iter)}, - sub_end{std::move(end)}, - filter_func(&in_filter_func) { - if (this->sub_iter != this->sub_end) { - this->item.reset(*this->sub_iter); - } - this->skip_passes(); - } - - typename Holder::reference operator*() { - return this->item.get(); - } - - typename Holder::pointer operator->() { - return this->item.get_ptr(); - } - - Iterator& operator++() { - this->inc_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->filter_func}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container), - this->filter_func}; - } -}; - -#endif diff --git a/enumerate.hpp b/enumerate.hpp deleted file mode 100644 index 6492c909..00000000 --- a/enumerate.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef ITER_ENUMERATE_H_ -#define ITER_ENUMERATE_H_ - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Enumerable; - - using EnumerateFn = IterToolFnOptionalBindSecond; - } - constexpr impl::EnumerateFn enumerate{}; -} - -template -class iter::impl::Enumerable { - private: - Container container; - const Index start; - - friend EnumerateFn; - - // for IterYield - using BasePair = std::pair>; - - // Value constructor for use only in the enumerate function - Enumerable(Container&& in_container, Index in_start) - : container(std::forward(in_container)), start{in_start} {} - - public: - Enumerable(Enumerable&&) = default; - - // "yielded" by the Enumerable::Iterator. Has a .index, and a - // .element referencing the value yielded by the subiterator - class IterYield : public BasePair { - public: - using BasePair::BasePair; - typename BasePair::first_type& index = BasePair::first; - typename BasePair::second_type& element = BasePair::second; - }; - - // 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. - class Iterator : public std::iterator { - private: - iterator_type sub_iter; - Index index; - - public: - Iterator(iterator_type&& si, Index start) - : sub_iter{std::move(si)}, index{start} {} - - IterYield operator*() { - return {this->index, *this->sub_iter}; - } - - ArrowProxy operator->() { - return {**this}; - } - - Iterator& operator++() { - ++this->sub_iter; - ++this->index; - 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), start}; - } - - Iterator end() { - return {std::end(this->container), start}; - } -}; - -#endif diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..23cf493a --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,22 @@ +# Note that some examples currently use boost.optional which we do no not search for in this file. +# You might have to use the "keep going" option to continue building on errors if boost.optional is not in the include path. +# For example, building with MSVC (from an examples/buildMsvc directory): +# set CXX=cl.exe +# cmake .. -G Ninja +# cmake --build . -- -k99 + +cmake_minimum_required(VERSION 3.8) +project(cppitertools_examples CXX) +set (CMAKE_CXX_STANDARD 17) + +include_directories( + .. +) + +file(GLOB _examples_files "*_examples.cpp") + +foreach(_file_cpp ${_examples_files}) + get_filename_component(_name_cpp "${_file_cpp}" NAME) + get_filename_component(_name_without_extension "${_name_cpp}" NAME_WE) + add_executable(${_name_without_extension} ${_file_cpp}) +endforeach() diff --git a/examples/SConstruct b/examples/SConstruct index 947064f9..cb5c373c 100644 --- a/examples/SConstruct +++ b/examples/SConstruct @@ -2,12 +2,12 @@ import os env = Environment( ENV=os.environ, - CXX='c++', CXXFLAGS= ['-g', '-Wall', '-Wextra', - '-pedantic', '-std=c++14', - '-I/usr/local/include'], + '-pedantic', '-std=c++17', + '-I/usr/local/include' + ], CPPPATH='..', - LINKFLAGS='-L/usr/local/lib') + LINKFLAGS=['-L/usr/local/lib']) # allows highighting to print to terminal from compiler output env['ENV']['TERM'] = os.environ['TERM'] @@ -15,6 +15,7 @@ env['ENV']['TERM'] = os.environ['TERM'] progs = Split( ''' accumulate + batched chain chunked combinatoric diff --git a/examples/accumulate_examples.cpp b/examples/accumulate_examples.cpp index 9d9a2f67..4d8569d2 100644 --- a/examples/accumulate_examples.cpp +++ b/examples/accumulate_examples.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/examples/batched_examples.cpp b/examples/batched_examples.cpp new file mode 100644 index 00000000..1dfa6abf --- /dev/null +++ b/examples/batched_examples.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include + +int main() { + std::vector v {1,2,3,4,5,6,7,8,9}; + std::cout << "num batches: 5\n"; + for (auto&& sec : iter::batched(v, 5)) { + for (auto&& i : sec) { + std::cout << i << " "; + } + std::cout << '\n'; + } + + std::cout << "num batches: 4\n"; + for (auto&& sec : iter::batched(v, 4)) { + for (auto&& i : sec) { + std::cout << i << " "; + } + std::cout << '\n'; + } + + std::cout << "num batches: 6\n"; + for (auto&& sec : iter::batched(v, 6)) { + for (auto&& i : sec) { + std::cout << i << " "; + } + std::cout << '\n'; + } +} diff --git a/examples/chain_examples.cpp b/examples/chain_examples.cpp index 607fa040..cdcd7fed 100644 --- a/examples/chain_examples.cpp +++ b/examples/chain_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/chunked_examples.cpp b/examples/chunked_examples.cpp index 9c3046f5..c3325df1 100644 --- a/examples/chunked_examples.cpp +++ b/examples/chunked_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/combinatoric_examples.cpp b/examples/combinatoric_examples.cpp index d7111a56..0915f985 100644 --- a/examples/combinatoric_examples.cpp +++ b/examples/combinatoric_examples.cpp @@ -1,8 +1,8 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -45,11 +45,8 @@ int main() { std::vector v3 = {"abc", "def"}; std::cout << "product of three vectors (int, int, string):\n"; - for (auto&& t : iter::product(v1,v2,v3)) { - std::cout << "{ " - << std::get<0>(t) << ' ' - << std::get<1>(t) << ' ' - << std::get<2>(t) << " }\n"; + for (auto&& [a, b, c] : iter::product(v1,v2,v3)) { + std::cout << "{ " << a << ' ' << b << ' ' << c << " }\n"; } std::cout << "powerset({1,2,3,4,5}):\n"; diff --git a/examples/compress_examples.cpp b/examples/compress_examples.cpp index b60197a9..dbabbbb9 100644 --- a/examples/compress_examples.cpp +++ b/examples/compress_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/count_examples.cpp b/examples/count_examples.cpp index 10ac8a72..70b876cc 100644 --- a/examples/count_examples.cpp +++ b/examples/count_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/examples/cycle_examples.cpp b/examples/cycle_examples.cpp index 7f973d66..0598431a 100644 --- a/examples/cycle_examples.cpp +++ b/examples/cycle_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/dropwhile_examples.cpp b/examples/dropwhile_examples.cpp index 8c628f1a..c8f993c0 100644 --- a/examples/dropwhile_examples.cpp +++ b/examples/dropwhile_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/enumerate_examples.cpp b/examples/enumerate_examples.cpp index 39786696..822abbbf 100644 --- a/examples/enumerate_examples.cpp +++ b/examples/enumerate_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -9,16 +9,16 @@ int main() { std::cout << "enumerating the characters of a string \"hello\":\n"; const std::string const_string("hello"); - for (auto&& e : iter::enumerate(const_string)) { - std::cout << '(' << e.index << ", " << e.element << ") "; + for (auto&& [i, c] : iter::enumerate(const_string)) { + std::cout << '(' << i << ", " << c << ") "; } std::cout << '\n'; std::vector vec = {20, 30, 50}; std::cout << "enumerating a vector of {20, 30, 50}:\n"; - for (auto&& e : iter::enumerate(vec)) { - std::cout << '(' << e.index << ", " << e.element << ") "; - e.element = 0; + for (auto&& [i, n] : iter::enumerate(vec)) { + std::cout << '(' << i << ", " << n << ") "; + n = 0; } std::cout << '\n'; assert(vec[0] == 0); @@ -28,15 +28,15 @@ int main() { // itertools supports raw arrays std::cout << "statically sized arrays can be enumerated\n"; int array[] = {1, 9, 8, 11}; - for (auto&& e : iter::enumerate(array)) { - std::cout << '(' << e.index << ", " << e.element << ") "; + for (auto&& [i, n] : iter::enumerate(array)) { + std::cout << '(' << i << ", " << n << ") "; } std::cout << '\n'; // itertools supports temporaries std::cout << "vector temporary of {5, 2}\n"; - for (auto&& e : iter::enumerate(std::vector(5,2))) { - std::cout << '(' << e.index << ", " << e.element << ") "; + for (auto&& [i, n] : iter::enumerate(std::vector(5,2))) { + std::cout << '(' << i << ", " << n << ") "; } std::cout << '\n'; } diff --git a/examples/filter_examples.cpp b/examples/filter_examples.cpp index d28c443a..5ce16af2 100644 --- a/examples/filter_examples.cpp +++ b/examples/filter_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/filterfalse_examples.cpp b/examples/filterfalse_examples.cpp index 772005a8..0edc004a 100644 --- a/examples/filterfalse_examples.cpp +++ b/examples/filterfalse_examples.cpp @@ -1,6 +1,7 @@ -#include +#include #include +#include #include bool greater_than_four(int i) { @@ -26,4 +27,10 @@ int main() { for (auto&& i : iter::filterfalse(ns)) { std::cout << i << '\n'; } + + // only print non-empty strings + std::vector words {"hello", "", "", "world", "", "goodbye", ""}; + for (auto&& s : iter::filterfalse(&std::string::empty, words)) { + std::cout << s << '\n'; + } } diff --git a/examples/groupby_examples.cpp b/examples/groupby_examples.cpp index 8d4a9918..5525f9ff 100644 --- a/examples/groupby_examples.cpp +++ b/examples/groupby_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/imap_examples.cpp b/examples/imap_examples.cpp index 812281b6..95fee802 100644 --- a/examples/imap_examples.cpp +++ b/examples/imap_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/mixed_examples.cpp b/examples/mixed_examples.cpp index 08ff774b..9f1ba056 100644 --- a/examples/mixed_examples.cpp +++ b/examples/mixed_examples.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/examples/range_examples.cpp b/examples/range_examples.cpp index 1dd03199..ff328ee7 100644 --- a/examples/range_examples.cpp +++ b/examples/range_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/examples/repeat_examples.cpp b/examples/repeat_examples.cpp index 24d37a8d..2dceaf98 100644 --- a/examples/repeat_examples.cpp +++ b/examples/repeat_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/examples/reversed_examples.cpp b/examples/reversed_examples.cpp index d4d804d9..1bb8dbed 100644 --- a/examples/reversed_examples.cpp +++ b/examples/reversed_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/slice_examples.cpp b/examples/slice_examples.cpp index 42a16e33..d9f2501a 100644 --- a/examples/slice_examples.cpp +++ b/examples/slice_examples.cpp @@ -1,7 +1,7 @@ #include -#include -#include +#include +#include #include #include diff --git a/examples/sliding_window_examples.cpp b/examples/sliding_window_examples.cpp index 3c0d39dd..7658182d 100644 --- a/examples/sliding_window_examples.cpp +++ b/examples/sliding_window_examples.cpp @@ -1,4 +1,4 @@ -#include "sliding_window.hpp" +#include "cppitertools/sliding_window.hpp" #include #include @@ -13,7 +13,7 @@ int main() { } std::cout << "Empty when window size is > length\n"; - for (auto&& sec : iter::sliding_window({1,2,3}, 10)) { + for (auto&& sec : iter::sliding_window(std::vector{1,2,3}, 10)) { for (auto&& i : sec) { std::cout << i << ' '; } diff --git a/examples/sorted_examples.cpp b/examples/sorted_examples.cpp index 109d137d..4e6b5a22 100644 --- a/examples/sorted_examples.cpp +++ b/examples/sorted_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/starmap_examples.cpp b/examples/starmap_examples.cpp index 9797756c..b375ef3e 100644 --- a/examples/starmap_examples.cpp +++ b/examples/starmap_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/takewhile_examples.cpp b/examples/takewhile_examples.cpp index 3062a141..d86d6442 100644 --- a/examples/takewhile_examples.cpp +++ b/examples/takewhile_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/unique_everseen_examples.cpp b/examples/unique_everseen_examples.cpp index a670f0b1..52dc02fa 100644 --- a/examples/unique_everseen_examples.cpp +++ b/examples/unique_everseen_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/unique_justseen_examples.cpp b/examples/unique_justseen_examples.cpp index 878cd4f8..7c8cd9aa 100644 --- a/examples/unique_justseen_examples.cpp +++ b/examples/unique_justseen_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/examples/zip_examples.cpp b/examples/zip_examples.cpp index 0b7f347a..51febf13 100644 --- a/examples/zip_examples.cpp +++ b/examples/zip_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -10,8 +10,8 @@ int main() { // zip terminates on the shortest sequence, and is variadic std::cout << "zipping a vector of ints and a vector of strings\n"; - for (auto&& e : iter::zip(ivec, svec)) { - std::cout << '(' << std::get<0>(e) << ", " << std::get<1>(e) << ")\n"; + for (auto&& [i, s] : iter::zip(ivec, svec)) { + std::cout << '(' << i << ", " << s << ")\n"; } } diff --git a/examples/zip_longest_examples.cpp b/examples/zip_longest_examples.cpp index 1bbffbf8..d3c80173 100644 --- a/examples/zip_longest_examples.cpp +++ b/examples/zip_longest_examples.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -22,8 +22,7 @@ int main() { std::vector svec = {"hello", "good day", "goodbye"}; std::cout << "zipping a vector of strings with a vector of ints:\n"; - for (auto&& e : iter::zip_longest(ivec, svec)) { - std::cout << '(' << std::get<0>(e) << ", " - << std::get<1>(e) << ")\n"; + for (auto&& [i, s] : iter::zip_longest(ivec, svec)) { + std::cout << '(' << i << ", " << s << ")\n"; } } diff --git a/filter.hpp b/filter.hpp deleted file mode 100644 index f7364f3a..00000000 --- a/filter.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef ITER_FILTER_H_ -#define ITER_FILTER_H_ - -#include "internal/iterbase.hpp" - -#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; - } - - constexpr impl::FilterFn filter{}; -} - -template -class iter::impl::Filtered { - private: - Container container; - FilterFunc filter_func; - - friend FilterFn; - - protected: - // Value constructor for use only in the filter function - Filtered(FilterFunc in_filter_func, Container&& in_container) - : container(std::forward(in_container)), - filter_func(in_filter_func) {} - - public: - Filtered(Filtered&&) = default; - - class Iterator : public std::iterator> { - protected: - using Holder = DerefHolder>; - iterator_type sub_iter; - iterator_type sub_end; - Holder item; - FilterFunc* filter_func; - - void inc_sub_iter() { - ++this->sub_iter; - if (this->sub_iter != this->sub_end) { - this->item.reset(*this->sub_iter); - } - } - - // increment until the iterator points to is true on the - // predicate. Called by constructor and operator++ - void skip_failures() { - while (this->sub_iter != this->sub_end - && !(*this->filter_func)(this->item.get())) { - this->inc_sub_iter(); - } - } - - public: - Iterator(iterator_type iter, iterator_type end, - FilterFunc& in_filter_func) - : sub_iter{iter}, sub_end{end}, filter_func(&in_filter_func) { - if (this->sub_iter != this->sub_end) { - this->item.reset(*this->sub_iter); - } - this->skip_failures(); - } - - typename Holder::reference operator*() { - return this->item.get(); - } - - typename Holder::pointer operator->() { - return this->item.get_ptr(); - } - - Iterator& operator++() { - this->inc_sub_iter(); - 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; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - }; - - Iterator begin() { - return {std::begin(this->container), std::end(this->container), - this->filter_func}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container), - this->filter_func}; - } -}; - -#endif diff --git a/groupby.hpp b/groupby.hpp deleted file mode 100644 index a0af1d4d..00000000 --- a/groupby.hpp +++ /dev/null @@ -1,268 +0,0 @@ -#ifndef ITER_GROUP_BY_HPP_ -#define ITER_GROUP_BY_HPP_ - -// this is easily the most functionally complex itertool - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class GroupProducer; - - struct Identity { - template - const T& operator()(const T& t) const { - return t; - } - }; - - using GroupByFn = IterToolFnOptionalBindSecond; - } - constexpr impl::GroupByFn groupby{}; -} - -template -class iter::impl::GroupProducer { - private: - Container container; - KeyFunc key_func; - - friend GroupByFn; - - using key_func_ret = std::result_of_t)>; - - GroupProducer(Container&& in_container, KeyFunc in_key_func) - : container(std::forward(in_container)), - key_func(in_key_func) {} - - public: - GroupProducer(GroupProducer&&) = default; - - class Iterator; - class Group; - - private: - using KeyGroupPair = std::pair; - using Holder = DerefHolder>; - - public: - class Iterator : public std::iterator { - private: - iterator_type sub_iter; - iterator_type sub_end; - Holder item; - KeyFunc* key_func; - - std::unique_ptr current_key_group_pair; - - public: - Iterator(iterator_type&& si, iterator_type&& end, - KeyFunc& in_key_func) - : sub_iter{std::move(si)}, - sub_end{std::move(end)}, - key_func(&in_key_func) { - if (this->sub_iter != this->sub_end) { - this->item.reset(*this->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; - } - this->sub_iter = other.sub_iter; - this->sub_end = other.sub_end; - this->item = other.item; - this->key_func = other.key_func; - this->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 *this->current_key_group_pair; - } - - KeyGroupPair* operator->() { - set_key_group_pair(); - return this->current_key_group_pair.get(); - } - - Iterator& operator++() { - if (!this->current_key_group_pair) { - this->set_key_group_pair(); - } - this->current_key_group_pair.reset(); - 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); - } - - void increment_iterator() { - if (this->sub_iter != this->sub_end) { - ++this->sub_iter; - if (this->sub_iter != this->sub_end) { - this->item.reset(*this->sub_iter); - } - } - } - - bool exhausted() const { - return !(this->sub_iter != this->sub_end); - } - - typename Holder::reference get() { - return this->item.get(); - } - - typename Holder::pointer get_ptr() { - return this->item.get_ptr(); - } - - key_func_ret next_key() { - return (*this->key_func)(this->item.get()); - } - - void set_key_group_pair() { - if (!this->current_key_group_pair) { - this->current_key_group_pair = - std::make_unique((*this->key_func)(this->item.get()), - Group{*this, this->next_key()}); - } - } - }; - - class Group { - private: - friend Iterator; - friend class GroupIterator; - Iterator& owner; - 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& in_owner, key_func_ret in_key) - : owner(in_owner), key(in_key) {} - - public: - ~Group() { - if (!this->completed) { - for (auto iter = this->begin(), end = this->end(); iter != end; - ++iter) { - } - } - } - - // move-constructible, non-copy-constructible, non-assignable - Group(Group&& other) noexcept : owner(other.owner), - key{other.key}, - completed{other.completed} { - other.completed = true; - } - - class GroupIterator : public std::iterator> { - private: - std::remove_reference_t* key; - Group* group_p; - - bool not_at_end() { - return !this->group_p->owner.exhausted() - && this->group_p->owner.next_key() == *this->key; - } - - public: - GroupIterator(Group* in_group_p, key_func_ret& in_key) - : key{&in_key}, group_p{in_group_p} {} - - bool operator!=(const GroupIterator& other) const { - return !(*this == other); - } - - bool operator==(const GroupIterator& other) const { - return this->group_p == other.group_p; - } - - GroupIterator& operator++() { - this->group_p->owner.increment_iterator(); - if (!this->not_at_end()) { - this->group_p->completed = true; - this->group_p = nullptr; - } - return *this; - } - - GroupIterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - iterator_deref operator*() { - return this->group_p->owner.get(); - } - - typename Holder::pointer operator->() { - return this->group_p->owner.get_ptr(); - } - }; - - GroupIterator begin() { - return {this, key}; - } - - GroupIterator end() { - return {nullptr, key}; - } - }; - - Iterator begin() { - return { - std::begin(this->container), std::end(this->container), this->key_func}; - } - - Iterator end() { - return { - std::end(this->container), std::end(this->container), this->key_func}; - } -}; - -#endif diff --git a/imap.hpp b/imap.hpp deleted file mode 100644 index 807a2826..00000000 --- a/imap.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef ITER_IMAP_H_ -#define ITER_IMAP_H_ - -#include "zip.hpp" -#include "starmap.hpp" - -#include - -namespace iter { - namespace impl { - struct IMapFn : PipeableAndBindFirst { - template - decltype(auto) operator()( - MapFunc map_func, Containers&&... containers) const { - return starmap(map_func, zip(std::forward(containers)...)); - } - using PipeableAndBindFirst::operator(); - }; - } - constexpr impl::IMapFn imap{}; -} - -#endif diff --git a/permutations.hpp b/permutations.hpp deleted file mode 100644 index dea9fa8c..00000000 --- a/permutations.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef ITER_PERMUTATIONS_HPP_ -#define ITER_PERMUTATIONS_HPP_ - -#include "internal/iterbase.hpp" -#include "internal/iteratoriterator.hpp" - -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Permuter; - using PermutationsFn = IterToolFn; - } - constexpr impl::PermutationsFn permutations{}; -} - -template -class iter::impl::Permuter { - private: - friend PermutationsFn; - Container container; - - using IndexVector = std::vector>; - using Permutable = IterIterWrapper; - - Permuter(Container&& in_container) - : container(std::forward(in_container)) {} - - public: - Permuter(Permuter&&) = default; - - class Iterator : public std::iterator { - private: - static constexpr const int COMPLETE = -1; - static bool cmp_iters(const iterator_type& lhs, - const iterator_type& rhs) noexcept { - return *lhs < *rhs; - } - - Permutable working_set; - int steps{}; - - public: - Iterator( - iterator_type&& sub_iter, iterator_type&& sub_end) - : steps{sub_iter != sub_end ? 0 : COMPLETE} { - // done like this instead of using vector ctor with - // two iterators because that causes a substitution - // failure when the iterator is minimal - while (sub_iter != sub_end) { - this->working_set.get().push_back(sub_iter); - ++sub_iter; - } - std::sort(std::begin(working_set.get()), std::end(working_set.get()), - cmp_iters); - } - - Permutable& operator*() { - return this->working_set; - } - - Permutable* operator->() { - return &this->working_set; - } - - Iterator& operator++() { - ++this->steps; - if (!std::next_permutation(std::begin(working_set.get()), - std::end(working_set.get()), cmp_iters)) { - this->steps = COMPLETE; - } - 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 {std::begin(this->container), std::end(this->container)}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container)}; - } -}; - -#endif diff --git a/powerset.hpp b/powerset.hpp deleted file mode 100644 index c0ea0ade..00000000 --- a/powerset.hpp +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef ITER_POWERSET_HPP_ -#define ITER_POWERSET_HPP_ - -#include "internal/iterbase.hpp" -#include "combinations.hpp" - -#include -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Powersetter; - - using PowersetFn = IterToolFn; - } - constexpr impl::PowersetFn powerset{}; -} - -template -class iter::impl::Powersetter { - private: - Container container; - using CombinatorType = decltype(combinations(std::declval(), 0)); - - friend PowersetFn; - - Powersetter(Container&& in_container) - : container(std::forward(in_container)) {} - - public: - Powersetter(Powersetter&&) = default; - - class Iterator - : public std::iterator { - private: - std::remove_reference_t* container_p; - std::size_t set_size; - std::shared_ptr comb; - iterator_type comb_iter; - iterator_type comb_end; - - public: - Iterator(Container& in_container, std::size_t sz) - : container_p{&in_container}, - set_size{sz}, - comb{ - std::make_shared(combinations(in_container, sz))}, - comb_iter{std::begin(*comb)}, - comb_end{std::end(*comb)} {} - - Iterator& operator++() { - ++this->comb_iter; - if (this->comb_iter == this->comb_end) { - ++this->set_size; - this->comb = std::make_shared( - combinations(*this->container_p, this->set_size)); - - this->comb_iter = std::begin(*this->comb); - this->comb_end = std::end(*this->comb); - } - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - iterator_deref operator*() { - return *this->comb_iter; - } - - iterator_arrow operator->() { - apply_arrow(this->comb_iter); - } - - bool operator!=(const Iterator& other) const { - return !(*this == other); - } - - bool operator==(const Iterator& other) const { - return this->set_size == other.set_size - && this->comb_iter == other.comb_iter; - } - }; - - Iterator begin() { - return {this->container, 0}; - } - - Iterator end() { - return {this->container, dumb_size(this->container) + 1}; - } -}; - -#endif diff --git a/product.hpp b/product.hpp deleted file mode 100644 index 8939a28e..00000000 --- a/product.hpp +++ /dev/null @@ -1,168 +0,0 @@ -#ifndef ITER_PRODUCT_HPP_ -#define ITER_PRODUCT_HPP_ - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Productor; - - template - class Productor; - - template <> - class Productor<>; - } - - template - impl::Productor product(Containers&&...); -} - -// specialization for at least 1 template argument -template -class iter::impl::Productor { - friend Productor iter::product( - Container&&, RestContainers&&...); - - template - friend class Productor; - - using ProdIterDeref = - std::tuple, iterator_deref...>; - - private: - Container container; - Productor rest_products; - Productor(Container&& in_container, RestContainers&&... rest) - : container(std::forward(in_container)), - rest_products{std::forward(rest)...} {} - - public: - Productor(Productor&&) = default; - class Iterator - : public std::iterator { - private: - using RestIter = typename Productor::Iterator; - - iterator_type iter; - iterator_type begin; - - RestIter rest_iter; - RestIter rest_end; - - public: - constexpr static const bool is_base_iter = false; - Iterator(const iterator_type& it, RestIter&& rest, - RestIter&& in_rest_end) - : iter{it}, begin{it}, rest_iter{rest}, rest_end{in_rest_end} {} - - void reset() { - this->iter = this->begin; - } - - Iterator& operator++() { - ++this->rest_iter; - if (!(this->rest_iter != this->rest_end)) { - this->rest_iter.reset(); - ++this->iter; - } - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return this->iter != other.iter - && (RestIter::is_base_iter || this->rest_iter != other.rest_iter); - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - - ProdIterDeref operator*() { - return std::tuple_cat( - std::tuple>{*this->iter}, *this->rest_iter); - } - - ArrowProxy operator->() { - return {**this}; - } - }; - - Iterator begin() { - return {std::begin(this->container), std::begin(this->rest_products), - std::end(this->rest_products)}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->rest_products), - std::end(this->rest_products)}; - } -}; - -template <> -class iter::impl::Productor<> { - public: - Productor(Productor&&) = default; - class Iterator : public std::iterator> { - public: - constexpr static const bool is_base_iter = true; - - void reset() {} - - Iterator& operator++() { - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - // see note in zip about base case operator!= - bool operator!=(const Iterator&) const { - return false; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - - std::tuple<> operator*() const { - return {}; - } - }; - - Iterator begin() { - return {}; - } - - Iterator end() { - return {}; - } -}; - -template -iter::impl::Productor iter::product(Containers&&... containers) { - return {std::forward(containers)...}; -} - -namespace iter { - constexpr std::array, 1> product() { - return {{}}; - } -} - -#endif diff --git a/reversed.hpp b/reversed.hpp deleted file mode 100644 index 82ae9b46..00000000 --- a/reversed.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef ITER_REVERSE_HPP_ -#define ITER_REVERSE_HPP_ - -#include "internal/iterbase.hpp" - -#include -#include - -namespace iter { - namespace impl { - template - class Reverser; - - using ReversedFn = IterToolFn; - } - constexpr impl::ReversedFn reversed{}; -} - -template -class iter::impl::Reverser { - private: - Container container; - friend ReversedFn; - - Reverser(Container&& in_container) - : container(std::forward(in_container)) {} - - using reverse_iterator_type = - decltype(std::rbegin(std::declval())); - - using reverse_iterator_deref = - decltype(*std::declval()); - - using reverse_iterator_traits_deref = - std::remove_reference_t; - - using reverse_iterator_arrow = detail::arrow; - - public: - Reverser(Reverser&&) = default; - class Iterator : public std::iterator { - private: - reverse_iterator_type sub_iter; - - public: - Iterator(reverse_iterator_type&& iter) : sub_iter{std::move(iter)} {} - - reverse_iterator_deref operator*() { - return *this->sub_iter; - } - - reverse_iterator_arrow operator->() { - return apply_arrow(this->sub_iter); - } - - 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 begin() { - return {std::rbegin(this->container)}; - } - - Iterator end() { - return {std::rend(this->container)}; - } -}; - -#endif diff --git a/slice.hpp b/slice.hpp deleted file mode 100644 index 8ff5a6af..00000000 --- a/slice.hpp +++ /dev/null @@ -1,157 +0,0 @@ -#ifndef ITER_SLICE_HPP_ -#define ITER_SLICE_HPP_ - -#include "internal/iterbase.hpp" - -#include -#include - -namespace iter { - namespace impl { - template - class Sliced; - - struct SliceFn; - } -} - -template -class iter::impl::Sliced { - private: - Container container; - DifferenceType start; - DifferenceType stop; - DifferenceType step; - - friend SliceFn; - - Sliced(Container&& in_container, DifferenceType in_start, - DifferenceType in_stop, DifferenceType in_step) - : container(std::forward(in_container)), - start{in_start < in_stop && in_step > 0 ? in_start : in_stop}, - stop{in_stop}, - step{in_step} {} - - public: - Sliced(Sliced&&) = default; - class Iterator : public std::iterator> { - private: - iterator_type sub_iter; - iterator_type sub_end; - DifferenceType current; - DifferenceType stop; - DifferenceType step; - - public: - Iterator(iterator_type&& si, iterator_type&& se, - DifferenceType in_start, DifferenceType in_stop, DifferenceType in_step) - : sub_iter{std::move(si)}, - sub_end{std::move(se)}, - current{in_start}, - stop{in_stop}, - step{in_step} {} - - iterator_deref operator*() { - return *this->sub_iter; - } - - iterator_arrow operator->() { - return apply_arrow(this->sub_iter); - } - - Iterator& operator++() { - dumb_advance(this->sub_iter, this->sub_end, this->step); - this->current += this->step; - if (this->stop < this->current) { - this->current = this->stop; - } - 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->current != other.current; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - }; - - Iterator begin() { - auto it = std::begin(this->container); - dumb_advance(it, std::end(this->container), this->start); - return {std::move(it), std::end(this->container), this->start, this->stop, - this->step}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container), this->stop, - this->stop, this->step}; - } -}; - -struct iter::impl::SliceFn { - private: - template - class FnPartial : public Pipeable> { - public: - template - Sliced operator()(Container&& container) const { - return {std::forward(container), start, stop, step}; - } - - private: - friend SliceFn; - constexpr FnPartial(DifferenceType in_start, DifferenceType in_stop, - DifferenceType in_step) noexcept : start{in_start}, - stop{in_stop}, - step{in_step} {} - DifferenceType start; - DifferenceType stop; - DifferenceType step; - }; - - public: - template >> - Sliced operator()(Container&& container, - DifferenceType start, DifferenceType stop, - DifferenceType step = 1) const { - return {std::forward(container), start, stop, step}; - } - - // only given the end, assume step is 1 and begin is 0 - template >> - iter::impl::Sliced operator()( - Container&& container, DifferenceType stop) const { - return {std::forward(container), 0, stop, 1}; - } - - template >> - constexpr FnPartial operator()(DifferenceType stop) const - noexcept { - return {0, stop, 1}; - } - - template >> - constexpr FnPartial operator()(DifferenceType start, - DifferenceType stop, DifferenceType step = 1) const noexcept { - return {start, stop, step}; - } -}; - -namespace iter { - constexpr impl::SliceFn slice{}; -} - -#endif diff --git a/sliding_window.hpp b/sliding_window.hpp deleted file mode 100644 index 9f58334b..00000000 --- a/sliding_window.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef ITER_SLIDING_WINDOW_HPP_ -#define ITER_SLIDING_WINDOW_HPP_ - -#include "internal/iterbase.hpp" -#include "internal/iteratoriterator.hpp" - -#include -#include -#include - -namespace iter { - namespace impl { - template - class WindowSlider; - using SlidingWindowFn = IterToolFnBindSizeTSecond; - } - constexpr impl::SlidingWindowFn sliding_window{}; -} - -template -class iter::impl::WindowSlider { - private: - Container container; - std::size_t window_size; - - friend SlidingWindowFn; - - WindowSlider(Container&& in_container, std::size_t win_sz) - : container(std::forward(in_container)), window_size{win_sz} {} - - using IndexVector = std::deque>; - using DerefVec = IterIterWrapper; - - public: - WindowSlider(WindowSlider&&) = default; - class Iterator : public std::iterator { - private: - iterator_type sub_iter; - DerefVec window; - - public: - Iterator(iterator_type&& in_iter, - const iterator_type& in_end, std::size_t window_sz) - : sub_iter(std::move(in_iter)) { - std::size_t i{0}; - while (i < window_sz && this->sub_iter != in_end) { - this->window.get().push_back(this->sub_iter); - ++i; - if (i != window_sz) { - ++this->sub_iter; - } - } - } - - bool operator!=(const Iterator& other) const { - return this->sub_iter != other.sub_iter; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - - DerefVec& operator*() { - return this->window; - } - - DerefVec* operator->() { - return this->window; - } - - Iterator& operator++() { - ++this->sub_iter; - this->window.get().pop_front(); - this->window.get().push_back(this->sub_iter); - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - }; - - Iterator begin() { - return {(this->window_size != 0 ? std::begin(this->container) - : std::end(this->container)), - std::end(this->container), this->window_size}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container), - this->window_size}; - } -}; - -#endif diff --git a/sorted.hpp b/sorted.hpp deleted file mode 100644 index 529b8dcc..00000000 --- a/sorted.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef ITER_SORTED_HPP_ -#define ITER_SORTED_HPP_ - -#include "internal/iterbase.hpp" -#include "internal/iteratoriterator.hpp" - -#include -#include -#include - -namespace iter { - namespace impl { - template - class SortedView; - using SortedFn = IterToolFnOptionalBindSecond>; - } - constexpr impl::SortedFn sorted{}; -} - -template -class iter::impl::SortedView { - private: - using IterIterWrap = IterIterWrapper>>; - using ItIt = iterator_type; - - friend SortedFn; - - Container container; - IterIterWrap sorted_iters; - - SortedView(Container&& in_container, CompareFunc compare_func) - : container(std::forward(in_container)) { - // Fill the sorted_iters vector with an iterator to each - // element in the container - for (auto iter = std::begin(this->container); - iter != std::end(this->container); ++iter) { - this->sorted_iters.get().push_back(iter); - } - - // sort by comparing the elements that the iterators point to - std::sort( - std::begin(sorted_iters.get()), std::end(sorted_iters.get()), - [compare_func](iterator_type it1, - iterator_type it2) { return compare_func(*it1, *it2); }); - } - - public: - SortedView(SortedView&&) = default; - - ItIt begin() { - return std::begin(sorted_iters); - } - - ItIt end() { - return std::end(sorted_iters); - } -}; - -#endif diff --git a/starmap.hpp b/starmap.hpp deleted file mode 100644 index e4658e50..00000000 --- a/starmap.hpp +++ /dev/null @@ -1,218 +0,0 @@ -#ifndef ITER_STARMAP_H_ -#define ITER_STARMAP_H_ - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class StarMapper; - - template - class TupleStarMapper; - - struct StarMapFn; - } -} - -// NOTE I don't know why, but clang gets very confused by having this-> in the -// Iterators' member functions for these classes - -// starmap with a container where T is one of tuple, pair, array -template -class iter::impl::StarMapper { - private: - Func func; - Container container; - - using StarIterDeref = std::remove_reference_t>()))>; - - StarMapper(Func f, Container&& c) - : func(std::move(f)), container(std::forward(c)) {} - - friend StarMapFn; - - public: - class Iterator - : public std::iterator { - private: - Func* func; - iterator_type sub_iter; - - public: - Iterator(Func& f, iterator_type&& iter) - : func(&f), sub_iter(std::move(iter)) {} - - bool operator!=(const Iterator& other) const { - return this->sub_iter != other.sub_iter; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - - Iterator& operator++() { - ++this->sub_iter; - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - decltype(auto) operator*() { - return call_with_tuple(*func, *sub_iter); - } - - auto operator-> () -> ArrowProxy { - return {**this}; - } - }; - - Iterator begin() { - return {this->func, std::begin(this->container)}; - } - - Iterator end() { - return {this->func, std::end(this->container)}; - } -}; - -// starmap for a tuple or pair of tuples or pairs -template -class iter::impl::TupleStarMapper { - private: - Func func; - TupType tup; - - private: - static_assert(sizeof...(Is) == std::tuple_size>::value, - "tuple size doesn't match size of Is"); - - friend StarMapFn; - - template - static decltype(auto) get_and_call_with_tuple(Func& f, TupType& t) { - return call_with_tuple(f, std::get(t)); - } - - using ResultType = decltype(get_and_call_with_tuple<0>(func, tup)); - using CallerFunc = ResultType (*)(Func&, TupType&); - - constexpr static std::array callers{ - {get_and_call_with_tuple...}}; - - using TraitsValue = std::remove_reference_t; - - TupleStarMapper(Func f, TupType t) - : func(std::move(f)), tup(std::forward(t)) {} - - public: - class Iterator : public std::iterator { - private: - Func* func; - std::remove_reference_t* tup; - std::size_t index; - - public: - Iterator(Func& f, TupType& t, std::size_t i) - : func{&f}, tup{&t}, index{i} {} - - decltype(auto) operator*() { - return callers[index](*func, *tup); - } - - auto operator-> () -> ArrowProxy { - return {**this}; - } - - Iterator& operator++() { - ++index; - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - return index != other.index; - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - }; - - Iterator begin() { - return {this->func, this->tup, 0}; - } - - Iterator end() { - return {this->func, this->tup, sizeof...(Is)}; - } -}; - -template -constexpr std::array< - typename iter::impl::TupleStarMapper::CallerFunc, - sizeof...(Is)> iter::impl::TupleStarMapper::callers; - -struct iter::impl::StarMapFn : PipeableAndBindFirst { - private: - template - TupleStarMapper helper_with_tuples( - Func func, TupType&& tup, std::index_sequence) const { - return {std::move(func), std::forward(tup)}; - } - - // handles tuple-like types - template - auto helper(Func func, TupType&& tup, std::true_type) const { - return helper_with_tuples(std::move(func), std::forward(tup), - std::make_index_sequence>:: - value>{}); - } - - // handles everything else - template - StarMapper helper( - Func func, Container&& container, std::false_type) const { - return {std::move(func), std::forward(container)}; - } - - template - struct is_tuple_like : public std::false_type {}; - - template - struct is_tuple_like>::value)>> - : public std::true_type {}; - - public: - template - auto operator()(Func func, Seq&& sequence) const { - return helper( - std::move(func), std::forward(sequence), is_tuple_like{}); - } - - using PipeableAndBindFirst::operator(); -}; - -namespace iter { - constexpr impl::StarMapFn starmap{}; -} - -#endif diff --git a/takewhile.hpp b/takewhile.hpp deleted file mode 100644 index 003e38a0..00000000 --- a/takewhile.hpp +++ /dev/null @@ -1,110 +0,0 @@ -#ifndef ITER_TAKEWHILE_H_ -#define ITER_TAKEWHILE_H_ - -#include "internal/iterbase.hpp" -#include "filter.hpp" - -#include -#include - -namespace iter { - namespace impl { - template - class Taker; - - using TakeWhileFn = IterToolFnOptionalBindFirst; - } - constexpr impl::TakeWhileFn takewhile{}; -} - -template -class iter::impl::Taker { - private: - Container container; - FilterFunc filter_func; - - friend TakeWhileFn; - - Taker(FilterFunc in_filter_func, Container&& in_container) - : container(std::forward(in_container)), - filter_func(in_filter_func) {} - - public: - Taker(Taker&&) = default; - - class Iterator : public std::iterator> { - private: - using Holder = DerefHolder>; - iterator_type sub_iter; - iterator_type sub_end; - Holder item; - FilterFunc* filter_func; - - void inc_sub_iter() { - ++this->sub_iter; - if (this->sub_iter != this->sub_end) { - this->item.reset(*this->sub_iter); - } - } - - void check_current() { - if (this->sub_iter != this->sub_end - && !(*this->filter_func)(this->item.get())) { - this->sub_iter = this->sub_end; - } - } - - public: - Iterator(iterator_type&& iter, iterator_type&& end, - FilterFunc& in_filter_func) - : sub_iter{std::move(iter)}, - sub_end{std::move(end)}, - filter_func(&in_filter_func) { - if (this->sub_iter != this->sub_end) { - this->item.reset(*this->sub_iter); - } - this->check_current(); - } - - typename Holder::reference operator*() { - return this->item.get(); - } - - typename Holder::pointer operator->() { - return this->item.get_ptr(); - } - - Iterator& operator++() { - this->inc_sub_iter(); - this->check_current(); - 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->filter_func}; - } - - Iterator end() { - return {std::end(this->container), std::end(this->container), - this->filter_func}; - } -}; - -#endif diff --git a/test/BUILD b/test/BUILD new file mode 100644 index 00000000..cc9aefe6 --- /dev/null +++ b/test/BUILD @@ -0,0 +1,49 @@ +load(":make_tests.bzl", "itertools_tests") + +progs = [ + "accumulate", + "batched", + "chain", + "chunked", + "combinations", + "combinations_with_replacement", + "compress", + "count", + "cycle", + "dropwhile", + "enumerate", + "filter", + "filterfalse", + "groupby", + "imap", + "permutations", + "powerset", + "product", + "range", + "repeat", + "reversed", + "slice", + "sliding_window", + "starmap", + "sorted", + "takewhile", + "unique_everseen", + "unique_justseen", + "zip", + "iteratoriterator", + "iterator_wrapper", + "iterbase", + "mixed", + "helpers", +] + +SANITIZE = "-fsanitize=address,undefined" + +cc_library( + name = "test_main", + srcs = ["test_main.cpp", "catch.hpp"], + copts = [SANITIZE, "-Wall", "-Wextra", "-std=c++17", "-g"], + linkopts = [SANITIZE], +) + +itertools_tests(progs) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..e9140f47 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,35 @@ +# Note that some examples currently use boost.optional which we do no not search for in this file. +# You might have to use the "keep going" option to continue building on errors if boost.optional is not in the include path. +# For example, building with MSVC (from a test/buildMsvc directory): +# set CXX=cl.exe +# cmake .. -G Ninja +# cmake --build . -- -k99 + +cmake_minimum_required(VERSION 3.8) +project(cppitertools_tests CXX) +set (CMAKE_CXX_STANDARD 17) + +find_package(Boost 1.60.0 REQUIRED) +include_directories( + .. + ${Boost_INCLUDE_DIRS} +) + + +include(CheckIncludeFileCXX) +set(CMAKE_REQUIRED_INCLUDES ${PROJECT_SOURCE_DIR}) +CHECK_INCLUDE_FILE_CXX(catch.hpp _has_catch) +if(NOT "${_has_catch}") + message("WARNING: catch.hpp not found, run ./download_catch.sh from test/ directory first") +endif() + +file(GLOB test_sources RELATIVE ${PROJECT_SOURCE_DIR} "test_*.cpp") +list(REMOVE_ITEM test_sources test_main.cpp) +add_library(test_main OBJECT test_main.cpp) + +foreach(_source_cpp ${test_sources}) + get_filename_component(_name_without_extension "${_source_cpp}" NAME_WE) + add_executable(${_name_without_extension} ${_source_cpp} $) +endforeach() + +add_executable(test_all ${test_sources} $) diff --git a/test/SConstruct b/test/SConstruct index cbf3cd3c..98af3051 100644 --- a/test/SConstruct +++ b/test/SConstruct @@ -3,7 +3,7 @@ import os env = Environment( ENV = os.environ, CXXFLAGS= ['-g', '-Wall', '-Wextra', - '-pedantic', '-std=c++14', + '-pedantic', '-std=c++17', '-I/usr/local/include', '-I.'], CPPPATH='..', LINKFLAGS=['-L/usr/local/lib']) @@ -11,9 +11,19 @@ env = Environment( # allows highighting to print to terminal from compiler output env['ENV']['TERM'] = os.environ['TERM'] +try: + env.Replace(CXX=os.environ['CXX']) +except KeyError: + pass + +if env['CXX'].startswith('clang++'): + env['CXXFLAGS'].append('-stdlib=libc++') + env['LINKFLAGS'].append('-stdlib=libc++') + progs = Split( ''' accumulate + batched chain chunked combinations @@ -43,6 +53,7 @@ progs = Split( zip iteratoriterator + iterator_wrapper iterbase mixed helpers diff --git a/test/download_catch.sh b/test/download_catch.sh index f88f5643..4f1ed06b 100755 --- a/test/download_catch.sh +++ b/test/download_catch.sh @@ -1,2 +1,2 @@ #!/usr/bin/env sh -wget -c https://raw.githubusercontent.com/philsquared/Catch/master/single_include/catch.hpp +wget -c https://github.com/catchorg/Catch2/releases/download/v2.13.10/catch.hpp diff --git a/test/helpers.hpp b/test/helpers.hpp index d16f3bd4..f94250f3 100644 --- a/test/helpers.hpp +++ b/test/helpers.hpp @@ -1,11 +1,14 @@ #ifndef TEST_HELPER_H_ #define TEST_HELPER_H_ -#include - +#include +#include +#include +#include #include #include #include +#include namespace itertest { @@ -28,6 +31,27 @@ namespace itertest { SolidInt(SolidInt&&) = delete; }; + class Integer { + private: + int i_{}; + + public: + constexpr Integer(int i) : i_{i} {} + constexpr bool is_zero() const { + return i_ == 0; + } + constexpr bool operator==(const Integer& other) const { + return i_ == other.i_; + } + constexpr bool operator!=(const Integer& other) const { + return i_ != other.i_; + } + + constexpr bool is_positive() const { + return i_ > 0; + } + }; + namespace { struct DoubleDereferenceError : std::exception { const char* what() const noexcept override { @@ -86,7 +110,7 @@ namespace itertest { T* data; std::size_t size; bool was_moved_from_ = false; - bool was_copied_from_ = false; + mutable bool was_copied_from_ = false; public: BasicIterable(std::initializer_list il) @@ -103,18 +127,17 @@ namespace itertest { BasicIterable& operator=(BasicIterable&&) = delete; BasicIterable& operator=(const BasicIterable&) = delete; +#ifndef DEFINE_BASIC_ITERABLE_COPY_CTOR BasicIterable(const BasicIterable&) = delete; -#if 0 - BasicIterable(const BasicIterable& other) - : data{new T[other.size()]}, - size{other.size} - { - for (auto it = this->begin(), o_it = other.begin(); - o_it != other.end(); - ++it, ++o_it) { - *it = *o_it; - } - } +#else + BasicIterable(const BasicIterable& other) + : data{new T[other.size]}, size{other.size} { + other.was_copied_from_ = true; + auto o_it = begin(other); + for (auto it = begin(*this); o_it != end(other); ++it, ++o_it) { + *it = *o_it; + } + } #endif BasicIterable(BasicIterable&& other) : data{other.data}, size{other.size} { @@ -134,15 +157,16 @@ namespace itertest { delete[] this->data; } + template class Iterator { private: - T* p; + U* p; public: #ifdef DEFINE_DEFAULT_ITERATOR_CTOR Iterator() = default; #endif - Iterator(T* b) : p{b} {} + Iterator(U* b) : p{b} {} bool operator!=(const Iterator& other) const { return this->p != other.p; } @@ -152,45 +176,54 @@ namespace itertest { return *this; } - T& operator*() { + U& operator*() { return *this->p; } }; - Iterator begin() { - return {this->data}; + friend BasicIterable::Iterator begin(BasicIterable& b) { + return {b.data}; + } + + friend BasicIterable::Iterator end(BasicIterable& b) { + return {b.data + b.size}; } - Iterator end() { - return {this->data + this->size}; +#ifdef DEFINE_BASIC_ITERABLE_CONST_BEGIN_AND_END + friend BasicIterable::Iterator begin(const BasicIterable& b) { + return {b.data}; } + friend BasicIterable::Iterator end(const BasicIterable& b) { + return {b.data + b.size}; + } +#endif + #ifdef DECLARE_REVERSE_ITERATOR - Iterator rbegin(); - Iterator rend(); -#endif // ifdef DECLARE_REVERSE_ITERATOR + Iterator rbegin(); + Iterator rend(); +#endif // ifdef DECLARE_REVERSE_ITERATOR }; - using iter::impl::void_t; - template struct IsIterator : std::false_type {}; + template + struct ReferenceMatchesDeref + : std::is_same())> {}; + template struct IsIterator())), // copyctor - decltype(std::declval() = - std::declval()), // copy = - decltype(*std::declval()), // operator* - decltype( - std::declval().operator->()), // operator-> - decltype(++std::declval()), // prefix ++ - decltype(std::declval()++), // postfix ++ - decltype(std::declval() - != std::declval()), // != - decltype(std::declval() - == std::declval()) // == - >> : std::true_type {}; + std::void_t())), // copyctor + decltype(std::declval() = std::declval()), // copy = + decltype(*std::declval()), // operator* + decltype(std::declval().operator->()), // operator-> + decltype(++std::declval()), // prefix ++ + decltype(std::declval()++), // postfix ++ + decltype(std::declval() + != std::declval()), // != + decltype(std::declval() == std::declval()) // == + >> : std::true_type {}; template struct IsForwardIterator @@ -204,6 +237,218 @@ namespace itertest { && !std::is_copy_assignable::value && !std::is_move_assignable::value && std::is_move_constructible::value> {}; + + struct Point { + int x; + int y; + int get_x() const { + return x; + } + int get_y() const { + return y; + } + + std::string prefix(const std::string& str) const { + std::ostringstream ss; + ss << str << "(" << x << ", " << y << ")"; + return ss.str(); + } + + bool operator==(Point other) const { + return x == other.x && y == other.y; + } + + bool operator!=(Point other) const { + return !(*this == other); + } + + friend std::ostream& operator<<(std::ostream& out, const Point& p) { + return out << p.prefix(""); + } + + bool left_of(Point other) const { + return x < other.x; + } + + Point add(Point other) const { + return {x + other.x, y + other.y}; + } + }; +} +template +class DiffEndRange { + private: + T start_; + T stop_; + std::vector all_results_; + + public: + constexpr DiffEndRange(T start, T stop) : start_{start}, stop_{stop} { + while (start < stop_) { + all_results_.push_back(start); + Inc{}(start); + } + } + + class Iterator; + class EndIterator; + + class Iterator { + using SubIter = typename std::vector::iterator; + + private: + SubIter it_; + SubIter end_; + + public: +#ifdef CHAR_RANGE_DEFAULT_CONSTRUCTIBLE + Iterator() = default; +#endif + Iterator(SubIter it, SubIter end_it) : it_{it}, end_{end_it} {} + + T& operator*() const { + return *it_; + } + T* operator->() const { + return &*it_; + } + + Iterator& operator++() { + ++it_; + return *this; + } + + bool operator!=(const Iterator& other) const { + return it_ != other.it_; + } + + bool operator!=(const EndIterator&) const { + return it_ != end_; + } + + friend bool operator!=(const EndIterator& lhs, const Iterator& rhs) { + return rhs != lhs; + } + }; + + class ReverseIterator { + using SubIter = typename std::vector::reverse_iterator; + + private: + SubIter it_; + SubIter end_; + + public: +#ifdef CHAR_RANGE_DEFAULT_CONSTRUCTIBLE + ReverseIterator() = default; +#endif + ReverseIterator(SubIter it, SubIter end_it) : it_{it}, end_{end_it} {} + + T& operator*() const { + return *it_; + } + T* operator->() const { + return &*it_; + } + + Iterator& operator++() { + ++it_; + return *this; + } + + bool operator!=(const Iterator& other) const { + return it_ != other.it_; + } + + bool operator!=(const EndIterator&) const { + return it_ != end_; + } + + friend bool operator!=(const EndIterator& lhs, const Iterator& rhs) { + return rhs != lhs; + } + }; + + class EndIterator {}; + class ReverseEndIterator {}; + + Iterator begin() { + return {std::begin(all_results_), std::end(all_results_)}; + } + + EndIterator end() { + return {}; + } + + ReverseIterator rbegin() { + return {std::rbegin(all_results_), std::rend(all_results_)}; + } + + ReverseEndIterator rend() { + return {}; + } +}; + +struct CharInc { + void operator()(char& c) { + ++c; + } +}; + +// A range from 'a' to stop, begin() and end() are different +class CharRange : public DiffEndRange { + public: + constexpr CharRange(char stop) : DiffEndRange('a', stop) {} +}; + +struct IncIntCharPair { + void operator()(std::pair& p) { + ++p.first; + ++p.second; + } +}; + +class IntCharPairRange + : public DiffEndRange, IncIntCharPair> { + public: + IntCharPairRange(std::pair stop) + : DiffEndRange, IncIntCharPair>({0, 'a'}, stop) {} +}; + +inline bool less_than_five(int i) { + return i < 5; } +class LessThanValue { + private: + int compare_val; + + public: + LessThanValue(int v) : compare_val(v) {} + + bool operator()(int i) { + return i < this->compare_val; + } +}; + +class MoveOnlyLessThanValue { + private: + // unique_ptr is better for triggering asan than an int if there's a dangling + // reference to the callable + std::unique_ptr compare_val; + + public: + MoveOnlyLessThanValue(int v) : compare_val{std::make_unique(v)} {} + + MoveOnlyLessThanValue(const MoveOnlyLessThanValue&) = delete; + MoveOnlyLessThanValue& operator=(const MoveOnlyLessThanValue&) = delete; + + MoveOnlyLessThanValue(MoveOnlyLessThanValue&&) = default; + MoveOnlyLessThanValue& operator=(MoveOnlyLessThanValue&&) = default; + + bool operator()(int i) { + return i < *compare_val; + } +}; + #endif diff --git a/test/make_tests.bzl b/test/make_tests.bzl new file mode 100644 index 00000000..68a428b8 --- /dev/null +++ b/test/make_tests.bzl @@ -0,0 +1,11 @@ +def itertools_tests(progs): + for p in progs: + native.cc_test( + name = "test_{}".format(p), + srcs = ["test_{}.cpp".format(p), "catch.hpp", "helpers.hpp"], + deps = [ + "//:cppitertools", + ":test_main", + ], + copts = ["-I.", "-std=c++17", "-Wall", "-Wextra", "-pedantic", "-g"], + ) diff --git a/test/test_accumulate.cpp b/test/test_accumulate.cpp index 7df80628..c868f0c5 100644 --- a/test/test_accumulate.cpp +++ b/test/test_accumulate.cpp @@ -1,9 +1,9 @@ -#include +#include #include "helpers.hpp" -#include #include #include +#include #include "catch.hpp" @@ -45,6 +45,42 @@ TEST_CASE("accumulate: With subtraction lambda", "[accumulate]") { REQUIRE(v == vc); } +TEST_CASE("accumulate: handles pointer to member function", "[accumulate]") { + using itertest::Point; + std::vector ps = {{1, 2}, {10, 50}, {300, 600}}; + auto a = accumulate(ps, &Point::add); + const std::vector v(std::begin(a), std::end(a)); + const std::vector vc = {{1, 2}, {11, 52}, {311, 652}}; + REQUIRE(v == vc); +} + +TEST_CASE("accumulate: const iterators", "[accumulate][const]") { + std::vector v; + SECTION("lvalue") { + Vec ns{1, 2, 3, 4, 5}; + const auto a = accumulate(ns); + v.assign(std::begin(a), std::end(a)); + } + SECTION("rvalue") { + const auto a = accumulate(Vec{1, 2, 3, 4, 5}); + v.assign(std::begin(a), std::end(a)); + } + SECTION("const lvalue") { + const Vec ns{1, 2, 3, 4, 5}; + const auto a = accumulate(ns); + v.assign(std::begin(a), std::end(a)); + } + Vec vc{1, 3, 6, 10, 15}; + REQUIRE(v == vc); +} + +TEST_CASE( + "accumulate: const iterators can be compared", "[accumulate][const]") { + auto e = accumulate(std::string("hello")); + const auto& ce = e; + (void)(std::begin(e) == std::end(ce)); +} + struct Integer { const int value; constexpr Integer(int i) : value{i} {} @@ -57,7 +93,7 @@ TEST_CASE("accumulate: intermidate type need not be default constructible", "[accumulate]") { std::vector v = {{2}, {3}, {10}}; auto a = accumulate(v, std::plus{}); - auto it = std::begin(a); + (void)std::begin(a); } TEST_CASE("accumulate: binds reference when it should", "[accumulate]") { @@ -95,11 +131,33 @@ TEST_CASE("accumulate: operator->", "[accumulate]") { } TEST_CASE("accumulate: iterator meets requirements", "[accumulate]") { - Vec ns{}; - auto a = accumulate(ns, [](int a, int b) { return a + b; }); - auto it = std::begin(a); - it = std::begin(a); - REQUIRE(itertest::IsIterator::value); + std::vector ns{}; + SECTION("with reference return type") { + auto acc = accumulate(ns, [](int& a, int&) -> int& { return a; }); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + + SECTION("with const reference return type") { + auto acc = accumulate(ns, [](int& a, int&) -> const int& { return a; }); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + + SECTION("with value return type") { + auto acc = accumulate(ns, [](int a, int) -> int { return a; }); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } +} + +TEST_CASE( + "accumulate: Works with different begin and end types", "[accumulate]") { + CharRange cr{'d'}; + auto a = accumulate(cr); + Vec v(a.begin(), a.end()); + Vec vc{'a', 'a' + 'b', 'a' + 'b' + 'c'}; + REQUIRE(v == vc); } template diff --git a/test/test_batched.cpp b/test/test_batched.cpp new file mode 100644 index 00000000..098acecc --- /dev/null +++ b/test/test_batched.cpp @@ -0,0 +1,135 @@ +#include + +#include +#include +#include +#include + +#include "catch.hpp" +#include "helpers.hpp" + +using iter::batched; +using Vec = std::vector; +using ResVec = std::vector; + +TEST_CASE("batched: basic test", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6}; + ResVec results; + SECTION("Normal call") { + for (auto&& g : batched(ns, 2)) { + results.emplace_back(std::begin(g), std::end(g)); + } + } + SECTION("Pipe") { + for (auto&& g : ns | batched(2)) { + results.emplace_back(std::begin(g), std::end(g)); + } + } + + ResVec rc = {{1, 2, 3}, {4 ,5, 6}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: uneven batch sizes", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + ResVec results; + for (auto&& g : batched(ns, 6)) { + results.emplace_back(std::begin(g), std::end(g)); + } + + ResVec rc = {{1, 2}, {3, 4}, {5, 6}, {7}, {8}, {9}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: const batched", "[batched][const]") { + Vec ns = {1, 2, 3, 4, 5, 6}; + ResVec results; + SECTION("Normal call") { + const auto& ch = batched(ns, 2); + for (auto&& g : ch) { + results.emplace_back(std::begin(g), std::end(g)); + } + } + ResVec rc = {{1, 2, 3}, {4, 5, 6}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: const iterators can be compared to non-const iterators", + "[batched][const]") { + auto c = batched(Vec{}, 1); + const auto& cc = c; + (void)(std::begin(c) == std::end(cc)); +} + +TEST_CASE("batched: len(iterable) % num_batches != 0", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6, 7}; + ResVec results; + for (auto&& g : batched(ns, 3)) { + results.emplace_back(std::begin(g), std::end(g)); + } + + ResVec rc = {{1, 2, 3}, {4, 5}, {6, 7}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: num_batches > len(iterable)", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6, 7}; + ResVec results; + for (auto&& g : batched(ns, 9)) { + results.emplace_back(std::begin(g), std::end(g)); + } + + ResVec rc = {{1}, {2}, {3}, {4}, {5}, {6}, {7}}; + + REQUIRE(results == rc); +} + +TEST_CASE("batched: iterators can be compared", "[batched]") { + Vec ns = {1, 2, 3, 4, 5, 6, 7}; + auto g = batched(ns, 3); + auto it = std::begin(g); + REQUIRE(it == std::begin(g)); + REQUIRE_FALSE(it != std::begin(g)); + ++it; + REQUIRE(it != std::begin(g)); + REQUIRE_FALSE(it == std::begin(g)); +} + +TEST_CASE("batched: size 0 is empty", "[batched]") { + Vec ns{1, 2, 3}; + auto g = batched(ns, 0); + REQUIRE(std::begin(g) == std::end(g)); +} + +TEST_CASE("batched: Works with different begin and end types", "[batched]") { + CharRange cr{'f'}; + std::vector> results; + for (auto&& g : batched(cr, 3)) { + results.emplace_back(std::begin(g), std::end(g)); + } + std::vector> rc = {{'a', 'b'}, {'c', 'd'}, {'e'}}; + REQUIRE(results == rc); +} + +TEST_CASE("batched: empty iterable gives empty batched", "[batched]") { + Vec ns{}; + auto g = batched(ns, 1); + REQUIRE(std::begin(g) == std::end(g)); +} + +TEST_CASE("batched: iterator meets requirements", "[batched]") { + std::string s{}; + auto c = batched(s, 1); + REQUIRE(itertest::IsIterator::value); +} + +template +using ImpT = decltype(batched(std::declval(), 1)); +TEST_CASE("batched: has correct ctor and assign ops", "[batched]") { + REQUIRE(itertest::IsMoveConstructibleOnly>::value); + REQUIRE(itertest::IsMoveConstructibleOnly>::value); +} diff --git a/test/test_chain.cpp b/test/test_chain.cpp index f026bfe8..29a81512 100644 --- a/test/test_chain.cpp +++ b/test/test_chain.cpp @@ -1,18 +1,17 @@ +#include #include "helpers.hpp" -#include -#include +#include #include #include -#include -#include #include +#include #include "catch.hpp" using iter::chain; -using itertest::SolidInt; using itertest::BasicIterable; +using itertest::SolidInt; using Vec = const std::vector; TEST_CASE("chain: three strings", "[chain]") { @@ -27,6 +26,46 @@ TEST_CASE("chain: three strings", "[chain]") { REQUIRE(v == vc); } +TEST_CASE("chain: const iteration", "[chain][const]") { + std::string s1{"abc"}; + const std::string s2{"mno"}; + const auto ch = chain(s1, s2, std::string{"xyz"}); + + Vec v(std::begin(ch), std::end(ch)); + Vec vc{'a', 'b', 'c', 'm', 'n', 'o', 'x', 'y', 'z'}; + + REQUIRE(v == vc); +} + +TEST_CASE("chain: const iterators can be compared to non-const itertors", + "[chain][const]") { + std::string s1{"abc"}; + std::list li{'m', 'n', 'o'}; + auto ch = chain(s1, li); + + const auto cch = chain(s1, li); + SECTION("begin and const begin compare equal") { + REQUIRE(std::begin(ch) == std::begin(cch)); + } + SECTION("begin and const end compare not-equal") { + REQUIRE_FALSE(std::begin(ch) == std::end(cch)); + } + SECTION("end and const end compare equal") { + REQUIRE(std::end(ch) == std::end(cch)); + } + SECTION( + "const and non-const iterator compare equal/not-equal at appropriate " + "pos.") { + auto iter = ch.begin(); + iter++; + auto citer = cch.begin(); + citer++; + REQUIRE(iter == citer); + citer++; + REQUIRE_FALSE(iter == citer); + } +} + TEST_CASE("chain: with different container types", "[chain]") { std::string s1{"abc"}; std::list li{'m', 'n', 'o'}; @@ -39,6 +78,19 @@ TEST_CASE("chain: with different container types", "[chain]") { REQUIRE(v == vc); } +TEST_CASE( + "chain: where one container has different begin and end types", "[chain]") { + std::string s1{"abc"}; + std::list li{'m', 'n', 'o'}; + CharRange cr('e'); + auto ch = chain(s1, li, cr); + + Vec v(std::begin(ch), std::end(ch)); + Vec vc{'a', 'b', 'c', 'm', 'n', 'o', 'a', 'b', 'c', 'd'}; + + REQUIRE(v == vc); +} + TEST_CASE("chain: handles empty containers", "[chain]") { std::string emp; std::string a{"a"}; @@ -172,6 +224,67 @@ TEST_CASE("chain.from_iterable: basic test", "[chain.from_iterable]") { REQUIRE(v == vc); } +TEST_CASE( + "chain.from_iterable: const iteration", "[chain.from_iterable][const]") { + std::vector sv{"abc", "xyz"}; + const auto ch = chain.from_iterable(sv); + std::vector v(std::begin(ch), std::end(ch)); + + std::vector vc{'a', 'b', 'c', 'x', 'y', 'z'}; + REQUIRE(v == vc); +} + +TEST_CASE( + "chain.from_iterable: const iterators can be compared to non-const " + "iterators", + "[chain.from_iterable][const]") { + std::vector> v{{1, 2}, {4, 6}}; + auto ch = chain.from_iterable(v); + const auto& cch = ch; + + SECTION("begin and const end compare not-equal") { + REQUIRE_FALSE(std::begin(ch) == std::end(cch)); + } + SECTION("begin and const begin compare equal") { + REQUIRE(std::begin(ch) == std::begin(cch)); + } + SECTION("end and const end compare not-equal") { + REQUIRE(std::end(ch) == std::end(cch)); + } + SECTION( + "const and non-const iterator compare equal/not-equal at appropriate " + "pos.") { + auto iter = ch.begin(); + iter++; + auto citer = cch.begin(); + citer++; + REQUIRE(iter == citer); + citer++; + REQUIRE_FALSE(iter == citer); + iter++; + REQUIRE(iter == citer); + } +} + +TEST_CASE("chain.fromm_iterable: Works with different begin and end types", + "[chain.from_iterable]") { + std::vector crv = {{'c'}, {'d'}}; + auto ch = chain.from_iterable(crv); + const std::vector v(std::begin(ch), std::end(ch)); + const std::vector vc = {'a', 'b', 'a', 'b', 'c'}; + REQUIRE(v == vc); +} + +TEST_CASE("chain.from_iterable: Works with empty subiterable", + "[chain.from_iterable]") { + std::vector> ivv{ + {}, {2, 4, 6}, {}, {8, 10, 12}, {14, 16, 18}, {}}; + auto ch = chain.from_iterable(ivv); + const std::vector v(std::begin(ch), std::end(ch)); + const std::vector vi = {2, 4, 6, 8, 10, 12, 14, 16, 18}; + REQUIRE(v == vi); +} + TEST_CASE( "chain.from_iterable: iterators cant be copy constructed " "and assigned", @@ -238,8 +351,8 @@ template using ImpT2 = decltype(chain.from_iterable(std::declval())); TEST_CASE("chain.from_iterable: has correct ctor and assign ops", "[chain.from_iterable]") { - REQUIRE(itertest::IsMoveConstructibleOnly>>:: - value); - REQUIRE(itertest::IsMoveConstructibleOnly&>>:: - value); + REQUIRE(itertest::IsMoveConstructibleOnly< + ImpT2>>::value); + REQUIRE(itertest::IsMoveConstructibleOnly< + ImpT2&>>::value); } diff --git a/test/test_chunked.cpp b/test/test_chunked.cpp index 2bca55f4..3aaca797 100644 --- a/test/test_chunked.cpp +++ b/test/test_chunked.cpp @@ -1,12 +1,12 @@ -#include +#include -#include #include #include #include +#include -#include "helpers.hpp" #include "catch.hpp" +#include "helpers.hpp" using iter::chunked; using Vec = std::vector; @@ -31,6 +31,27 @@ TEST_CASE("chunked: basic test", "[chunked]") { REQUIRE(results == rc); } +TEST_CASE("chunked: const chunked", "[chunked][const]") { + Vec ns = {1, 2, 3, 4, 5, 6}; + ResVec results; + SECTION("Normal call") { + const auto& ch = chunked(ns, 2); + for (auto&& g : ch) { + results.emplace_back(std::begin(g), std::end(g)); + } + } + ResVec rc = {{1, 2}, {3, 4}, {5, 6}}; + + REQUIRE(results == rc); +} + +TEST_CASE("chunked: const iterators can be compared to non-const iterators", + "[chunked][const]") { + auto c = chunked(Vec{}, 1); + const auto& cc = c; + (void)(std::begin(c) == std::end(cc)); +} + TEST_CASE("chunked: len(iterable) % groupsize != 0", "[chunked]") { Vec ns = {1, 2, 3, 4, 5, 6, 7}; ResVec results; @@ -60,6 +81,16 @@ TEST_CASE("chunked: size 0 is empty", "[chunked]") { REQUIRE(std::begin(g) == std::end(g)); } +TEST_CASE("chunked: Works with different begin and end types", "[chunked]") { + CharRange cr{'f'}; + std::vector> results; + for (auto&& g : chunked(cr, 3)) { + results.emplace_back(std::begin(g), std::end(g)); + } + std::vector> rc = {{'a', 'b', 'c'}, {'d', 'e'}}; + REQUIRE(results == rc); +} + TEST_CASE("chunked: empty iterable gives empty chunked", "[chunked]") { Vec ns{}; auto g = chunked(ns, 1); diff --git a/test/test_combinations.cpp b/test/test_combinations.cpp index 2b994a75..14548240 100644 --- a/test/test_combinations.cpp +++ b/test/test_combinations.cpp @@ -1,13 +1,14 @@ #define DEFINE_DEFAULT_ITERATOR_CTOR +#define CHAR_RANGE_DEFAULT_CONSTRUCTIBLE #include "helpers.hpp" +#undef CHAR_RANGE_DEFAULT_CONSTRUCTIBLE #undef DEFINE_DEFAULT_ITERATOR_CTOR -#include - +#include +#include #include -#include #include -#include +#include #include "catch.hpp" @@ -35,6 +36,40 @@ TEST_CASE("combinations: Simple combination of 4", "[combinations]") { REQUIRE(ans == sc); } +TEST_CASE("combinations: const iteration", "[combinations][const]") { + std::string s{"ABCD"}; + CharCombSet sc; + const auto comb = combinations(s, 2); + for (auto&& v : comb) { + sc.emplace_back(std::begin(v), std::end(v)); + } + + CharCombSet ans = { + {'A', 'B'}, {'A', 'C'}, {'A', 'D'}, {'B', 'C'}, {'B', 'D'}, {'C', 'D'}}; + REQUIRE(ans == sc); +} + +TEST_CASE( + "combinations: const iterators can be compared to non-const iterators", + "[combinations][const]") { + std::string s{"ABC"}; + auto c = combinations(s, 2); + const auto& cc = c; + (void)(std::begin(c) == std::end(cc)); +} + +TEST_CASE("combinations: Works with different begin and end types", + "[combinations]") { + CharRange cr{'e'}; + CharCombSet sc; + for (auto&& v : combinations(cr, 2)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + CharCombSet ans = { + {'a', 'b'}, {'a', 'c'}, {'a', 'd'}, {'b', 'c'}, {'b', 'd'}, {'c', 'd'}}; + REQUIRE(ans == sc); +} + TEST_CASE("combinations: iterators can be compared", "[combinations]") { std::string s{"ABCD"}; auto c = combinations(s, 2); @@ -59,10 +94,31 @@ TEST_CASE("combinations: size too large gives no results", "[combinations]") { REQUIRE(std::begin(c) == std::end(c)); } -TEST_CASE("combinations: size 0 gives nothing", "[combinations]") { +TEST_CASE("combinations: size 0 gives one empty result", "[combinations]") { std::string s{"ABCD"}; - auto c = combinations(s, 0); - REQUIRE(std::begin(c) == std::end(c)); + + CharCombSet ans = {{}}; + + CharCombSet sc; + for (auto&& v : combinations(s, 0)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + + REQUIRE(ans == sc); +} + +TEST_CASE("combinations: size 0 gives one empty result for empty input", + "[combinations]") { + std::string s{}; + + CharCombSet ans = {{}}; + + CharCombSet sc; + for (auto&& v : combinations(s, 0)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + + REQUIRE(ans == sc); } TEST_CASE( diff --git a/test/test_combinations_with_replacement.cpp b/test/test_combinations_with_replacement.cpp index 9db79414..2c3ee0f6 100644 --- a/test/test_combinations_with_replacement.cpp +++ b/test/test_combinations_with_replacement.cpp @@ -1,11 +1,12 @@ -#include - +#include #include -#include #include +#include #include +#define CHAR_RANGE_DEFAULT_CONSTRUCTIBLE #include "helpers.hpp" +#undef CHAR_RANGE_DEFAULT_CONSTRUCTIBLE #include "catch.hpp" using iter::combinations_with_replacement; @@ -31,6 +32,42 @@ TEST_CASE("combinations_with_replacement: Simple combination", REQUIRE(ans == sc); } +TEST_CASE("combinations_with_replacement: const iteration", + "[combinations_with_replacement]") { + std::string s{"ABC"}; + CharCombSet sc; + const auto cwr = combinations_with_replacement(s, 2); + for (auto v : cwr) { + sc.emplace_back(std::begin(v), std::end(v)); + } + CharCombSet ans = { + {'A', 'A'}, {'A', 'B'}, {'A', 'C'}, {'B', 'B'}, {'B', 'C'}, {'C', 'C'}}; + REQUIRE(ans == sc); +} + +TEST_CASE( + "combinations_with_replacement: const iterators can be compared to " + "non-const iterators", + "[combinations_with_replacement][const]") { + std::string s{"AB"}; + auto cwr = combinations_with_replacement(s, 2); + const auto& ccwr = cwr; + (void)(std::begin(cwr) == std::end(ccwr)); +} + +TEST_CASE( + "combinations_with_replacement: Works with different begin and end types", + "[combinations_with_replacement]") { + CharRange cr{'d'}; + CharCombSet sc; + for (auto&& v : combinations_with_replacement(cr, 2)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + CharCombSet ans = { + {'a', 'a'}, {'a', 'b'}, {'a', 'c'}, {'b', 'b'}, {'b', 'c'}, {'c', 'c'}}; + REQUIRE(ans == sc); +} + TEST_CASE("combinations_with_replacement: iterators can be compared", "[combinations_with_replacement]") { std::string s{"ABCD"}; @@ -58,8 +95,23 @@ TEST_CASE("combinations_with_replacement: big size is no problem", TEST_CASE("combinations_with_replacement: 0 size is empty", "[combinations_with_replacement]") { std::string s{"A"}; - auto cwr = combinations_with_replacement(s, 0); - REQUIRE(std::begin(cwr) == std::end(cwr)); + CharCombSet sc; + for (auto v : combinations_with_replacement(s, 0)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + CharCombSet ans = {{}}; + REQUIRE(ans == sc); +} + +TEST_CASE("combinations_with_replacement: 0 size is empty with empty container", + "[combinations_with_replacement]") { + std::string s{}; + CharCombSet sc; + for (auto v : combinations_with_replacement(s, 0)) { + sc.emplace_back(std::begin(v), std::end(v)); + } + CharCombSet ans = {{}}; + REQUIRE(ans == sc); } TEST_CASE("combinations_with_replacement: operator->", diff --git a/test/test_compress.cpp b/test/test_compress.cpp index 9a0c30b2..70aa3f89 100644 --- a/test/test_compress.cpp +++ b/test/test_compress.cpp @@ -1,11 +1,10 @@ +#include #include "helpers.hpp" -#include -#include -#include -#include #include +#include #include +#include #include "catch.hpp" @@ -24,6 +23,23 @@ TEST_CASE("compress: alternating", "[compress]") { REQUIRE(v == vc); } +TEST_CASE("compress: const iteration ", "[compress][const]") { + std::vector ivec{1, 2, 3, 4, 5, 6}; + std::vector bvec{true, false, true, false, true, false}; + const auto c = compress(ivec, bvec); + Vec v(std::begin(c), std::end(c)); + Vec vc = {1, 3, 5}; + + REQUIRE(v == vc); +} + +TEST_CASE("compress: const iterators can be compared to non-const iterators", + "[compress][const]") { + auto c = compress(std::vector{}, std::vector{}); + const auto& cc = c; + (void)(std::begin(c) == std::end(cc)); +} + TEST_CASE("compress: consecutive falses", "[compress]") { std::vector ivec{1, 2, 3, 4, 5}; std::vector bvec{true, false, false, false, true}; @@ -136,6 +152,14 @@ TEST_CASE("compress: iterator meets requirements", "[compress]") { REQUIRE(itertest::IsIterator::value); } +TEST_CASE("compress: Works with different begin and end types", "[compress]") { + CharRange cr{'d'}; + auto c = compress(cr, std::vector{true, false, true}); + Vec v(c.begin(), c.end()); + Vec vc{'a', 'c'}; + REQUIRE(v == vc); +} + template using ImpT = decltype(compress(std::declval(), std::declval())); TEST_CASE("compress: has correct ctor and assign ops", "[compress]") { diff --git a/test/test_count.cpp b/test/test_count.cpp index 643819ea..d5248ac2 100644 --- a/test/test_count.cpp +++ b/test/test_count.cpp @@ -1,9 +1,9 @@ +#include #include "helpers.hpp" -#include -#include #include #include +#include #include "catch.hpp" @@ -20,6 +20,18 @@ TEST_CASE("count: watch for 10 elements", "[count]") { REQUIRE(v == vc); } +TEST_CASE("count: const watch for 10 elements", "[count][const]") { + std::vector v{}; + const auto c = count(); + for (auto i : c) { + v.push_back(i); + if (i == 9) break; + } + + const std::vector vc{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + REQUIRE(v == vc); +} + TEST_CASE("count: start at 10", "[count]") { std::vector v{}; for (auto i : count(10)) { @@ -53,6 +65,68 @@ TEST_CASE("count: with step > 1", "[count]") { REQUIRE(v == vc); } +TEST_CASE("count: with unsigned", "[count]") { + constexpr unsigned int uint_max = + static_cast(std::numeric_limits::max()); + std::vector v{}; + int steps = 0; + for (auto i : count(uint_max)) { + v.push_back(i); + if (steps == 2) { + break; + } + ++steps; + } + const std::vector vc{uint_max, uint_max + 1, uint_max + 2}; + + REQUIRE(v == vc); +} + +TEST_CASE("count: with negative step", "[count]") { + std::vector v{}; + int steps = 0; + for (auto i : count(0, -1)) { + v.push_back(i); + if (steps == 2) { + break; + } + ++steps; + } + const std::vector vc{0, -1, -2}; + + REQUIRE(v == vc); +} + +TEST_CASE("count: with double", "[count]") { + std::vector v{}; + int steps = 0; + for (auto i : count(1.0, 0.5)) { + v.push_back(i); + if (steps == 3) { + break; + } + ++steps; + } + const std::vector vc{1.0, 1.5, 2.0, 2.5}; + + REQUIRE(v == vc); +} + +TEST_CASE("count: with negative double step", "[count]") { + std::vector v{}; + int steps = 0; + for (auto i : count(1.0, -0.5)) { + v.push_back(i); + if (steps == 3) { + break; + } + ++steps; + } + const std::vector vc{1.0, 0.5, 0.0, -0.5}; + + REQUIRE(v == vc); +} + TEST_CASE("count: can bo constexpr", "[count]") { constexpr auto c = count(); constexpr auto c2 = count(5); diff --git a/test/test_cycle.cpp b/test/test_cycle.cpp index 8d4ab37c..0974a87c 100644 --- a/test/test_cycle.cpp +++ b/test/test_cycle.cpp @@ -1,10 +1,10 @@ -#include +#include #include "helpers.hpp" -#include -#include #include +#include +#include #include "catch.hpp" @@ -17,7 +17,27 @@ TEST_CASE("cycle: iterate twice", "[cycle]") { for (auto i : cycle(ns)) { v.push_back(i); ++count; - if (count == ns.size() * 2) break; + if (count == ns.size() * 2) { + break; + } + } + + auto vc = ns; + vc.insert(std::end(vc), std::begin(ns), std::end(ns)); + REQUIRE(v == vc); +} + +TEST_CASE("cycle: const iteration, iterate twice", "[cycle][const]") { + std::vector ns{2, 4, 6}; + std::vector v{}; + std::size_t count = 0; + const auto c = cycle(ns); + for (auto i : c) { + v.push_back(i); + ++count; + if (count == ns.size() * 2) { + break; + } } auto vc = ns; @@ -25,6 +45,30 @@ TEST_CASE("cycle: iterate twice", "[cycle]") { REQUIRE(v == vc); } +TEST_CASE("cycle: const iterators can be compared to non-const iterators", + "[cycle][const]") { + auto c = cycle(std::vector{}); + const auto& cc = c; + (void)(std::begin(c) == std::end(cc)); +} + +TEST_CASE("cycle: Works with different begin and end types", "[cycle]") { + constexpr auto sz = 'd' - 'a'; + CharRange cr{'d'}; + const std::vector vc{'a', 'b', 'c', 'a', 'b', 'c'}; + std::vector v; + std::size_t count = 0; + for (auto i : cycle(cr)) { + v.push_back(i); + ++count; + if (count == sz * 2) { + break; + } + } + + REQUIRE(v == vc); +} + TEST_CASE("cycle: with pipe", "[cycle]") { std::vector ns{2, 4, 6}; std::vector v; @@ -32,7 +76,9 @@ TEST_CASE("cycle: with pipe", "[cycle]") { for (auto i : ns | cycle) { v.push_back(i); ++count; - if (count == ns.size() * 2) break; + if (count == ns.size() * 2) { + break; + } } auto vc = ns; diff --git a/test/test_dropwhile.cpp b/test/test_dropwhile.cpp index 599ab407..077bfd73 100644 --- a/test/test_dropwhile.cpp +++ b/test/test_dropwhile.cpp @@ -1,17 +1,60 @@ -#include - -#include "helpers.hpp" - -#include -#include +#include #include +#include +#include #include "catch.hpp" +#include "helpers.hpp" using iter::dropwhile; using Vec = const std::vector; +TEST_CASE("dropwhile: handles different callable types", "[dropwhile]") { + Vec ns = {1, 3, 4, 20, 2, 4, 6, 8}; + Vec vc = {20, 2, 4, 6, 8}; + std::vector v; + SECTION("with function pointer") { + auto d = dropwhile(less_than_five, ns); + v = Vec(std::begin(d), std::end(d)); + } + + SECTION("with callable object") { + auto d = dropwhile(LessThanValue{5}, ns); + v = Vec(std::begin(d), std::end(d)); + } + + SECTION("with lvalue callable object") { + auto lt = LessThanValue{5}; + SECTION("normal call") { + auto d = dropwhile(lt, ns); + v = Vec(std::begin(d), std::end(d)); + } + SECTION("pipe") { + auto d = ns | dropwhile(lt); + v = Vec(std::begin(d), std::end(d)); + } + } + + SECTION("with move-only callable object") { + SECTION("normal call") { + auto d = dropwhile(MoveOnlyLessThanValue{5}, ns); + v = Vec(std::begin(d), std::end(d)); + } + SECTION("pipe") { + auto d = ns | dropwhile(MoveOnlyLessThanValue{5}); + v = Vec(std::begin(d), std::end(d)); + } + } + + SECTION("with lambda") { + auto ltf = [](int i) { return i < 5; }; + auto d = dropwhile(ltf, ns); + v = Vec(std::begin(d), std::end(d)); + } + REQUIRE(v == vc); +} + TEST_CASE("dropwhile: skips initial elements", "[dropwhile]") { Vec ns{1, 2, 3, 4, 5, 6, 7, 8}; std::vector v; @@ -27,6 +70,58 @@ TEST_CASE("dropwhile: skips initial elements", "[dropwhile]") { REQUIRE(v == vc); } +TEST_CASE("dropwhile: handles pointer to member", "[dropwhile]") { + using itertest::Point; + const std::vector ps = { + {5, 0}, {3, 5}, {2, 1}, {0, 1}, {2, 2}, {6, 0}}; + std::vector v; + SECTION("with pointer to data member") { + auto dw = dropwhile(&Point::x, ps); + v.assign(std::begin(dw), std::end(dw)); + } + + SECTION("with pointer to member function") { + auto dw = dropwhile(&Point::get_x, ps); + v.assign(std::begin(dw), std::end(dw)); + } + + const std::vector vc = {{0, 1}, {2, 2}, {6, 0}}; + REQUIRE(v == vc); +} + +TEST_CASE("dropwhile: drop zeros from front", "[dropwhile]") { + using itertest::Integer; + const std::vector nums = {0, 0, 3, 4, 0, 5, 0}; + auto dw = dropwhile(&Integer::is_zero, nums); + const std::vector v(std::begin(dw), std::end(dw)); + const std::vector vc = {3, 4, 0, 5, 0}; + REQUIRE(v == vc); +} + +TEST_CASE("dropwhile: const iteration", "[dropwhile][const]") { + Vec ns{1, 2, 3, 4, 5, 6, 7, 8}; + const auto d = dropwhile(LessThanValue{5}, ns); + Vec v(std::begin(d), std::end(d)); + Vec vc = {5, 6, 7, 8}; + REQUIRE(v == vc); +} + +TEST_CASE("dropwhile: const iterators can be compared to non-const iterators", + "[dropwhile][const]") { + auto d = dropwhile(LessThanValue{5}, Vec{}); + const auto& cd = d; + (void)(std::begin(d) == std::end(cd)); +} + +TEST_CASE( + "dropwhile: Works with different begin and end types", "[dropwhile]") { + CharRange cr{'f'}; + auto d = dropwhile([](char c) { return c < 'c'; }, cr); + Vec v(d.begin(), d.end()); + Vec vc{'c', 'd', 'e'}; + REQUIRE(v == vc); +} + TEST_CASE("dropwhile: doesn't skip anything if it shouldn't", "[dropwhile]") { Vec ns{3, 4, 5, 6}; auto d = dropwhile([](int i) { return i < 3; }, ns); @@ -39,11 +134,16 @@ TEST_CASE("dropwhile: skips all elements when all are true under predicate", "[dropwhile]") { Vec ns{3, 4, 5, 6}; auto d = dropwhile([](int i) { return i != 0; }, ns); - REQUIRE(std::begin(d) == std::end(d)); + SECTION("normal compare") { + REQUIRE(std::begin(d) == std::end(d)); + } + SECTION("reversed compare") { + REQUIRE(std::end(d) == std::begin(d)); + } } TEST_CASE("dropwhile: identity", "[dropwhile]") { - Vec ns {1, 2, 0, 3, 1, 0}; + Vec ns{1, 2, 0, 3, 1, 0}; auto d = dropwhile(ns); Vec v(std::begin(d), std::end(d)); Vec vc = {0, 3, 1, 0}; @@ -53,7 +153,12 @@ TEST_CASE("dropwhile: identity", "[dropwhile]") { TEST_CASE("dropwhile: empty case is empty", "[dropwhile]") { Vec ns{}; auto d = dropwhile([](int i) { return i != 0; }, ns); - REQUIRE(std::begin(d) == std::end(d)); + SECTION("normal compare") { + REQUIRE(std::begin(d) == std::end(d)); + } + SECTION("reversed compare") { + REQUIRE(std::end(d) == std::begin(d)); + } } TEST_CASE("dropwhile: only drops from beginning", "[dropwhile]") { @@ -71,12 +176,6 @@ TEST_CASE("dropwhile: operator->", "[dropwhile]") { REQUIRE(it->size() == 6); } -namespace { - int less_than_five(int i) { - return i < 5; - } -} - TEST_CASE("dropwhile: works with function pointer", "[dropwhile]") { Vec ns{1, 2, 3, 4, 5, 6, 7, 8}; auto d = dropwhile(less_than_five, ns); diff --git a/test/test_enumerate.cpp b/test/test_enumerate.cpp index be5209ff..777146e6 100644 --- a/test/test_enumerate.cpp +++ b/test/test_enumerate.cpp @@ -1,12 +1,12 @@ -#include +#include #include "helpers.hpp" -#include -#include #include -#include #include +#include +#include +#include namespace Catch { template @@ -34,6 +34,50 @@ TEST_CASE("Basic Functioning enumerate", "[enumerate]") { REQUIRE(v == vc); } +TEST_CASE("const enumerate", "[enumerate][const]") { + Vec v; + SECTION("lvalue") { + std::string str = "abc"; + const auto e = enumerate(str); + v.assign(std::begin(e), std::end(e)); + } + SECTION("rvalue") { + const auto e = enumerate(std::string("abc")); + v.assign(std::begin(e), std::end(e)); + } + SECTION("const lvalue") { + const std::string str = "abc"; + const auto e = enumerate(str); + v.assign(std::begin(e), std::end(e)); + } + + Vec vc{{0, 'a'}, {1, 'b'}, {2, 'c'}}; + + REQUIRE(v == vc); +} + +TEST_CASE("enumerate: can modify underlying sequence", "[enumerate]") { + std::string s = "abc"; + for (auto&& [i, c] : enumerate(s)) { + c = '-'; + } + REQUIRE(s == "---"); +} + +TEST_CASE("enumerate: const iterators can be compared", "[enumerate][const]") { + auto e = enumerate(std::string("hello")); + const auto& ce = e; + (void)(std::begin(e) == std::end(ce)); +} + +TEST_CASE("enumerate: has .index, .element, .first, and .second") { + std::string s = "abc"; + auto e = enumerate(s); + auto it = std::begin(e); + REQUIRE(it->index == it->first); + REQUIRE(&it->element == &it->second); +} + TEST_CASE("Empty enumerate", "[enumerate]") { std::string emp{}; auto e = enumerate(emp); @@ -48,6 +92,23 @@ TEST_CASE("Postfix ++ enumerate", "[enumerate]") { REQUIRE((*it).first == 1); } +TEST_CASE("enumerate: structured bindings", "[enumerate]") { + { + std::string s{"amz"}; + auto e = enumerate(s); + auto it = std::begin(e); + REQUIRE(std::tuple_size>{} == 2); + REQUIRE(std::get<0>(*it) == it->first); + } + + Vec v; + for (auto && [ i, c ] : enumerate(std::string{"xyz"})) { + v.emplace_back(i, c); + } + const Vec vc{{0, 'x'}, {1, 'y'}, {2, 'z'}}; + REQUIRE(v == vc); +} + TEST_CASE("enumerate: with starting value", "[enumerate]") { std::string str = "hey"; auto e = enumerate(str, 5u); @@ -109,6 +170,40 @@ TEST_CASE("enumerate: operator->", "[enumerate]") { REQUIRE(it->second == 50); } +TEST_CASE("enumerate: index and element", "[enumerate]") { + std::string s{"ace"}; + auto e = enumerate(s); + auto it = std::begin(e); + REQUIRE((*it).index == 0); + REQUIRE((*it).element == 'a'); + + Vec v; + for (auto&& p : enumerate(s)) { + v.emplace_back(p.index, p.element); + } + Vec vc{{0, 'a'}, {1, 'c'}, {2, 'e'}}; + REQUIRE(v == vc); +} + +TEST_CASE("enumerate: index and element through arrow", "[enumerate]") { + std::string s{"ace"}; + auto e = enumerate(s); + SECTION("One inspection") { + auto it = std::begin(e); + REQUIRE(it->index == 0); + REQUIRE(it->element == 'a'); + } + + SECTION("full loop") { + Vec v; + for (auto it = std::begin(e), end_it = std::end(e); it != end_it; ++it) { + v.emplace_back(it->index, it->element); + } + Vec vc{{0, 'a'}, {1, 'c'}, {2, 'e'}}; + REQUIRE(v == vc); + } +} + TEST_CASE("Works with const iterable", "[enumerate]") { const std::string s{"ace"}; auto e = enumerate(s); @@ -148,6 +243,15 @@ TEST_CASE("enumerate: works index and pipe", "[enumerate]") { REQUIRE(v == vc); } +TEST_CASE( + "enumerate: Works with different begin and end types", "[enumerate]") { + CharRange cr{'d'}; + auto e = enumerate(cr); + Vec v(e.begin(), e.end()); + Vec vc{{0, 'a'}, {1, 'b'}, {2, 'c'}}; + REQUIRE(v == vc); +} + template using ImpT = decltype(enumerate(std::declval())); TEST_CASE("enumerate: has correct ctor and assign ops", "[enumerate]") { diff --git a/test/test_filter.cpp b/test/test_filter.cpp index 63b8e068..445476c8 100644 --- a/test/test_filter.cpp +++ b/test/test_filter.cpp @@ -1,56 +1,91 @@ -#include - -#include "helpers.hpp" - -#include -#include +#include #include +#include +#include #include "catch.hpp" +#include "helpers.hpp" using iter::filter; using Vec = const std::vector; -namespace { - bool less_than_five(int i) { - return i < 5; - } - - class LessThanValue { - private: - int compare_val; - - public: - LessThanValue(int v) : compare_val(v) {} - - bool operator()(int i) { - return i < this->compare_val; - } - }; -} - -TEST_CASE("filter: handles different functor types", "[filter]") { +TEST_CASE("filter: handles different callable types", "[filter]") { Vec ns = {1, 2, 5, 6, 3, 1, 7, -1, 5}; Vec vc = {1, 2, 3, 1, -1}; + std::vector v; SECTION("with function pointer") { auto f = filter(less_than_five, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); } SECTION("with callable object") { auto f = filter(LessThanValue{5}, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); + } + + SECTION("with lvalue callable object") { + auto lt = LessThanValue{5}; + SECTION("normal call") { + auto f = filter(lt, ns); + v = Vec(std::begin(f), std::end(f)); + } + SECTION("pipe") { + auto f = ns | filter(lt); + v = Vec(std::begin(f), std::end(f)); + } + } + + SECTION("with move-only callable object") { + SECTION("normal call") { + auto f = filter(MoveOnlyLessThanValue{5}, ns); + v = Vec(std::begin(f), std::end(f)); + } + SECTION("pipe") { + auto f = ns | filter(MoveOnlyLessThanValue{5}); + v = Vec(std::begin(f), std::end(f)); + } } SECTION("with lambda") { auto ltf = [](int i) { return i < 5; }; auto f = filter(ltf, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); + } + REQUIRE(v == vc); +} + +TEST_CASE("filter: handles pointer to member", "[filter]") { + using itertest::Point; + const std::vector ps = {{0, 3}, {4, 0}, {0, 1}, {-1, -1}}; + std::vector v; + SECTION("with pointer to data member") { + auto f = filter(&Point::x, ps); + v.assign(std::begin(f), std::end(f)); } + + SECTION("with pointer to member function") { + auto f = filter(&Point::get_x, ps); + v.assign(std::begin(f), std::end(f)); + } + + const std::vector vc = {{4, 0}, {-1, -1}}; + REQUIRE(v == vc); +} + +TEST_CASE("filter: const iteration", "[filter][const]") { + Vec ns = {1, 2, 5, 6, 3, 1, 7, -1, 5}; + const auto f = filter(LessThanValue{5}, ns); + Vec v(std::begin(f), std::end(f)); + Vec vc = {1, 2, 3, 1, -1}; + REQUIRE(v == vc); +} + +TEST_CASE("filter: const iterator can be compared to non-const iterator", + "[filter][const]") { + auto f = filter(LessThanValue{5}, Vec{}); + const auto& cf = f; + (void)(std::begin(f) == std::end(cf)); } TEST_CASE("filter: iterator with lambda can be assigned", "[filter]") { @@ -116,7 +151,12 @@ TEST_CASE("filter: all elements fail predicate", "[filter]") { Vec ns{10, 20, 30, 40, 50}; auto f = filter(less_than_five, ns); - REQUIRE(std::begin(f) == std::end(f)); + SECTION("normal compare") { + REQUIRE(std::begin(f) == std::end(f)); + } + SECTION("reversed compare") { + REQUIRE(std::end(f) == std::begin(f)); + } } TEST_CASE("filter: doesn't move or copy elements of iterable", "[filter]") { @@ -145,6 +185,14 @@ TEST_CASE("filter: using identity and pipe", "[filter]") { REQUIRE(v == vc); } +TEST_CASE("filter: Works with different begin and end types", "[filter]") { + CharRange cr{'d'}; + auto f = filter([](char c) { return c != 'b'; }, cr); + Vec v(f.begin(), f.end()); + Vec vc{'a', 'c'}; + REQUIRE(v == vc); +} + TEST_CASE("filter: iterator meets requirements", "[filter]") { std::string s{}; auto c = filter([] { return true; }, s); diff --git a/test/test_filterfalse.cpp b/test/test_filterfalse.cpp index b9de4bd2..ad79646a 100644 --- a/test/test_filterfalse.cpp +++ b/test/test_filterfalse.cpp @@ -1,63 +1,100 @@ -#include - -#include "helpers.hpp" - -#include -#include +#include #include +#include +#include #include "catch.hpp" +#include "helpers.hpp" using iter::filterfalse; using Vec = const std::vector; -namespace { - bool less_than_five(int i) { - return i < 5; - } - - class LessThanValue { - private: - int compare_val; - - public: - LessThanValue(int v) : compare_val(v) {} - - bool operator()(int i) { - return i < this->compare_val; - } - }; -} - -TEST_CASE("filterfalse: handles different functor types", "[filterfalse]") { +TEST_CASE("filterfalse: handles different callable types", "[filterfalse]") { Vec ns = {1, 2, 5, 6, 3, 1, 7, -1, 5}; Vec vc = {5, 6, 7, 5}; + std::vector v; SECTION("with function pointer") { auto f = filterfalse(less_than_five, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); } SECTION("with callable object") { - std::vector v; - SECTION("Normal call") { - auto f = filterfalse(LessThanValue{5}, ns); - v.assign(std::begin(f), std::end(f)); + auto f = filterfalse(LessThanValue{5}, ns); + v = Vec(std::begin(f), std::end(f)); + } + + SECTION("with lvalue callable object") { + auto lt = LessThanValue{5}; + SECTION("normal call") { + auto f = filterfalse(lt, ns); + v = Vec(std::begin(f), std::end(f)); + } + SECTION("pipe") { + auto f = ns | filterfalse(lt); + v = Vec(std::begin(f), std::end(f)); + } + } + + SECTION("with move-only callable object") { + SECTION("normal call") { + auto f = filterfalse(MoveOnlyLessThanValue{5}, ns); + v = Vec(std::begin(f), std::end(f)); } - SECTION("Pipe") { - auto f = ns | filterfalse(LessThanValue{5}); - v.assign(std::begin(f), std::end(f)); + SECTION("pipe") { + auto f = ns | filterfalse(MoveOnlyLessThanValue{5}); + v = Vec(std::begin(f), std::end(f)); } - REQUIRE(v == vc); } SECTION("with lambda") { auto ltf = [](int i) { return i < 5; }; auto f = filterfalse(ltf, ns); - Vec v(std::begin(f), std::end(f)); - REQUIRE(v == vc); + v = Vec(std::begin(f), std::end(f)); + } + REQUIRE(v == vc); +} + +TEST_CASE("filterfalse: handles pointer to member", "[filterfalse]") { + using itertest::Point; + const std::vector ps = {{0, 3}, {4, 0}, {0, 1}, {-1, -1}}; + std::vector v; + SECTION("with pointer to data member") { + auto f = filterfalse(&Point::x, ps); + v.assign(std::begin(f), std::end(f)); } + + SECTION("with pointer to member function") { + auto f = filterfalse(&Point::get_x, ps); + v.assign(std::begin(f), std::end(f)); + } + + const std::vector vc = {{0, 3}, {0, 1}}; + REQUIRE(v == vc); +} + +TEST_CASE("filterfalse: const iteration", "[filterfalse][const]") { + Vec ns = {1, 2, 5, 6, 3, 1, 7, -1, 5}; + const auto f = filterfalse(LessThanValue{5}, ns); + Vec v(std::begin(f), std::end(f)); + Vec vc = {5, 6, 7, 5}; + REQUIRE(v == vc); +} + +TEST_CASE("filterfalse: const iterator and non-const iterator can be compared", + "[filterfalse][const]") { + auto f = filterfalse(LessThanValue{5}, Vec{}); + const auto& cf = f; + (void)(std::begin(f) == std::end(cf)); +} + +TEST_CASE( + "filterfalse: Works with different begin and end types", "[filterfalse]") { + CharRange cr{'d'}; + auto f = filterfalse([](char c) { return c == 'b'; }, cr); + Vec v(f.begin(), f.end()); + Vec vc{'a', 'c'}; + REQUIRE(v == vc); } TEST_CASE("filterfalse: using identity", "[filterfalse]") { diff --git a/test/test_groupby.cpp b/test/test_groupby.cpp index a102692f..02798f39 100644 --- a/test/test_groupby.cpp +++ b/test/test_groupby.cpp @@ -1,10 +1,11 @@ -#include +#include #include "helpers.hpp" -#include -#include #include +#include +#include +#include #include "catch.hpp" @@ -21,22 +22,75 @@ namespace { } }; + struct MoveOnlySizer { + // here to trigger asan if a dangling reference gets used + std::unique_ptr counter_ = std::make_unique(); + + MoveOnlySizer(const MoveOnlySizer&) = delete; + MoveOnlySizer& operator=(const MoveOnlySizer&) = delete; + + MoveOnlySizer(MoveOnlySizer&&) = default; + MoveOnlySizer& operator=(MoveOnlySizer&&) = default; + + int operator()(const std::string& s) { + ++*counter_; + return s.size(); + } + }; + const std::vector vec = { "hi", "ab", "ho", "abc", "def", "abcde", "efghi"}; + + struct Person { + std::string name; + int id; + bool operator==(const Person& other) const { + return id == other.id; + } + }; + + std::string& get_name(Person& p) { + return p.name; + } + + template + std::vector extract_person_group(G g) { + return {std::begin(g), std::end(g)}; + } +} + +TEST_CASE("groupby: handle key function that returns reference", "[groupby]") { + std::vector people = {{"first", 1}, {"first", 2}, {"first", 3}}; + std::vector keys; + std::vector> groups; + + for (auto&& gb : groupby(people, get_name)) { + groups.push_back(extract_person_group(std::move(gb.second))); + keys.push_back(gb.first); + } + + const std::vector kc = {"first"}; + const std::vector> gc = { + {{"first", 1}, {"first", 2}, {"first", 3}}}; + + REQUIRE(people[0].name == "first"); + REQUIRE(gc[0][0].name == "first"); + REQUIRE(keys == kc); + REQUIRE(groups == gc); } -TEST_CASE("groupby: works with lambda, callable, and function pointer") { +TEST_CASE("groupby: handles different callable types", "[groupby]") { std::vector keys; std::vector> groups; - SECTION("Function pointer") { - SECTION("Normal call") { + SECTION("with function pointer") { + SECTION("normal call") { for (auto&& gb : groupby(vec, length)) { keys.push_back(gb.first); groups.emplace_back(std::begin(gb.second), std::end(gb.second)); } } - SECTION("Pipe") { + SECTION("pipe") { for (auto&& gb : vec | groupby(length)) { keys.push_back(gb.first); groups.emplace_back(std::begin(gb.second), std::end(gb.second)); @@ -44,16 +98,155 @@ TEST_CASE("groupby: works with lambda, callable, and function pointer") { } } + SECTION("with callable object") { + SECTION("normal call") { + for (auto&& gb : groupby(vec, Sizer{})) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + SECTION("pipe") { + for (auto&& gb : vec | groupby(Sizer{})) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + } + + SECTION("with lvalue callable object") { + auto sizer = Sizer{}; + SECTION("normal call") { + for (auto&& gb : groupby(vec, sizer)) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + SECTION("pipe") { + for (auto&& gb : vec | groupby(sizer)) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + } + + SECTION("with move-only callable object") { + SECTION("normal call") { + for (auto&& gb : groupby(vec, MoveOnlySizer{})) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + SECTION("pipe") { + for (auto&& gb : vec | groupby(MoveOnlySizer{})) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + } + + SECTION("with lambda") { + for (auto&& gb : + groupby(vec, [](const std::string& s) { return s.size(); })) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + + const std::vector kc = {2, 3, 5}; + REQUIRE(keys == kc); + + const std::vector> gc = { + {"hi", "ab", "ho"}, + {"abc", "def"}, + {"abcde", "efghi"}, + }; + + REQUIRE(groups == gc); +} + +TEST_CASE("groupby: handles pointer to member function", "[groupby]") { + std::vector nums = { + 10, 20, 30, -5, 40, -6, -7, 50, 60, -8, -9, -10, -11, 70}; + + std::vector> groups; + std::vector keys; + for (auto&& gb : groupby(nums, &itertest::Integer::is_positive)) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + const std::vector kc = {true, false, true, false, true, false, true}; + const std::vector> gc = { + {10, 20, 30}, {-5}, {40}, {-6, -7}, {50, 60}, {-8, -9, -10, -11}, {70}}; + REQUIRE(keys == kc); + REQUIRE(groups == gc); +} + +TEST_CASE("groupby: handles pointer to data member", "[groupby]") { + using itertest::Point; + const std::vector ps = { + {0, 2}, {0, 4}, {1, 3}, {1, 7}, {1, 10}, {1, 12}, {3, 5}}; + + std::vector> groups; + std::vector keys; + + SECTION("with pointer to data member") { + auto g = groupby(ps, &Point::x); + for (auto&& gb : g) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + + SECTION("with pointer member function") { + auto g = groupby(ps, &Point::get_x); + for (auto&& gb : g) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + + const std::vector kc = {0, 1, 3}; + REQUIRE(keys == kc); + + const std::vector> gc = { + {{0, 2}, {0, 4}}, {{1, 3}, {1, 7}, {1, 10}, {1, 12}}, {{3, 5}}}; + + REQUIRE(groups == gc); +} + +TEST_CASE("groupby: const iteration", "[groupby][const]") { + std::vector keys; + std::vector> groups; + + SECTION("Function pointer") { + SECTION("lvalue") { + std::vector local_vec(vec); + const auto g = groupby(local_vec, length); + for (auto&& gb : g) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + SECTION("rvalue") { + const auto g = groupby(std::vector(vec), length); + for (auto&& gb : g) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + } + } + SECTION("Callable object") { - for (auto&& gb : groupby(vec, Sizer{})) { + const auto g = groupby(vec, Sizer{}); + for (auto&& gb : g) { keys.push_back(gb.first); groups.emplace_back(std::begin(gb.second), std::end(gb.second)); } } SECTION("lambda function") { - for (auto&& gb : - groupby(vec, [](const std::string& s) { return s.size(); })) { + const auto g = groupby(vec, [](const std::string& s) { return s.size(); }); + for (auto&& gb : g) { keys.push_back(gb.first); groups.emplace_back(std::begin(gb.second), std::end(gb.second)); } @@ -63,12 +256,45 @@ TEST_CASE("groupby: works with lambda, callable, and function pointer") { REQUIRE(keys == kc); const std::vector> gc = { - {"hi", "ab", "ho"}, {"abc", "def"}, {"abcde", "efghi"}, + {"hi", "ab", "ho"}, + {"abc", "def"}, + {"abcde", "efghi"}, }; REQUIRE(groups == gc); } +TEST_CASE("groupby: iterators compare equal to non-const iterators", + "[groupby][const]") { + auto gb = groupby(std::vector{"hi"}, length); + const auto& cgb = gb; + + auto gb_it = std::begin(gb); + (void)(gb_it == std::end(cgb)); + +// TODO figure out how to make GroupIterator and +// GroupIterator> comparable +#if 0 + auto group = std::begin(gb_it->second); + const auto& cgroup = group; + (void)(std::begin(group) == std::begin(cgroup)); +#endif +} + +TEST_CASE("groupby: Works with different begin and end types", "[groupby]") { + CharRange cr{'f'}; + std::vector keys; + std::vector> groups; + for (auto&& gb : groupby(cr, [](char c) { return c == 'c'; })) { + keys.push_back(gb.first); + groups.emplace_back(std::begin(gb.second), std::end(gb.second)); + } + const std::vector kc = {false, true, false}; + const std::vector> gc = {{'a', 'b'}, {'c'}, {'d', 'e'}}; + REQUIRE(keys == kc); + REQUIRE(groups == gc); +} + TEST_CASE("groupby: groups can be skipped completely", "[groupby]") { std::vector keys; std::vector> groups; @@ -84,7 +310,8 @@ TEST_CASE("groupby: groups can be skipped completely", "[groupby]") { REQUIRE(keys == kc); const std::vector> gc = { - {"hi", "ab", "ho"}, {"abcde", "efghi"}, + {"hi", "ab", "ho"}, + {"abcde", "efghi"}, }; REQUIRE(groups == gc); @@ -107,7 +334,9 @@ TEST_CASE("groupby: groups can be skipped partially", "[groupby]") { REQUIRE(keys == kc); const std::vector> gc = { - {"hi", "ab", "ho"}, {"abc"}, {"abcde", "efghi"}, + {"hi", "ab", "ho"}, + {"abc"}, + {"abcde", "efghi"}, }; REQUIRE(groups == gc); @@ -134,7 +363,12 @@ TEST_CASE("groupby: single argument uses elements as keys", "[groupby]") { REQUIRE(keys == kc); std::vector> gc = { - {5, 5}, {6, 6}, {19, 19, 19, 19}, {69}, {0}, {10, 10}, + {5, 5}, + {6, 6}, + {19, 19, 19, 19}, + {69}, + {0}, + {10, 10}, }; REQUIRE(groups == gc); diff --git a/test/test_imap.cpp b/test/test_imap.cpp index 4912b11f..cfda992b 100644 --- a/test/test_imap.cpp +++ b/test/test_imap.cpp @@ -1,10 +1,11 @@ -#include +#include #include "helpers.hpp" -#include -#include +#include #include +#include +#include #include "catch.hpp" @@ -16,10 +17,29 @@ namespace { return i + 1; } - class PlusOner { + struct PlusOner { + int operator()(int i) const { + return i + 1; + } + }; + + class MoveOnlyAdder { + private: + // unique_ptr is better for triggering asan than an int if there's a + // dangling reference to the callable + std::unique_ptr add_amount_; + public: + MoveOnlyAdder(int v) : add_amount_{std::make_unique(v)} {} + + MoveOnlyAdder(const MoveOnlyAdder&) = delete; + MoveOnlyAdder& operator=(const MoveOnlyAdder&) = delete; + + MoveOnlyAdder(MoveOnlyAdder&&) = default; + MoveOnlyAdder& operator=(MoveOnlyAdder&&) = default; + int operator()(int i) { - return i + 1; + return i + *add_amount_; } }; @@ -32,34 +52,108 @@ namespace { } } -TEST_CASE("imap: works with lambda, callable, and function", "[imap]") { - Vec ns = {10, 20, 30}; +TEST_CASE("imap: handles different callable types", "[imap]") { + Vec ns = {10, 15, 300}; + Vec vc = {11, 16, 301}; std::vector v; - SECTION("with lambda") { - auto im = imap([](int i) { return i + 1; }, ns); - v.assign(std::begin(im), std::end(im)); + SECTION("with function pointer") { + auto m = imap(plusone, ns); + v = Vec(std::begin(m), std::end(m)); + } + + SECTION("with callable object") { + auto m = imap(PlusOner{}, ns); + v = Vec(std::begin(m), std::end(m)); } - SECTION("with callable") { - SECTION("Normal call") { - auto im = imap(PlusOner{}, ns); - v.assign(std::begin(im), std::end(im)); + SECTION("with lvalue callable object") { + auto lt = PlusOner{}; + SECTION("normal call") { + auto m = imap(lt, ns); + v = Vec(std::begin(m), std::end(m)); } - SECTION("Pipe") { - auto im = ns | imap(PlusOner{}); - v.assign(std::begin(im), std::end(im)); + SECTION("pipe") { + auto m = ns | imap(lt); + v = Vec(std::begin(m), std::end(m)); } } - SECTION("with function") { - auto im = imap(PlusOner{}, ns); + SECTION("with move-only callable object") { + SECTION("normal call") { + auto m = imap(MoveOnlyAdder{1}, ns); + v = Vec(std::begin(m), std::end(m)); + } + SECTION("pipe") { + auto m = ns | imap(MoveOnlyAdder{1}); + v = Vec(std::begin(m), std::end(m)); + } + } + + SECTION("with lambda") { + auto ltf = [](int i) { return i + 1; }; + auto m = imap(ltf, ns); + v = Vec(std::begin(m), std::end(m)); + } + REQUIRE(v == vc); +} + +TEST_CASE("imap: works with pointer to member", "[imap]") { + using itertest::Point; + std::vector ps = {{3, 6}, {20, 25}}; + std::vector v; + SECTION("with pointer to member function") { + auto im = imap(&Point::get_y, ps); + v.assign(std::begin(im), std::end(im)); + } + + SECTION("with pointer to data member") { + auto im = imap(&Point::y, ps); v.assign(std::begin(im), std::end(im)); } + Vec vc = {6, 25}; + REQUIRE(v == vc); +} + +TEST_CASE("imap: works with pointer to member function taking argument") { + using itertest::Point; + std::vector ps = {{10, 20}, {6, 8}, {3, 15}}; + std::vector strs = {"a", "point", "pos"}; + + auto im = imap(&Point::prefix, ps, strs); + + std::vector v(std::begin(im), std::end(im)); + const std::vector vc = { + "a(10, 20)", "point(6, 8)", "pos(3, 15)"}; + + REQUIRE(v == vc); +} + +// TODO enable once zip supports const +#if 0 +TEST_CASE("imap: supports const iteration", "[imap][const]") { + Vec ns = {10, 20, 30}; + const auto m = imap(PlusOner{}, ns); + Vec v(std::begin(m), std::end(m)); Vec vc = {11, 21, 31}; REQUIRE(v == vc); } +TEST_CASE("imap: const iterators can be compared to non-const iterators", "[imap][const]") { + auto m = imap(PlusOner{}, Vec{}); + const auto& cm = m; + (void)(std::begin(m) == std::end(cm)); +} +#endif + +TEST_CASE("imap: Works with different begin and end types", "[imap]") { + CharRange cr{'d'}; + auto m = imap([](char c) { return std::toupper(c); }, cr); + Vec v(m.begin(), m.end()); + Vec vc{'A', 'B', 'C'}; + REQUIRE(v == vc); +} + TEST_CASE("imap: works with multiple sequences", "[imap]") { Vec bases = {0, 1, 2, 3}; Vec exps = {1, 2, 3, 4}; diff --git a/test/test_iterator_wrapper.cpp b/test/test_iterator_wrapper.cpp new file mode 100644 index 00000000..e5b0238a --- /dev/null +++ b/test/test_iterator_wrapper.cpp @@ -0,0 +1,222 @@ +// NOTE this header tests implementation details + +#include "catch.hpp" +#include "cppitertools/internal/iterator_wrapper.hpp" + +// I'm using a std::vector of 1 int instead of just an int in order to give +// the iterator types non-trivial constructors, destructors, and assignment. + +// same begin() and end() types +struct SameTypes { + struct iterator { + iterator(int) : value_(1) {} + + bool operator!=(const iterator& other) const { + return value_ != other.value_; + } + iterator& operator++() { + ++value_.front(); + return *this; + } + const int& operator*() const { + return value_.front(); + } + std::vector value_; // non-trvial operations + }; + + iterator begin() const { + return {0}; + } + iterator end() const { + return {0}; + } +}; + +// different begin() and end() types +struct DifferentTypes { + struct iterator; + struct end_iterator; + struct iterator { + iterator() : value_{} { + REQUIRE(false); + } + iterator(int i) : value_(1, i) {} + + bool operator!=(const iterator& other) const { + return value() != other.value(); + } + bool operator!=(const end_iterator&) const { + return value() != 3; + } + iterator& operator++() { + ++value_.front(); + return *this; + } + const int& operator*() const { + return value(); + } + + const int& value() const { + return value_.front(); + } + std::vector value_; + }; + + struct end_iterator { + end_iterator() { + REQUIRE(false); + } + end_iterator(int) {} + + bool operator!=(const end_iterator&) const { + return false; + } + bool operator!=(const iterator& other) const { + return other.value() != 3; + } + end_iterator& operator++() { + return *this; + } + const int& operator*() const { + assert(false); + return value(); + } + + const int& value() const { + return value_.front(); + } + std::vector value_{}; + }; + + iterator begin() const { + return {0}; + } + end_iterator end() const { + return {0}; + } +}; + +// Explicit instatiations, which could cause failures if the implementation +// details of the implementation details change. +template class iter::impl:: + IteratorWrapperImpl, + iter::impl::iterator_end_type>; + +using iter::impl::IteratorWrapper; + +TEST_CASE("ensure test type iterators are totally comparable", "[test_util") { + { + SameTypes s{}; + auto it = s.begin(); + (void)(it != it); + } + + { + DifferentTypes d{}; + auto b = d.begin(); + auto e = d.end(); + (void)(b != b); + (void)(e != e); + (void)(b != e); + (void)(e != b); + } +} + +TEST_CASE( + "Operations on IteratorWrappers with SameTypes work", "[base_iterator]") { + SameTypes s; + IteratorWrapper it(s.begin()); + REQUIRE((std::is_same, + std::decay_t>{})); + REQUIRE(*it == 0); + ++it; + REQUIRE(*it == 1); +} + +TEST_CASE("Operations on IteratorWrappers with DifferentTypes work", + "[base_iterator]") { + DifferentTypes d; + using BI = IteratorWrapper; + BI it(d.begin()); + REQUIRE((!std::is_same, + std::decay_t>{})); + REQUIRE(*it == 0); + ++it; + REQUIRE(*it == 1); + + BI it2(d.begin()); + + REQUIRE(it != it2); + REQUIRE(it2 != it); + ++it2; + + REQUIRE_FALSE(it != it2); + REQUIRE_FALSE(it2 != it); + + BI bend(d.end()); + REQUIRE(it != bend); + REQUIRE(bend != it); + + ++it; + ++it; + REQUIRE_FALSE(it != bend); + REQUIRE_FALSE(bend != it); +} + +TEST_CASE( + "Can copy construct a IteratorWrapper with SameTypes", "[base_iterator]") { + SameTypes s; + using BI = IteratorWrapper; + BI it(s.begin()); + BI it2(it); + REQUIRE_FALSE(it != it2); + ++it2; + REQUIRE(it != it2); +} + +TEST_CASE( + "Can copy assign a IteratorWrapper with SameTypes", "[base_iterator]") { + SameTypes s; + using BI = IteratorWrapper; + BI it(s.begin()); + BI it2(s.begin()); + REQUIRE_FALSE(it != it2); + ++it2; + REQUIRE(it != it2); + it = it2; + REQUIRE_FALSE(it != it2); +} + +TEST_CASE("Can copy construct a IteratorWrapper with DifferentTypes", + "[base_iterator]") { + using BI = IteratorWrapper; + DifferentTypes d; + BI it(d.begin()); + BI it2(it); + REQUIRE_FALSE(it != it2); + ++it; + REQUIRE(it != it2); +} + +TEST_CASE("Can copy construct a IteratorWrapper with DifferenTypes", + "[base_iterator]") { + using BI = IteratorWrapper; + DifferentTypes d; + BI it(d.begin()); + BI it2(it); + it = it2; + // break assignment into a different test + REQUIRE_FALSE(it != it2); + BI it_end(d.end()); + REQUIRE(it != it_end); + SECTION("normal = end") { + it = it_end; + REQUIRE_FALSE(it != it_end); + } + SECTION("end = normal") { + it_end = BI{d.begin()}; + REQUIRE_FALSE(it != it_end); + } +} + +// TODO test move operations diff --git a/test/test_iteratoriterator.cpp b/test/test_iteratoriterator.cpp index c3abd95a..11c1b16c 100644 --- a/test/test_iteratoriterator.cpp +++ b/test/test_iteratoriterator.cpp @@ -1,7 +1,8 @@ -#include +#include -#include #include +#include +#include #include "catch.hpp" @@ -61,3 +62,108 @@ TEST_CASE("Iterate over a vector of string iterators", "[iteratoriterator]") { std::iterator_traits::reference>::value, "iterator is mis marked"); } + +TEST_CASE("IteratorIterator supports mutable RandomAccessIterator operators", + "[iteratoriterator]") { + using std::vector; + struct S { + int value; + }; + vector v = {{2}, {4}, {6}, {8}}; + + IterIterWrapper::iterator>> itr; + itr.get().push_back(std::begin(v) + 1); + itr.get().push_back(std::end(v) - 1); + itr.get().push_back(std::begin(v)); + + // RandomAccessIterator (and ForwardIterator): + auto a = itr.begin(); + auto r = a; + auto r2 = r; + ((r += 2) -= 2) += 2; + REQUIRE(&(++r2) == &r2); // Required by OutputIterator. + REQUIRE(&(*r2++) == &a[1]); + REQUIRE(r == r2); + auto test_const_or_not = [&itr](auto& a, auto& b) { + REQUIRE(!(b == a)); + REQUIRE(b == a + 2); + REQUIRE(b == 2 + a); + REQUIRE(b - 2 == a); + REQUIRE(&a[2] == &b[0]); + REQUIRE(b - a == 2); + REQUIRE(a < b); + REQUIRE(!(a < a)); + REQUIRE(b > a); + REQUIRE(!(a > a)); + REQUIRE(a <= b); + REQUIRE(!(b <= a)); + REQUIRE(a <= a); + REQUIRE(b >= a); + REQUIRE(!(a >= b)); + REQUIRE(a >= a); + + // InputIterator: + REQUIRE(b != a); + REQUIRE(!(a != a)); + REQUIRE(&(*a) != &(*b)); + REQUIRE(&(a->value) == &(*a).value); + + // Added methods, not from ...Iterator: + REQUIRE(a.get() == std::begin(itr.get())); + }; + test_const_or_not(a, r); + test_const_or_not(std::as_const(a), r); + test_const_or_not(a, std::as_const(r)); + test_const_or_not(std::as_const(a), std::as_const(r)); + + // BidirectionalIterator (and RandomAccessIterator): + REQUIRE((--r)-- == a + 1); + REQUIRE(r == a); + REQUIRE(&(*r2--) == &a[2]); + + // OutputIterator (and RandomAccessIterator): + *r++ = {10}; + REQUIRE(r == a + 1); + REQUIRE(v[1].value == 10); + *++r = {12}; + REQUIRE(r == a + 2); + REQUIRE(v[0].value == 12); + *r = {14}; + REQUIRE(r == a + 2); + REQUIRE(v[0].value == 14); + a[1] = {16}; + REQUIRE(a == itr.begin()); + REQUIRE(v[3].value == 16); +} + +TEST_CASE("IterIterWrapper supports several SequenceContainer methodes", + "[iteratoriterator]") { + using std::vector; + vector v = {2, 4, 6, 8}; + + IterIterWrapper::iterator>> itr; + itr.get().push_back(std::begin(v) + 1); + itr.get().push_back(std::end(v) - 1); + + auto test_const_or_not = [&v](auto& c) { + REQUIRE(c.at(0) == 4); + REQUIRE(c.at(1) == 8); + REQUIRE(c[0] == 4); + REQUIRE(c[1] == 8); + REQUIRE(!c.empty()); + REQUIRE(c.size() == 2); + REQUIRE(*c.begin() == 4); + REQUIRE(*(c.end() - 1) == 8); + REQUIRE(*c.cbegin() == 4); + REQUIRE(*(c.cend() - 1) == 8); + REQUIRE(*c.rbegin() == 8); + REQUIRE(*(c.rend() - 1) == 4); + REQUIRE(*c.crbegin() == 8); + REQUIRE(*(c.crend() - 1) == 4); + + // Added methods, not from SequenceContainer: + REQUIRE(c.get()[0] == std::begin(v) + 1); + }; + test_const_or_not(itr); + test_const_or_not(std::as_const(itr)); +} diff --git a/test/test_iterbase.cpp b/test/test_iterbase.cpp index 91671155..c904b841 100644 --- a/test/test_iterbase.cpp +++ b/test/test_iterbase.cpp @@ -2,13 +2,13 @@ // on any of this. Users of the library must consider all of this undocumented // -#include -#include -#include +#include +#include #include -#include #include -#include +#include +#include +#include #include "catch.hpp" #include "helpers.hpp" @@ -49,7 +49,7 @@ TEST_CASE("advance, next, size", "[iterbase]") { auto itr = std::begin(v); REQUIRE(it::apply_arrow(itr) == &v[0]); - it::dumb_advance(itr, 3); + it::dumb_advance_unsafe(itr, 3); REQUIRE(itr == (std::begin(v) + 3)); REQUIRE(it::dumb_next(std::begin(v), 3) == std::begin(v) + 3); REQUIRE(it::dumb_size(v) == v.size()); @@ -63,6 +63,7 @@ TEST_CASE("are_same", "[iterbase]") { } TEST_CASE("DerefHolder lvalue reference", "[iterbase]") { + REQUIRE_FALSE(it::DerefHolder::stores_value); it::DerefHolder dh; int a = 2; int b = 5; @@ -78,6 +79,7 @@ TEST_CASE("DerefHolder lvalue reference", "[iterbase]") { } TEST_CASE("DerefHolder non-reference", "[iterbase]") { + REQUIRE(it::DerefHolder::stores_value); it::DerefHolder dh; int a = 2; int b = 5; @@ -85,7 +87,45 @@ TEST_CASE("DerefHolder non-reference", "[iterbase]") { dh.reset(std::move(a)); REQUIRE(dh.get() == 2); REQUIRE(&dh.get() != &a); + REQUIRE(dh.get_ptr() != &a); dh.reset(std::move(b)); REQUIRE(dh.get() == 5); } + +TEST_CASE("get_begin returns correct type", "[iterbase]") { + std::vector v; + REQUIRE((std::is_same{})); +} + +namespace NS1 { + + struct Dummy { + auto begin() { + return 0; + } + auto end() { + return 0; + } + }; + + template + auto begin(T& t) { + return t.begin(); + } + + template + auto end(T& t) { + return t.end(); + } + +} // namespace NS1 + +TEST_CASE("Detects is_iterable with ADL conflicts", "[iterbase]") { + int a[1]{}; + const int b[1]{}; + REQUIRE(iter::impl::is_iterable); + REQUIRE(iter::impl::is_iterable>); + REQUIRE(iter::impl::is_iterable); + REQUIRE(iter::impl::is_iterable); +} diff --git a/test/test_mixed.cpp b/test/test_mixed.cpp index cc607e64..b190adf6 100644 --- a/test/test_mixed.cpp +++ b/test/test_mixed.cpp @@ -1,12 +1,11 @@ // mixing different itertools, there is nothing called iter::mixed() -#include "itertools.hpp" - -#include "catch.hpp" - #include #include +#include "catch.hpp" +#include "cppitertools/itertools.hpp" + class MyUnMovable { int val; @@ -21,8 +20,8 @@ class MyUnMovable { constexpr int get_val() const { return val; } - void set_val(int val) { - this->val = val; + void set_val(int new_val) { + this->val = new_val; } bool operator==(const MyUnMovable& other) const { @@ -75,8 +74,8 @@ TEST_CASE("filtering doesn't dereference multiple times", "[imap][filter]") { } TEST_CASE("dropwhile doesn't dereference multiple times", "[imap][dropwhile]") { - using iter::imap; using iter::dropwhile; + using iter::imap; std::array arr = {{{41}, {42}, {43}}}; @@ -123,22 +122,156 @@ TEST_CASE("takewhile doesn't dereference multiple times", "[imap][takewhile]") { } TEST_CASE("sorted(chain.from_iterable)", "[sorted][chain.from_iterable]") { - std::vector> v = {{2,4,6}}; + std::vector> v = {{2, 4, 6}}; auto s = iter::sorted(iter::chain.from_iterable(v)); } TEST_CASE("filter into enumerate with pipe", "[filter][enumerate]") { - using iter::imap; - using iter::filter; using iter::enumerate; + using iter::filter; + using iter::imap; std::array arr = {{{41}, {42}, {43}, {44}}}; - auto seq = arr - | filter([](const MyUnMovable& mv) { return mv.get_val() % 2 == 0; }) - | enumerate - | imap([] (const auto& imv) { return std::make_pair(imv.first, imv.second.get_val());}); + auto seq = + arr | filter([](const MyUnMovable& mv) { return mv.get_val() % 2 == 0; }) + | enumerate | imap([](const auto& imv) { + return std::make_pair(imv.first, imv.second.get_val()); + }); using Vec = std::vector>; const Vec v(std::begin(seq), std::end(seq)); const Vec vc = {{0, 42}, {1, 44}}; REQUIRE(v == vc); } + +TEST_CASE("enumerate(filter(chunked()))", "[filter][enumerate][chunked]") { + using iter::chunked; + using iter::enumerate; + using iter::filter; + std::vector v(500); + auto chunks = chunked(v, 100); + auto filtered = filter([](auto&) { return true; }, chunks); + for (auto&& [i, chunk] : enumerate(filtered)) { + (void)i; + REQUIRE(chunk.size() == 100); + } +} + +TEST_CASE("zip(filter(chunked()))", "[filter][chunked][zip]") { + using iter::chunked; + using iter::filter; + using iter::zip; + std::vector v(500); + auto chunks = chunked(v, 100); + auto filtered = filter([](auto&) { return true; }, chunks); + for (auto&& [chunk] : zip(filtered)) { + REQUIRE(chunk.size() == 100); + } +} + +TEST_CASE("zip(filter(sliding_window()))", "[filter][sliding_window][zip]") { + using iter::filter; + using iter::sliding_window; + using iter::zip; + std::vector v(15); + auto windows = sliding_window(v, 10); + auto filtered = filter([](auto&) { return true; }, windows); + for (auto&& [window] : zip(filtered)) { + REQUIRE(window.size() == 10); + } +} + +TEST_CASE("imap(filter(groupby()))", "[filter][groupby][imap])") { + using iter::filter; + using iter::groupby; + using iter::imap; + + std::vector v{true, true, true, false, false, true, true}; + auto a = groupby(v, [](bool b) { return b; }); + auto b = filter([](auto& g) { return g.first; }, a); + auto c = imap( + [](auto& g) { return std::distance(g.second.begin(), g.second.end()); }, + b); + for (auto x : c) { + (void)x; + } +} + +TEST_CASE("imap(dropwhile(groupby()))", "[dropwhile][groupby][imap])") { + using iter::dropwhile; + using iter::groupby; + using iter::imap; + + std::vector v{false, false, true, true, false, true, true}; + auto a = groupby(v, [](bool b) { return b; }); + auto b = dropwhile([](auto& g) { return g.first; }, a); + auto c = imap( + [](auto& g) { return std::distance(g.second.begin(), g.second.end()); }, + b); + for (auto x : c) { + (void)x; + } +} + +TEST_CASE("imap(takewhile(groupby()))", "[takewhile][groupby][imap])") { + using iter::groupby; + using iter::imap; + using iter::takewhile; + + std::vector v{true, true, true, false, false}; + auto a = groupby(v, [](bool b) { return b; }); + auto b = takewhile([](auto& g) { return g.first; }, a); + auto c = imap( + [](auto& g) { return std::distance(g.second.begin(), g.second.end()); }, + b); + for (auto x : c) { + (void)x; + } +} + +TEST_CASE("chain.from_iterable: accept imap result that yields rvalues", + "[chain.from_iterable][imap]") { + using iter::chain; + using iter::imap; + const std::vector> ns = {{'a'}, {'q'}, {'x', 'z'}}; + auto ch = iter::chain.from_iterable(iter::imap([](auto v) { return v; }, ns)); + const std::vector v(std::begin(ch), std::end(ch)); + + const std::vector vc = {'a', 'q', 'x', 'z'}; + + REQUIRE(v == vc); +} + +TEST_CASE( + "filter(enumerate()), DerefHolder works correctly (see github issue #62", + "[filter][enumerate]") { + using iter::enumerate; + using iter::filter; + std::vector ns = {50, 55, 60, 65}; + auto f = + iter::filter([](auto& i) { return std::get<0>(i) > 1; }, enumerate(ns)); + const std::vector> v(std::begin(f), std::end(f)); + + const std::vector> vc = {{2, 60}, {3, 65}}; + + REQUIRE(v == vc); +} + +TEST_CASE("reversed(repeat(v, n))", "[repeat][reversed]") { + using iter::repeat; + using iter::reversed; + + auto rr = reversed(repeat('x', 5)); + std::string s(rr.begin(), rr.end()); + + REQUIRE(s == "xxxxx"); +} + +TEST_CASE("reversed(repeat(v))", "[repeat][reversed]") { + using iter::repeat; + using iter::reversed; + + auto rr = reversed(repeat('x')); + auto it = rr.begin(); + + REQUIRE(*it == 'x'); +} diff --git a/test/test_permutations.cpp b/test/test_permutations.cpp index c184e651..8d77d2f2 100644 --- a/test/test_permutations.cpp +++ b/test/test_permutations.cpp @@ -1,10 +1,11 @@ -#include +#include #include "helpers.hpp" -#include -#include #include +#include +#include +#include #include "catch.hpp" @@ -31,6 +32,41 @@ TEST_CASE("permutations: basic test, 3 element sequence", "[permutations]") { REQUIRE(v == vc); } +TEST_CASE("permutations: const iteration", "[permutations][const]") { + const std::vector ns = {1, 7, 9}; + + IntPermSet v; + const auto perm = permutations(ns); + for (auto&& st : perm) { + v.emplace(std::begin(st), std::end(st)); + } + const IntPermSet vc = { + {1, 7, 9}, {1, 9, 7}, {7, 1, 9}, {7, 9, 1}, {9, 1, 7}, {9, 7, 1}}; + REQUIRE(v == vc); +} + +TEST_CASE( + "permutations: const iterators can be compared to non-const iteration", + "[permutations][const]") { + auto p = permutations(std::vector{}); + const auto& cp = p; + (void)(std::begin(p) == std::end(cp)); +} + +TEST_CASE("permutations: Works with different begin and end types", + "[permutations]") { + CharRange cr{'d'}; + using CharPermSet = std::multiset>; + CharPermSet sc; + for (auto&& v : permutations(cr)) { + sc.emplace(std::begin(v), std::end(v)); + } + const CharPermSet ans = {{'a', 'b', 'c'}, {'a', 'c', 'b'}, {'b', 'a', 'c'}, + {'b', 'c', 'a'}, {'c', 'a', 'b'}, {'c', 'b', 'a'}}; + + REQUIRE(ans == sc); +} + TEST_CASE( "permutations: empty sequence has one empy permutation", "[permutations]") { const std::vector ns{}; diff --git a/test/test_powerset.cpp b/test/test_powerset.cpp index 923baa13..44a7d8b5 100644 --- a/test/test_powerset.cpp +++ b/test/test_powerset.cpp @@ -1,10 +1,13 @@ -#include +#include +#define CHAR_RANGE_DEFAULT_CONSTRUCTIBLE #include "helpers.hpp" +#undef CHAR_RANGE_DEFAULT_CONSTRUCTIBLE -#include -#include #include +#include +#include +#include #include "catch.hpp" @@ -26,11 +29,46 @@ TEST_CASE("powerset: basic test, [1, 2, 3]", "[powerset]") { } const IntPermSet vc = { - std::multiset{}, {1}, {2}, {3}, - {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}}; + std::multiset{}, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}}; + REQUIRE(v == vc); +} + +TEST_CASE("powerset: const iteration", "[powerset][const]") { + const std::vector ns = {1, 2, 3}; + IntPermSet v; + const auto ps = powerset(ns); + for (auto&& st : ps) { + v.emplace(std::begin(st), std::end(st)); + } + + const IntPermSet vc = { + std::multiset{}, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}}; REQUIRE(v == vc); } +// TODO this doesn't work because two different Powersetter::Iterator types use +// two different Combinator types +#if 0 +TEST_CASE("powerset: const iterators can be compared to non-const iterators", "[powerset][const]") { + auto ps = powerset(std::vector{}); + const auto& cps = ps; + (void)(std::begin(ps) == std::end(cps)); +} +#endif + +TEST_CASE("powerset: Works with different begin and end types", "[powerset]") { + CharRange cr{'d'}; + using CharPermSet = std::multiset>; + CharPermSet sc; + for (auto&& v : powerset(cr)) { + sc.emplace(std::begin(v), std::end(v)); + } + const CharPermSet ans = {{}, {'a'}, {'b'}, {'c'}, {'a', 'b'}, {'a', 'c'}, + {'b', 'c'}, {'a', 'b', 'c'}}; + + REQUIRE(ans == sc); +} + TEST_CASE("powerset: empty sequence gives only empty set", "[powerset]") { const std::vector ns = {}; auto ps = powerset(ns); diff --git a/test/test_product.cpp b/test/test_product.cpp index d42cde31..a453da99 100644 --- a/test/test_product.cpp +++ b/test/test_product.cpp @@ -1,10 +1,14 @@ -#include +#include +#define DEFINE_BASIC_ITERABLE_COPY_CTOR +#define DEFINE_BASIC_ITERABLE_CONST_BEGIN_AND_END #include "helpers.hpp" +#undef DEFINE_BASIC_ITERABLE_CONST_BEGIN_AND_END +#undef DEFINE_BASIC_ITERABLE_COPY_CTOR -#include -#include #include +#include +#include #include "catch.hpp" @@ -26,6 +30,45 @@ TEST_CASE("product: basic test, two sequences", "[product]") { REQUIRE(v == vc); } +TEST_CASE("product: const iteration", "[product][const]") { + using TP = std::tuple; + using ResType = std::vector; + + Vec n1 = {0, 1}; + const std::string s{"abc"}; + + const auto p = product(n1, s); + ResType v(std::begin(p), std::end(p)); + ResType vc = { + TP{0, 'a'}, TP{0, 'b'}, TP{0, 'c'}, TP{1, 'a'}, TP{1, 'b'}, TP{1, 'c'}}; + + REQUIRE(v == vc); +} + +TEST_CASE("product: const iterators can be compared to non-const iterators", + "[product][const]") { + std::string s; + auto p = product(Vec{}, s); + const auto& cp = p; + (void)(std::begin(p) == std::end(cp)); +} + +TEST_CASE("product: two sequences where one has different begin and end", + "[product]") { + using TP = std::tuple; + using ResType = std::vector; + + Vec n1 = {0, 1}; + CharRange cr('d'); + + auto p = product(n1, cr); + ResType v(std::begin(p), std::end(p)); + ResType vc = { + TP{0, 'a'}, TP{0, 'b'}, TP{0, 'c'}, TP{1, 'a'}, TP{1, 'b'}, TP{1, 'c'}}; + + REQUIRE(v == vc); +} + TEST_CASE("product: three sequences", "[product]") { using TP = std::tuple; using ResType = const std::vector; @@ -42,6 +85,45 @@ TEST_CASE("product: three sequences", "[product]") { REQUIRE(v == vc); } +TEST_CASE("product: with repeat", "[product]") { + using TP = std::tuple; + using ResType = const std::vector; + const std::string s = "hop"; + auto p = product<3>(s); + ResType v(std::begin(p), std::end(p)); + + ResType vc = { + TP{'h', 'h', 'h'}, + TP{'h', 'h', 'o'}, + TP{'h', 'h', 'p'}, + TP{'h', 'o', 'h'}, + TP{'h', 'o', 'o'}, + TP{'h', 'o', 'p'}, + TP{'h', 'p', 'h'}, + TP{'h', 'p', 'o'}, + TP{'h', 'p', 'p'}, + TP{'o', 'h', 'h'}, + TP{'o', 'h', 'o'}, + TP{'o', 'h', 'p'}, + TP{'o', 'o', 'h'}, + TP{'o', 'o', 'o'}, + TP{'o', 'o', 'p'}, + TP{'o', 'p', 'h'}, + TP{'o', 'p', 'o'}, + TP{'o', 'p', 'p'}, + TP{'p', 'h', 'h'}, + TP{'p', 'h', 'o'}, + TP{'p', 'h', 'p'}, + TP{'p', 'o', 'h'}, + TP{'p', 'o', 'o'}, + TP{'p', 'o', 'p'}, + TP{'p', 'p', 'h'}, + TP{'p', 'p', 'o'}, + TP{'p', 'p', 'p'}, + }; + REQUIRE(v == vc); +} + TEST_CASE("product: empty when any iterable is empty", "[product]") { Vec n1 = {0, 1}; Vec n2 = {0, 1, 2}; @@ -91,14 +173,41 @@ TEST_CASE("product: binds to lvalues and moves rvalues", "[product]") { SECTION("First ref'd, second moved") { product(bi, std::move(bi2)); REQUIRE_FALSE(bi.was_moved_from()); + REQUIRE_FALSE(bi.was_copied_from()); REQUIRE(bi2.was_moved_from()); } SECTION("First moved, second ref'd") { product(std::move(bi), bi2); REQUIRE(bi.was_moved_from()); + REQUIRE_FALSE(bi2.was_copied_from()); REQUIRE_FALSE(bi2.was_moved_from()); } + + SECTION("repeat, lvalue not moved or copied") { + product<2>(bi); + REQUIRE_FALSE(bi.was_moved_from()); + REQUIRE_FALSE(bi.was_copied_from()); + } + + SECTION("repeat, const lvalue not moved or copied") { + const auto& r = bi; + product<2>(r); + REQUIRE_FALSE(bi.was_moved_from()); + REQUIRE_FALSE(bi.was_copied_from()); + } + + SECTION("repeat, rvalue copied") { + product<2>(std::move(bi)); + REQUIRE_FALSE(bi.was_moved_from()); + REQUIRE(bi.was_copied_from()); + } +} + +TEST_CASE("product: handles a lot of containers values", "[product]") { + constexpr char str[] = ""; + auto p = product<50>(str); + p.begin(); } TEST_CASE("product: doesn't move or copy elements of iterable", "[product]") { @@ -112,6 +221,7 @@ TEST_CASE("product: iterator meets requirements", "[product]") { std::string s{"abc"}; auto c = product(s, s); REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); } template diff --git a/test/test_range.cpp b/test/test_range.cpp index 52482df0..b2b15f98 100644 --- a/test/test_range.cpp +++ b/test/test_range.cpp @@ -1,15 +1,67 @@ -#include "range.hpp" +#include "cppitertools/range.hpp" -#include -#include #include +#include +#include -#include "helpers.hpp" #include "catch.hpp" +#include "helpers.hpp" using Vec = const std::vector; using iter::range; +TEST_CASE("range: .start(), .stop(), and .step()", "[range]") { + SECTION("one arg") { + auto r = range(3); + REQUIRE(r.start() == 0); + REQUIRE(r.stop() == 3); + REQUIRE(r.step() == 1); + + // make sure iterators aren't changing the value + auto it = r.begin(); + ++it; + + REQUIRE(r.start() == 0); + REQUIRE(r.stop() == 3); + REQUIRE(r.step() == 1); + } + + SECTION("two args") { + auto r = range(2, 10); + REQUIRE(r.start() == 2); + REQUIRE(r.stop() == 10); + REQUIRE(r.step() == 1); + } + + SECTION("three args") { + auto r = range(-6, 20, 3); + REQUIRE(r.start() == -6); + REQUIRE(r.stop() == 20); + REQUIRE(r.step() == 3); + } + + SECTION("one arg (double)") { + auto r = range(3.5); + REQUIRE(r.start() == 0); + REQUIRE(r.stop() == Approx(3.5)); + REQUIRE(r.step() == Approx(1.0)); + } + + SECTION("two args (double)") { + auto r = range(20.1, 31.7); + REQUIRE(r.start() == Approx(20.1)); + REQUIRE(r.stop() == Approx(31.7)); + REQUIRE(r.step() == Approx(1.0)); + } + + SECTION("three args (double)") { + auto r = range(-6.3, 5.7, 0.1); + REQUIRE(r.start() == Approx(-6.3)); + REQUIRE(r.stop() == Approx(5.7)); + REQUIRE(r.step() == Approx(0.1)); + } +} + TEST_CASE("range: works with only stop", "[range]") { auto r = range(5); Vec v(std::begin(r), std::end(r)); @@ -112,6 +164,12 @@ TEST_CASE("range: can create constexpr ranges", "[range]") { static_assert(f == 0.0, "range's begin has tho wrong value (float)"); } +TEST_CASE("range: const iterators compare to non-const iterators", "[range]") { + auto r = range(0); + const auto& cr = r; + (void)(std::begin(r) == std::end(cr)); +} + TEST_CASE("range: works with a variable start, stop, and step", "[range]") { constexpr int a = 10; constexpr int b = 100; @@ -254,3 +312,74 @@ TEST_CASE("range: iterator meets forward iterator requirements", "[range]") { REQUIRE(itertest::IsForwardIterator::value); REQUIRE(itertest::IsForwardIterator::value); } + +TEST_CASE("range: operator[] simple tests", "[range]") { + SECTION("range(start)") { + auto r = range(4); + REQUIRE(r[0] == 0); + REQUIRE(r[1] == 1); + REQUIRE(r[2] == 2); + REQUIRE(r[3] == 3); + } + SECTION("range(start, stop)") { + auto r = range(10, 14); + REQUIRE(r[0] == 10); + REQUIRE(r[1] == 11); + REQUIRE(r[2] == 12); + REQUIRE(r[3] == 13); + } + SECTION("range(start, stop, step)") { + auto r = range(20, 30, 3); + REQUIRE(r[0] == 20); + REQUIRE(r[1] == 23); + REQUIRE(r[2] == 26); + REQUIRE(r[3] == 29); + } + SECTION("range(start, stop, step) with double") { + auto r = range(50.0, 50.99, 0.2); + REQUIRE(r[0] == Approx(50.0)); + REQUIRE(r[1] == Approx(50.2)); + REQUIRE(r[2] == Approx(50.4)); + REQUIRE(r[3] == Approx(50.6)); + REQUIRE(r[4] == Approx(50.8)); + } +} + +TEMPLATE_TEST_CASE("range: .size() with signed integrals", "[range]", + signed char, short, int, long, long long) { + constexpr TestType N = 5; + constexpr TestType INC = 1; + for (TestType start = -N; start < N; start += INC) { + for (TestType stop = -N; stop < N; stop += INC) { + for (TestType step = -N; step < N; step += INC) { + if (step == 0) { + continue; + } + auto r = range(start, stop, step); + REQUIRE(r.size() + == static_cast( + std::distance(std::begin(r), std::end(r)))); + } + } + } +} + +TEMPLATE_TEST_CASE("range: .size() with unsigned integrals", "[range]", + unsigned char, unsigned short, unsigned int, unsigned long, + unsigned long long) { + constexpr TestType N = 5; + constexpr TestType INC = 1; + for (TestType start = 0; start < N; start += INC) { + for (TestType stop = 0; stop < N; stop += INC) { + for (TestType step = 0; step < N; step += INC) { + if (step == 0) { + continue; + } + auto r = range(start, stop, step); + REQUIRE(r.size() + == static_cast( + std::distance(std::begin(r), std::end(r)))); + } + } + } +} diff --git a/test/test_repeat.cpp b/test/test_repeat.cpp index 3cd928df..5427909a 100644 --- a/test/test_repeat.cpp +++ b/test/test_repeat.cpp @@ -1,12 +1,10 @@ -#include - -#include "helpers.hpp" - -#include -#include #include +#include +#include +#include #include "catch.hpp" +#include "helpers.hpp" using iter::repeat; @@ -50,6 +48,12 @@ TEST_CASE("repeat: can be used as constexpr", "[repeat]") { } } +TEST_CASE("repeat: iterators compare to const iterators", "[repeat]") { + auto r = repeat(1); + const auto& cr = r; + (void)(std::begin(r) == std::end(cr)); +} + TEST_CASE("repeat: two argument repeats a number of times", "[repeat]") { auto r = repeat('a', 3); std::string s(std::begin(r), std::end(r)); @@ -80,6 +84,22 @@ TEST_CASE("repeat: iterator meets requirements", "[repeat]") { REQUIRE(itertest::IsIterator::value); } +TEST_CASE("repeat: one-argument is reversible", "[repeat]") { + auto r = repeat('c'); + auto it = std::rbegin(r); + (void)(it != std::rend(r)); + + REQUIRE(*it == 'c'); + ++it; + REQUIRE(*it == 'c'); +} + +TEST_CASE("repeat: two-argument is reversible", "[repeat]") { + auto r = repeat('b', 4); + std::string s(std::rbegin(r), std::rend(r)); + REQUIRE(s == "bbbb"); +} + template using ImpT = decltype(repeat(std::declval())); diff --git a/test/test_reversed.cpp b/test/test_reversed.cpp index 1cd6fcfa..10dfe4d9 100644 --- a/test/test_reversed.cpp +++ b/test/test_reversed.cpp @@ -1,9 +1,9 @@ -#include +#include -#include #include #include #include +#include #include "catch.hpp" @@ -31,6 +31,32 @@ TEST_CASE("reversed: can reverse a vector", "[reversed]") { REQUIRE(v == vc); } +TEST_CASE("reversed: const iteration", "[reversed][const]") { + Vec ns = {10, 20, 30, 40}; + const auto r = reversed(ns); + Vec v(std::begin(r), std::end(r)); + Vec vc = {40, 30, 20, 10}; + REQUIRE(v == vc); +} + +TEST_CASE("reversed: const iterators can be compared to non-const iterators", + "[reversed][const]") { + auto r = reversed(Vec{}); + const auto& cr = r; + (void)(std::begin(r) == std::end(cr)); +} + +#if 0 +TEST_CASE("reversed: Works with different begin and end types", + "[reversed]") { + CharRange cr{'d'}; + auto r = reversed(cr); + Vec v(r.begin(), r.end()); + Vec vc{'c', 'b', 'a'}; + REQUIRE(v == vc); +} +#endif + TEST_CASE("reversed: can reverse an array", "[reversed]") { int ns[] = {10, 20, 30, 40}; auto r = reversed(ns); diff --git a/test/test_slice.cpp b/test/test_slice.cpp index 1500da6e..fc5b42fe 100644 --- a/test/test_slice.cpp +++ b/test/test_slice.cpp @@ -1,11 +1,11 @@ -#include +#include -#include #include #include +#include -#include "helpers.hpp" #include "catch.hpp" +#include "helpers.hpp" using iter::slice; using Vec = const std::vector; @@ -27,6 +27,23 @@ TEST_CASE("slice: take from beginning", "[slice]") { REQUIRE(v == vc); } +TEST_CASE("slice: const iteration", "[slice][const]") { + Vec ns = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + + const auto sl = slice(ns, 5); + Vec v(std::begin(sl), std::end(sl)); + + Vec vc = {10, 11, 12, 13, 14}; + REQUIRE(v == vc); +} + +TEST_CASE("slice: const iterator can be compared to non-const iterator", + "[slice][const]") { + auto sl = slice(Vec{}, 1); + const auto& csl = sl; + (void)(std::begin(sl) == std::end(csl)); +} + TEST_CASE("slice: start and stop", "[slice]") { Vec ns = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; @@ -44,6 +61,14 @@ TEST_CASE("slice: start and stop", "[slice]") { REQUIRE(v == vc); } +TEST_CASE("slice: Works with different begin and end types", "[slice]") { + CharRange cr{'z'}; + auto sl = slice(cr, 2, 5); + std::vector v(std::begin(sl), std::end(sl)); + const std::vector vc = {'c', 'd', 'e'}; + REQUIRE(v == vc); +} + TEST_CASE("slice: start, stop, step", "[slice]") { Vec ns = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; std::vector v; @@ -123,9 +148,18 @@ TEST_CASE("slice: with iterable doesn't move or copy elems", "[slice]") { } TEST_CASE("slice: iterator meets requirements", "[slice]") { - std::string s{"abcdef"}; - auto c = slice(s, 1, 3); - REQUIRE(itertest::IsIterator::value); + SECTION("with iterable yielding references") { + std::string s{"abcdef"}; + auto c = slice(s, 1, 3); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + SECTION("with iterable yielding values") { + itertest::InputIterable it{}; + auto c = slice(it, 1, 3); + REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } } template diff --git a/test/test_sliding_window.cpp b/test/test_sliding_window.cpp index 0cd26a52..e031ce75 100644 --- a/test/test_sliding_window.cpp +++ b/test/test_sliding_window.cpp @@ -1,12 +1,12 @@ -#include +#include -#include #include #include #include +#include -#include "helpers.hpp" #include "catch.hpp" +#include "helpers.hpp" using iter::sliding_window; using Vec = const std::vector; @@ -28,6 +28,37 @@ TEST_CASE("sliding_window: window of size 3", "[sliding_window]") { REQUIRE(v == vc); } +TEST_CASE("sliding_window: const iteration", "[sliding_window][const]") { + Vec ns = {10, 20, 30, 40, 50}; + std::vector> vc = {{10, 20, 30}, {20, 30, 40}, {30, 40, 50}}; + std::vector> v; + const auto sw = sliding_window(ns, 3); + for (auto&& win : sw) { + v.emplace_back(std::begin(win), std::end(win)); + } + REQUIRE(v == vc); +} + +TEST_CASE( + "sliding_window: const iterators can be compared to non-const iterators", + "[sliding_window][const]") { + auto sw = sliding_window(Vec{}, 2); + const auto& csw = sw; + (void)(std::begin(sw) == std::end(csw)); +} + +TEST_CASE("sliding_window: Works with different begin and end types", + "[sliding_window]") { + CharRange cr{'f'}; + std::vector> results; + for (auto&& g : sliding_window(cr, 3)) { + results.emplace_back(std::begin(g), std::end(g)); + } + std::vector> rc = { + {'a', 'b', 'c'}, {'b', 'c', 'd'}, {'c', 'd', 'e'}}; + REQUIRE(results == rc); +} + TEST_CASE("sliding window: oversized window is empty", "[sliding_window]") { Vec ns = {10, 20, 30}; auto sw = sliding_window(ns, 5); diff --git a/test/test_sorted.cpp b/test/test_sorted.cpp index d04f9364..ea80decf 100644 --- a/test/test_sorted.cpp +++ b/test/test_sorted.cpp @@ -1,14 +1,14 @@ -#include +#include -#include -#include -#include #include +#include #include +#include #include +#include -#include "helpers.hpp" #include "catch.hpp" +#include "helpers.hpp" using iter::sorted; @@ -29,6 +29,31 @@ TEST_CASE("sorted: iterates through a vector in sorted order", "[sorted]") { REQUIRE(v == vc); } +TEST_CASE("sorted: handles pointer to member function", "[sorted]") { + using itertest::Point; + const std::vector ps = {{5, 0}, {3, 0}, {10, 0}, {6, 0}}; + auto s = sorted(ps, &Point::left_of); + const std::vector v(std::begin(s), std::end(s)); + const std::vector vc = {{3, 0}, {5, 0}, {6, 0}, {10, 0}}; + REQUIRE(v == vc); +} + +TEST_CASE("sorted: const iteration", "[sorted][const]") { + Vec ns = {4, 0, 5, 1, 6, 7, 9, 3, 2, 8}; + const auto s = sorted(ns); + Vec v(std::begin(s), std::end(s)); + Vec vc = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + REQUIRE(v == vc); +} + +//FIXME: This test currently fails (STL assertion fails on MSVC with debug library, simple test failure on gcc). The problem is 'sorted' will sort twice, once for non-const and once for const container; the resulting iterators are thus not on the same container (violating domain of == as specified in C++17 [forward.iterators]�2). Remove [!hide] tag when fixed. +TEST_CASE("sorted: const iterators can be compared to non-const iterators", + "[sorted][const][!hide]") { + auto s = sorted(Vec{1}); + const auto& cs = s; + REQUIRE(std::begin(s) == std::begin(cs)); +} + TEST_CASE("sorted: can modify elements through sorted", "[sorted]") { std::vector ns(3, 9); for (auto&& n : sorted(ns)) { @@ -38,6 +63,20 @@ TEST_CASE("sorted: can modify elements through sorted", "[sorted]") { REQUIRE(ns == vc); } +char inc_vowels(char c) { + return c == 'a' || c == 'e' ? c + 10 : c; +} + +TEST_CASE("sorted: Works with different begin and end types", "[sorted]") { + using VecC = std::vector; + CharRange cr{'g'}; + auto s = + sorted(cr, [](char x, char y) { return inc_vowels(x) < inc_vowels(y); }); + VecC v(s.begin(), s.end()); + VecC vc{'b', 'c', 'd', 'f', 'a', 'e'}; + REQUIRE(v == vc); +} + TEST_CASE("sorted: can iterate over unordered container", "[sorted]") { std::unordered_set ns = {1, 3, 2, 0, 4}; auto s = sorted(ns); @@ -179,7 +218,7 @@ TEST_CASE("sorted: moves rvalues and binds to lvalues", "[sorted]") { TEST_CASE("sorted: doesn't move or copy elements of iterable", "[sorted]") { using itertest::SolidInt; constexpr SolidInt arr[] = {{6}, {7}, {8}}; - for (auto &&i : sorted(arr, [](const SolidInt &lhs, const SolidInt &rhs) { + for (auto &&i : sorted(arr, [](const SolidInt&lhs, const SolidInt&rhs) { return lhs.getint() < rhs.getint(); })) { (void)i; diff --git a/test/test_starmap.cpp b/test/test_starmap.cpp index 25451dcf..c8758669 100644 --- a/test/test_starmap.cpp +++ b/test/test_starmap.cpp @@ -1,11 +1,11 @@ -#include - +#include #include "helpers.hpp" -#include +#include #include #include -#include +#include +#include #include "catch.hpp" @@ -16,6 +16,18 @@ namespace { return d * i; } + int& larger_ref(int& a, int& b) { + return a > b ? a : b; + } + + const int& larger_const_ref(const int& a, const int& b) { + return a > b ? a : b; + } + + int larger(int a, int b) { + return a > b ? a : b; + } + std::string g(const std::string& s, int i, char c) { std::stringstream ss; ss << s << ' ' << i << ' ' << c; @@ -27,20 +39,50 @@ namespace { return a + b + c; } + int operator()(double a, int b) { + return int(a + b); + } + int operator()(int a) { return a; } }; + + struct Adder { + long operator()(long a, int b) { + return a + b; + } + }; + + struct MoveOnlyAddAndPlus { + private: + // unique_ptr is better for triggering asan than an int if there's a + // dangling reference to the callable + std::unique_ptr add_amount_; + + public: + MoveOnlyAddAndPlus(int v) : add_amount_{std::make_unique(v)} {} + + MoveOnlyAddAndPlus(const MoveOnlyAddAndPlus&) = delete; + MoveOnlyAddAndPlus& operator=(const MoveOnlyAddAndPlus&) = delete; + + MoveOnlyAddAndPlus(MoveOnlyAddAndPlus&&) = default; + MoveOnlyAddAndPlus& operator=(MoveOnlyAddAndPlus&&) = default; + + int operator()(long a, int b) { + return a + b + *add_amount_; + } + }; } TEST_CASE("starmap: works with function pointer and lambda", "[starmap]") { - using Vec = const std::vector; - const std::vector> v1 = {{1l, 2}, {3l, 11}, {6l, 7}}; - Vec vc = {2l, 33l, 42l}; + const std::vector> v1 = {{1l, 2}, {3l, 11}, {6l, 7}}; + const std::vector greater_vc = {2l, 33l, 42l}; + const std::vector added_vc = {3l, 14l, 13l}; - std::vector v; - SECTION("with function") { - SECTION("Normal call") { + SECTION("with function pointer") { + std::vector v; + SECTION("normal call") { auto sm = starmap(f, v1); v.assign(std::begin(sm), std::end(sm)); } @@ -48,18 +90,118 @@ TEST_CASE("starmap: works with function pointer and lambda", "[starmap]") { auto sm = v1 | starmap(f); v.assign(std::begin(sm), std::end(sm)); } + REQUIRE(v == greater_vc); + } + + SECTION("with callable object") { + std::vector v; + SECTION("normal call") { + auto sm = starmap(Adder{}, v1); + v.assign(std::begin(sm), std::end(sm)); + } + SECTION("pipe") { + auto sm = v1 | starmap(Adder{}); + v.assign(std::begin(sm), std::end(sm)); + } + REQUIRE(v == added_vc); + } + + SECTION("with lvalue callable object") { + std::vector v; + auto adder = Adder{}; + SECTION("normal call") { + auto sm = starmap(adder, v1); + v.assign(std::begin(sm), std::end(sm)); + } + SECTION("pipe") { + auto sm = v1 | starmap(adder); + v.assign(std::begin(sm), std::end(sm)); + } + REQUIRE(v == std::vector{3l, 14l, 13l}); + } + + SECTION("with move-only callable object") { + const std::vector sum_plus_one_vc = {4l, 14l, 13l}; + std::vector v; + SECTION("normal call") { + auto m = starmap(MoveOnlyAddAndPlus{1}, v1); + v.assign(std::begin(m), std::end(m)); + } + SECTION("pipe") { + auto m = v1 | starmap(MoveOnlyAddAndPlus{1}); + v.assign(std::begin(m), std::end(m)); + } } SECTION("with lambda") { auto sm = starmap([](long a, int b) { return a * b; }, v1); - v.assign(std::begin(sm), std::end(sm)); + std::vector v(std::begin(sm), std::end(sm)); + REQUIRE(v == greater_vc); } +} + +TEST_CASE("starmap: works with pointer to member function", "[starmap]") { + using itertest::Point; + std::vector> tup = { + {{10, 20}, "a"}, {{6, 8}, "point"}, {{3, 15}, "pos"}}; + auto sm = starmap(&Point::prefix, tup); + std::vector v(std::begin(sm), std::end(sm)); + const std::vector vc = { + "a(10, 20)", "point(6, 8)", "pos(3, 15)"}; + REQUIRE(v == vc); +} + +TEST_CASE("starmap: vector of pairs const iteration", "[starmap][const]") { + using Vec = const std::vector; + const std::vector> v1 = { + {1.0, 2}, {3.0, 11}, {6.0, 7}}; + + const auto sm = starmap(Callable{}, v1); + std::vector v(std::begin(sm), std::end(sm)); + Vec vc = {3, 14, 13}; + REQUIRE(v == vc); +} + +TEST_CASE( + "starmap: vector of pairs const iterators can be compared to non-const " + "iterators", + "[starmap][const]") { + const std::vector> v1; + auto sm = starmap(Callable{}, v1); + const auto& csm = sm; + (void)(std::begin(sm) == std::end(csm)); +} + +TEST_CASE("starmap: Works with different begin and end types", "[starmap]") { + IntCharPairRange icr{{3, 'd'}}; + using Vec = std::vector; + auto sm = starmap([](int i, char c) { return std::to_string(i) + c; }, icr); + Vec v(sm.begin(), sm.end()); + Vec vc{"0a", "1b", "2c"}; REQUIRE(v == vc); } +TEST_CASE("starmap: tuple of tuples const iteration", "[starmap][const]") { + using Vec = const std::vector; + auto tup = std::make_tuple(std::make_tuple(10, 19, 60), std::make_tuple(7)); + const auto sm = starmap(Callable{}, tup); + Vec v(std::begin(sm), std::end(sm)); +} + +TEST_CASE( + "starmap: tuple of tuples const iterators can be compared to non-const " + "iterator", + "[starmap][const]") { + auto tup = std::make_tuple(std::make_tuple(10, 19, 60), std::make_tuple(7)); + auto sm = starmap(Callable{}, tup); + const auto& csm = sm; + (void)(std::begin(sm) == std::end(csm)); + (void)(std::begin(csm) == std::end(sm)); +} + TEST_CASE("starmap: list of tuples", "[starmap]") { using Vec = const std::vector; - using T = std::tuple; + using T = std::tuple; std::list li = {T{"hey", 42, 'a'}, T{"there", 3, 'b'}, T{"yall", 5, 'c'}}; auto sm = starmap(g, li); @@ -110,7 +252,7 @@ TEST_CASE("starmap: moves rvalues, binds to lvalues", "[starmap]") { TEST_CASE("starmap: iterator meets requirements", "[starmap]") { std::string s{}; const std::vector> v1; - auto sm = starmap([](long a, int b) { return a * b; }, v1); + auto sm = starmap([](double a, int b) { return a * b; }, v1); REQUIRE(itertest::IsIterator::value); } @@ -120,3 +262,36 @@ TEST_CASE( auto sm = starmap(Callable{}, tup); REQUIRE(itertest::IsIterator::value); } + +TEST_CASE("starmap: iterator dereference type matches 'reference' type alias", + "[starmap]") { + std::vector> input; + SECTION("with reference return type") { + auto sm = iter::starmap(larger_ref, input); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + SECTION("with const reference return type") { + auto sm = iter::starmap(larger_const_ref, input); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } + SECTION("with value return type") { + auto sm = iter::starmap(larger, input); + REQUIRE(itertest::ReferenceMatchesDeref::value); + } +} + +TEST_CASE("starmap: iterator has correct 'value' type alias", "[starmap]") { + std::vector> input; + SECTION("with reference return type") { + auto sm = iter::starmap(larger_ref, input); + REQUIRE(std::is_same_v); + } + SECTION("with const reference return type") { + auto sm = iter::starmap(larger_const_ref, input); + REQUIRE(std::is_same_v); + } + SECTION("with value return type") { + auto sm = iter::starmap(larger, input); + REQUIRE(std::is_same_v); + } +} diff --git a/test/test_takewhile.cpp b/test/test_takewhile.cpp index af5f6091..f562b438 100644 --- a/test/test_takewhile.cpp +++ b/test/test_takewhile.cpp @@ -1,60 +1,101 @@ -#include - -#include #include +#include #include #include +#include -#include "helpers.hpp" #include "catch.hpp" +#include "helpers.hpp" using iter::takewhile; using Vec = const std::vector; -namespace { - bool under_ten(int i) { - return i < 10; +TEST_CASE("takewhile: handles different callable types", "[takewhile]") { + Vec ns = {1, 3, 4, 20, 2, 4, 6, 8}; + Vec vc = {1, 3, 4}; + std::vector v; + SECTION("with function pointer") { + auto tw = takewhile(less_than_five, ns); + v = Vec(std::begin(tw), std::end(tw)); } - struct UnderTen { - bool operator()(int i) { - return i < 10; - } - }; -} - -TEST_CASE("takewhile: works with lambda, callable, and function pointer", - "[takewhile]") { - Vec ns = {1, 3, 5, 20, 2, 4, 6, 8}; - SECTION("function pointer") { - auto tw = takewhile(under_ten, ns); - Vec v(std::begin(tw), std::end(tw)); - Vec vc = {1, 3, 5}; - REQUIRE(v == vc); + SECTION("with callable object") { + auto tw = takewhile(LessThanValue{5}, ns); + v = Vec(std::begin(tw), std::end(tw)); } - SECTION("callable object") { - std::vector v; - SECTION("Normal call") { - auto tw = takewhile(UnderTen{}, ns); - v.assign(std::begin(tw), std::end(tw)); + SECTION("with lvalue callable object") { + auto lt = LessThanValue{5}; + SECTION("normal call") { + auto tw = takewhile(lt, ns); + v = Vec(std::begin(tw), std::end(tw)); } + SECTION("pipe") { + auto tw = ns | takewhile(lt); + v = Vec(std::begin(tw), std::end(tw)); + } + } - SECTION("Pipe") { - auto tw = ns | takewhile(UnderTen{}); - v.assign(std::begin(tw), std::end(tw)); + SECTION("with move-only callable object") { + SECTION("normal call") { + auto tw = takewhile(MoveOnlyLessThanValue{5}, ns); + v = Vec(std::begin(tw), std::end(tw)); + } + SECTION("pipe") { + auto tw = ns | takewhile(MoveOnlyLessThanValue{5}); + v = Vec(std::begin(tw), std::end(tw)); } + } - Vec vc = {1, 3, 5}; - REQUIRE(v == vc); + SECTION("with lambda") { + auto ltf = [](int i) { return i < 5; }; + auto tw = takewhile(ltf, ns); + v = Vec(std::begin(tw), std::end(tw)); } + REQUIRE(v == vc); +} - SECTION("lambda") { - auto tw = takewhile([](int i) { return i < 10; }, ns); - Vec v(std::begin(tw), std::end(tw)); - Vec vc = {1, 3, 5}; - REQUIRE(v == vc); +TEST_CASE("takewhile: handles pointer to member", "[takewhile]") { + using itertest::Point; + const std::vector ps = { + {5, 0}, {3, 5}, {2, 1}, {0, 1}, {2, 2}, {6, 0}}; + std::vector v; + SECTION("with pointer to data member") { + auto tw = takewhile(&Point::x, ps); + v.assign(std::begin(tw), std::end(tw)); } + + SECTION("with pointer to member function") { + auto tw = takewhile(&Point::get_x, ps); + v.assign(std::begin(tw), std::end(tw)); + } + + const std::vector vc = {{5, 0}, {3, 5}, {2, 1}}; + REQUIRE(v == vc); +} + +TEST_CASE("takewhile: supports const iteration", "[takewhile][const]") { + Vec ns = {1, 3, 5, 20, 2, 4, 6, 8}; + const auto tw = takewhile(LessThanValue{10}, ns); + Vec v(std::begin(tw), std::end(tw)); + Vec vc = {1, 3, 5}; + REQUIRE(v == vc); +} + +TEST_CASE("takewhile: const iterator and non-const iterator are comparable", + "[takewhile][const]") { + auto tw = takewhile(LessThanValue{10}, Vec{}); + const auto& ctw = tw; + (void)(std::begin(tw) == std::end(ctw)); +} + +TEST_CASE( + "takewhile: Works with different begin and end types", "[takewhile]") { + CharRange cr{'f'}; + auto t = takewhile([](char c) { return c < 'd'; }, cr); + Vec v(t.begin(), t.end()); + Vec vc{'a', 'b', 'c'}; + REQUIRE(v == vc); } TEST_CASE("takewhile: identity", "[takewhile]") { @@ -68,21 +109,26 @@ TEST_CASE("takewhile: identity", "[takewhile]") { auto tw = ns | takewhile; v.assign(std::begin(tw), std::end(tw)); } - Vec vc = {1,2,3}; + Vec vc = {1, 2, 3}; REQUIRE(v == vc); } TEST_CASE("takewhile: everything passes predicate", "[takewhile]") { Vec ns{1, 2, 3}; - auto tw = takewhile(under_ten, ns); + auto tw = takewhile(less_than_five, ns); Vec v(std::begin(tw), std::end(tw)); Vec vc = {1, 2, 3}; } TEST_CASE("takewhile: empty iterable is empty", "[takewhile]") { Vec ns{}; - auto tw = takewhile(under_ten, ns); - REQUIRE(std::begin(tw) == std::end(tw)); + auto tw = takewhile(less_than_five, ns); + SECTION("normal compare") { + REQUIRE(std::begin(tw) == std::end(tw)); + } + SECTION("reversed compare") { + REQUIRE(std::end(tw) == std::begin(tw)); + } } TEST_CASE( @@ -90,23 +136,33 @@ TEST_CASE( "[takewhile]") { SECTION("First element is only element") { Vec ns = {20}; - auto tw = takewhile(under_ten, ns); - REQUIRE(std::begin(tw) == std::end(tw)); + auto tw = takewhile(less_than_five, ns); + SECTION("normal compare") { + REQUIRE(std::begin(tw) == std::end(tw)); + } + SECTION("reversed compare") { + REQUIRE(std::end(tw) == std::begin(tw)); + } } SECTION("First element followed by elements that pass") { Vec ns = {20, 1, 1}; - auto tw = takewhile(under_ten, ns); - REQUIRE(std::begin(tw) == std::end(tw)); + auto tw = takewhile(less_than_five, ns); + SECTION("normal compare") { + REQUIRE(std::begin(tw) == std::end(tw)); + } + SECTION("reversed compare") { + REQUIRE(std::end(tw) == std::begin(tw)); + } } } TEST_CASE("takewhile: moves rvalues, binds to lvalues", "[takewhile]") { itertest::BasicIterable bi{1, 2}; - takewhile(under_ten, bi); + takewhile(less_than_five, bi); REQUIRE_FALSE(bi.was_moved_from()); - takewhile(under_ten, std::move(bi)); + takewhile(less_than_five, std::move(bi)); REQUIRE(bi.was_moved_from()); } diff --git a/test/test_unique_everseen.cpp b/test/test_unique_everseen.cpp index f83b358e..c5fedad3 100644 --- a/test/test_unique_everseen.cpp +++ b/test/test_unique_everseen.cpp @@ -1,10 +1,10 @@ -#include +#include #include "helpers.hpp" -#include -#include #include +#include +#include #include "catch.hpp" @@ -20,6 +20,22 @@ TEST_CASE("unique everseen: adjacent repeating values", "[unique_everseen]") { REQUIRE(v == vc); } +TEST_CASE("unique everseen: const iteration", "[unique_everseen][const]") { + Vec ns = {1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 7, 8, 8, 8, 8, 9, 9}; + const auto ue = unique_everseen(ns); + Vec v(std::begin(ue), std::end(ue)); + Vec vc = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + REQUIRE(v == vc); +} + +TEST_CASE( + "unique everseen: const iterators can be compared to non-const iterators", + "[unique_everseen][const]") { + auto ue = unique_everseen(std::vector{}); + const auto& cue = ue; + (void)(std::begin(ue) == std::end(cue)); +} + TEST_CASE( "unique everseen: nonadjacent repeating values", "[unique_everseen]") { Vec ns = {1, 2, 3, 4, 3, 2, 1, 5, 6}; @@ -46,6 +62,16 @@ TEST_CASE( REQUIRE(bi.was_moved_from()); } +TEST_CASE("unique everseen: Works with different begin and end types", + "[unique_everseen]") { + CharRange cr{'d'}; + using VecC = std::vector; + auto ue = unique_everseen(cr); + VecC v(ue.begin(), ue.end()); + VecC vc{'a', 'b', 'c'}; + REQUIRE(v == vc); +} + TEST_CASE("unique_everseen: iterator meets requirements", "[unique_everseen]") { std::string s{}; auto c = unique_everseen(s); @@ -59,3 +85,33 @@ TEST_CASE( REQUIRE(itertest::IsMoveConstructibleOnly>::value); REQUIRE(itertest::IsMoveConstructibleOnly>::value); } + +struct IntWrapper { + int n; +}; + +struct IntWrapperHash { + int operator()(const IntWrapper& iw) const { + return iw.n % 10; + } +}; + +struct IntWrapperEq { + int operator()(const IntWrapper& lhs, const IntWrapper& rhs) const { + return lhs.n == rhs.n; + } +}; + +TEST_CASE("unique_everseen: works with custom hash and equality functions", + "[unique_everseen]") { + std::vector iwv = { + {2}, {3}, {4}, {2}, {10}, {2}, {2}, {12}, {10}}; + Vec vc{2, 3, 4, 10, 12}; + + std::vector v; + for (auto&& iw : unique_everseen(iwv, IntWrapperHash{}, IntWrapperEq{})) { + v.push_back(iw.n); + } + + REQUIRE(v == vc); +} diff --git a/test/test_unique_justseen.cpp b/test/test_unique_justseen.cpp index e66bdee6..530ab869 100644 --- a/test/test_unique_justseen.cpp +++ b/test/test_unique_justseen.cpp @@ -1,16 +1,16 @@ -#include +#include #include "helpers.hpp" -#include -#include #include +#include +#include #include "catch.hpp" using iter::unique_justseen; -using Vec = const std::vector; +using Vec = std::vector; TEST_CASE("unique justseen: adjacent repeating values", "[unique_justseen]") { Vec ns = {1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 7, 8, 8, 8, 8, 9, 9}; @@ -20,6 +20,22 @@ TEST_CASE("unique justseen: adjacent repeating values", "[unique_justseen]") { REQUIRE(v == vc); } +TEST_CASE("unique justseen: const iteration", "[unique_justseen][const]") { + Vec ns = {1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 7, 8, 8, 8, 8, 9, 9}; + const auto uj = unique_justseen(ns); + Vec v(std::begin(uj), std::end(uj)); + Vec vc = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + REQUIRE(v == vc); +} + +TEST_CASE( + "unique justseen: const iterator can be compared to non-const iterators", + "[unique_justseen][const]") { + auto uj = unique_justseen(Vec{}); + const auto& cuj = uj; + (void)(std::begin(uj) == std::begin(cuj)); +} + TEST_CASE("unique justseen: some repeating values", "[unique_justseen]") { Vec ns = {1, 2, 2, 3, 4, 4, 5, 6, 6}; std::vector v; @@ -35,6 +51,16 @@ TEST_CASE("unique justseen: some repeating values", "[unique_justseen]") { REQUIRE(v == vc); } +TEST_CASE("unique justseen: Works with different begin and end types", + "[unique_justseen]") { + CharRange cr{'d'}; + using VecC = std::vector; + auto uj = unique_justseen(cr); + VecC v(uj.begin(), uj.end()); + VecC vc{'a', 'b', 'c'}; + REQUIRE(v == vc); +} + TEST_CASE("unique justseen: doesn't omit non-adjacent duplicates", "[unique_justseen]") { Vec ns = {1, 2, 3, 2, 1, 2, 3, 2, 1}; @@ -65,3 +91,58 @@ TEST_CASE( REQUIRE(itertest::IsMoveConstructibleOnly>::value); REQUIRE(itertest::IsMoveConstructibleOnly>::value); } + +struct IntWrapper { + int n; +}; + +struct IntWrapperKey { + int operator()(const IntWrapper& iw) const { + return iw.n; + } +}; + +struct MoveOnlyIntWrapperKey { + MoveOnlyIntWrapperKey(const MoveOnlyIntWrapperKey&) = delete; + MoveOnlyIntWrapperKey& operator=(const MoveOnlyIntWrapperKey&) = delete; + + MoveOnlyIntWrapperKey(MoveOnlyIntWrapperKey&&) = default; + MoveOnlyIntWrapperKey& operator=(MoveOnlyIntWrapperKey&&) = default; + int operator()(const IntWrapper& iw) const { + return iw.n; + } +}; + +TEST_CASE("unique_justseen: works with key function", "[unique_justseen]") { + std::vector iwv = { + {2}, {3}, {4}, {2}, {10}, {2}, {2}, {12}, {10}}; + Vec vc{2, 3, 4, 2, 10, 2, 12, 10}; + + std::vector v; + SECTION("with callable") { + SECTION("Normal call") { + for (auto&& iw : unique_justseen(iwv, IntWrapperKey{})) { + v.push_back(iw.n); + } + } + SECTION("Pipe") { + for (auto&& iw : iwv | unique_justseen(IntWrapperKey{})) { + v.push_back(iw.n); + } + } + } + SECTION("with move-only callable") { + SECTION("Normal call") { + for (auto&& iw : unique_justseen(iwv, MoveOnlyIntWrapperKey{})) { + v.push_back(iw.n); + } + } + SECTION("Pipe") { + for (auto&& iw : iwv | unique_justseen(MoveOnlyIntWrapperKey{})) { + v.push_back(iw.n); + } + } + } + + REQUIRE(v == vc); +} diff --git a/test/test_zip.cpp b/test/test_zip.cpp index d8cb80e5..87c927f8 100644 --- a/test/test_zip.cpp +++ b/test/test_zip.cpp @@ -1,14 +1,13 @@ -#include +#include #include "helpers.hpp" -#include -#include -#include #include -#include #include -#include +#include +#include +#include +#include #include "catch.hpp" @@ -29,6 +28,43 @@ TEST_CASE("zip: Simple case, same length", "[zip]") { REQUIRE(v == vc); } +TEST_CASE("zip: const iteration", "[zip][const]") { + using Tu = std::tuple; + using ResVec = const std::vector; + std::vector iv{10, 20, 30}; + std::string s{"hey"}; + double arr[] = {1.0, 2.0, 4.0}; + + const auto z = zip(iv, s, arr); + + ResVec v(std::begin(z), std::end(z)); + ResVec vc{Tu{10, 'h', 1.0}, Tu{20, 'e', 2.0}, Tu{30, 'y', 4.0}}; + REQUIRE(v == vc); +} + +TEST_CASE("zip: const iterators can be compared to non-const iterators", + "[zip][const]") { + std::vector v; + std::string s; + auto z = zip(std::vector{}, s); + const auto& cz = z; + (void)(std::begin(z) == std::end(cz)); +} + +TEST_CASE( + "zip: three sequences, one sequence has different begin and end", "[zip]") { + using Tu = std::tuple; + using ResVec = const std::vector; + std::vector iv{10, 20, 30}; + CharRange cr('d'); + double arr[] = {1.0, 2.0, 4.0}; + + auto z = zip(iv, cr, arr); + ResVec v(std::begin(z), std::end(z)); + ResVec vc{Tu{10, 'a', 1.0}, Tu{20, 'b', 2.0}, Tu{30, 'c', 4.0}}; + REQUIRE(v == vc); +} + TEST_CASE("zip: One empty, all empty", "[zip]") { std::vector iv = {1, 2, 3}; std::string s{}; @@ -54,9 +90,9 @@ TEST_CASE("zip: Empty", "[zip]") { TEST_CASE("zip: Modify sequence through zip", "[zip]") { std::vector iv{1, 2, 3}; std::vector iv2{1, 2, 3, 4}; - for (auto&& t : zip(iv, iv2)) { - std::get<0>(t) = -1; - std::get<1>(t) = -1; + for (auto&& [a, b] : zip(iv, iv2)) { + a = -1; + b = -1; } const std::vector vc{-1, -1, -1}; @@ -104,8 +140,10 @@ TEST_CASE("zip: iterator meets requirements", "[zip]") { std::string s{}; auto c = zip(s); REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); auto c2 = zip(s, s); REQUIRE(itertest::IsIterator::value); + REQUIRE(itertest::ReferenceMatchesDeref::value); } template diff --git a/test/test_zip_longest.cpp b/test/test_zip_longest.cpp index c8694e9e..cdf29269 100644 --- a/test/test_zip_longest.cpp +++ b/test/test_zip_longest.cpp @@ -1,15 +1,14 @@ -#include +#include #include "helpers.hpp" -#include -#include -#include -#include -#include +#include #include #include -#include +#include +#include +#include +#include #include "catch.hpp" @@ -80,6 +79,50 @@ TEST_CASE( } } +TEST_CASE( + "zip_longest: three sequences, one sequence has different begin and end", + "[zip_longest]") { + // using TP = const_opt_tuple; + using TP = std::tuple; + using ResVec = std::vector; + + std::vector iv{10, 20}; + CharRange cr('c'); + + ResVec v; + for (auto&& [i, c] : zip_longest(iv, cr)) { + v.push_back(TP{*i, *c}); + } + ResVec vc{TP{10, 'a'}, TP{20, 'b'}}; + REQUIRE(v == vc); +} + +TEST_CASE("zip_longest: const iteration", "[zip_longest][const]") { + // using TP = const_opt_tuple; + using TP = std::tuple; + using ResVec = std::vector; + + char cr[] = {'a', 'b'}; + + ResVec v; + const auto zl = zip_longest(std::vector{10, 20}, cr); + + for (auto&& p : zl) { + v.push_back(TP{*std::get<0>(p), *std::get<1>(p)}); + } + ResVec vc{TP{10, 'a'}, TP{20, 'b'}}; + REQUIRE(v == vc); +} + +TEST_CASE("zip_longest: const iterators can be compared to non-const iterators", + "[zip_longest][const]") { + auto zl = zip_longest(std::vector{}); + const auto& czl = zl; + (void)std::begin(zl); + (void)std::begin(czl); + (void)(std::begin(zl) == std::end(czl)); +} + TEST_CASE( "zip longest: when all are empty, terminates right away", "[zip_longest]") { const std::vector ivec{}; @@ -93,9 +136,9 @@ TEST_CASE( TEST_CASE("zip longest: can modify zipped sequences", "[zip_longest]") { std::vector ns1 = {1, 2, 3}; std::vector ns2 = {10, 11, 12}; - for (auto&& t : zip_longest(ns1, ns2)) { - *std::get<0>(t) = -1; - *std::get<1>(t) = -1; + for (auto&& [a, b] : zip_longest(ns1, ns2)) { + *a = -1; + *b = -1; } std::vector vc = {-1, -1, -1}; diff --git a/unique_everseen.hpp b/unique_everseen.hpp deleted file mode 100644 index 21b15d21..00000000 --- a/unique_everseen.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef ITER_UNIQUE_EVERSEEN_HPP_ -#define ITER_UNIQUE_EVERSEEN_HPP_ - -#include "internal/iterbase.hpp" -#include "filter.hpp" - -#include -#include -#include -#include -#include - -namespace iter { - namespace impl { - struct UniqueEverseenFn : Pipeable { - template - auto operator()(Container&& container) const { - using elem_type = impl::iterator_deref; - auto func = [elem_seen = std::unordered_set>()]( - const elem_type& e) mutable { - return elem_seen.insert(e).second; - }; - return filter(func, std::forward(container)); - } - }; - } - - constexpr impl::UniqueEverseenFn unique_everseen{}; -} - -#endif diff --git a/unique_justseen.hpp b/unique_justseen.hpp deleted file mode 100644 index caad5076..00000000 --- a/unique_justseen.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef ITER_UNIQUE_JUSTSEEN_HPP -#define ITER_UNIQUE_JUSTSEEN_HPP - -#include "groupby.hpp" -#include "imap.hpp" - -#include -#include - -namespace iter { - namespace impl { - struct UniqueJustseenFn : Pipeable { - template - auto operator()(Container&& container) const { - // explicit return type in lambda so reference types are preserved - return imap([](auto&& group) -> impl::iterator_deref { - return *std::begin(group.second); - }, groupby(std::forward(container))); - } - }; - } - constexpr impl::UniqueJustseenFn unique_justseen{}; -} - -#endif diff --git a/zip.hpp b/zip.hpp deleted file mode 100644 index ede3514e..00000000 --- a/zip.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef ITER_ZIP_HPP_ -#define ITER_ZIP_HPP_ - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class Zipped; - - template - Zipped zip_impl(TupleType&&, std::index_sequence); - } - - template - auto zip(Containers&&... containers); -} - -template -class iter::impl::Zipped { - private: - TupleType containers; - friend Zipped iter::impl::zip_impl( - TupleType&&, std::index_sequence); - - using ZipIterDeref = iterator_deref_tuple; - - Zipped(TupleType&& in_containers) : containers(std::move(in_containers)) {} - - public: - Zipped(Zipped&&) = default; - class Iterator : public std::iterator { - private: - iterator_tuple_type iters; - - public: - Iterator(iterator_tuple_type&& its) : iters(std::move(its)) {} - - Iterator& operator++() { - absorb(++std::get(this->iters)...); - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - if (sizeof...(Is) == 0) return false; - - bool results[] = { - true, (std::get(this->iters) != std::get(other.iters))...}; - return std::all_of( - std::begin(results), std::end(results), [](bool b) { return b; }); - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - - ZipIterDeref operator*() { - return ZipIterDeref{(*std::get(this->iters))...}; - } - - auto operator-> () -> ArrowProxy { - return {**this}; - } - }; - - Iterator begin() { - return {iterator_tuple_type{ - std::begin(std::get(this->containers))...}}; - } - - Iterator end() { - return {iterator_tuple_type{ - std::end(std::get(this->containers))...}}; - } -}; - -template -iter::impl::Zipped iter::impl::zip_impl( - TupleType&& in_containers, std::index_sequence) { - return {std::move(in_containers)}; -} - -template -auto iter::zip(Containers&&... containers) { - return impl::zip_impl( - std::tuple{std::forward(containers)...}, - std::index_sequence_for{}); -} - -#endif diff --git a/zip_longest.hpp b/zip_longest.hpp deleted file mode 100644 index ad4cafab..00000000 --- a/zip_longest.hpp +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef ITER_ZIP_LONGEST_HPP_ -#define ITER_ZIP_LONGEST_HPP_ - -#include "internal/iterbase.hpp" - -#include -#include -#include -#include - -namespace iter { - namespace impl { - template - class ZippedLongest; - - template - ZippedLongest zip_longest_impl( - TupleType&&, std::index_sequence); - } - - template - auto zip_longest(Containers&&... containers); -} - -template -class iter::impl::ZippedLongest { - private: - TupleType containers; - friend ZippedLongest zip_longest_impl( - TupleType&&, std::index_sequence); - - template - using OptType = - boost::optional>>; - - using ZipIterDeref = std::tuple...>; - - ZippedLongest(TupleType&& in_containers) - : containers(std::move(in_containers)) {} - - public: - ZippedLongest(ZippedLongest&&) = default; - class Iterator : public std::iterator { - private: - iterator_tuple_type iters; - iterator_tuple_type ends; - - public: - Iterator(iterator_tuple_type&& in_iters, - iterator_tuple_type&& in_ends) - : iters(std::move(in_iters)), ends(std::move(in_ends)) {} - - Iterator& operator++() { - // increment every iterator that's not already at - // the end - absorb(((std::get(this->iters) != std::get(this->ends)) - ? (++std::get(this->iters), 0) - : 0)...); - return *this; - } - - Iterator operator++(int) { - auto ret = *this; - ++*this; - return ret; - } - - bool operator!=(const Iterator& other) const { - if (sizeof...(Is) == 0) return false; - - bool results[] = { - false, (std::get(this->iters) != std::get(other.iters))...}; - return std::any_of( - std::begin(results), std::end(results), [](bool b) { return b; }); - } - - bool operator==(const Iterator& other) const { - return !(*this != other); - } - - ZipIterDeref operator*() { - return ZipIterDeref{ - ((std::get(this->iters) != std::get(this->ends)) - ? OptType{*std::get(this->iters)} - : OptType{})...}; - } - - auto operator-> () -> ArrowProxy { - return {**this}; - } - }; - - Iterator begin() { - return {iterator_tuple_type{ - std::begin(std::get(this->containers))...}, - iterator_tuple_type{ - std::end(std::get(this->containers))...}}; - } - - Iterator end() { - return {iterator_tuple_type{ - std::end(std::get(this->containers))...}, - iterator_tuple_type{ - std::end(std::get(this->containers))...}}; - } -}; - -template -iter::impl::ZippedLongest iter::impl::zip_longest_impl( - TupleType&& in_containers, std::index_sequence) { - return {std::move(in_containers)}; -} - -template -auto iter::zip_longest(Containers&&... containers) { - return impl::zip_longest_impl( - std::tuple{std::forward(containers)...}, - std::index_sequence_for{}); -} - -#endif