├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── check.yml │ ├── docs.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── changes └── .gitignore ├── docs ├── changelog.md ├── code_of_conduct.md ├── contributing.md ├── index.md ├── predicates.md ├── reference │ ├── async_iters.md │ ├── async_utils.md │ ├── iters.md │ ├── mapping_view.md │ ├── mappings.md │ ├── ordered_set.md │ ├── sequence_view.md │ ├── typing.md │ └── utils.md └── security.md ├── iters ├── __init__.py ├── async_iters.py ├── async_utils.py ├── async_wraps.py ├── constants.py ├── iters.py ├── mapping_view.py ├── mappings.py ├── ordered_set.py ├── py.typed ├── sequence_view.py ├── state.py ├── types.py ├── typing.py ├── utils.py └── wraps.py ├── mkdocs.yml ├── pyproject.toml └── tests ├── __init__.py └── test_state.py /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | indent_style = space 9 | indent_size = 4 10 | 11 | charset = utf-8 12 | 13 | [*.yaml] 14 | indent_size = 2 15 | 16 | [*.yml] 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # automatically detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nekitdev 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nekitdev] 2 | custom: https://nekit.dev/funding 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | 7 | labels: 8 | - A-dependencies 9 | - P-normal 10 | - S-triage 11 | 12 | schedule: 13 | interval: daily 14 | 15 | open-pull-requests-limit: 10 16 | 17 | - package-ecosystem: pip 18 | directory: "/" 19 | 20 | labels: 21 | - A-dependencies 22 | - P-normal 23 | - S-triage 24 | 25 | schedule: 26 | interval: daily 27 | 28 | open-pull-requests-limit: 10 29 | 30 | versioning-strategy: increase 31 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: check 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - "**" 11 | 12 | jobs: 13 | check: 14 | name: Check 15 | 16 | strategy: 17 | matrix: 18 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Setup Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: Install Poetry 32 | run: pipx install poetry 33 | 34 | - name: Configure Poetry 35 | run: poetry config virtualenvs.in-project true 36 | 37 | - name: Specify the version 38 | run: poetry env use python 39 | 40 | - name: Install dependencies 41 | run: poetry install 42 | 43 | - name: Run type checks 44 | run: poetry run mypy . 45 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | docs: 10 | name: Docs 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup Python 3.12 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: "3.12" 20 | 21 | - name: Install Poetry 22 | run: pipx install poetry 23 | 24 | - name: Configure Poetry 25 | run: poetry config virtualenvs.in-project true 26 | 27 | - name: Specify the version 28 | run: poetry env use python 29 | 30 | - name: Install dependencies 31 | run: poetry install --with docs 32 | 33 | - name: Pull 34 | run: git pull 35 | 36 | - name: Deploy the documentation 37 | run: poetry run mkdocs gh-deploy 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Python 3.12 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: "3.12" 22 | 23 | - name: Install Poetry 24 | run: pipx install poetry 25 | 26 | - name: Configure Poetry 27 | run: poetry config virtualenvs.in-project true 28 | 29 | - name: Specify the version 30 | run: poetry env use python 31 | 32 | - name: Build 33 | run: poetry build 34 | 35 | - name: Publish to PyPI 36 | run: poetry publish 37 | env: 38 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} 39 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - "**" 11 | 12 | jobs: 13 | test: 14 | name: Test 15 | 16 | strategy: 17 | matrix: 18 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Setup Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: Install Poetry 32 | run: pipx install poetry 33 | 34 | - name: Configure Poetry 35 | run: poetry config virtualenvs.in-project true 36 | 37 | - name: Specify the version 38 | run: poetry env use python 39 | 40 | - name: Install dependencies 41 | run: poetry install 42 | 43 | - name: Run tests 44 | run: poetry run pytest --cov-report xml 45 | 46 | - name: Upload coverage 47 | uses: codecov/codecov-action@v4 48 | with: 49 | files: "./coverage.xml" 50 | token: ${{ secrets.CODECOV_TOKEN }} 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled bytecode / extensions 2 | 3 | __pycache__/ 4 | 5 | *.pyc 6 | *.pyo 7 | *.pyd 8 | 9 | # distribution / packaging 10 | 11 | build/ 12 | dist/ 13 | wheels/ 14 | 15 | *.egg-info/ 16 | *.egg 17 | 18 | MANIFEST 19 | 20 | *.manifest 21 | *.spec 22 | 23 | # coverage / reports 24 | 25 | .coverage 26 | coverage/ 27 | 28 | coverage.* 29 | 30 | # testing 31 | 32 | .hypothesis/ 33 | 34 | .pytest_cache/ 35 | 36 | # IPython 37 | 38 | profile_default/ 39 | ipython_config.py 40 | 41 | # local packages 42 | 43 | __pypackages__/ 44 | 45 | # virtual environments 46 | 47 | .python-version 48 | 49 | .env 50 | env/ 51 | env.bak/ 52 | 53 | .venv 54 | venv/ 55 | venv.bak/ 56 | 57 | # documentation 58 | 59 | site/ 60 | 61 | # translations 62 | 63 | *.mo 64 | *.pot 65 | 66 | # type checkers 67 | 68 | .mypy_cache/ 69 | .pytype/ 70 | 71 | # formatters 72 | 73 | .ruff_cache/ 74 | 75 | # cython debugging information 76 | 77 | cython_debug/ 78 | 79 | # various logs 80 | 81 | *.log 82 | 83 | # remove lock file if it should be included 84 | poetry.lock 85 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.2.2 4 | hooks: 5 | - id: ruff 6 | name: Run the Ruff linter 7 | types_or: [python, pyi] 8 | args: [--fix] 9 | - id: ruff-format 10 | name: Run the Ruff formatter 11 | types_or: [python, pyi] 12 | 13 | - repo: https://github.com/python-poetry/poetry 14 | rev: 1.7.1 15 | hooks: 16 | - id: poetry-lock 17 | stages: [push] 18 | name: Run Poetry lock hook 19 | args: [--no-update] 20 | - id: poetry-check 21 | stages: [push] 22 | name: Run Poetry check hook 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | ## 0.18.0 (2024-04-23) 6 | 7 | ### Changes 8 | 9 | - Migrated to the latest version of the `wraps` library. 10 | 11 | ## 0.17.0 (2024-03-23) 12 | 13 | ### Features 14 | 15 | - Added `at_most_one` and `exactly_one` methods. 16 | 17 | ### Changes 18 | 19 | - Improved error messages. 20 | 21 | ## 0.16.3 (2024-03-21) 22 | 23 | ### Internal 24 | 25 | - `is_marker` and `is_no_default` now take `Any` as the argument. 26 | 27 | ## 0.16.2 (2024-03-20) 28 | 29 | ### Internal 30 | 31 | - Improved typing. 32 | 33 | ## 0.16.1 (2024-02-26) 34 | 35 | No significant changes. 36 | 37 | ## 0.16.0 (2024-02-25) 38 | 39 | ### Internal 40 | 41 | - Improved typing. 42 | 43 | ## 0.15.0 (2023-12-01) 44 | 45 | ### Changes 46 | 47 | - Changed `reduce` and `reduce_await` on `Iter[T]` and `AsyncIter[T]` to return `Option[T]` 48 | instead of erroring on empty iterators. 49 | 50 | ### Internal 51 | 52 | - Changed `variable is marker` and `variable is no_default` 53 | to `is_marker(variable)` and `is_no_default(variable)` respectively. 54 | 55 | ## 0.14.1 (2023-12-01) 56 | 57 | No significant changes. 58 | 59 | ## 0.14.0 (2023-12-01) 60 | 61 | ### Internal 62 | 63 | - Migrated to Python 3.8. 64 | 65 | ## 0.13.1 (2023-05-24) 66 | 67 | ### Fixes 68 | 69 | - Fixed `final` import to be compatible with Python 3.7. 70 | 71 | ## 0.13.0 (2023-05-21) 72 | 73 | ### Internal 74 | 75 | - Migrated to using `typing-aliases` library. 76 | 77 | ## 0.12.0 (2023-05-10) 78 | 79 | ### Changes 80 | 81 | - This release contains lots of breaking changes. Please refer to the API documentation. 82 | 83 | ## 0.11.0 (2023-01-29) 84 | 85 | ### Internal 86 | 87 | - `async-extensions` is now used instead of reimplementing `collect_iterable` functionality. 88 | 89 | ## 0.10.0 (2023-01-08) 90 | 91 | ### Internal 92 | 93 | - Marked the internals of the `OrderedSet[Q]` private. 94 | 95 | ## 0.9.0 (2023-01-07) 96 | 97 | ### Features 98 | 99 | - Added `collect_iter` method for `AsyncIter[T]` and `Iter[T]`. 100 | 101 | ## 0.8.0 (2022-12-22) 102 | 103 | ### Features 104 | 105 | - Added `into_iter` method for `AsyncIter[T]`. 106 | - Added `into_async_iter` method for `Iter[T]`. 107 | 108 | ## 0.7.0 (2022-12-20) 109 | 110 | ### Features 111 | 112 | - Added `OrderedSet[Q]` type within the `iters.ordered_set` module. 113 | - Added `ordered_set` method to `Iter[T]` and `AsyncIter[T]`. 114 | 115 | ## 0.6.0 (2022-11-08) 116 | 117 | ### Internal 118 | 119 | - Migrated to using [`named`](https://github.com/nekitdev/named) and 120 | [`solus`](https://github.com/nekitdev/solus) packages instead of 121 | reimplementing their functionality. ([#18](https://github.com/nekitdev/iters/pull/18)) 122 | 123 | ## 0.5.0 (2022-10-11) 124 | 125 | ### Changes 126 | 127 | - Functions taking `Predicate[T]` have been updated to accept `OptionalPredicate[T]`. 128 | Passing `None` as an argument is identical to passing `bool`. 129 | 130 | There are three functions which do not accept `None`, though: 131 | - `drop_while` 132 | - `skip_while` 133 | - `take_while` 134 | 135 | This choice is motivated by the fact that it does not make much sense to `do_while(None)`. 136 | 137 | ## 0.4.0 (2022-10-08) 138 | 139 | ### Changes 140 | 141 | - The following functions have been changed: 142 | - `async_iter` is now an alias of `AsyncIter`; 143 | - `iter` is now an alias of `Iter`; 144 | - `reversed` is now an alias of `iter.reversed`. 145 | 146 | ## 0.3.0 (2022-08-17) 147 | 148 | ### Changes 149 | 150 | - Changed functions of various arity returning `Awaitable[T]` to async functions returning `T`. 151 | ([#15](https://github.com/nekitdev/iters/pull/15)) 152 | 153 | ## 0.2.0 (2022-08-15) 154 | 155 | ### Changes 156 | 157 | - Added `await async_iter`, equivalent to `await async_iter.list()`. 158 | 159 | ## 0.1.0 (2022-08-01) 160 | 161 | Initial release. 162 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official email address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement to 63 | [conduct@nekit.dev][Email]. 64 | 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][Home], 119 | version 2.1, available at 120 | [https://contributor-covenant.org/version/2/1/code_of_conduct][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's Code of Conduct enforcement ladder][Mozilla Code of Conduct]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://contributor-covenant.org/translations][Translations]. 128 | 129 | [Email]: mailto:conduct@nekit.dev 130 | 131 | [Home]: https://contributor-covenant.org/ 132 | [v2.1]: https://contributor-covenant.org/version/2/1/code_of_conduct 133 | 134 | [Mozilla Code of Conduct]: https://github.com/mozilla/diversity 135 | 136 | [FAQ]: https://contributor-covenant.org/faq 137 | [Translations]: https://contributor-covenant.org/translations 138 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekitdev/iters/07cee611790a7396fb2e74f6698c1cca97f83c03/CONTRIBUTING.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present, Nikita Tikhonov (nekitdev) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `iters` 2 | 3 | [![License][License Badge]][License] 4 | [![Version][Version Badge]][Package] 5 | [![Downloads][Downloads Badge]][Package] 6 | [![Discord][Discord Badge]][Discord] 7 | 8 | [![Documentation][Documentation Badge]][Documentation] 9 | [![Check][Check Badge]][Actions] 10 | [![Test][Test Badge]][Actions] 11 | [![Coverage][Coverage Badge]][Coverage] 12 | 13 | > *Composable external iteration.* 14 | 15 | If you have found yourself with a *collection* of some kind, and needed to perform 16 | an operation on the elements of said collection, you will quickly run into *iterators*. 17 | Iterators are heavily used in idiomatic Python code, so becoming familiar with them is essential. 18 | 19 | ## Installing 20 | 21 | **Python 3.8 or above is required.** 22 | 23 | ### pip 24 | 25 | Installing the library with `pip` is quite simple: 26 | 27 | ```console 28 | $ pip install iters 29 | ``` 30 | 31 | Alternatively, the library can be installed from source: 32 | 33 | ```console 34 | $ git clone https://github.com/nekitdev/iters.git 35 | $ cd iters 36 | $ python -m pip install . 37 | ``` 38 | 39 | ### poetry 40 | 41 | You can add `iters` as a dependency with the following command: 42 | 43 | ```console 44 | $ poetry add iters 45 | ``` 46 | 47 | Or by directly specifying it in the configuration like so: 48 | 49 | ```toml 50 | [tool.poetry.dependencies] 51 | iters = "^0.18.0" 52 | ``` 53 | 54 | Alternatively, you can add it directly from the source: 55 | 56 | ```toml 57 | [tool.poetry.dependencies.iters] 58 | git = "https://github.com/nekitdev/iters.git" 59 | ``` 60 | 61 | ## Examples 62 | 63 | ### Simple 64 | 65 | Squaring only even numbers in some sequence: 66 | 67 | ```python 68 | from iters import iter 69 | 70 | 71 | def is_even(value: int) -> bool: 72 | return not value % 2 73 | 74 | 75 | def square(value: int) -> int: 76 | return value * value 77 | 78 | 79 | numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 80 | 81 | result = iter(numbers).filter(is_even).map(square).list() 82 | 83 | print(result) # [0, 4, 16, 36, 64] 84 | ``` 85 | 86 | ### Asynchronous 87 | 88 | Asynchronous iteration is fully supported by `iters`, and its API is similar to its 89 | synchronous counterpart. 90 | 91 | ## Documentation 92 | 93 | You can find the documentation [here][Documentation]. 94 | 95 | ## Support 96 | 97 | If you need support with the library, you can send an [email][Email] 98 | or refer to the official [Discord server][Discord]. 99 | 100 | ## Changelog 101 | 102 | You can find the changelog [here][Changelog]. 103 | 104 | ## Security Policy 105 | 106 | You can find the Security Policy of `iters` [here][Security]. 107 | 108 | ## Contributing 109 | 110 | If you are interested in contributing to `iters`, make sure to take a look at the 111 | [Contributing Guide][Contributing Guide], as well as the [Code of Conduct][Code of Conduct]. 112 | 113 | ## License 114 | 115 | `iters` is licensed under the MIT License terms. See [License][License] for details. 116 | 117 | [Email]: mailto:support@nekit.dev 118 | 119 | [Discord]: https://nekit.dev/discord 120 | 121 | [Actions]: https://github.com/nekitdev/iters/actions 122 | 123 | [Changelog]: https://github.com/nekitdev/iters/blob/main/CHANGELOG.md 124 | [Code of Conduct]: https://github.com/nekitdev/iters/blob/main/CODE_OF_CONDUCT.md 125 | [Contributing Guide]: https://github.com/nekitdev/iters/blob/main/CONTRIBUTING.md 126 | [Security]: https://github.com/nekitdev/iters/blob/main/SECURITY.md 127 | 128 | [License]: https://github.com/nekitdev/iters/blob/main/LICENSE 129 | 130 | [Package]: https://pypi.org/project/iters 131 | [Coverage]: https://codecov.io/gh/nekitdev/iters 132 | [Documentation]: https://nekitdev.github.io/iters 133 | 134 | [Discord Badge]: https://img.shields.io/badge/chat-discord-5865f2 135 | [License Badge]: https://img.shields.io/pypi/l/iters 136 | [Version Badge]: https://img.shields.io/pypi/v/iters 137 | [Downloads Badge]: https://img.shields.io/pypi/dm/iters 138 | 139 | [Documentation Badge]: https://github.com/nekitdev/iters/workflows/docs/badge.svg 140 | [Check Badge]: https://github.com/nekitdev/iters/workflows/check/badge.svg 141 | [Test Badge]: https://github.com/nekitdev/iters/workflows/test/badge.svg 142 | [Coverage Badge]: https://codecov.io/gh/nekitdev/iters/branch/main/graph/badge.svg 143 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting 4 | 5 | Thank you for taking the time to responsibly disclose any problems you find. 6 | 7 | **Do not file public issues as they are open for everyone to see!** 8 | 9 | All security vulnerabilities in `iters` should be reported by email 10 | to [security@nekit.dev][Security Email]. 11 | Your report will be acknowledged within 24 hours, and you will receive a more 12 | detailed response within 48 hours indicating the next steps in handling your report. 13 | 14 | You can encrypt your report using our public key: 15 | [`6AF9DDF87B37BBE6E83F5DF2B8F5B86F98F12F5E`][Security Key]. 16 | This key is also available on [MIT's Key Server][MIT Key Server] 17 | and [reproduced below](#security-key). 18 | 19 | After the initial reply to your report, the core team will try to keep you 20 | informed of the progress being made towards a fix and official announcement. 21 | These updates will be sent at least every five days. In reality, this is 22 | more likely to be every 24-48 hours. 23 | 24 | ## Disclosure Policy 25 | 26 | `iters` has a 5-step disclosure process: 27 | 28 | 1. The security report is received and is assigned a primary handler. 29 | This person will coordinate the fix and release process. 30 | 31 | 2. The problem is confirmed and a list of all affected versions is determined. 32 | 33 | 3. Code is audited to find any potential similar problems. 34 | 35 | 4. Fixes are prepared for all releases which are still under maintenance. 36 | These fixes are not committed to the public repository but rather 37 | held locally pending the announcement. 38 | 39 | 5. On the embargo date, the changes are pushed to the public repository 40 | and new builds are deployed. 41 | 42 | This process can take some time, especially when coordination is required 43 | with maintainers of other projects. Every effort will be made to handle 44 | the issue in as timely a manner as possible, however it is important that 45 | we follow the release process above to ensure that the disclosure is handled 46 | in a consistent manner. 47 | 48 | ## Security Key 49 | 50 | ```text 51 | -----BEGIN PGP PUBLIC KEY BLOCK----- 52 | 53 | mQINBGVV4JcBEAC7PTswfzA2iMTVSig51NVDV08XABrR01qslTfhIVw6Uwr2iCoY 54 | F+hkNn3++pgoF95Fx/iREDFV/AG4GGKl1GbAI3YD6aOoh0FGWtxg3MMa3oHjRUZs 55 | f0VwKk8sA5d21V05OiMuptAqxXuLrdR5SINtxKE10H6K9o22988VOmWUCIEaxKM5 56 | M5HCfhe8fl5pKpdIf3i1F073qset4DXGkvm/v+dWYHPvv0NlHhnJ5Lcaq4aTvkEg 57 | y2NhDobR4VpdP1aQZbEONussUaKLxBTBJN5NNnf7SI1qVYcaglYrXM7uQGXuL32X 58 | XAILtOCM0LO2059Z7ZMkI6lkkbei1j08j2Tha/1GvN2rIClNyV912GvAQhzlwhdT 59 | Wmk+ymrwbed7MkRW3IB3b1zFb7Dhz6a5yBS8iT5ikkrGaR/i7O3V/DS02j7Rao2k 60 | nfXIncuBuXSXb1pIhCuYuV6VYBgFWfpKDjOzEy83h3DSI/jrR31e6aiBes+fyFRG 61 | IuoFRTsaMq2T9M5F6pDvmtoexHxXevYoSt+7DURY1pSWnk4MjZUj7yDFPSyfPleZ 62 | aNq/3aGQt7vnY5QgyGjKaX5jSVuNEKsUlhrKUWt9weoJrF5ZyYHY0RPg1q1Fz0mY 63 | Z7QWeaKA0uOeziG0bHf6yNEzxnaYCfi09/WOL4GH0pBsdubNHpWno/D6PwARAQAB 64 | tC9OaWtpdGEgVGlraG9ub3YgKHNlY3VyaXR5KSA8c2VjdXJpdHlAbmVraXQuZGV2 65 | PokCTgQTAQoAOBYhBGr53fh7N7vm6D9d8rj1uG+Y8S9eBQJlVeCXAhsDBQsJCAcC 66 | BhUKCQgLAgQWAgMBAh4BAheAAAoJELj1uG+Y8S9ed4kP+wYE1OZtcWoRSK2Xqvaf 67 | P5+YcXC1vdCZ16depb6kGOR91G9eEMJhSDlSzzUzOmkvT4TknZi/Y17m9TvQccET 68 | SwgWvDs9XwMby24mkxD1iYu2uIZXXhRbIKJPi4EpGgamEveYLLTd0L8yX2l/YXuq 69 | VcM4vqgRtnovlW+cCUmmtpRcb+Ldfxu2RixjnG4fznzzlMOnU0zpWUMBqH+mSyfH 70 | RmY5vgOR/adgQcIviQdhRPMC4TAa3GNdTd2Qpxo3xelum15yLKxkm/EvBSPsL1fj 71 | JQBYnZFk4KBKNiXXYwWuU0mpOx1TMtYPVnHer17QL0vXfsmVNkXVzucvrNfHpFc9 72 | hXzmm5wHwMrGClyQBA6sDWDfQOKYibQTcKzyJr2Gl31luNPSRchzC4lbosLzRkqh 73 | Yh5dco+ITiKDe7g54w+Fy+KdumwN/GvBlQptGIpaxA1+xAbNVs+fDo+WrQEL+AZO 74 | OQR91YUsjIdvVdk5BcgUYvEe2YyyMZ7LSqWACpRknqz5FNcdmO2bz7jl732EYLRm 75 | Q90oSG6xcIFuPZRNVIUJds9Gg2u1PBV5z0vnFGiJ6NK6DrYYecMKU9uAQUZcSW8v 76 | +fn92V0DkVeOfeMbq4yytZx5W4VrsWT1XyfjTzg867jzmo1JmZQeZ4KXh7AYRlC6 77 | n8NwYZ13+pUFeTPm9jCwJMrGuQINBGVV4JcBEACg5zXucth9KIdryYUxyBgA7Ist 78 | hJmyxtSHSiKRFOiQBmQqHeQgDdCnBeDw+cb+8wB4NL3PNw5xHKRvQGTWaBTV1IPf 79 | CV3P2c/sZLDCU8PNMu3lsmEbN2ippOiJi1fw478EGlNity8ktI+TEhsdniypKoiw 80 | DNf3wdawWiraODM9KuYplcsnFHl5r97BjHR0EbOOKkTc4PwysQ7WVHZ/nwGzNb5T 81 | CI7A/TF0RTL/Wkdz7WZM7r5BELz+z0ksjsS8eMObtm/uG4lfAmbIGohPTlir4WWL 82 | /GYZpAjvv/6zNaydMpY3uQKrdqN05j10uYnkbsclwSBBbRovFBRWEInbO0cqpzc0 83 | JiWt4U91F6UNbSDPo3KaiDjJXDb7cr4gQv0C1T9LtmKSfY/JVcUj7csGXslOAvXf 84 | z08iDCJu3zj7QjZPKA1/MxmTo88hAvhHlOYrXaaRjzXt6r9+qdDxVYJGe9K3LkJS 85 | 9Yc0U9xBGAfzw9Ebs/ZPDtjgupPHJXq6VBSndU3c53jr7SEZBIFMPg75CeJJ6IgH 86 | A4zwW1uzalZi3mYWWCKiGhDBPOo5yGwKocxMzSuerlMW21fjhOMymSKVksteJlmZ 87 | Ay6ExDNOK663V6iFnsn4iIFbE1jOznHhSsbyKqQ/QukpMqAyrQVSNyutXVl0VuW0 88 | ZsZeFff7ScnrTgB7/QARAQABiQI2BBgBCgAgFiEEavnd+Hs3u+boP13yuPW4b5jx 89 | L14FAmVV4JcCGwwACgkQuPW4b5jxL15jNw/9EQkahEieTABEKAKxGetODA7HTiNR 90 | cM3aKgDU0msYjfgfAi+wQzx/8k8Yf/Kjma6JqsksCj0ygFkXS87tOAUfJTpgmKVS 91 | V3XaDXFwTcdG0+/Cx5RllduJmnLTLSuvm2uxu7ErPGtnYWBw88nmQ/8f9nkmvCsY 92 | CuF6DHAUNzTLgerFKSGNMwOv6kKBCgNkstclcHp5YbzssN1w34dPV/swuCjc+6JM 93 | nW5WuPD3R2Y9522Ov/bEwr9raFf3R5A6ETK4GOZUqNmPG4MJgbyiJlk96TuF06mO 94 | nFpKnBtxD+t20jAFTMRokyiQT65X8KnrpT8CpTJ6xzmBO5IYGhUSqt3CH/YzwqRa 95 | v9FTJ/qSPM5OXPH4pK7VzNDVhEPQhLAGENLwOnasnXXGvj/MQIRYyjGAXQfB34a7 96 | z0x4rQ+fyaody6BW10KJBQuRrB3dPaOPU3LU/4TxzyudDxiOJGiWAlw56a2lviEG 97 | JExMJrSvP5kiCfPlLZiLfqaw2ZYeyosnv8bmC4H2Sr9IEggtCyrzNOoJQx+w/f/L 98 | 6a14Cshc3UYLC+0yh74Mc5vUu2SfwI6zSevjI1LWj4qc592J/q3QNHiJN9F60tyP 99 | r46uNM25Y+C5qgVneqRjHmWSIdOvYXcBTLj03eDiQHCJz3ZT6ztLwQxQ800MS1Yd 100 | pbmAGLbBB2TBok4= 101 | =Ir8m 102 | -----END PGP PUBLIC KEY BLOCK----- 103 | ``` 104 | 105 | ## Attribution 106 | 107 | This Security Policy is adapted from [Rust's Security Policy][Rust Security Policy]. 108 | 109 | [Security Email]: mailto:security@nekit.dev 110 | [Security Key]: https://nekit.dev/keys/security 111 | [MIT Key Server]: https://pgp.mit.edu/pks/lookup?op=index&search=0x6AF9DDF87B37BBE6E83F5DF2B8F5B86F98F12F5E 112 | [Rust Security Policy]: https://rust-lang.org/policies/security 113 | -------------------------------------------------------------------------------- /changes/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | --8<-- "CHANGELOG.md" 2 | -------------------------------------------------------------------------------- /docs/code_of_conduct.md: -------------------------------------------------------------------------------- 1 | --8<-- "CODE_OF_CONDUCT.md" 2 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | --8<-- "CONTRIBUTING.md" 2 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --8<-- "README.md" 2 | -------------------------------------------------------------------------------- /docs/predicates.md: -------------------------------------------------------------------------------- 1 | # Predicates 2 | 3 | `iters` defines all `predicate` arguments as [`OptionalPredicate[T]`][iters.typing.OptionalPredicate] 4 | where `T` is the item type of the iterable. 5 | -------------------------------------------------------------------------------- /docs/reference/async_iters.md: -------------------------------------------------------------------------------- 1 | ::: iters.async_iters 2 | -------------------------------------------------------------------------------- /docs/reference/async_utils.md: -------------------------------------------------------------------------------- 1 | ::: iters.async_utils -------------------------------------------------------------------------------- /docs/reference/iters.md: -------------------------------------------------------------------------------- 1 | ::: iters.iters 2 | -------------------------------------------------------------------------------- /docs/reference/mapping_view.md: -------------------------------------------------------------------------------- 1 | ::: iters.mapping_view 2 | -------------------------------------------------------------------------------- /docs/reference/mappings.md: -------------------------------------------------------------------------------- 1 | ::: iters.mappings 2 | -------------------------------------------------------------------------------- /docs/reference/ordered_set.md: -------------------------------------------------------------------------------- 1 | ::: iters.ordered_set 2 | -------------------------------------------------------------------------------- /docs/reference/sequence_view.md: -------------------------------------------------------------------------------- 1 | ::: iters.sequence_view 2 | -------------------------------------------------------------------------------- /docs/reference/typing.md: -------------------------------------------------------------------------------- 1 | ::: iters.typing 2 | -------------------------------------------------------------------------------- /docs/reference/utils.md: -------------------------------------------------------------------------------- 1 | ::: iters.utils 2 | -------------------------------------------------------------------------------- /docs/security.md: -------------------------------------------------------------------------------- 1 | --8<-- "SECURITY.md" 2 | -------------------------------------------------------------------------------- /iters/__init__.py: -------------------------------------------------------------------------------- 1 | """Composable external iteration. 2 | 3 | If you have found yourself with a *collection* of some kind, and needed to perform 4 | an operation on the elements of said collection, you will quickly run into *iterators*. 5 | Iterators are heavily used in idiomatic Python code, so becoming familiar with them is essential. 6 | """ 7 | 8 | __description__ = "Composable external iteration." 9 | __url__ = "https://github.com/nekitdev/iters" 10 | 11 | __title__ = "iters" 12 | __author__ = "nekitdev" 13 | __license__ = "MIT" 14 | __version__ = "0.18.0" 15 | 16 | from iters.async_iters import ( 17 | AsyncIter, 18 | async_iter, 19 | async_next, 20 | async_next_unchecked, 21 | standard_async_iter, 22 | standard_async_next, 23 | wrap_async_iter, 24 | ) 25 | from iters.iters import Iter, iter, reversed, standard_iter, standard_reversed, wrap_iter 26 | from iters.mapping_view import MappingView, mapping_view 27 | from iters.ordered_set import OrderedSet, ordered_set, ordered_set_unchecked 28 | from iters.sequence_view import SequenceView, sequence_view 29 | from iters.state import State, stateful 30 | 31 | __all__ = ( 32 | # the async iterator type 33 | "AsyncIter", 34 | # an alias of the previous type 35 | "async_iter", 36 | # next functions; checked version works on any iterator, unchecked assumes async iteration 37 | "async_next", 38 | "async_next_unchecked", 39 | # since we are "shadowing" standard functions 40 | "standard_async_iter", 41 | "standard_async_next", 42 | # wrap results of function calls into async iterators 43 | "wrap_async_iter", 44 | # the iterator type 45 | "Iter", 46 | # an alias of the previous type 47 | "iter", 48 | # an alias of `iter.reversed` 49 | "reversed", 50 | # since we are shadowing standard functions 51 | "standard_iter", 52 | "standard_reversed", 53 | # wrap results of function calls into iterators 54 | "wrap_iter", 55 | # ordered set 56 | "OrderedSet", 57 | "ordered_set", 58 | "ordered_set_unchecked", 59 | # mapping view 60 | "MappingView", 61 | "mapping_view", 62 | # sequence view 63 | "SequenceView", 64 | "sequence_view", 65 | # state 66 | "State", 67 | "stateful", 68 | ) 69 | -------------------------------------------------------------------------------- /iters/async_wraps.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncIterator, TypeVar 2 | 3 | from typing_aliases import AnyIterable, AsyncBinary, AsyncUnary, Binary, Unary 4 | from wraps.primitives.option import NULL, Option, Some 5 | from wraps.primitives.result import Error, Ok, Result 6 | 7 | from iters.async_utils import async_chain, async_iter, async_next_unchecked 8 | from iters.types import is_marker, marker 9 | 10 | S = TypeVar("S") 11 | T = TypeVar("T") 12 | U = TypeVar("U") 13 | 14 | 15 | async def async_scan( 16 | state: S, function: Binary[S, T, Option[U]], iterable: AnyIterable[T] 17 | ) -> AsyncIterator[U]: 18 | async for item in async_iter(iterable): 19 | option = function(state, item) 20 | 21 | if option.is_some(): 22 | yield option.unwrap() 23 | 24 | else: 25 | break 26 | 27 | 28 | async def async_scan_await( 29 | state: S, function: AsyncBinary[S, T, Option[U]], iterable: AnyIterable[T] 30 | ) -> AsyncIterator[U]: 31 | async for item in async_iter(iterable): 32 | option = await function(state, item) 33 | 34 | if option.is_some(): 35 | yield option.unwrap() 36 | 37 | else: 38 | break 39 | 40 | 41 | async def async_filter_map_option( 42 | function: Unary[T, Option[U]], iterable: AnyIterable[T] 43 | ) -> AsyncIterator[U]: 44 | async for item in async_iter(iterable): 45 | option = function(item) 46 | 47 | if option.is_some(): 48 | yield option.unwrap() 49 | 50 | 51 | async def async_filter_map_option_await( 52 | function: AsyncUnary[T, Option[U]], iterable: AnyIterable[T] 53 | ) -> AsyncIterator[U]: 54 | async for item in async_iter(iterable): 55 | option = await function(item) 56 | 57 | if option.is_some(): 58 | yield option.unwrap() 59 | 60 | 61 | async def async_exactly_one(iterable: AnyIterable[T]) -> Result[T, Option[AsyncIterator[T]]]: 62 | iterator = async_iter(iterable) 63 | 64 | first = await async_next_unchecked(iterator, marker) 65 | 66 | if is_marker(first): 67 | return Error(NULL) 68 | 69 | second = await async_next_unchecked(iterator, marker) 70 | 71 | if not is_marker(second): 72 | return Error(Some(async_chain((first, second), iterator))) 73 | 74 | return Ok(first) 75 | 76 | 77 | async def async_at_most_one(iterable: AnyIterable[T]) -> Result[Option[T], AsyncIterator[T]]: 78 | iterator = async_iter(iterable) 79 | 80 | first = await async_next_unchecked(iterator, marker) 81 | 82 | if is_marker(first): 83 | return Ok(NULL) 84 | 85 | second = await async_next_unchecked(iterator, marker) 86 | 87 | if not is_marker(second): 88 | return Error(async_chain((first, second), iterator)) 89 | 90 | return Ok(Some(first)) 91 | -------------------------------------------------------------------------------- /iters/constants.py: -------------------------------------------------------------------------------- 1 | __all__ = ("EMPTY_BYTES", "EMPTY_STRING", "DEFAULT_START", "DEFAULT_STEP") 2 | 3 | EMPTY_BYTES = bytes() 4 | """An empty [`bytes`][bytes] instance.""" 5 | EMPTY_STRING = str() 6 | """An empty [`str`][str] instance.""" 7 | 8 | DEFAULT_START = 0 9 | """The default start value for counting iterators.""" 10 | DEFAULT_STEP = 1 11 | """The default step value for counting iterators.""" 12 | -------------------------------------------------------------------------------- /iters/mapping_view.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Iterator, Mapping, TypeVar, final 2 | 3 | from attrs import frozen 4 | from typing_extensions import Self 5 | from wraps.wraps.option import wrap_option_on 6 | 7 | __all__ = ("MappingView", "mapping_view") 8 | 9 | K = TypeVar("K") 10 | V = TypeVar("V") 11 | 12 | wrap_key_error = wrap_option_on(KeyError) 13 | 14 | 15 | @final 16 | @frozen() 17 | class MappingView(Mapping[K, V]): 18 | """Represents view over mappings.""" 19 | 20 | mapping: Mapping[K, V] 21 | """The mapping to view.""" 22 | 23 | def __iter__(self) -> Iterator[K]: 24 | yield from self.mapping 25 | 26 | def __getitem__(self, key: K) -> V: 27 | return self.mapping[key] 28 | 29 | def __contains__(self, key: Any) -> bool: 30 | return key in self.mapping 31 | 32 | def __len__(self) -> int: 33 | return len(self.mapping) 34 | 35 | @wrap_key_error 36 | def get_option(self, key: K) -> V: 37 | return self[key] 38 | 39 | def copy(self) -> Self: 40 | return type(self)(self) 41 | 42 | 43 | def mapping_view(mapping: Mapping[K, V]) -> MappingView[K, V]: 44 | """Returns the [`MappingView[K, V]`][iters.mapping_view.MappingView] over the given mapping. 45 | 46 | Arguments: 47 | mapping: The mapping to view into. 48 | 49 | Returns: 50 | The view over the mapping. 51 | """ 52 | return MappingView(mapping) 53 | -------------------------------------------------------------------------------- /iters/mappings.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Hashable, Mapping, TypeVar, overload 2 | 3 | from typing_aliases import StringDict, StringMapping 4 | 5 | __all__ = ("merge",) 6 | 7 | Q = TypeVar("Q", bound=Hashable) 8 | T = TypeVar("T") 9 | 10 | 11 | @overload 12 | def merge(*mappings: Mapping[Q, T]) -> Dict[Q, T]: ... 13 | 14 | 15 | @overload 16 | def merge(*mappings: StringMapping[T], **keywords: T) -> StringDict[T]: ... 17 | 18 | 19 | def merge(*mappings: Mapping[Any, Any], **keywords: Any) -> Dict[Any, Any]: 20 | """Merges multiple `mappings` and `keywords` into one dictionary. 21 | 22 | Arguments: 23 | *mappings: Mappings to merge. 24 | **keywords: Keywords to add to the merged dictionary. 25 | 26 | Returns: 27 | The merged dictionary. 28 | """ 29 | merged: Dict[Any, Any] = {} 30 | 31 | for mapping in mappings: 32 | merged.update(mapping) 33 | 34 | merged.update(keywords) 35 | 36 | return merged 37 | -------------------------------------------------------------------------------- /iters/ordered_set.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import ( 4 | Any, 5 | Dict, 6 | Hashable, 7 | Iterable, 8 | Iterator, 9 | List, 10 | MutableSet, 11 | Optional, 12 | Sequence, 13 | TypeVar, 14 | Union, 15 | overload, 16 | ) 17 | 18 | from mixed_methods import mixed_method 19 | from named import get_type_name 20 | from typing_aliases import AnySet, is_instance, is_sized, is_slice 21 | from typing_extensions import TypeIs 22 | from wraps.wraps.option import wrap_option_on 23 | 24 | __all__ = ("OrderedSet", "ordered_set", "ordered_set_unchecked") 25 | 26 | Q = TypeVar("Q", bound=Hashable) 27 | R = TypeVar("R", bound=Hashable) 28 | 29 | SLICE_ALL = slice(None) 30 | LAST = ~0 31 | """The last index.""" 32 | 33 | EMPTY_REPRESENTATION = "{}()" 34 | ITEMS_REPRESENTATION = "{}({})" 35 | 36 | 37 | ITEM_NOT_IN_ORDERED_SET = "item {!r} is not in the ordered set" 38 | item_not_in_ordered_set = ITEM_NOT_IN_ORDERED_SET.format 39 | 40 | 41 | T = TypeVar("T") 42 | 43 | 44 | def is_sequence(iterable: Iterable[T]) -> TypeIs[Sequence[T]]: 45 | return is_instance(iterable, Sequence) 46 | 47 | 48 | def is_any_set(iterable: Iterable[T]) -> TypeIs[AnySet[T]]: 49 | return is_instance(iterable, AnySet) 50 | 51 | 52 | wrap_index_error = wrap_option_on(IndexError) 53 | 54 | 55 | class OrderedSet(MutableSet[Q], Sequence[Q]): 56 | """Represents ordered sets, i.e. mutable hash sets that preserve insertion order. 57 | 58 | The implementation is rather simple: it uses an *array* to store the items 59 | and a *hash map* to store the indices of the items in the array along with ensuring uniqueness. 60 | 61 | The complexity of the operations assumes that *hash maps* 62 | have `O(1)` *insertion*, *lookup* and *deletion* as well 63 | as that *arrays* have `O(1)` *by-index lookup* and *length checking*. 64 | 65 | It is assumed that *clearing* is `O(n)`, where `n` is the number of elements. 66 | """ 67 | 68 | def __init__(self, iterable: Iterable[Q] = ()) -> None: 69 | self._items: List[Q] = [] 70 | self._item_to_index: Dict[Q, int] = {} 71 | 72 | self.update(iterable) 73 | 74 | @classmethod 75 | def create(cls, iterable: Iterable[R] = ()) -> OrderedSet[R]: 76 | """Creates an ordered set from an iterable. 77 | 78 | Complexity: 79 | `O(n)`, where `n` is the length of the iterable. 80 | 81 | Example: 82 | ```python 83 | >>> array = [0, 1, 1, 0, 1, 1, 1, 0] 84 | >>> order_set = ordered_set.create(array) 85 | >>> order_set 86 | OrderedSet([0, 1]) 87 | ``` 88 | 89 | Arguments: 90 | iterable: The iterable to create the ordered set from. 91 | 92 | Returns: 93 | The created ordered set. 94 | """ 95 | return cls(iterable) # type: ignore[arg-type, return-value] 96 | 97 | @classmethod 98 | def create_unchecked(cls, iterable: Iterable[R] = ()) -> OrderedSet[R]: 99 | """Creates an ordered set from an iterable without checking if the items are unique. 100 | 101 | This method is useful when constructing an ordered set from an iterable that is known to 102 | contain unique items only. 103 | 104 | Complexity: 105 | `O(n)`, where `n` is the length of the iterable. 106 | 107 | Example: 108 | ```python 109 | >>> array = [1, 2, 3] # we know that the items are unique 110 | >>> order_set = ordered_set.create_unchecked(array) 111 | >>> order_set 112 | OrderedSet([1, 2, 3]) 113 | ``` 114 | 115 | Arguments: 116 | iterable: The iterable to create the ordered set from. 117 | 118 | Returns: 119 | The created ordered set. 120 | """ 121 | self: OrderedSet[R] = cls.create() 122 | 123 | items = self._items 124 | item_to_index = self._item_to_index 125 | 126 | items.extend(iterable) 127 | 128 | for index, item in enumerate(items): 129 | item_to_index[item] = index 130 | 131 | return self 132 | 133 | @classmethod 134 | def create_union(cls, *iterables: Iterable[R]) -> OrderedSet[R]: 135 | """Creates an ordered set that is the union of given iterables. 136 | 137 | Arguments: 138 | *iterables: The iterables to create the ordered set union from. 139 | 140 | Returns: 141 | The ordered set union. 142 | """ 143 | return cls.create(chain(*iterables)) 144 | 145 | @classmethod 146 | def create_intersection(cls, *iterables: Iterable[R]) -> OrderedSet[R]: 147 | """Creates an ordered set that is the intersection of given iterables. 148 | 149 | The order is determined by the first iterable. 150 | 151 | Arguments: 152 | *iterables: The iterables to create the ordered set intersection from. 153 | 154 | Returns: 155 | The ordered set intersection. 156 | """ 157 | if iterables: 158 | head, *tail = iterables 159 | 160 | return cls.create(head).apply_intersection(*tail) 161 | 162 | return cls.create() 163 | 164 | @classmethod 165 | def create_difference(cls, *iterables: Iterable[R]) -> OrderedSet[R]: 166 | """Creates an ordered set that is the difference of given iterables. 167 | 168 | The order is determined by the first iterable. 169 | 170 | Arguments: 171 | *iterables: The iterables to create the orderd set difference from. 172 | 173 | Returns: 174 | The ordered set difference. 175 | """ 176 | if iterables: 177 | head, *tail = iterables 178 | 179 | return cls.create(head).apply_difference(*tail) 180 | 181 | return cls.create() 182 | 183 | @classmethod 184 | def create_symmetric_difference(cls, *iterables: Iterable[R]) -> OrderedSet[R]: 185 | """Creates an ordered set that is the symmetric difference of given iterables. 186 | 187 | The order is determined by the first iterable. 188 | 189 | Arguments: 190 | *iterables: The iterables to create the ordered set symmetric difference from. 191 | 192 | Returns: 193 | The ordered set symmetric difference. 194 | """ 195 | if iterables: 196 | head, *tail = iterables 197 | 198 | return cls.create(head).apply_symmetric_difference(*tail) 199 | 200 | return cls.create() 201 | 202 | def __len__(self) -> int: 203 | return len(self._items) 204 | 205 | @overload 206 | def __getitem__(self, index: int) -> Q: ... 207 | 208 | @overload 209 | def __getitem__(self, index: slice) -> OrderedSet[Q]: ... 210 | 211 | def __getitem__(self, index: Union[int, slice]) -> Union[Q, OrderedSet[Q]]: 212 | if is_slice(index): 213 | if index == SLICE_ALL: 214 | return self.copy() 215 | 216 | return self.create_unchecked(self._items[index]) 217 | 218 | return self._items[index] 219 | 220 | def copy(self) -> OrderedSet[Q]: 221 | """Copies the ordered set. 222 | 223 | This is equivalent to: 224 | 225 | ```python 226 | order_set.create_unchecked(order_set) 227 | ``` 228 | 229 | Complexity: 230 | `O(n)`, where `n` is the length of the ordered set. 231 | 232 | Example: 233 | ```python 234 | >>> order_set = ordered_set([1, 2, 3]) 235 | >>> order_set 236 | OrderedSet([1, 2, 3]) 237 | >>> order_set.copy() 238 | OrderedSet([1, 2, 3]) 239 | ``` 240 | 241 | Returns: 242 | The copied ordered set. 243 | """ 244 | return self.create_unchecked(self) 245 | 246 | def __contains__(self, item: Any) -> bool: 247 | return item in self._item_to_index 248 | 249 | def add(self, item: Q) -> None: 250 | """Adds an item to the ordered set. 251 | 252 | Complexity: 253 | `O(1)`. 254 | 255 | Example: 256 | ```python 257 | >>> order_set = ordered_set() 258 | >>> order_set 259 | OrderedSet() 260 | >>> order_set.add(0) 261 | >>> order_set.add(1) 262 | >>> order_set.add(0) 263 | >>> order_set 264 | OrderedSet([0, 1]) 265 | ``` 266 | 267 | Arguments: 268 | item: The item to add. 269 | """ 270 | item_to_index = self._item_to_index 271 | 272 | if item not in item_to_index: 273 | items = self._items 274 | 275 | item_to_index[item] = len(items) 276 | 277 | items.append(item) 278 | 279 | append = add 280 | """An alias of [`add`][iters.ordered_set.OrderedSet.add].""" 281 | 282 | def update(self, iterable: Iterable[Q]) -> None: 283 | """Updates the ordered set with the items from an iterable. 284 | 285 | This is equivalent to: 286 | 287 | ```python 288 | for item in iterable: 289 | ordered_set.add(item) 290 | ``` 291 | 292 | Complexity: 293 | `O(n)`, where `n` is the length of the iterable. 294 | 295 | Example: 296 | ```python 297 | >>> order_set = ordered_set() 298 | >>> order_set.update([0, 1]) 299 | >>> order_set.update([1, 2, 3]) 300 | >>> order_set 301 | OrderedSet([0, 1, 2, 3]) 302 | ``` 303 | 304 | Arguments: 305 | iterable: The iterable to update the ordered set with. 306 | """ 307 | for item in iterable: 308 | self.add(item) 309 | 310 | extend = update 311 | """An alias of [`update`][iters.ordered_set.OrderedSet.update].""" 312 | 313 | def index(self, item: Q, start: Optional[int] = None, stop: Optional[int] = None) -> int: 314 | """Gets the index of an item in the ordered set. 315 | 316 | Complexity: 317 | `O(1)`. 318 | 319 | Example: 320 | ```python 321 | >>> order_set = ordered_set([1, 2, 3]) 322 | >>> order_set.index(1) 323 | 0 324 | >>> order_set.index(5) 325 | Traceback (most recent call last): 326 | ... 327 | ValueError: 5 is not in the ordered set 328 | ``` 329 | 330 | Arguments: 331 | item: The item to get the index of. 332 | start: The index to start searching from. 333 | stop: The index to stop searching at. 334 | 335 | Raises: 336 | ValueError: The item is not in the ordered set. 337 | 338 | Returns: 339 | The index of the item. 340 | """ 341 | index = self._item_to_index.get(item) 342 | 343 | if index is None: 344 | raise ValueError(item_not_in_ordered_set(item)) 345 | 346 | if start is not None: 347 | if index < start: 348 | raise ValueError(item_not_in_ordered_set(item)) 349 | 350 | if stop is not None: 351 | if index >= stop: 352 | raise ValueError(item_not_in_ordered_set(item)) 353 | 354 | return index 355 | 356 | get_index = wrap_index_error(index) 357 | """An alias of [`index`][iters.ordered_set.OrderedSet.index] wrapped to return 358 | [`Option[int]`][wraps.option.Option] instead of erroring. 359 | """ 360 | 361 | def count(self, item: Q) -> int: 362 | """Returns `1` if an item is in the ordered set, `0` otherwise. 363 | 364 | Complexity: 365 | `O(1)`. 366 | 367 | Arguments: 368 | item: The item to count. 369 | 370 | Returns: 371 | `1` if the `item` is in the ordered set, `0` otherwise. 372 | """ 373 | return int(item in self._item_to_index) 374 | 375 | def pop(self, index: int = LAST) -> Q: 376 | """Pops an item from the ordered set at `index`. 377 | 378 | Complexity: 379 | `O(n)`, see [`discard`][iters.ordered_set.OrderedSet.discard]. 380 | 381 | Example: 382 | ```python 383 | >>> order_set = ordered_set([0, 1]) 384 | >>> order_set.pop() 385 | 1 386 | >>> order_set.pop(0) 387 | 0 388 | >>> order_set.pop() 389 | Traceback (most recent call last): 390 | ... 391 | IndexError: list index out of range 392 | ``` 393 | 394 | Arguments: 395 | index: The index to pop the item from. 396 | 397 | Raises: 398 | IndexError: The index is out of range. 399 | 400 | Returns: 401 | The popped item. 402 | """ 403 | items = self._items 404 | 405 | item = items[index] 406 | 407 | self.discard(item) 408 | 409 | return item 410 | 411 | get_pop = wrap_index_error(pop) 412 | """An alias of [`pop`][iters.ordered_set.OrderedSet.pop] wrapped to return 413 | [`Option[Q]`][wraps.option.Option] instead of erroring. 414 | """ 415 | 416 | def discard(self, item: Q) -> None: 417 | """Discards an item from the ordered set. 418 | 419 | Complexity: 420 | `O(n)`, where `n` is the length of the ordered set. 421 | This is because all indices after the removed index must be decremented. 422 | 423 | Example: 424 | ```python 425 | >>> order_set = ordered_set([0, 1]) 426 | >>> order_set.discard(1) 427 | >>> order_set 428 | OrderedSet([0]) 429 | >>> order_set.discard(1) 430 | >>> order_set.discard(0) 431 | >>> order_set 432 | OrderedSet() 433 | ``` 434 | 435 | Arguments: 436 | item: The item to discard. 437 | """ 438 | item_to_index = self._item_to_index 439 | 440 | if item in item_to_index: 441 | index = item_to_index[item] 442 | 443 | del self._items[index] 444 | 445 | for item_in, index_in in item_to_index.items(): 446 | if index_in >= index: 447 | item_to_index[item_in] -= 1 448 | 449 | def remove(self, item: Q) -> None: 450 | """A checked version of [`discard`][iters.ordered_set.OrderedSet.discard]. 451 | 452 | Complexity: `O(n)`, see [`discard`][iters.ordered_set.OrderedSet.discard]. 453 | 454 | Example: 455 | ```python 456 | >>> order_set = ordered_set([0, 1]) 457 | >>> order_set.remove(1) 458 | >>> order_set 459 | OrderedSet([0]) 460 | >>> order_set.remove(1) 461 | Traceback (most recent call last): 462 | ... 463 | ValueError: 1 is not in the ordered set 464 | >>> order_set.remove(0) 465 | >>> order_set 466 | OrderedSet() 467 | ``` 468 | 469 | Arguments: 470 | item: The item to remove. 471 | 472 | Raises: 473 | ValueError: The item is not in the ordered set. 474 | """ 475 | if item in self: 476 | self.discard(item) 477 | 478 | else: 479 | raise ValueError(item_not_in_ordered_set(item)) 480 | 481 | def insert(self, index: int, item: Q) -> None: 482 | """Inserts an item into the ordered set at `index`. 483 | 484 | Complexity: 485 | `O(n)`, where `n` is the length of the ordered set. 486 | This is because all indices after the inserted index must be incremented. 487 | 488 | Example: 489 | ```python 490 | >>> order_set = ordered_set([1, 3]) 491 | >>> order_set.insert(1, 2) 492 | >>> order_set 493 | OrderedSet([1, 2, 3]) 494 | ``` 495 | 496 | Arguments: 497 | index: The index to insert the item at. 498 | item: The item to insert. 499 | """ 500 | item_to_index = self._item_to_index 501 | 502 | if item in item_to_index: 503 | return 504 | 505 | items = self._items 506 | 507 | if index < len(items): 508 | items.insert(index, item) 509 | 510 | for item_in, index_in in item_to_index.items(): 511 | if index_in >= index: 512 | item_to_index[item_in] += 1 513 | 514 | item_to_index[item] = index 515 | 516 | else: 517 | self.append(item) 518 | 519 | def clear(self) -> None: 520 | """Clears the ordered set. 521 | 522 | Complexity: 523 | `O(n)`. 524 | """ 525 | self._items.clear() 526 | self._item_to_index.clear() 527 | 528 | def __iter__(self) -> Iterator[Q]: 529 | return iter(self._items) 530 | 531 | def __reversed__(self) -> Iterator[Q]: 532 | return reversed(self._items) 533 | 534 | def __repr__(self) -> str: 535 | name = get_type_name(self) 536 | 537 | items = self._items 538 | 539 | if not items: 540 | return EMPTY_REPRESENTATION.format(name) 541 | 542 | return ITEMS_REPRESENTATION.format(name, items) 543 | 544 | def __eq__(self, other: Any) -> bool: 545 | try: 546 | iterator = iter(other) 547 | 548 | except TypeError: 549 | return False 550 | 551 | if is_sequence(other): 552 | return self._items == list(iterator) 553 | 554 | return set(self._item_to_index) == set(iterator) 555 | 556 | def apply_union(self, *iterables: Iterable[Q]) -> OrderedSet[Q]: 557 | """Returns the union of the ordered set and `iterables`. 558 | 559 | Arguments: 560 | *iterables: The iterables to find the union with. 561 | 562 | Returns: 563 | The union of the ordered set and `iterables`. 564 | """ 565 | if iterables: 566 | return self.create_union(self, *iterables) 567 | 568 | return self.copy() 569 | 570 | union = mixed_method(create_union, apply_union) 571 | """Mixes [`create_union`][iters.ordered_set.OrderedSet.create_union] 572 | and [`apply_union`][iters.ordered_set.OrderedSet.apply_union]. 573 | """ 574 | 575 | def apply_intersection(self, *iterables: Iterable[Q]) -> OrderedSet[Q]: 576 | """Returns the intersection of the ordered set and `iterables`. 577 | 578 | Arguments: 579 | *iterables: The iterables to find the intersection with. 580 | 581 | Returns: 582 | The intersection of the ordered set and `iterables`. 583 | """ 584 | if iterables: 585 | intersection = set.intersection(*map(set, iterables)) 586 | 587 | iterator = (item for item in self if item in intersection) 588 | 589 | return self.create_unchecked(iterator) 590 | 591 | return self.copy() 592 | 593 | intersection = mixed_method(create_intersection, apply_intersection) 594 | """Mixes [`create_intersection`][iters.ordered_set.OrderedSet.create_intersection] 595 | and [`apply_intersection`][iters.ordered_set.OrderedSet.apply_intersection]. 596 | """ 597 | 598 | def intersection_update(self, *iterables: Iterable[Q]) -> None: 599 | """Updates the ordered set to be the intersection of itself and `iterables`. 600 | 601 | Arguments: 602 | *iterables: The iterables to find the intersection with. 603 | """ 604 | if iterables: 605 | intersection = self.intersection(*iterables) 606 | 607 | self.clear() 608 | 609 | self.update(intersection) 610 | 611 | def apply_difference(self, *iterables: Iterable[Q]) -> OrderedSet[Q]: 612 | """Returns the difference of the ordered set and `iterables`. 613 | 614 | Arguments: 615 | *iterables: The iterables to find the difference with. 616 | 617 | Returns: 618 | The difference of the ordered set and `iterables`. 619 | """ 620 | if iterables: 621 | union = set.union(*map(set, iterables)) 622 | iterator = (item for item in self if item not in union) 623 | 624 | return self.create_unchecked(iterator) 625 | 626 | return self.copy() 627 | 628 | difference = mixed_method(create_difference, apply_difference) 629 | """Mixes [`create_difference`][iters.ordered_set.OrderedSet.create_difference] 630 | and [`apply_difference`][iters.ordered_set.OrderedSet.apply_difference]. 631 | """ 632 | 633 | def difference_update(self, *iterables: Iterable[Q]) -> None: 634 | """Updates the ordered set to be the difference of itself and `iterables`. 635 | 636 | Arguments: 637 | *iterables: The iterables to find the difference with. 638 | """ 639 | if iterables: 640 | difference = self.difference(*iterables) 641 | 642 | self.clear() 643 | 644 | self.update(difference) 645 | 646 | def single_symmetric_difference(self, other: Iterable[Q]) -> OrderedSet[Q]: 647 | ordered = self.create(other) 648 | 649 | return self.difference(ordered).union(ordered.difference(self)) 650 | 651 | def apply_symmetric_difference(self, *iterables: Iterable[Q]) -> OrderedSet[Q]: 652 | """Returns the symmetric difference of the ordered set and `iterables`. 653 | 654 | Arguments: 655 | *iterables: The iterables to find the symmetric difference with. 656 | 657 | Returns: 658 | The symmetric difference of the ordered set and `iterables`. 659 | """ 660 | if iterables: 661 | result = self 662 | 663 | for iterable in iterables: 664 | result = result.single_symmetric_difference(iterable) 665 | 666 | return result 667 | 668 | return self.copy() 669 | 670 | symmetric_difference = mixed_method(create_symmetric_difference, apply_symmetric_difference) 671 | """Mixes 672 | [`create_symmetric_difference`][iters.ordered_set.OrderedSet.create_symmetric_difference] and 673 | [`apply_symmetric_difference`][iters.ordered_set.OrderedSet.apply_symmetric_difference]. 674 | """ 675 | 676 | def symmetric_difference_update(self, *iterables: Iterable[Q]) -> None: 677 | """Updates the ordered set to be the symmetric difference of itself and `iterables`. 678 | 679 | Arguments: 680 | *iterables: The iterables to find the symmetric difference with. 681 | """ 682 | if iterables: 683 | symmetric_difference = self.symmetric_difference(*iterables) 684 | 685 | self.clear() 686 | 687 | self.update(symmetric_difference) 688 | 689 | def is_subset(self, other: Iterable[Q]) -> bool: 690 | """Checks if the ordered set is a subset of `other`. 691 | 692 | Arguments: 693 | other: The iterable to check if the ordered set is a subset of. 694 | 695 | Returns: 696 | Whether the ordered set is a subset of `other`. 697 | """ 698 | if is_sized(other): # cover obvious cases 699 | if len(self) > len(other): 700 | return False 701 | 702 | if is_any_set(other): # speedup for sets 703 | return all(item in other for item in self) 704 | 705 | other_set = set(other) 706 | 707 | return len(self) <= len(other_set) and all(item in other_set for item in self) 708 | 709 | def is_strict_subset(self, other: Iterable[Q]) -> bool: 710 | """Checks if the ordered set is a strict subset of `other`. 711 | 712 | Arguments: 713 | other: The iterable to check if the ordered set is a strict subset of. 714 | 715 | Returns: 716 | Whether the ordered set is a strict subset of `other`. 717 | """ 718 | if is_sized(other): # cover obvious cases 719 | if len(self) >= len(other): 720 | return False 721 | 722 | if is_any_set(other): # speedup for sets 723 | return all(item in other for item in self) 724 | 725 | other_set = set(other) # default case 726 | 727 | return len(self) < len(other_set) and all(item in other_set for item in self) 728 | 729 | def is_superset(self, other: Iterable[Q]) -> bool: 730 | """Checks if the ordered set is a superset of `other`. 731 | 732 | Arguments: 733 | other: The iterable to check if the ordered set is a superset of. 734 | 735 | Returns: 736 | Whether the ordered set is a superset of `other`. 737 | """ 738 | if is_sized(other): # speedup for sized iterables 739 | return len(self) >= len(other) and all(item in self for item in other) 740 | 741 | return all(item in self for item in other) # default case 742 | 743 | def is_strict_superset(self, other: Iterable[Q]) -> bool: 744 | """Checks if the ordered set is a strict superset of `other`. 745 | 746 | Arguments: 747 | other: The iterable to check if the ordered set is a strict superset of. 748 | 749 | Returns: 750 | Whether the ordered set is a strict superset of `other`. 751 | """ 752 | if is_sized(other): # speedup for sized iterables 753 | return len(self) > len(other) and all(item in self for item in other) 754 | 755 | array = list(other) # default case 756 | 757 | return len(self) > len(array) and all(item in self for item in array) 758 | 759 | def is_disjoint(self, other: Iterable[Q]) -> bool: 760 | """Checks if the ordered set is disjoint with `other`. 761 | 762 | Arguments: 763 | other: The iterable to check if the ordered set is disjoint with. 764 | 765 | Returns: 766 | Whether the ordered set is disjoint with `other`. 767 | """ 768 | return none(item in self for item in other) 769 | 770 | # I honestly hate these names ~ nekit 771 | 772 | issubset = is_subset 773 | issuperset = is_superset 774 | isdisjoint = is_disjoint 775 | 776 | 777 | ordered_set = OrderedSet 778 | """An alias of [`OrderedSet`][iters.ordered_set.OrderedSet].""" 779 | ordered_set_unchecked = ordered_set.create_unchecked 780 | """An alias of [`ordered_set.create_unchecked`][iters.ordered_set.OrderedSet.create_unchecked].""" 781 | 782 | from iters.utils import chain, none 783 | -------------------------------------------------------------------------------- /iters/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekitdev/iters/07cee611790a7396fb2e74f6698c1cca97f83c03/iters/py.typed -------------------------------------------------------------------------------- /iters/sequence_view.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Sequence, TypeVar, Union, final, overload 4 | 5 | from attrs import frozen 6 | from typing_aliases import is_slice 7 | from wraps.wraps.option import wrap_option_on 8 | 9 | __all__ = ("SequenceView", "sequence_view") 10 | 11 | T = TypeVar("T") 12 | 13 | wrap_index_error = wrap_option_on(IndexError) 14 | 15 | 16 | @final 17 | @frozen() 18 | class SequenceView(Sequence[T]): 19 | """Represents views over sequences.""" 20 | 21 | sequence: Sequence[T] 22 | """The sequence to view.""" 23 | 24 | @overload 25 | def __getitem__(self, index: int) -> T: ... 26 | 27 | @overload 28 | def __getitem__(self, index: slice) -> SequenceView[T]: ... 29 | 30 | def __getitem__(self, index: Union[int, slice]) -> Union[T, SequenceView[T]]: 31 | if is_slice(index): 32 | return type(self)(self.sequence[index]) 33 | 34 | return self.sequence[index] 35 | 36 | def __len__(self) -> int: 37 | return len(self.sequence) 38 | 39 | @wrap_index_error 40 | def get(self, index: int) -> T: 41 | return self.sequence[index] 42 | 43 | 44 | def sequence_view(sequence: Sequence[T]) -> SequenceView[T]: 45 | """Returns the [`SequenceView[T]`][iters.sequence_view.SequenceView] over the given sequence. 46 | 47 | Arguments: 48 | sequence: The sequence to view into. 49 | 50 | Returns: 51 | The view over the sequence. 52 | """ 53 | return SequenceView(sequence) 54 | -------------------------------------------------------------------------------- /iters/state.py: -------------------------------------------------------------------------------- 1 | from typing import Generic, TypeVar, final 2 | 3 | from attrs import define 4 | from typing_extensions import Self 5 | 6 | __all__ = ("State", "stateful") 7 | 8 | T = TypeVar("T") 9 | 10 | 11 | @final 12 | @define() 13 | class State(Generic[T]): 14 | """Represents the mutable state of iteration.""" 15 | 16 | value: T 17 | """The wrapped value.""" 18 | 19 | def get(self) -> T: 20 | return self.value 21 | 22 | def set(self, value: T) -> Self: 23 | self.value = value 24 | 25 | return self 26 | 27 | 28 | def stateful(value: T) -> State[T]: 29 | """Wraps the given value into a [`State[T]`][wraps.state.State]. 30 | 31 | Arguments: 32 | value: The value to wrap. 33 | 34 | Returns: 35 | The state wrapping the value. 36 | """ 37 | return State(value) 38 | -------------------------------------------------------------------------------- /iters/types.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, TypeVar, Union 4 | 5 | from solus import Singleton 6 | from typing_extensions import TypeIs 7 | from wraps.primitives.option import NULL, Option, Some 8 | 9 | T = TypeVar("T") 10 | 11 | 12 | class NoDefault(Singleton): 13 | pass 14 | 15 | 16 | no_default = NoDefault() 17 | 18 | 19 | NoDefaultOr = Union[NoDefault, T] 20 | 21 | 22 | def is_no_default(item: Any) -> TypeIs[NoDefault]: 23 | return item is no_default 24 | 25 | 26 | class Marker(Singleton): 27 | pass 28 | 29 | 30 | marker = Marker() 31 | 32 | MarkerOr = Union[Marker, T] 33 | 34 | 35 | def is_marker(item: Any) -> TypeIs[Marker]: 36 | return item is marker 37 | 38 | 39 | def wrap_marked(item: MarkerOr[T]) -> Option[T]: 40 | return NULL if is_marker(item) else Some(item) 41 | -------------------------------------------------------------------------------- /iters/typing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional, Protocol, TypeVar, runtime_checkable 4 | 5 | from typing_aliases import Predicate, required 6 | from typing_extensions import Self 7 | 8 | __all__ = ("OptionalPredicate", "Sum", "Product") 9 | 10 | T = TypeVar("T") 11 | 12 | OptionalPredicate = Optional[Predicate[T]] 13 | """Represents optional predicates. 14 | 15 | Passing [`None`][None] is equivalent to passing [`bool`][bool], though most functions 16 | are optimized to reduce the overhead of calling [`bool`][bool]. 17 | """ 18 | 19 | 20 | @runtime_checkable 21 | class Sum(Protocol): 22 | """Represents types for which adding `self` to `other: Self` returns `Self`.""" 23 | 24 | @required 25 | def __add__(self, __other: Self) -> Self: 26 | raise NotImplementedError 27 | 28 | 29 | @runtime_checkable 30 | class Product(Protocol): 31 | """Represents types for which multiplying `self` with `other: Self` returns `Self`.""" 32 | 33 | @required 34 | def __mul__(self, __other: Self) -> Self: 35 | raise NotImplementedError 36 | -------------------------------------------------------------------------------- /iters/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | from builtins import zip as standard_zip 5 | from collections import Counter as counter_dict 6 | from collections import deque 7 | from functools import reduce as standard_reduce 8 | from heapq import merge 9 | from itertools import accumulate as standard_accumulate 10 | from itertools import chain, compress, cycle 11 | from itertools import combinations as standard_combinations 12 | from itertools import combinations_with_replacement as standard_combinations_with_replacement 13 | from itertools import count as standard_count 14 | from itertools import dropwhile as standard_drop_while 15 | from itertools import filterfalse as filter_false 16 | from itertools import groupby as standard_group 17 | from itertools import islice as iter_slice 18 | from itertools import permutations as standard_permutations 19 | from itertools import product as standard_product 20 | from itertools import takewhile as standard_take_while 21 | from itertools import tee as standard_copy 22 | from itertools import zip_longest as standard_zip_longest 23 | from math import copysign as copy_sign 24 | from operator import add, mul 25 | from operator import ge as greater_or_equal 26 | from operator import gt as greater 27 | from operator import le as less_or_equal 28 | from operator import lt as less 29 | from typing import ( 30 | Any, 31 | AnyStr, 32 | ContextManager, 33 | Counter, 34 | Dict, 35 | Hashable, 36 | Iterable, 37 | Iterator, 38 | List, 39 | Literal, 40 | Optional, 41 | Set, 42 | Tuple, 43 | TypeVar, 44 | Union, 45 | overload, 46 | ) 47 | 48 | from funcs.unpacking import unpack_binary 49 | from orderings import LenientOrdered, Ordering, StrictOrdered 50 | from typing_aliases import ( 51 | AnyErrorType, 52 | Binary, 53 | Compare, 54 | DynamicTuple, 55 | EmptyTuple, 56 | ForEach, 57 | Inspect, 58 | Nullary, 59 | Pair, 60 | RecursiveIterable, 61 | Tuple1, 62 | Tuple2, 63 | Tuple3, 64 | Tuple4, 65 | Tuple5, 66 | Tuple6, 67 | Tuple7, 68 | Tuple8, 69 | Unary, 70 | Validate, 71 | is_bytes, 72 | is_reversible, 73 | is_string, 74 | ) 75 | from typing_extensions import Never 76 | 77 | from iters.constants import DEFAULT_START, DEFAULT_STEP 78 | from iters.types import is_marker, is_no_default, marker, no_default 79 | from iters.typing import OptionalPredicate, Product, Sum 80 | 81 | __all__ = ( 82 | "accumulate_fold", 83 | "accumulate_product", 84 | "accumulate_reduce", 85 | "accumulate_sum", 86 | "all_equal", 87 | "all_unique", 88 | "all_unique_fast", 89 | "append", 90 | "at", 91 | "at_or_last", 92 | "cartesian_power", 93 | "cartesian_product", 94 | "chain", 95 | "chain_from_iterable", 96 | "chunks", 97 | "collapse", 98 | "combinations", 99 | "combinations_with_replacement", 100 | "combine", 101 | "compare", 102 | "compress", 103 | "consume", 104 | "contains", 105 | "contains_identity", 106 | "copy", 107 | "copy_infinite", 108 | "copy_unsafe", 109 | "count", 110 | "count_dict", 111 | "cycle", 112 | "distribute", 113 | "distribute_infinite", 114 | "distribute_unsafe", 115 | "divide", 116 | "drop", 117 | "drop_while", 118 | "duplicates", 119 | "duplicates_fast", 120 | "empty", 121 | "filter_except", 122 | "filter_false", 123 | "filter_false_map", 124 | "filter_map", 125 | "find", 126 | "find_all", 127 | "find_or_first", 128 | "find_or_last", 129 | "first", 130 | "flat_map", 131 | "flatten", 132 | "fold", 133 | "for_each", 134 | "group", 135 | "group_dict", 136 | "group_list", 137 | "groups", 138 | "groups_longest", 139 | "has_next", 140 | "inspect", 141 | "interleave", 142 | "interleave_longest", 143 | "intersperse", 144 | "intersperse_with", 145 | "is_empty", 146 | "is_sorted", 147 | "iter_chunks", 148 | "iter_chunks_infinite", 149 | "iter_chunks_unsafe", 150 | "iter_except", 151 | "iter_function", 152 | "iter_length", 153 | "iter_slice", 154 | "iter_windows", 155 | "iter_with", 156 | "iterate", 157 | "last", 158 | "last_with_tail", 159 | "list_windows", 160 | "map_except", 161 | "merge", 162 | "min_max", 163 | "next_of", 164 | "next_of_iterable", 165 | "none", 166 | "once", 167 | "once_with", 168 | "pad", 169 | "pad_with", 170 | "pairs", 171 | "pairs_longest", 172 | "pairs_windows", 173 | "partition", 174 | "partition_infinite", 175 | "partition_unsafe", 176 | "peek", 177 | "permutations", 178 | "permute", 179 | "position", 180 | "position_all", 181 | "power_set", 182 | "prepend", 183 | "product", 184 | "reduce", 185 | "remove", 186 | "remove_duplicates", 187 | "repeat", 188 | "repeat_each", 189 | "repeat_last", 190 | "repeat_with", 191 | "rest", 192 | "reverse", 193 | "set_windows", 194 | "skip", 195 | "skip_while", 196 | "sort", 197 | "spy", 198 | "step_by", 199 | "sum", 200 | "tabulate", 201 | "tail", 202 | "take", 203 | "take_while", 204 | "transpose", 205 | "tuple_windows", 206 | "unary_tuple", 207 | "unique", 208 | "unique_fast", 209 | "unpack_unary_tuple", 210 | "zip", 211 | "zip_equal", 212 | "zip_longest", 213 | ) 214 | 215 | T = TypeVar("T") 216 | U = TypeVar("U") 217 | V = TypeVar("V") 218 | W = TypeVar("W") 219 | 220 | R = TypeVar("R") 221 | 222 | A = TypeVar("A") 223 | B = TypeVar("B") 224 | C = TypeVar("C") 225 | D = TypeVar("D") 226 | E = TypeVar("E") 227 | F = TypeVar("F") 228 | G = TypeVar("G") 229 | H = TypeVar("H") 230 | 231 | # I = TypeVar("I", bound=Iterable) # I[T] 232 | 233 | Q = TypeVar("Q", bound=Hashable) 234 | 235 | S = TypeVar("S", bound=Sum) 236 | P = TypeVar("P", bound=Product) 237 | 238 | LT = TypeVar("LT", bound=LenientOrdered) 239 | ST = TypeVar("ST", bound=StrictOrdered) 240 | 241 | 242 | def take_while(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> Iterator[T]: 243 | return standard_take_while(predicate or bool, iterable) 244 | 245 | 246 | def drop_while(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> Iterator[T]: 247 | return standard_drop_while(predicate or bool, iterable) 248 | 249 | 250 | def none(iterable: Iterable[Any]) -> bool: 251 | return not any(iterable) 252 | 253 | 254 | chain_from_iterable = chain.from_iterable 255 | skip_while = drop_while 256 | 257 | 258 | @overload 259 | def compare( 260 | left_iterable: Iterable[ST], right_iterable: Iterable[ST], key: None = ... 261 | ) -> Ordering: ... 262 | 263 | 264 | @overload 265 | def compare( 266 | left_iterable: Iterable[T], right_iterable: Iterable[T], key: Unary[T, ST] 267 | ) -> Ordering: ... 268 | 269 | 270 | def compare( 271 | left_iterable: Iterable[Any], 272 | right_iterable: Iterable[Any], 273 | key: Optional[Unary[Any, Any]] = None, 274 | ) -> Ordering: 275 | if key is None: 276 | return compare_simple(left_iterable, right_iterable) 277 | 278 | return compare_by(left_iterable, right_iterable, key) 279 | 280 | 281 | def compare_simple(left_iterable: Iterable[ST], right_iterable: Iterable[ST]) -> Ordering: 282 | for left, right in zip_longest(left_iterable, right_iterable, fill=marker): 283 | if is_marker(left): 284 | return Ordering.LESS 285 | 286 | if is_marker(right): 287 | return Ordering.GREATER 288 | 289 | if left < right: 290 | return Ordering.LESS 291 | 292 | if left > right: 293 | return Ordering.GREATER 294 | 295 | return Ordering.EQUAL 296 | 297 | 298 | def compare_by( 299 | left_iterable: Iterable[T], right_iterable: Iterable[T], key: Unary[T, ST] 300 | ) -> Ordering: 301 | return compare_simple(map(key, left_iterable), map(key, right_iterable)) 302 | 303 | 304 | def iter_function(function: Nullary[T], sentinel: V) -> Iterator[T]: 305 | return iter(function, sentinel) 306 | 307 | 308 | def empty() -> Iterator[Never]: 309 | return 310 | yield 311 | 312 | 313 | def once(value: T) -> Iterator[T]: 314 | yield value 315 | 316 | 317 | def once_with(function: Nullary[T]) -> Iterator[T]: 318 | yield function() 319 | 320 | 321 | def repeat(value: T, count: Optional[int] = None) -> Iterator[T]: 322 | if count is None: 323 | while True: 324 | yield value 325 | 326 | else: 327 | for _ in range(count): 328 | yield value 329 | 330 | 331 | def repeat_with(function: Nullary[T], count: Optional[int] = None) -> Iterator[T]: 332 | if count is None: 333 | while True: 334 | yield function() 335 | 336 | else: 337 | for _ in range(count): 338 | yield function() 339 | 340 | 341 | def repeat_factory(count: int) -> Unary[T, Iterator[T]]: 342 | def actual_repeat(value: T) -> Iterator[T]: 343 | return repeat(value, count) 344 | 345 | return actual_repeat 346 | 347 | 348 | def repeat_each(count: int, iterable: Iterable[T]) -> Iterator[T]: 349 | return flat_map(repeat_factory(count), iterable) 350 | 351 | 352 | def repeat_last(iterable: Iterable[T]) -> Iterator[T]: 353 | item = marker 354 | 355 | for item in iterable: # type: ignore[assignment] 356 | yield item # type: ignore[misc] 357 | 358 | if is_marker(item): 359 | return 360 | 361 | yield from repeat(item) 362 | 363 | 364 | def count(start: int = DEFAULT_START, step: int = DEFAULT_STEP) -> Iterator[int]: 365 | return standard_count(start, step) 366 | 367 | 368 | def tabulate( 369 | function: Unary[int, T], start: int = DEFAULT_START, step: int = DEFAULT_STEP 370 | ) -> Iterator[T]: 371 | return map(function, count(start, step)) 372 | 373 | 374 | def consume(iterable: Iterable[T]) -> None: 375 | deque(iterable, 0) 376 | 377 | 378 | def for_each(function: ForEach[T], iterable: Iterable[T]) -> None: 379 | for item in iterable: 380 | function(item) 381 | 382 | 383 | FIRST_ON_EMPTY = "`first` called on an empty iterable" 384 | 385 | 386 | @overload 387 | def first(iterable: Iterable[T]) -> T: ... 388 | 389 | 390 | @overload 391 | def first(iterable: Iterable[T], default: U) -> Union[T, U]: ... 392 | 393 | 394 | def first(iterable: Iterable[Any], default: Any = no_default) -> Any: 395 | iterator = iter(iterable) 396 | 397 | result = next(iterator, marker) 398 | 399 | if is_marker(result): 400 | if is_no_default(default): 401 | raise ValueError(FIRST_ON_EMPTY) 402 | 403 | return default 404 | 405 | return result 406 | 407 | 408 | LAST_ON_EMPTY = "`last` called on an empty iterable" 409 | 410 | 411 | @overload 412 | def last(iterable: Iterable[T]) -> T: ... 413 | 414 | 415 | @overload 416 | def last(iterable: Iterable[T], default: U) -> Union[T, U]: ... 417 | 418 | 419 | def last(iterable: Iterable[Any], default: Any = no_default) -> Any: 420 | result = marker 421 | 422 | if is_reversible(iterable): 423 | iterator = reversed(iterable) 424 | 425 | result = next(iterator, marker) 426 | 427 | else: 428 | for result in iterable: 429 | pass 430 | 431 | if is_marker(result): 432 | if is_no_default(default): 433 | raise ValueError(LAST_ON_EMPTY) 434 | 435 | return default 436 | 437 | return result 438 | 439 | 440 | REDUCE_ON_EMPTY = "`reduce` called on an empty iterable" 441 | 442 | 443 | @overload 444 | def reduce(function: Binary[T, T, T], iterable: Iterable[T]) -> T: ... 445 | 446 | 447 | @overload 448 | def reduce(function: Binary[T, T, T], iterable: Iterable[T], default: U) -> Union[T, U]: ... 449 | 450 | 451 | def reduce( 452 | function: Binary[Any, Any, Any], iterable: Iterable[Any], default: Any = no_default 453 | ) -> Any: 454 | empty, iterator = is_empty(iterable) 455 | 456 | if empty: 457 | if is_no_default(default): 458 | raise ValueError(REDUCE_ON_EMPTY) 459 | 460 | return default 461 | 462 | return standard_reduce(function, iterator) 463 | 464 | 465 | def fold(initial: U, function: Binary[U, T, U], iterable: Iterable[T]) -> U: 466 | return standard_reduce(function, iterable, initial) 467 | 468 | 469 | def accumulate_reduce(function: Binary[T, T, T], iterable: Iterable[T]) -> Iterator[T]: 470 | return standard_accumulate(iterable, function) 471 | 472 | 473 | def accumulate_fold(initial: U, function: Binary[U, T, U], iterable: Iterable[T]) -> Iterator[U]: 474 | return standard_accumulate(iterable, function, initial=initial) 475 | 476 | 477 | @overload 478 | def accumulate_sum(iterable: Iterable[S]) -> Iterator[S]: ... 479 | 480 | 481 | @overload 482 | def accumulate_sum(iterable: Iterable[S], initial: S) -> Iterator[S]: ... 483 | 484 | 485 | def accumulate_sum(iterable: Iterable[Any], initial: Any = no_default) -> Iterator[Any]: 486 | if is_no_default(initial): 487 | return accumulate_reduce(add, iterable) 488 | 489 | return accumulate_fold(initial, add, iterable) 490 | 491 | 492 | @overload 493 | def accumulate_product(iterable: Iterable[P]) -> Iterator[P]: ... 494 | 495 | 496 | @overload 497 | def accumulate_product(iterable: Iterable[P], initial: P) -> Iterator[P]: ... 498 | 499 | 500 | def accumulate_product(iterable: Iterable[Any], initial: Any = no_default) -> Iterator[Any]: 501 | if is_no_default(initial): 502 | return accumulate_reduce(mul, iterable) 503 | 504 | return accumulate_fold(initial, mul, iterable) 505 | 506 | 507 | AT_ON_EMPTY = "`at` called on an iterable with not enough items" 508 | 509 | 510 | @overload 511 | def at(index: int, iterable: Iterable[T]) -> T: ... 512 | 513 | 514 | @overload 515 | def at(index: int, iterable: Iterable[T], default: U) -> Union[T, U]: ... 516 | 517 | 518 | def at(index: int, iterable: Iterable[Any], default: Any = no_default) -> Any: 519 | iterator = drop(index, iterable) 520 | 521 | result = next(iterator, marker) 522 | 523 | if is_marker(result): 524 | if is_no_default(default): 525 | raise ValueError(AT_ON_EMPTY) 526 | 527 | return default 528 | 529 | return result 530 | 531 | 532 | AT_OR_LAST_ON_EMPTY = "`at_or_last` called on an empty iterable" 533 | 534 | 535 | @overload 536 | def at_or_last(index: int, iterable: Iterable[T]) -> T: ... 537 | 538 | 539 | @overload 540 | def at_or_last(index: int, iterable: Iterable[T], default: U) -> Union[T, U]: ... 541 | 542 | 543 | def at_or_last(index: int, iterable: Iterable[Any], default: Any = no_default) -> Any: 544 | length = index + 1 545 | 546 | iterator = take(length, iterable) 547 | 548 | result = last(iterator, marker) 549 | 550 | if is_marker(result): 551 | if is_no_default(default): 552 | raise ValueError(AT_OR_LAST_ON_EMPTY) 553 | 554 | return default 555 | 556 | return result 557 | 558 | 559 | @overload 560 | def copy_unsafe(iterable: Iterable[T]) -> Pair[Iterator[T]]: ... 561 | 562 | 563 | @overload 564 | def copy_unsafe(iterable: Iterable[T], copies: Literal[0]) -> EmptyTuple: ... 565 | 566 | 567 | @overload 568 | def copy_unsafe(iterable: Iterable[T], copies: Literal[1]) -> Tuple1[Iterator[T]]: ... 569 | 570 | 571 | @overload 572 | def copy_unsafe(iterable: Iterable[T], copies: Literal[2]) -> Tuple2[Iterator[T]]: ... 573 | 574 | 575 | @overload 576 | def copy_unsafe(iterable: Iterable[T], copies: Literal[3]) -> Tuple3[Iterator[T]]: ... 577 | 578 | 579 | @overload 580 | def copy_unsafe(iterable: Iterable[T], copies: Literal[4]) -> Tuple4[Iterator[T]]: ... 581 | 582 | 583 | @overload 584 | def copy_unsafe(iterable: Iterable[T], copies: Literal[5]) -> Tuple5[Iterator[T]]: ... 585 | 586 | 587 | @overload 588 | def copy_unsafe(iterable: Iterable[T], copies: Literal[6]) -> Tuple6[Iterator[T]]: ... 589 | 590 | 591 | @overload 592 | def copy_unsafe(iterable: Iterable[T], copies: Literal[7]) -> Tuple7[Iterator[T]]: ... 593 | 594 | 595 | @overload 596 | def copy_unsafe(iterable: Iterable[T], copies: Literal[8]) -> Tuple8[Iterator[T]]: ... 597 | 598 | 599 | @overload 600 | def copy_unsafe(iterable: Iterable[T], copies: int) -> DynamicTuple[Iterator[T]]: ... 601 | 602 | 603 | def copy_unsafe(iterable: Iterable[T], copies: int = 2) -> DynamicTuple[Iterator[T]]: 604 | return standard_copy(iterable, copies) 605 | 606 | 607 | copy_infinite = copy_unsafe 608 | 609 | 610 | @overload 611 | def copy(iterable: Iterable[T]) -> Pair[Iterator[T]]: ... 612 | 613 | 614 | @overload 615 | def copy(iterable: Iterable[T], copies: Literal[0]) -> EmptyTuple: ... 616 | 617 | 618 | @overload 619 | def copy(iterable: Iterable[T], copies: Literal[1]) -> Tuple1[Iterator[T]]: ... 620 | 621 | 622 | @overload 623 | def copy(iterable: Iterable[T], copies: Literal[2]) -> Tuple2[Iterator[T]]: ... 624 | 625 | 626 | @overload 627 | def copy(iterable: Iterable[T], copies: Literal[3]) -> Tuple3[Iterator[T]]: ... 628 | 629 | 630 | @overload 631 | def copy(iterable: Iterable[T], copies: Literal[4]) -> Tuple4[Iterator[T]]: ... 632 | 633 | 634 | @overload 635 | def copy(iterable: Iterable[T], copies: Literal[5]) -> Tuple5[Iterator[T]]: ... 636 | 637 | 638 | @overload 639 | def copy(iterable: Iterable[T], copies: Literal[6]) -> Tuple6[Iterator[T]]: ... 640 | 641 | 642 | @overload 643 | def copy(iterable: Iterable[T], copies: Literal[7]) -> Tuple7[Iterator[T]]: ... 644 | 645 | 646 | @overload 647 | def copy(iterable: Iterable[T], copies: Literal[8]) -> Tuple8[Iterator[T]]: ... 648 | 649 | 650 | @overload 651 | def copy(iterable: Iterable[T], copies: int) -> DynamicTuple[Iterator[T]]: ... 652 | 653 | 654 | def copy(iterable: Iterable[T], copies: int = 2) -> DynamicTuple[Iterator[T]]: 655 | collected = tuple(iterable) 656 | 657 | return tuple(iter(collected) for _ in range(copies)) 658 | 659 | 660 | def drop(size: int, iterable: Iterable[T]) -> Iterator[T]: 661 | return iter_slice(iterable, size, None) 662 | 663 | 664 | skip = drop 665 | 666 | 667 | def rest(iterable: Iterable[T]) -> Iterator[T]: 668 | return drop(1, iterable) 669 | 670 | 671 | def take(size: int, iterable: Iterable[T]) -> Iterator[T]: 672 | return iter_slice(iterable, size) 673 | 674 | 675 | def step_by(step: int, iterable: Iterable[T]) -> Iterator[T]: 676 | return iter_slice(iterable, None, None, step) 677 | 678 | 679 | @overload 680 | def groups(size: Literal[0], iterable: Iterable[T]) -> Iterator[Never]: ... 681 | 682 | 683 | @overload 684 | def groups(size: Literal[1], iterable: Iterable[T]) -> Iterator[Tuple1[T]]: ... 685 | 686 | 687 | @overload 688 | def groups(size: Literal[2], iterable: Iterable[T]) -> Iterator[Tuple2[T]]: ... 689 | 690 | 691 | @overload 692 | def groups(size: Literal[3], iterable: Iterable[T]) -> Iterator[Tuple3[T]]: ... 693 | 694 | 695 | @overload 696 | def groups(size: Literal[4], iterable: Iterable[T]) -> Iterator[Tuple4[T]]: ... 697 | 698 | 699 | @overload 700 | def groups(size: Literal[5], iterable: Iterable[T]) -> Iterator[Tuple5[T]]: ... 701 | 702 | 703 | @overload 704 | def groups(size: Literal[6], iterable: Iterable[T]) -> Iterator[Tuple6[T]]: ... 705 | 706 | 707 | @overload 708 | def groups(size: Literal[7], iterable: Iterable[T]) -> Iterator[Tuple7[T]]: ... 709 | 710 | 711 | @overload 712 | def groups(size: Literal[8], iterable: Iterable[T]) -> Iterator[Tuple8[T]]: ... 713 | 714 | 715 | @overload 716 | def groups(size: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: ... 717 | 718 | 719 | def groups(size: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: 720 | return zip(*repeat(iter(iterable), size)) 721 | 722 | 723 | @overload 724 | def groups_longest(size: Literal[0], iterable: Iterable[T]) -> Iterator[Never]: ... 725 | 726 | 727 | @overload 728 | def groups_longest(size: Literal[1], iterable: Iterable[T]) -> Iterator[Tuple1[T]]: ... 729 | 730 | 731 | @overload 732 | def groups_longest(size: Literal[2], iterable: Iterable[T]) -> Iterator[Tuple2[Optional[T]]]: ... 733 | 734 | 735 | @overload 736 | def groups_longest(size: Literal[3], iterable: Iterable[T]) -> Iterator[Tuple3[Optional[T]]]: ... 737 | 738 | 739 | @overload 740 | def groups_longest(size: Literal[4], iterable: Iterable[T]) -> Iterator[Tuple4[Optional[T]]]: ... 741 | 742 | 743 | @overload 744 | def groups_longest(size: Literal[5], iterable: Iterable[T]) -> Iterator[Tuple5[Optional[T]]]: ... 745 | 746 | 747 | @overload 748 | def groups_longest(size: Literal[6], iterable: Iterable[T]) -> Iterator[Tuple6[Optional[T]]]: ... 749 | 750 | 751 | @overload 752 | def groups_longest(size: Literal[7], iterable: Iterable[T]) -> Iterator[Tuple7[Optional[T]]]: ... 753 | 754 | 755 | @overload 756 | def groups_longest(size: Literal[8], iterable: Iterable[T]) -> Iterator[Tuple8[Optional[T]]]: ... 757 | 758 | 759 | @overload 760 | def groups_longest(size: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[Optional[T]]]: ... 761 | 762 | 763 | @overload 764 | def groups_longest(size: Literal[0], iterable: Iterable[T], fill: U) -> Iterator[Never]: ... 765 | 766 | 767 | @overload 768 | def groups_longest(size: Literal[1], iterable: Iterable[T], fill: U) -> Iterator[Tuple1[T]]: ... 769 | 770 | 771 | @overload 772 | def groups_longest( 773 | size: Literal[2], iterable: Iterable[T], fill: U 774 | ) -> Iterator[Tuple2[Union[T, U]]]: ... 775 | 776 | 777 | @overload 778 | def groups_longest( 779 | size: Literal[3], iterable: Iterable[T], fill: U 780 | ) -> Iterator[Tuple3[Union[T, U]]]: ... 781 | 782 | 783 | @overload 784 | def groups_longest( 785 | size: Literal[4], iterable: Iterable[T], fill: U 786 | ) -> Iterator[Tuple4[Union[T, U]]]: ... 787 | 788 | 789 | @overload 790 | def groups_longest( 791 | size: Literal[5], iterable: Iterable[T], fill: U 792 | ) -> Iterator[Tuple5[Union[T, U]]]: ... 793 | 794 | 795 | @overload 796 | def groups_longest( 797 | size: Literal[6], iterable: Iterable[T], fill: U 798 | ) -> Iterator[Tuple6[Union[T, U]]]: ... 799 | 800 | 801 | @overload 802 | def groups_longest( 803 | size: Literal[7], iterable: Iterable[T], fill: U 804 | ) -> Iterator[Tuple7[Union[T, U]]]: ... 805 | 806 | 807 | @overload 808 | def groups_longest( 809 | size: Literal[8], iterable: Iterable[T], fill: U 810 | ) -> Iterator[Tuple8[Union[T, U]]]: ... 811 | 812 | 813 | @overload 814 | def groups_longest( 815 | size: int, iterable: Iterable[T], fill: U 816 | ) -> Iterator[DynamicTuple[Union[T, U]]]: ... 817 | 818 | 819 | def groups_longest( 820 | size: int, iterable: Iterable[Any], fill: Optional[Any] = None 821 | ) -> Iterator[DynamicTuple[Any]]: 822 | return zip_longest(*repeat(iter(iterable), size), fill=fill) 823 | 824 | 825 | @overload 826 | def pairs_longest(iterable: Iterable[T]) -> Iterator[Pair[Optional[T]]]: ... 827 | 828 | 829 | @overload 830 | def pairs_longest(iterable: Iterable[T], fill: U) -> Iterator[Pair[Union[T, U]]]: ... 831 | 832 | 833 | def pairs_longest(iterable: Iterable[Any], fill: Optional[Any] = None) -> Iterator[Pair[Any]]: 834 | return groups_longest(2, iterable, fill) 835 | 836 | 837 | def pairs(iterable: Iterable[T]) -> Iterator[Pair[T]]: 838 | return groups(2, iterable) 839 | 840 | 841 | def flatten(nested: Iterable[Iterable[T]]) -> Iterator[T]: 842 | return chain_from_iterable(nested) 843 | 844 | 845 | def flat_map(function: Unary[T, Iterable[U]], iterable: Iterable[T]) -> Iterator[U]: 846 | return flatten(map(function, iterable)) 847 | 848 | 849 | def filter_map( 850 | predicate: OptionalPredicate[T], function: Unary[T, U], iterable: Iterable[T] 851 | ) -> Iterator[U]: 852 | if predicate is None: 853 | for item in iterable: 854 | if item: 855 | yield function(item) 856 | 857 | else: 858 | for item in iterable: 859 | if predicate(item): 860 | yield function(item) 861 | 862 | 863 | def filter_false_map( 864 | predicate: OptionalPredicate[T], function: Unary[T, U], iterable: Iterable[T] 865 | ) -> Iterator[U]: 866 | if predicate is None: 867 | for item in iterable: 868 | if not item: 869 | yield function(item) 870 | 871 | else: 872 | for item in iterable: 873 | if not predicate(item): 874 | yield function(item) 875 | 876 | 877 | def partition_unsafe(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> Pair[Iterator[T]]: 878 | for_true, for_false = copy_unsafe(iterable) 879 | 880 | return filter(predicate, for_true), filter_false(predicate, for_false) 881 | 882 | 883 | partition_infinite = partition_unsafe 884 | 885 | 886 | def partition(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> Pair[Iterator[T]]: 887 | for_true, for_false = copy(iterable) 888 | 889 | return filter(predicate, for_true), filter_false(predicate, for_false) 890 | 891 | 892 | def prepend(value: T, iterable: Iterable[T]) -> Iterator[T]: 893 | return chain(once(value), iterable) 894 | 895 | 896 | def append(value: T, iterable: Iterable[T]) -> Iterator[T]: 897 | return chain(iterable, once(value)) 898 | 899 | 900 | @overload 901 | def group(iterable: Iterable[T], key: None = ...) -> Iterator[Tuple[T, Iterator[T]]]: ... 902 | 903 | 904 | @overload 905 | def group(iterable: Iterable[T], key: Unary[T, U]) -> Iterator[Tuple[U, Iterator[T]]]: ... 906 | 907 | 908 | def group( 909 | iterable: Iterable[Any], key: Optional[Unary[Any, Any]] = None 910 | ) -> Iterator[Tuple[Any, Iterator[Any]]]: 911 | return standard_group(iterable, key) 912 | 913 | 914 | @overload 915 | def group_list(iterable: Iterable[T], key: None = ...) -> Iterator[Tuple[T, List[T]]]: ... 916 | 917 | 918 | @overload 919 | def group_list(iterable: Iterable[T], key: Unary[T, U]) -> Iterator[Tuple[U, List[T]]]: ... 920 | 921 | 922 | def group_list( 923 | iterable: Iterable[Any], key: Optional[Unary[Any, Any]] = None 924 | ) -> Iterator[Tuple[Any, List[Any]]]: 925 | for group_key, group_iterator in group(iterable, key): 926 | yield (group_key, list(group_iterator)) 927 | 928 | 929 | @overload 930 | def group_dict(iterable: Iterable[Q], key: None = ...) -> Dict[Q, List[Q]]: ... 931 | 932 | 933 | @overload 934 | def group_dict(iterable: Iterable[T], key: Unary[T, Q]) -> Dict[Q, List[T]]: ... 935 | 936 | 937 | def group_dict( 938 | iterable: Iterable[Any], key: Optional[Unary[Any, Any]] = None 939 | ) -> Dict[Any, List[Any]]: 940 | result: Dict[Any, List[Any]] = {} 941 | 942 | for group_key, group_iterator in group(iterable, key): 943 | result.setdefault(group_key, []).extend(group_iterator) 944 | 945 | return result 946 | 947 | 948 | @overload 949 | def count_dict(iterable: Iterable[Q], key: None = ...) -> Counter[Q]: ... 950 | 951 | 952 | @overload 953 | def count_dict(iterable: Iterable[T], key: Unary[T, Q]) -> Counter[Q]: ... 954 | 955 | 956 | def count_dict(iterable: Iterable[Any], key: Optional[Unary[Any, Any]] = None) -> Counter[Any]: 957 | return counter_dict(iterable if key is None else map(key, iterable)) 958 | 959 | 960 | def chunks(size: int, iterable: Iterable[T]) -> Iterator[List[T]]: 961 | iterator = iter(iterable) 962 | 963 | while True: 964 | chunk = list(take(size, iterator)) 965 | 966 | if not chunk: 967 | break 968 | 969 | yield chunk 970 | 971 | 972 | def iter_chunks_unsafe(size: int, iterable: Iterable[T]) -> Iterator[Iterator[T]]: 973 | source = iter(iterable) 974 | 975 | while True: 976 | empty, source = is_empty(source) 977 | 978 | if empty: 979 | return 980 | 981 | source, iterator = copy_unsafe(source) 982 | 983 | yield take(size, iterator) 984 | 985 | consume(take(size, source)) 986 | 987 | 988 | iter_chunks_infinite = iter_chunks_unsafe 989 | 990 | 991 | def iter_chunks(size: int, iterable: Iterable[T]) -> Iterator[Iterator[T]]: 992 | source = iter(iterable) 993 | 994 | while True: 995 | empty, source = is_empty(source) 996 | 997 | if empty: 998 | return 999 | 1000 | source, iterator = copy(source) 1001 | 1002 | yield take(size, iterator) 1003 | 1004 | consume(take(size, source)) 1005 | 1006 | 1007 | def iter_length(iterable: Iterable[T]) -> int: 1008 | counting = count() 1009 | 1010 | consume(zip(iterable, counting)) 1011 | 1012 | return next(counting) 1013 | 1014 | 1015 | @overload 1016 | def sum(iterable: Iterable[S]) -> S: ... 1017 | 1018 | 1019 | @overload 1020 | def sum(iterable: Iterable[S], initial: S) -> S: ... 1021 | 1022 | 1023 | def sum(iterable: Iterable[Any], initial: Any = no_default) -> Any: 1024 | if is_no_default(initial): 1025 | return reduce(add, iterable) 1026 | 1027 | return fold(initial, add, iterable) 1028 | 1029 | 1030 | @overload 1031 | def product(iterable: Iterable[P]) -> P: ... 1032 | 1033 | 1034 | @overload 1035 | def product(iterable: Iterable[P], initial: P) -> P: ... 1036 | 1037 | 1038 | def product(iterable: Iterable[Any], initial: Any = no_default) -> Any: 1039 | if is_no_default(initial): 1040 | return reduce(mul, iterable) 1041 | 1042 | return fold(initial, mul, iterable) 1043 | 1044 | 1045 | def iterate(function: Unary[T, T], value: T, count: Optional[int] = None) -> Iterator[T]: 1046 | if count is None: 1047 | while True: 1048 | yield value 1049 | value = function(value) 1050 | 1051 | else: 1052 | for _ in range(count): 1053 | yield value 1054 | value = function(value) 1055 | 1056 | 1057 | def iter_with(context_manager: ContextManager[Iterable[T]]) -> Iterator[T]: 1058 | with context_manager as iterable: 1059 | yield from iterable 1060 | 1061 | 1062 | @overload 1063 | def collapse(node: RecursiveIterable[AnyStr]) -> Iterator[AnyStr]: ... 1064 | 1065 | 1066 | @overload 1067 | def collapse(node: RecursiveIterable[T]) -> Iterator[T]: ... 1068 | 1069 | 1070 | def collapse(node: RecursiveIterable[Any]) -> Iterator[Any]: 1071 | if is_string(node) or is_bytes(node): 1072 | yield node 1073 | 1074 | else: 1075 | try: 1076 | tree = iter(node) 1077 | 1078 | except TypeError: 1079 | yield node 1080 | 1081 | else: 1082 | for child in tree: 1083 | yield from collapse(child) 1084 | 1085 | 1086 | def pad( 1087 | value: T, 1088 | iterable: Iterable[T], 1089 | size: Optional[int] = None, 1090 | *, 1091 | multiple: bool = False, 1092 | ) -> Iterator[T]: 1093 | if size is None: 1094 | yield from chain(iterable, repeat(value)) 1095 | 1096 | else: 1097 | count = 0 1098 | 1099 | for item in iterable: 1100 | count += 1 1101 | yield item 1102 | 1103 | length = (size - count) % size if multiple else (size - count) 1104 | 1105 | if length > 0: 1106 | yield from repeat(value, length) 1107 | 1108 | 1109 | def pad_with( 1110 | function: Unary[int, T], 1111 | iterable: Iterable[T], 1112 | size: Optional[int] = None, 1113 | *, 1114 | multiple: bool = False, 1115 | ) -> Iterator[T]: 1116 | index = 0 1117 | 1118 | for item in iterable: 1119 | yield item 1120 | index += 1 1121 | 1122 | if size is None: 1123 | while True: 1124 | yield function(index) 1125 | index += 1 1126 | 1127 | else: 1128 | length = (size - index) % size if multiple else (size - index) 1129 | 1130 | if length > 0: 1131 | for index in range(index, index + length): 1132 | yield function(index) 1133 | 1134 | 1135 | def contains(value: U, iterable: Iterable[T]) -> bool: 1136 | return any(item == value for item in iterable) 1137 | 1138 | 1139 | def contains_identity(value: T, iterable: Iterable[T]) -> bool: 1140 | return any(item is value for item in iterable) 1141 | 1142 | 1143 | @overload 1144 | def all_unique_fast(iterable: Iterable[Q], key: None = ...) -> bool: ... 1145 | 1146 | 1147 | @overload 1148 | def all_unique_fast(iterable: Iterable[T], key: Unary[T, Q]) -> bool: ... 1149 | 1150 | 1151 | def all_unique_fast(iterable: Iterable[Any], key: Optional[Unary[Any, Any]] = None) -> bool: 1152 | is_unique, _ = is_empty(duplicates_fast(iterable, key)) 1153 | 1154 | return is_unique 1155 | 1156 | 1157 | def all_unique(iterable: Iterable[T], key: Optional[Unary[T, U]] = None) -> bool: 1158 | is_unique, _ = is_empty(duplicates(iterable, key)) 1159 | 1160 | return is_unique 1161 | 1162 | 1163 | def all_equal(iterable: Iterable[T], key: Optional[Unary[T, U]] = None) -> bool: 1164 | groups = group(iterable, key) 1165 | 1166 | return is_marker(next(groups, marker)) or is_marker(next(groups, marker)) 1167 | 1168 | 1169 | def remove(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> Iterator[T]: 1170 | return filter_false(predicate, iterable) 1171 | 1172 | 1173 | # XXX: more concise name? 1174 | 1175 | 1176 | def remove_duplicates(iterable: Iterable[T], key: Optional[Unary[T, U]] = None) -> Iterator[T]: 1177 | for item in group(iterable, key): 1178 | _, iterator = item 1179 | 1180 | yield next(iterator) 1181 | 1182 | 1183 | def spy(size: int, iterable: Iterable[T]) -> Tuple[List[T], Iterator[T]]: 1184 | iterator = iter(iterable) 1185 | 1186 | head = list(take(size, iterator)) 1187 | 1188 | return head.copy(), chain(head, iterator) 1189 | 1190 | 1191 | PEEK_ON_EMPTY = "`peek` called on an empty iterable" 1192 | 1193 | 1194 | @overload 1195 | def peek(iterable: Iterable[T]) -> Tuple[T, Iterator[T]]: ... 1196 | 1197 | 1198 | @overload 1199 | def peek(iterable: Iterable[T], default: U) -> Tuple[Union[T, U], Iterator[T]]: ... 1200 | 1201 | 1202 | def peek(iterable: Iterable[Any], default: Any = no_default) -> Tuple[Any, Iterator[Any]]: 1203 | iterator = iter(iterable) 1204 | 1205 | result = next(iterator, marker) 1206 | 1207 | if is_marker(result): 1208 | if is_no_default(default): 1209 | raise ValueError(PEEK_ON_EMPTY) 1210 | 1211 | return (default, iterator) 1212 | 1213 | return (result, prepend(result, iterator)) 1214 | 1215 | 1216 | def has_next(iterable: Iterable[T]) -> Tuple[bool, Iterator[T]]: 1217 | result, iterator = peek(iterable, marker) 1218 | 1219 | return (not is_marker(result), iterator) 1220 | 1221 | 1222 | def is_empty(iterable: Iterable[T]) -> Tuple[bool, Iterator[T]]: 1223 | result, iterator = peek(iterable, marker) 1224 | 1225 | return (is_marker(result), iterator) 1226 | 1227 | 1228 | def next_of(iterator: Iterator[T]) -> Nullary[T]: 1229 | def call() -> T: 1230 | return next(iterator) 1231 | 1232 | return call 1233 | 1234 | 1235 | def next_of_iterable(iterable: Iterable[T]) -> Nullary[T]: 1236 | return next_of(iter(iterable)) 1237 | 1238 | 1239 | def combine(*iterables: Iterable[T]) -> Iterator[T]: 1240 | pending = len(iterables) 1241 | nexts: Iterable[Nullary[T]] = cycle(map(next_of_iterable, iterables)) 1242 | 1243 | while pending: 1244 | try: 1245 | for next in nexts: 1246 | yield next() 1247 | 1248 | except StopIteration: 1249 | pending -= 1 1250 | nexts = cycle(take(pending, nexts)) 1251 | 1252 | 1253 | @overload 1254 | def distribute_unsafe(count: Literal[0], iterable: Iterable[T]) -> EmptyTuple: ... 1255 | 1256 | 1257 | @overload 1258 | def distribute_unsafe(count: Literal[1], iterable: Iterable[T]) -> Tuple1[Iterator[T]]: ... 1259 | 1260 | 1261 | @overload 1262 | def distribute_unsafe(count: Literal[2], iterable: Iterable[T]) -> Tuple2[Iterator[T]]: ... 1263 | 1264 | 1265 | @overload 1266 | def distribute_unsafe(count: Literal[3], iterable: Iterable[T]) -> Tuple3[Iterator[T]]: ... 1267 | 1268 | 1269 | @overload 1270 | def distribute_unsafe(count: Literal[4], iterable: Iterable[T]) -> Tuple4[Iterator[T]]: ... 1271 | 1272 | 1273 | @overload 1274 | def distribute_unsafe(count: Literal[5], iterable: Iterable[T]) -> Tuple5[Iterator[T]]: ... 1275 | 1276 | 1277 | @overload 1278 | def distribute_unsafe(count: Literal[6], iterable: Iterable[T]) -> Tuple6[Iterator[T]]: ... 1279 | 1280 | 1281 | @overload 1282 | def distribute_unsafe(count: Literal[7], iterable: Iterable[T]) -> Tuple7[Iterator[T]]: ... 1283 | 1284 | 1285 | @overload 1286 | def distribute_unsafe(count: Literal[8], iterable: Iterable[T]) -> Tuple8[Iterator[T]]: ... 1287 | 1288 | 1289 | @overload 1290 | def distribute_unsafe(count: int, iterable: Iterable[T]) -> DynamicTuple[Iterator[T]]: ... 1291 | 1292 | 1293 | def distribute_unsafe(count: int, iterable: Iterable[T]) -> DynamicTuple[Iterator[T]]: 1294 | iterators = copy_unsafe(iterable, count) 1295 | 1296 | return tuple(step_by(count, drop(index, iterator)) for index, iterator in enumerate(iterators)) 1297 | 1298 | 1299 | distribute_infinite = distribute_unsafe 1300 | 1301 | 1302 | @overload 1303 | def distribute(count: Literal[0], iterable: Iterable[T]) -> EmptyTuple: ... 1304 | 1305 | 1306 | @overload 1307 | def distribute(count: Literal[1], iterable: Iterable[T]) -> Tuple1[Iterator[T]]: ... 1308 | 1309 | 1310 | @overload 1311 | def distribute(count: Literal[2], iterable: Iterable[T]) -> Tuple2[Iterator[T]]: ... 1312 | 1313 | 1314 | @overload 1315 | def distribute(count: Literal[3], iterable: Iterable[T]) -> Tuple3[Iterator[T]]: ... 1316 | 1317 | 1318 | @overload 1319 | def distribute(count: Literal[4], iterable: Iterable[T]) -> Tuple4[Iterator[T]]: ... 1320 | 1321 | 1322 | @overload 1323 | def distribute(count: Literal[5], iterable: Iterable[T]) -> Tuple5[Iterator[T]]: ... 1324 | 1325 | 1326 | @overload 1327 | def distribute(count: Literal[6], iterable: Iterable[T]) -> Tuple6[Iterator[T]]: ... 1328 | 1329 | 1330 | @overload 1331 | def distribute(count: Literal[7], iterable: Iterable[T]) -> Tuple7[Iterator[T]]: ... 1332 | 1333 | 1334 | @overload 1335 | def distribute(count: Literal[8], iterable: Iterable[T]) -> Tuple8[Iterator[T]]: ... 1336 | 1337 | 1338 | @overload 1339 | def distribute(count: int, iterable: Iterable[T]) -> DynamicTuple[Iterator[T]]: ... 1340 | 1341 | 1342 | def distribute(count: int, iterable: Iterable[T]) -> DynamicTuple[Iterator[T]]: 1343 | iterators = copy(iterable, count) 1344 | 1345 | return tuple(step_by(count, drop(index, iterator)) for index, iterator in enumerate(iterators)) 1346 | 1347 | 1348 | def divide(count: int, iterable: Iterable[T]) -> Iterator[Iterator[T]]: 1349 | array = list(iterable) 1350 | 1351 | size, last = divmod(len(array), count) 1352 | 1353 | stop = 0 1354 | 1355 | for index in range(count): 1356 | start = stop 1357 | stop += size 1358 | 1359 | if index < last: 1360 | stop += 1 1361 | 1362 | yield iter(array[start:stop]) 1363 | 1364 | 1365 | def intersperse(value: T, iterable: Iterable[T]) -> Iterator[T]: 1366 | return rest(interleave(repeat(value), iterable)) 1367 | 1368 | 1369 | def intersperse_with(function: Nullary[T], iterable: Iterable[T]) -> Iterator[T]: 1370 | return rest(interleave(repeat_with(function), iterable)) 1371 | 1372 | 1373 | def interleave(*iterables: Iterable[T]) -> Iterator[T]: 1374 | return flatten(zip(*iterables)) 1375 | 1376 | 1377 | def interleave_longest(*iterables: Iterable[T]) -> Iterator[T]: 1378 | iterator = flatten(zip_longest(*iterables, fill=marker)) 1379 | return (item for item in iterator if not is_marker(item)) 1380 | 1381 | 1382 | def position_all(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> Iterator[int]: 1383 | if predicate is None: 1384 | for index, item in enumerate(iterable): 1385 | if item: 1386 | yield index 1387 | 1388 | else: 1389 | for index, item in enumerate(iterable): 1390 | if predicate(item): 1391 | yield index 1392 | 1393 | 1394 | POSITION_NO_MATCH = "`position` has not found any matches" 1395 | 1396 | 1397 | @overload 1398 | def position(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> int: ... 1399 | 1400 | 1401 | @overload 1402 | def position( 1403 | predicate: OptionalPredicate[T], iterable: Iterable[T], default: U 1404 | ) -> Union[int, U]: ... 1405 | 1406 | 1407 | def position( 1408 | predicate: OptionalPredicate[T], iterable: Iterable[T], default: Any = no_default 1409 | ) -> Any: 1410 | index = next(position_all(predicate, iterable), None) 1411 | 1412 | if index is None: 1413 | if is_no_default(default): 1414 | raise ValueError(POSITION_NO_MATCH) 1415 | 1416 | return default 1417 | 1418 | return index 1419 | 1420 | 1421 | def find_all(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> Iterator[T]: 1422 | return filter(predicate, iterable) 1423 | 1424 | 1425 | FIND_NO_MATCH = "`find` has not found any matches" 1426 | FIND_ON_EMPTY = "`find` called on an empty iterable" 1427 | 1428 | 1429 | @overload 1430 | def find(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> T: ... 1431 | 1432 | 1433 | @overload 1434 | def find(predicate: OptionalPredicate[T], iterable: Iterable[T], default: U) -> Union[T, U]: ... 1435 | 1436 | 1437 | def find( 1438 | predicate: OptionalPredicate[Any], iterable: Iterable[Any], default: Any = no_default 1439 | ) -> Any: 1440 | item = marker 1441 | 1442 | if predicate is None: 1443 | for item in iterable: 1444 | if item: 1445 | return item 1446 | 1447 | else: 1448 | for item in iterable: 1449 | if predicate(item): 1450 | return item 1451 | 1452 | if is_no_default(default): 1453 | raise ValueError(FIND_ON_EMPTY if is_marker(item) else FIND_NO_MATCH) 1454 | 1455 | return default 1456 | 1457 | 1458 | FIND_OR_FIRST_ON_EMPTY = "`find_or_first` called on an empty iterable" 1459 | 1460 | 1461 | @overload 1462 | def find_or_first(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> T: ... 1463 | 1464 | 1465 | @overload 1466 | def find_or_first( 1467 | predicate: OptionalPredicate[T], iterable: Iterable[T], default: U 1468 | ) -> Union[T, U]: ... 1469 | 1470 | 1471 | def find_or_first( 1472 | predicate: OptionalPredicate[Any], iterable: Iterable[Any], default: Any = no_default 1473 | ) -> Any: 1474 | iterator = iter(iterable) 1475 | 1476 | first = next(iterator, marker) 1477 | 1478 | if is_marker(first): 1479 | if is_no_default(default): 1480 | raise ValueError(FIND_OR_FIRST_ON_EMPTY) 1481 | 1482 | first = default 1483 | 1484 | iterator = prepend(first, iterator) 1485 | 1486 | if predicate is None: 1487 | for item in iterator: 1488 | if item: 1489 | return item 1490 | 1491 | else: 1492 | for item in iterator: 1493 | if predicate(item): 1494 | return item 1495 | 1496 | return first 1497 | 1498 | 1499 | FIND_OR_LAST_ON_EMPTY = "`find_or_last` called on an empty iterable" 1500 | 1501 | 1502 | @overload 1503 | def find_or_last(predicate: OptionalPredicate[T], iterable: Iterable[T]) -> T: ... 1504 | 1505 | 1506 | @overload 1507 | def find_or_last( 1508 | predicate: OptionalPredicate[T], iterable: Iterable[T], default: U 1509 | ) -> Union[T, U]: ... 1510 | 1511 | 1512 | def find_or_last( 1513 | predicate: OptionalPredicate[Any], iterable: Iterable[Any], default: Any = no_default 1514 | ) -> Any: 1515 | item = marker 1516 | 1517 | if predicate is None: 1518 | for item in iterable: 1519 | if item: 1520 | return item 1521 | 1522 | else: 1523 | for item in iterable: 1524 | if predicate(item): 1525 | return item 1526 | 1527 | if is_marker(item): 1528 | if is_no_default(default): 1529 | raise ValueError(FIND_OR_LAST_ON_EMPTY) 1530 | 1531 | return default 1532 | 1533 | return item 1534 | 1535 | 1536 | MIN_MAX_ON_EMPTY = "`min_max` called on an empty iterable" 1537 | 1538 | 1539 | @overload 1540 | def min_max(iterable: Iterable[ST], *, key: None = ...) -> Pair[ST]: ... 1541 | 1542 | 1543 | @overload 1544 | def min_max(iterable: Iterable[T], *, key: Unary[T, ST]) -> Pair[T]: ... 1545 | 1546 | 1547 | @overload 1548 | def min_max(iterable: Iterable[ST], *, key: None = ..., default: U) -> Union[Pair[ST], U]: ... 1549 | 1550 | 1551 | @overload 1552 | def min_max(iterable: Iterable[T], *, key: Unary[T, ST], default: U) -> Union[Pair[T], U]: ... 1553 | 1554 | 1555 | def min_max( 1556 | iterable: Iterable[Any], 1557 | *, 1558 | key: Optional[Unary[Any, Any]] = None, 1559 | default: Any = no_default, 1560 | ) -> Any: 1561 | iterator = iter(iterable) 1562 | 1563 | result = next(iterator, marker) 1564 | 1565 | if is_marker(result): 1566 | if is_no_default(default): 1567 | raise ValueError(MIN_MAX_ON_EMPTY) 1568 | 1569 | return default 1570 | 1571 | return min_max_simple(iterator, result) if key is None else min_max_by(iterator, result, key) 1572 | 1573 | 1574 | def min_max_simple(iterable: Iterable[ST], value: ST) -> Pair[ST]: 1575 | low = high = value 1576 | 1577 | for item in iterable: 1578 | if item < low: 1579 | low = item 1580 | 1581 | if high < item: 1582 | high = item 1583 | 1584 | return (low, high) 1585 | 1586 | 1587 | def min_max_by(iterable: Iterable[T], value: T, key: Unary[T, ST]) -> Pair[T]: 1588 | low = high = value 1589 | low_key = high_key = key(value) 1590 | 1591 | for item in iterable: 1592 | item_key = key(item) 1593 | 1594 | if item_key < low_key: 1595 | low_key = item_key 1596 | low = item 1597 | 1598 | if high_key < item_key: 1599 | high_key = item_key 1600 | high = item 1601 | 1602 | return (low, high) 1603 | 1604 | 1605 | def filter_except( 1606 | validate: Validate[T], iterable: Iterable[T], *errors: AnyErrorType 1607 | ) -> Iterator[T]: 1608 | for item in iterable: 1609 | try: 1610 | validate(item) 1611 | 1612 | except errors: 1613 | pass 1614 | 1615 | else: 1616 | yield item 1617 | 1618 | 1619 | def map_except(function: Unary[T, U], iterable: Iterable[T], *errors: AnyErrorType) -> Iterator[U]: 1620 | for item in iterable: 1621 | try: 1622 | yield function(item) 1623 | 1624 | except errors: 1625 | pass 1626 | 1627 | 1628 | def iter_except(function: Nullary[T], *errors: AnyErrorType) -> Iterator[T]: 1629 | try: 1630 | while True: 1631 | yield function() 1632 | 1633 | except errors: 1634 | pass 1635 | 1636 | 1637 | LAST_WITH_TAIL_ON_EMPTY = "`last_with_tail` called on an empty iterable" 1638 | 1639 | 1640 | @overload 1641 | def last_with_tail(iterable: Iterable[T]) -> T: ... 1642 | 1643 | 1644 | @overload 1645 | def last_with_tail(iterable: Iterable[T], default: U) -> Union[T, U]: ... 1646 | 1647 | 1648 | def last_with_tail(iterable: Iterable[Any], default: Any = no_default) -> Any: 1649 | iterator: Iterator[Any] 1650 | 1651 | if is_reversible(iterable): 1652 | iterator = reversed(iterable) 1653 | 1654 | else: 1655 | iterator = tail(1, iterable) 1656 | 1657 | result = next(iterator, marker) 1658 | 1659 | if is_marker(result): 1660 | if is_no_default(default): 1661 | raise ValueError(LAST_WITH_TAIL_ON_EMPTY) 1662 | 1663 | return default 1664 | 1665 | return result 1666 | 1667 | 1668 | def tail(size: int, iterable: Iterable[T]) -> Iterator[T]: 1669 | return iter(deque(iterable, size)) 1670 | 1671 | 1672 | STRICT_TRUE = True 1673 | STRICT_FALSE = False 1674 | REVERSE_TRUE = True 1675 | REVERSE_FALSE = False 1676 | 1677 | 1678 | COMPARE: Dict[ 1679 | Pair[bool], 1680 | Union[Compare[LenientOrdered, LenientOrdered], Compare[StrictOrdered, StrictOrdered]], 1681 | ] = { 1682 | (STRICT_FALSE, REVERSE_FALSE): less_or_equal, 1683 | (STRICT_FALSE, REVERSE_TRUE): greater_or_equal, 1684 | (STRICT_TRUE, REVERSE_FALSE): less, 1685 | (STRICT_TRUE, REVERSE_TRUE): greater, 1686 | } 1687 | 1688 | 1689 | @overload 1690 | def is_sorted( 1691 | iterable: Iterable[LT], 1692 | key: None = ..., 1693 | *, 1694 | strict: Literal[False] = ..., 1695 | reverse: bool = ..., 1696 | ) -> bool: ... 1697 | 1698 | 1699 | @overload 1700 | def is_sorted( 1701 | iterable: Iterable[ST], 1702 | key: None = ..., 1703 | *, 1704 | strict: Literal[True], 1705 | reverse: bool = ..., 1706 | ) -> bool: ... 1707 | 1708 | 1709 | @overload 1710 | def is_sorted( 1711 | iterable: Iterable[T], 1712 | key: Unary[T, LT], 1713 | *, 1714 | strict: Literal[False] = ..., 1715 | reverse: bool = ..., 1716 | ) -> bool: ... 1717 | 1718 | 1719 | @overload 1720 | def is_sorted( 1721 | iterable: Iterable[T], 1722 | key: Unary[T, ST], 1723 | *, 1724 | strict: Literal[True], 1725 | reverse: bool = ..., 1726 | ) -> bool: ... 1727 | 1728 | 1729 | def is_sorted( 1730 | iterable: Iterable[Any], 1731 | key: Optional[Unary[Any, Any]] = None, 1732 | *, 1733 | strict: bool = False, 1734 | reverse: bool = False, 1735 | ) -> bool: 1736 | return ( 1737 | is_sorted_simple(iterable, strict=strict, reverse=reverse) 1738 | if key is None 1739 | else is_sorted_by(iterable, key, strict=strict, reverse=reverse) 1740 | ) 1741 | 1742 | 1743 | def is_sorted_simple( 1744 | iterable: Iterable[Any], *, strict: bool = False, reverse: bool = False 1745 | ) -> bool: 1746 | compare = COMPARE[strict, reverse] 1747 | return all(map(unpack_binary(compare), pairs_windows(iterable))) 1748 | 1749 | 1750 | def is_sorted_by( 1751 | iterable: Iterable[Any], key: Unary[Any, Any], *, strict: bool = False, reverse: bool = False 1752 | ) -> bool: 1753 | return is_sorted_simple(map(key, iterable), strict=strict, reverse=reverse) 1754 | 1755 | 1756 | @overload 1757 | def sort(iterable: Iterable[ST], *, key: None = ..., reverse: bool = ...) -> Iterator[ST]: ... 1758 | 1759 | 1760 | @overload 1761 | def sort(iterable: Iterable[T], *, key: Unary[T, ST], reverse: bool = ...) -> Iterator[T]: ... 1762 | 1763 | 1764 | def sort( 1765 | iterable: Iterable[Any], 1766 | *, 1767 | key: Optional[Unary[Any, Any]] = None, 1768 | reverse: bool = False, 1769 | ) -> Iterator[Any]: 1770 | return iter(sorted(iterable, key=key, reverse=reverse)) 1771 | 1772 | 1773 | def reverse(iterable: Iterable[T]) -> Iterator[T]: 1774 | if is_reversible(iterable): 1775 | return reversed(iterable) 1776 | 1777 | return reversed(list(iterable)) 1778 | 1779 | 1780 | # def circular_list_windows(size: int, iterable: Iterable[T]) -> Iterator[List[T]]: 1781 | # ... 1782 | 1783 | 1784 | # def circular_tuple_windows(size: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: 1785 | # ... 1786 | 1787 | 1788 | # def circular_set_windows(size: int, iterable: Iterable[T]) -> Iterator[Set[T]]: 1789 | # ... 1790 | 1791 | 1792 | def windows_with(function: Unary[Iterable[T], U], size: int, iterable: Iterable[T]) -> Iterator[U]: 1793 | iterator = iter(iterable) 1794 | 1795 | window = deque(take(size, iterator), size) 1796 | 1797 | if len(window) == size: 1798 | yield function(window) 1799 | 1800 | window_append = window.append 1801 | 1802 | for item in iterator: 1803 | window_append(item) 1804 | 1805 | yield function(window) 1806 | 1807 | 1808 | def list_windows(size: int, iterable: Iterable[T]) -> Iterator[List[T]]: 1809 | return windows_with(list, size, iterable) 1810 | 1811 | 1812 | @overload 1813 | def tuple_windows(size: Literal[0], iterable: Iterable[T]) -> Iterator[EmptyTuple]: ... 1814 | 1815 | 1816 | @overload 1817 | def tuple_windows(size: Literal[1], iterable: Iterable[T]) -> Iterator[Tuple1[T]]: ... 1818 | 1819 | 1820 | @overload 1821 | def tuple_windows(size: Literal[2], iterable: Iterable[T]) -> Iterator[Tuple2[T]]: ... 1822 | 1823 | 1824 | @overload 1825 | def tuple_windows(size: Literal[3], iterable: Iterable[T]) -> Iterator[Tuple3[T]]: ... 1826 | 1827 | 1828 | @overload 1829 | def tuple_windows(size: Literal[4], iterable: Iterable[T]) -> Iterator[Tuple4[T]]: ... 1830 | 1831 | 1832 | @overload 1833 | def tuple_windows(size: Literal[5], iterable: Iterable[T]) -> Iterator[Tuple5[T]]: ... 1834 | 1835 | 1836 | @overload 1837 | def tuple_windows(size: Literal[6], iterable: Iterable[T]) -> Iterator[Tuple6[T]]: ... 1838 | 1839 | 1840 | @overload 1841 | def tuple_windows(size: Literal[7], iterable: Iterable[T]) -> Iterator[Tuple7[T]]: ... 1842 | 1843 | 1844 | @overload 1845 | def tuple_windows(size: Literal[8], iterable: Iterable[T]) -> Iterator[Tuple8[T]]: ... 1846 | 1847 | 1848 | @overload 1849 | def tuple_windows(size: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: ... 1850 | 1851 | 1852 | def tuple_windows(size: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: 1853 | return windows_with(tuple, size, iterable) 1854 | 1855 | 1856 | def pairs_windows(iterable: Iterable[T]) -> Iterator[Pair[T]]: 1857 | return tuple_windows(2, iterable) 1858 | 1859 | 1860 | def iter_windows(size: int, iterable: Iterable[T]) -> Iterator[Iterator[T]]: 1861 | return map(iter, list_windows(size, iterable)) 1862 | 1863 | 1864 | def set_windows(size: int, iterable: Iterable[Q]) -> Iterator[Set[Q]]: 1865 | return windows_with(set, size, iterable) 1866 | 1867 | 1868 | def inspect(function: Inspect[T], iterable: Iterable[T]) -> Iterator[T]: 1869 | for item in iterable: 1870 | function(item) 1871 | yield item 1872 | 1873 | 1874 | def duplicates_fast_simple(iterable: Iterable[Q]) -> Iterator[Q]: 1875 | seen: Set[Q] = set() 1876 | add_to_seen = seen.add 1877 | 1878 | for item in iterable: 1879 | if item in seen: 1880 | yield item 1881 | 1882 | else: 1883 | add_to_seen(item) 1884 | 1885 | 1886 | def duplicates_fast_by(iterable: Iterable[T], key: Unary[T, Q]) -> Iterator[T]: 1887 | seen_values: Set[Q] = set() 1888 | add_to_seen_values = seen_values.add 1889 | 1890 | for item in iterable: 1891 | value = key(item) 1892 | 1893 | if value in seen_values: 1894 | yield item 1895 | 1896 | else: 1897 | add_to_seen_values(value) 1898 | 1899 | 1900 | @overload 1901 | def duplicates_fast(iterable: Iterable[Q], key: None = ...) -> Iterator[Q]: ... 1902 | 1903 | 1904 | @overload 1905 | def duplicates_fast(iterable: Iterable[T], key: Unary[T, Q]) -> Iterator[T]: ... 1906 | 1907 | 1908 | def duplicates_fast( 1909 | iterable: Iterable[Any], key: Optional[Unary[Any, Any]] = None 1910 | ) -> Iterator[Any]: 1911 | return duplicates_fast_simple(iterable) if key is None else duplicates_fast_by(iterable, key) 1912 | 1913 | 1914 | def duplicates_simple(iterable: Iterable[T]) -> Iterator[T]: 1915 | seen_set: Set[T] = set() 1916 | add_to_seen_set = seen_set.add 1917 | seen_array: List[T] = [] 1918 | add_to_seen_array = seen_array.append 1919 | 1920 | for item in iterable: 1921 | try: 1922 | if item in seen_set: 1923 | yield item 1924 | 1925 | else: 1926 | add_to_seen_set(item) 1927 | 1928 | except TypeError: 1929 | if item in seen_array: 1930 | yield item 1931 | 1932 | else: 1933 | add_to_seen_array(item) 1934 | 1935 | 1936 | def duplicates_by(iterable: Iterable[T], key: Unary[T, U]) -> Iterator[T]: 1937 | seen_values_set: Set[U] = set() 1938 | add_to_seen_values_set = seen_values_set.add 1939 | seen_values_array: List[U] = [] 1940 | add_to_seen_values_array = seen_values_array.append 1941 | 1942 | for item in iterable: 1943 | value = key(item) 1944 | 1945 | try: 1946 | if value in seen_values_set: 1947 | yield item 1948 | 1949 | else: 1950 | add_to_seen_values_set(value) 1951 | 1952 | except TypeError: 1953 | if value in seen_values_array: 1954 | yield item 1955 | 1956 | else: 1957 | add_to_seen_values_array(value) 1958 | 1959 | 1960 | def duplicates(iterable: Iterable[T], key: Optional[Unary[T, U]] = None) -> Iterator[T]: 1961 | return duplicates_simple(iterable) if key is None else duplicates_by(iterable, key) 1962 | 1963 | 1964 | def unary_tuple(item: T) -> Tuple[T]: 1965 | return (item,) 1966 | 1967 | 1968 | def unpack_unary_tuple(value: Tuple[T]) -> T: 1969 | (item,) = value 1970 | 1971 | return item 1972 | 1973 | 1974 | def unique_fast_simple(iterable: Iterable[Q]) -> Iterator[Q]: 1975 | seen: Set[Q] = set() 1976 | add_to_seen = seen.add 1977 | 1978 | for item in iterable: 1979 | if item not in seen: 1980 | add_to_seen(item) 1981 | 1982 | yield item 1983 | 1984 | 1985 | def unique_fast_by(iterable: Iterable[T], key: Unary[T, Q]) -> Iterator[T]: 1986 | seen_values: Set[Q] = set() 1987 | add_to_seen_values = seen_values.add 1988 | 1989 | for item in iterable: 1990 | value = key(item) 1991 | 1992 | if value not in seen_values: 1993 | add_to_seen_values(value) 1994 | 1995 | yield item 1996 | 1997 | 1998 | @overload 1999 | def unique_fast(iterable: Iterable[Q], key: None = ...) -> Iterator[Q]: ... 2000 | 2001 | 2002 | @overload 2003 | def unique_fast(iterable: Iterable[T], key: Unary[T, Q]) -> Iterator[T]: ... 2004 | 2005 | 2006 | def unique_fast(iterable: Iterable[Any], key: Optional[Unary[Any, Any]] = None) -> Iterator[Any]: 2007 | return unique_fast_simple(iterable) if key is None else unique_fast_by(iterable, key) 2008 | 2009 | 2010 | def unique_simple(iterable: Iterable[T]) -> Iterator[T]: 2011 | seen_set: Set[T] = set() 2012 | add_to_seen_set = seen_set.add 2013 | seen_array: List[T] = [] 2014 | add_to_seen_array = seen_array.append 2015 | 2016 | for item in iterable: 2017 | try: 2018 | if item not in seen_set: 2019 | add_to_seen_set(item) 2020 | 2021 | yield item 2022 | 2023 | except TypeError: 2024 | if item not in seen_array: 2025 | add_to_seen_array(item) 2026 | 2027 | yield item 2028 | 2029 | 2030 | def unique_by(iterable: Iterable[T], key: Unary[T, U]) -> Iterator[T]: 2031 | seen_values_set: Set[U] = set() 2032 | add_to_seen_values_set = seen_values_set.add 2033 | seen_values_array: List[U] = [] 2034 | add_to_seen_values_array = seen_values_array.append 2035 | 2036 | for item in iterable: 2037 | value = key(item) 2038 | 2039 | try: 2040 | if value not in seen_values_set: 2041 | add_to_seen_values_set(value) 2042 | 2043 | yield item 2044 | 2045 | except TypeError: 2046 | if value not in seen_values_array: 2047 | add_to_seen_values_array(value) 2048 | 2049 | yield item 2050 | 2051 | 2052 | def unique(iterable: Iterable[T], key: Optional[Unary[T, U]] = None) -> Iterator[T]: 2053 | return unique_simple(iterable) if key is None else unique_by(iterable, key) 2054 | 2055 | 2056 | @overload 2057 | def zip() -> Iterator[Never]: ... 2058 | 2059 | 2060 | @overload 2061 | def zip(__iterable_a: Iterable[A]) -> Iterator[Tuple[A]]: ... 2062 | 2063 | 2064 | @overload 2065 | def zip(__iterable_a: Iterable[A], __iterable_b: Iterable[B]) -> Iterator[Tuple[A, B]]: ... 2066 | 2067 | 2068 | @overload 2069 | def zip( 2070 | __iterable_a: Iterable[A], __iterable_b: Iterable[B], __iterable_c: Iterable[C] 2071 | ) -> Iterator[Tuple[A, B, C]]: ... 2072 | 2073 | 2074 | @overload 2075 | def zip( 2076 | __iterable_a: Iterable[A], 2077 | __iterable_b: Iterable[B], 2078 | __iterable_c: Iterable[C], 2079 | __iterable_d: Iterable[D], 2080 | ) -> Iterator[Tuple[A, B, C, D]]: ... 2081 | 2082 | 2083 | @overload 2084 | def zip( 2085 | __iterable_a: Iterable[A], 2086 | __iterable_b: Iterable[B], 2087 | __iterable_c: Iterable[C], 2088 | __iterable_d: Iterable[D], 2089 | __iterable_e: Iterable[E], 2090 | ) -> Iterator[Tuple[A, B, C, D, E]]: ... 2091 | 2092 | 2093 | @overload 2094 | def zip( 2095 | __iterable_a: Iterable[A], 2096 | __iterable_b: Iterable[B], 2097 | __iterable_c: Iterable[C], 2098 | __iterable_d: Iterable[D], 2099 | __iterable_e: Iterable[E], 2100 | __iterable_f: Iterable[F], 2101 | ) -> Iterator[Tuple[A, B, C, D, E, F]]: ... 2102 | 2103 | 2104 | @overload 2105 | def zip( 2106 | __iterable_a: Iterable[A], 2107 | __iterable_b: Iterable[B], 2108 | __iterable_c: Iterable[C], 2109 | __iterable_d: Iterable[D], 2110 | __iterable_e: Iterable[E], 2111 | __iterable_f: Iterable[F], 2112 | __iterable_g: Iterable[G], 2113 | ) -> Iterator[Tuple[A, B, C, D, E, F, G]]: ... 2114 | 2115 | 2116 | @overload 2117 | def zip( 2118 | __iterable_a: Iterable[A], 2119 | __iterable_b: Iterable[B], 2120 | __iterable_c: Iterable[C], 2121 | __iterable_d: Iterable[D], 2122 | __iterable_e: Iterable[E], 2123 | __iterable_f: Iterable[F], 2124 | __iterable_g: Iterable[G], 2125 | __iterable_h: Iterable[H], 2126 | ) -> Iterator[Tuple[A, B, C, D, E, F, G, H]]: ... 2127 | 2128 | 2129 | @overload 2130 | def zip( 2131 | __iterable_a: Iterable[Any], 2132 | __iterable_b: Iterable[Any], 2133 | __iterable_c: Iterable[Any], 2134 | __iterable_d: Iterable[Any], 2135 | __iterable_e: Iterable[Any], 2136 | __iterable_f: Iterable[Any], 2137 | __iterable_g: Iterable[Any], 2138 | __iterable_h: Iterable[Any], 2139 | __iterable_n: Iterable[Any], 2140 | *iterables: Iterable[Any], 2141 | ) -> Iterator[DynamicTuple[Any]]: ... 2142 | 2143 | 2144 | def zip(*iterables: Iterable[Any]) -> Iterator[DynamicTuple[Any]]: 2145 | return standard_zip(*iterables) 2146 | 2147 | 2148 | @overload 2149 | def zip_equal() -> Iterator[Never]: ... 2150 | 2151 | 2152 | @overload 2153 | def zip_equal(__iterable_a: Iterable[A]) -> Iterator[Tuple[A]]: ... 2154 | 2155 | 2156 | @overload 2157 | def zip_equal(__iterable_a: Iterable[A], __iterable_b: Iterable[B]) -> Iterator[Tuple[A, B]]: ... 2158 | 2159 | 2160 | @overload 2161 | def zip_equal( 2162 | __iterable_a: Iterable[A], __iterable_b: Iterable[B], __iterable_c: Iterable[C] 2163 | ) -> Iterator[Tuple[A, B, C]]: ... 2164 | 2165 | 2166 | @overload 2167 | def zip_equal( 2168 | __iterable_a: Iterable[A], 2169 | __iterable_b: Iterable[B], 2170 | __iterable_c: Iterable[C], 2171 | __iterable_d: Iterable[D], 2172 | ) -> Iterator[Tuple[A, B, C, D]]: ... 2173 | 2174 | 2175 | @overload 2176 | def zip_equal( 2177 | __iterable_a: Iterable[A], 2178 | __iterable_b: Iterable[B], 2179 | __iterable_c: Iterable[C], 2180 | __iterable_d: Iterable[D], 2181 | __iterable_e: Iterable[E], 2182 | ) -> Iterator[Tuple[A, B, C, D, E]]: ... 2183 | 2184 | 2185 | @overload 2186 | def zip_equal( 2187 | __iterable_a: Iterable[A], 2188 | __iterable_b: Iterable[B], 2189 | __iterable_c: Iterable[C], 2190 | __iterable_d: Iterable[D], 2191 | __iterable_e: Iterable[E], 2192 | __iterable_f: Iterable[F], 2193 | ) -> Iterator[Tuple[A, B, C, D, E, F]]: ... 2194 | 2195 | 2196 | @overload 2197 | def zip_equal( 2198 | __iterable_a: Iterable[A], 2199 | __iterable_b: Iterable[B], 2200 | __iterable_c: Iterable[C], 2201 | __iterable_d: Iterable[D], 2202 | __iterable_e: Iterable[E], 2203 | __iterable_f: Iterable[F], 2204 | __iterable_g: Iterable[G], 2205 | ) -> Iterator[Tuple[A, B, C, D, E, F, G]]: ... 2206 | 2207 | 2208 | @overload 2209 | def zip_equal( 2210 | __iterable_a: Iterable[A], 2211 | __iterable_b: Iterable[B], 2212 | __iterable_c: Iterable[C], 2213 | __iterable_d: Iterable[D], 2214 | __iterable_e: Iterable[E], 2215 | __iterable_f: Iterable[F], 2216 | __iterable_g: Iterable[G], 2217 | __iterable_h: Iterable[H], 2218 | ) -> Iterator[Tuple[A, B, C, D, E, F, G, H]]: ... 2219 | 2220 | 2221 | @overload 2222 | def zip_equal( 2223 | __iterable_a: Iterable[Any], 2224 | __iterable_b: Iterable[Any], 2225 | __iterable_c: Iterable[Any], 2226 | __iterable_d: Iterable[Any], 2227 | __iterable_e: Iterable[Any], 2228 | __iterable_f: Iterable[Any], 2229 | __iterable_g: Iterable[Any], 2230 | __iterable_h: Iterable[Any], 2231 | __iterable_n: Iterable[Any], 2232 | *iterables: Iterable[Any], 2233 | ) -> Iterator[DynamicTuple[Any]]: ... 2234 | 2235 | 2236 | def zip_equal(*iterables: Iterable[Any]) -> Iterator[DynamicTuple[Any]]: 2237 | if sys.version_info >= (3, 10): 2238 | return standard_zip(*iterables, strict=True) 2239 | 2240 | return zip_equal_simple(*iterables) 2241 | 2242 | 2243 | SINGULAR = " " 2244 | PLURAL = "s 1-" 2245 | 2246 | SHORTER = "`zip_equal` argument {short} is shorter than argument{plural}{index}" 2247 | format_shorter = SHORTER.format 2248 | 2249 | LONGER = "`zip_equal` argument {long} is longer than argument{plural}{index}" 2250 | format_longer = LONGER.format 2251 | 2252 | 2253 | def shorter(index: int) -> str: 2254 | return format_shorter( 2255 | short=index + 1, 2256 | index=index, 2257 | plural=(PLURAL if index - 1 else SINGULAR), 2258 | ) 2259 | 2260 | 2261 | def longer(index: int) -> str: 2262 | return format_longer( 2263 | long=index + 1, 2264 | index=index, 2265 | plural=(PLURAL if index - 1 else SINGULAR), 2266 | ) 2267 | 2268 | 2269 | def zip_equal_simple(*iterables: Iterable[Any]) -> Iterator[DynamicTuple[Any]]: 2270 | if not iterables: 2271 | return # early return 2272 | 2273 | for item in zip_longest(*iterables, fill=marker): # check for length 2274 | head, *tail = item 2275 | 2276 | if is_marker(head): # argument longer than previous arguments 2277 | for index, value in enumerate(tail, 1): 2278 | if value is not marker: 2279 | raise ValueError(longer(index)) 2280 | 2281 | else: # argument shorter than previous ones 2282 | for index, value in enumerate(tail, 1): 2283 | if is_marker(value): 2284 | raise ValueError(shorter(index)) 2285 | 2286 | yield item # simply yield if everything is alright 2287 | 2288 | 2289 | @overload 2290 | def zip_longest() -> Iterator[Never]: ... 2291 | 2292 | 2293 | @overload 2294 | def zip_longest(__iterable_a: Iterable[A]) -> Iterator[Tuple[A]]: ... 2295 | 2296 | 2297 | @overload 2298 | def zip_longest( 2299 | __iterable_a: Iterable[A], __iterable_b: Iterable[B] 2300 | ) -> Iterator[Tuple[Optional[A], Optional[B]]]: ... 2301 | 2302 | 2303 | @overload 2304 | def zip_longest( 2305 | __iterable_a: Iterable[A], 2306 | __iterable_b: Iterable[B], 2307 | __iterable_c: Iterable[C], 2308 | ) -> Iterator[Tuple[Optional[A], Optional[B], Optional[C]]]: ... 2309 | 2310 | 2311 | @overload 2312 | def zip_longest( 2313 | __iterable_a: Iterable[A], 2314 | __iterable_b: Iterable[B], 2315 | __iterable_c: Iterable[C], 2316 | __iterable_d: Iterable[D], 2317 | ) -> Iterator[Tuple[Optional[A], Optional[B], Optional[C], Optional[D]]]: ... 2318 | 2319 | 2320 | @overload 2321 | def zip_longest( 2322 | __iterable_a: Iterable[A], 2323 | __iterable_b: Iterable[B], 2324 | __iterable_c: Iterable[C], 2325 | __iterable_d: Iterable[D], 2326 | __iterable_e: Iterable[E], 2327 | ) -> Iterator[Tuple[Optional[A], Optional[B], Optional[C], Optional[D], Optional[E]]]: ... 2328 | 2329 | 2330 | @overload 2331 | def zip_longest( 2332 | __iterable_a: Iterable[A], 2333 | __iterable_b: Iterable[B], 2334 | __iterable_c: Iterable[C], 2335 | __iterable_d: Iterable[D], 2336 | __iterable_e: Iterable[E], 2337 | __iterable_f: Iterable[F], 2338 | ) -> Iterator[ 2339 | Tuple[Optional[A], Optional[B], Optional[C], Optional[D], Optional[E], Optional[F]] 2340 | ]: ... 2341 | 2342 | 2343 | @overload 2344 | def zip_longest( 2345 | __iterable_a: Iterable[A], 2346 | __iterable_b: Iterable[B], 2347 | __iterable_c: Iterable[C], 2348 | __iterable_d: Iterable[D], 2349 | __iterable_e: Iterable[E], 2350 | __iterable_f: Iterable[F], 2351 | __iterable_g: Iterable[G], 2352 | ) -> Iterator[ 2353 | Tuple[ 2354 | Optional[A], 2355 | Optional[B], 2356 | Optional[C], 2357 | Optional[D], 2358 | Optional[E], 2359 | Optional[F], 2360 | Optional[G], 2361 | ] 2362 | ]: ... 2363 | 2364 | 2365 | @overload 2366 | def zip_longest( 2367 | __iterable_a: Iterable[A], 2368 | __iterable_b: Iterable[B], 2369 | __iterable_c: Iterable[C], 2370 | __iterable_d: Iterable[D], 2371 | __iterable_e: Iterable[E], 2372 | __iterable_f: Iterable[F], 2373 | __iterable_g: Iterable[G], 2374 | __iterable_h: Iterable[H], 2375 | ) -> Iterator[ 2376 | Tuple[ 2377 | Optional[A], 2378 | Optional[B], 2379 | Optional[C], 2380 | Optional[D], 2381 | Optional[E], 2382 | Optional[F], 2383 | Optional[G], 2384 | Optional[H], 2385 | ] 2386 | ]: ... 2387 | 2388 | 2389 | @overload 2390 | def zip_longest( 2391 | __iterable_a: Iterable[Any], 2392 | __iterable_b: Iterable[Any], 2393 | __iterable_c: Iterable[Any], 2394 | __iterable_d: Iterable[Any], 2395 | __iterable_e: Iterable[Any], 2396 | __iterable_f: Iterable[Any], 2397 | __iterable_g: Iterable[Any], 2398 | __iterable_h: Iterable[Any], 2399 | __iterable_n: Iterable[Any], 2400 | *iterables: Iterable[Any], 2401 | ) -> Iterator[DynamicTuple[Optional[Any]]]: ... 2402 | 2403 | 2404 | @overload 2405 | def zip_longest(*, fill: T) -> Iterator[Never]: ... 2406 | 2407 | 2408 | @overload 2409 | def zip_longest(__iterable_a: Iterable[A], *, fill: T) -> Iterator[Tuple[A]]: ... 2410 | 2411 | 2412 | @overload 2413 | def zip_longest( 2414 | __iterable_a: Iterable[A], __iterable_b: Iterable[B], *, fill: T 2415 | ) -> Iterator[Tuple[Union[A, T], Union[B, T]]]: ... 2416 | 2417 | 2418 | @overload 2419 | def zip_longest( 2420 | __iterable_a: Iterable[A], 2421 | __iterable_b: Iterable[B], 2422 | __iterable_c: Iterable[C], 2423 | *, 2424 | fill: T, 2425 | ) -> Iterator[Tuple[Union[A, T], Union[B, T], Union[C, T]]]: ... 2426 | 2427 | 2428 | @overload 2429 | def zip_longest( 2430 | __iterable_a: Iterable[A], 2431 | __iterable_b: Iterable[B], 2432 | __iterable_c: Iterable[C], 2433 | __iterable_d: Iterable[D], 2434 | *, 2435 | fill: T, 2436 | ) -> Iterator[Tuple[Union[A, T], Union[B, T], Union[C, T], Union[D, T]]]: ... 2437 | 2438 | 2439 | @overload 2440 | def zip_longest( 2441 | __iterable_a: Iterable[A], 2442 | __iterable_b: Iterable[B], 2443 | __iterable_c: Iterable[C], 2444 | __iterable_d: Iterable[D], 2445 | __iterable_e: Iterable[E], 2446 | *, 2447 | fill: T, 2448 | ) -> Iterator[Tuple[Union[A, T], Union[B, T], Union[C, T], Union[D, T], Union[E, T]]]: ... 2449 | 2450 | 2451 | @overload 2452 | def zip_longest( 2453 | __iterable_a: Iterable[A], 2454 | __iterable_b: Iterable[B], 2455 | __iterable_c: Iterable[C], 2456 | __iterable_d: Iterable[D], 2457 | __iterable_e: Iterable[E], 2458 | __iterable_f: Iterable[F], 2459 | *, 2460 | fill: T, 2461 | ) -> Iterator[ 2462 | Tuple[Union[A, T], Union[B, T], Union[C, T], Union[D, T], Union[E, T], Union[F, T]] 2463 | ]: ... 2464 | 2465 | 2466 | @overload 2467 | def zip_longest( 2468 | __iterable_a: Iterable[A], 2469 | __iterable_b: Iterable[B], 2470 | __iterable_c: Iterable[C], 2471 | __iterable_d: Iterable[D], 2472 | __iterable_e: Iterable[E], 2473 | __iterable_f: Iterable[F], 2474 | __iterable_g: Iterable[G], 2475 | *, 2476 | fill: T, 2477 | ) -> Iterator[ 2478 | Tuple[ 2479 | Union[A, T], 2480 | Union[B, T], 2481 | Union[C, T], 2482 | Union[D, T], 2483 | Union[E, T], 2484 | Union[F, T], 2485 | Union[G, T], 2486 | ] 2487 | ]: ... 2488 | 2489 | 2490 | @overload 2491 | def zip_longest( 2492 | __iterable_a: Iterable[A], 2493 | __iterable_b: Iterable[B], 2494 | __iterable_c: Iterable[C], 2495 | __iterable_d: Iterable[D], 2496 | __iterable_e: Iterable[E], 2497 | __iterable_f: Iterable[F], 2498 | __iterable_g: Iterable[G], 2499 | __iterable_h: Iterable[H], 2500 | *, 2501 | fill: T, 2502 | ) -> Iterator[ 2503 | Tuple[ 2504 | Union[A, T], 2505 | Union[B, T], 2506 | Union[C, T], 2507 | Union[D, T], 2508 | Union[E, T], 2509 | Union[F, T], 2510 | Union[G, T], 2511 | Union[H, T], 2512 | ] 2513 | ]: ... 2514 | 2515 | 2516 | @overload 2517 | def zip_longest( 2518 | __iterable_a: Iterable[Any], 2519 | __iterable_b: Iterable[Any], 2520 | __iterable_c: Iterable[Any], 2521 | __iterable_d: Iterable[Any], 2522 | __iterable_e: Iterable[Any], 2523 | __iterable_f: Iterable[Any], 2524 | __iterable_g: Iterable[Any], 2525 | __iterable_h: Iterable[Any], 2526 | __iterable_n: Iterable[Any], 2527 | *iterables: Iterable[Any], 2528 | fill: T, 2529 | ) -> Iterator[DynamicTuple[Union[Any, T]]]: ... 2530 | 2531 | 2532 | def zip_longest( 2533 | *iterables: Iterable[Any], fill: Optional[Any] = None 2534 | ) -> Iterator[DynamicTuple[Any]]: 2535 | return standard_zip_longest(*iterables, fillvalue=fill) 2536 | 2537 | 2538 | def transpose(iterable: Iterable[Iterable[T]]) -> Iterator[DynamicTuple[T]]: 2539 | return zip_equal(*iterable) 2540 | 2541 | 2542 | def power_set(iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: 2543 | pool = tuple(iterable) 2544 | 2545 | return flatten(combinations(count, pool) for count in inclusive(range(len(pool)))) 2546 | 2547 | 2548 | def inclusive(non_inclusive: range) -> range: 2549 | step = non_inclusive.step 2550 | 2551 | return range(non_inclusive.start, non_inclusive.stop + sign(step), step) 2552 | 2553 | 2554 | def sign(value: int) -> int: 2555 | return int(copy_sign(1, value)) 2556 | 2557 | 2558 | @overload 2559 | def cartesian_product() -> Iterator[EmptyTuple]: ... 2560 | 2561 | 2562 | @overload 2563 | def cartesian_product(__iterable_a: Iterable[A]) -> Iterator[Tuple[A]]: ... 2564 | 2565 | 2566 | @overload 2567 | def cartesian_product( 2568 | __iterable_a: Iterable[A], __iterable_b: Iterable[B] 2569 | ) -> Iterator[Tuple[A, B]]: ... 2570 | 2571 | 2572 | @overload 2573 | def cartesian_product( 2574 | __iterable_a: Iterable[A], __iterable_b: Iterable[B], __iterable_c: Iterable[C] 2575 | ) -> Iterator[Tuple[A, B, C]]: ... 2576 | 2577 | 2578 | @overload 2579 | def cartesian_product( 2580 | __iterable_a: Iterable[A], 2581 | __iterable_b: Iterable[B], 2582 | __iterable_c: Iterable[C], 2583 | __iterable_d: Iterable[D], 2584 | ) -> Iterator[Tuple[A, B, C, D]]: ... 2585 | 2586 | 2587 | @overload 2588 | def cartesian_product( 2589 | __iterable_a: Iterable[A], 2590 | __iterable_b: Iterable[B], 2591 | __iterable_c: Iterable[C], 2592 | __iterable_d: Iterable[D], 2593 | __iterable_e: Iterable[E], 2594 | ) -> Iterator[Tuple[A, B, C, D, E]]: ... 2595 | 2596 | 2597 | @overload 2598 | def cartesian_product( 2599 | __iterable_a: Iterable[A], 2600 | __iterable_b: Iterable[B], 2601 | __iterable_c: Iterable[C], 2602 | __iterable_d: Iterable[D], 2603 | __iterable_e: Iterable[E], 2604 | __iterable_f: Iterable[F], 2605 | ) -> Iterator[Tuple[A, B, C, D, E, F]]: ... 2606 | 2607 | 2608 | @overload 2609 | def cartesian_product( 2610 | __iterable_a: Iterable[A], 2611 | __iterable_b: Iterable[B], 2612 | __iterable_c: Iterable[C], 2613 | __iterable_d: Iterable[D], 2614 | __iterable_e: Iterable[E], 2615 | __iterable_f: Iterable[F], 2616 | __iterable_g: Iterable[G], 2617 | ) -> Iterator[Tuple[A, B, C, D, E, F, G]]: ... 2618 | 2619 | 2620 | @overload 2621 | def cartesian_product( 2622 | __iterable_a: Iterable[A], 2623 | __iterable_b: Iterable[B], 2624 | __iterable_c: Iterable[C], 2625 | __iterable_d: Iterable[D], 2626 | __iterable_e: Iterable[E], 2627 | __iterable_f: Iterable[F], 2628 | __iterable_g: Iterable[G], 2629 | __iterable_h: Iterable[H], 2630 | ) -> Iterator[Tuple[A, B, C, D, E, F, G, H]]: ... 2631 | 2632 | 2633 | @overload 2634 | def cartesian_product( 2635 | __iterable_a: Iterable[Any], 2636 | __iterable_b: Iterable[Any], 2637 | __iterable_c: Iterable[Any], 2638 | __iterable_d: Iterable[Any], 2639 | __iterable_e: Iterable[Any], 2640 | __iterable_f: Iterable[Any], 2641 | __iterable_g: Iterable[Any], 2642 | __iterable_h: Iterable[Any], 2643 | __iterable_n: Iterable[Any], 2644 | *iterables: Iterable[Any], 2645 | ) -> Iterator[DynamicTuple[Any]]: ... 2646 | 2647 | 2648 | def cartesian_product(*iterables: Iterable[Any]) -> Iterator[DynamicTuple[Any]]: 2649 | return standard_product(*iterables) 2650 | 2651 | 2652 | @overload 2653 | def cartesian_power(power: Literal[0], iterable: Iterable[T]) -> Iterator[EmptyTuple]: ... 2654 | 2655 | 2656 | @overload 2657 | def cartesian_power(power: Literal[1], iterable: Iterable[T]) -> Iterator[Tuple1[T]]: ... 2658 | 2659 | 2660 | @overload 2661 | def cartesian_power(power: Literal[2], iterable: Iterable[T]) -> Iterator[Tuple2[T]]: ... 2662 | 2663 | 2664 | @overload 2665 | def cartesian_power(power: Literal[3], iterable: Iterable[T]) -> Iterator[Tuple3[T]]: ... 2666 | 2667 | 2668 | @overload 2669 | def cartesian_power(power: Literal[4], iterable: Iterable[T]) -> Iterator[Tuple4[T]]: ... 2670 | 2671 | 2672 | @overload 2673 | def cartesian_power(power: Literal[5], iterable: Iterable[T]) -> Iterator[Tuple5[T]]: ... 2674 | 2675 | 2676 | @overload 2677 | def cartesian_power(power: Literal[6], iterable: Iterable[T]) -> Iterator[Tuple6[T]]: ... 2678 | 2679 | 2680 | @overload 2681 | def cartesian_power(power: Literal[7], iterable: Iterable[T]) -> Iterator[Tuple7[T]]: ... 2682 | 2683 | 2684 | @overload 2685 | def cartesian_power(power: Literal[8], iterable: Iterable[T]) -> Iterator[Tuple8[T]]: ... 2686 | 2687 | 2688 | @overload 2689 | def cartesian_power(power: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: ... 2690 | 2691 | 2692 | def cartesian_power(power: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: 2693 | return standard_product(iterable, repeat=power) 2694 | 2695 | 2696 | @overload 2697 | def permutations(count: Literal[0], iterable: Iterable[T]) -> Iterator[EmptyTuple]: ... 2698 | 2699 | 2700 | @overload 2701 | def permutations(count: Literal[1], iterable: Iterable[T]) -> Iterator[Tuple1[T]]: ... 2702 | 2703 | 2704 | @overload 2705 | def permutations(count: Literal[2], iterable: Iterable[T]) -> Iterator[Tuple2[T]]: ... 2706 | 2707 | 2708 | @overload 2709 | def permutations(count: Literal[3], iterable: Iterable[T]) -> Iterator[Tuple3[T]]: ... 2710 | 2711 | 2712 | @overload 2713 | def permutations(count: Literal[4], iterable: Iterable[T]) -> Iterator[Tuple4[T]]: ... 2714 | 2715 | 2716 | @overload 2717 | def permutations(count: Literal[5], iterable: Iterable[T]) -> Iterator[Tuple5[T]]: ... 2718 | 2719 | 2720 | @overload 2721 | def permutations(count: Literal[6], iterable: Iterable[T]) -> Iterator[Tuple6[T]]: ... 2722 | 2723 | 2724 | @overload 2725 | def permutations(count: Literal[7], iterable: Iterable[T]) -> Iterator[Tuple7[T]]: ... 2726 | 2727 | 2728 | @overload 2729 | def permutations(count: Literal[8], iterable: Iterable[T]) -> Iterator[Tuple8[T]]: ... 2730 | 2731 | 2732 | @overload 2733 | def permutations(count: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: ... 2734 | 2735 | 2736 | def permutations(count: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: 2737 | return standard_permutations(iterable, count) 2738 | 2739 | 2740 | def permute(iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: 2741 | return standard_permutations(iterable) 2742 | 2743 | 2744 | @overload 2745 | def combinations(count: Literal[0], iterable: Iterable[T]) -> Iterator[EmptyTuple]: ... 2746 | 2747 | 2748 | @overload 2749 | def combinations(count: Literal[1], iterable: Iterable[T]) -> Iterator[Tuple1[T]]: ... 2750 | 2751 | 2752 | @overload 2753 | def combinations(count: Literal[2], iterable: Iterable[T]) -> Iterator[Tuple2[T]]: ... 2754 | 2755 | 2756 | @overload 2757 | def combinations(count: Literal[3], iterable: Iterable[T]) -> Iterator[Tuple3[T]]: ... 2758 | 2759 | 2760 | @overload 2761 | def combinations(count: Literal[4], iterable: Iterable[T]) -> Iterator[Tuple4[T]]: ... 2762 | 2763 | 2764 | @overload 2765 | def combinations(count: Literal[5], iterable: Iterable[T]) -> Iterator[Tuple5[T]]: ... 2766 | 2767 | 2768 | @overload 2769 | def combinations(count: Literal[6], iterable: Iterable[T]) -> Iterator[Tuple6[T]]: ... 2770 | 2771 | 2772 | @overload 2773 | def combinations(count: Literal[7], iterable: Iterable[T]) -> Iterator[Tuple7[T]]: ... 2774 | 2775 | 2776 | @overload 2777 | def combinations(count: Literal[8], iterable: Iterable[T]) -> Iterator[Tuple8[T]]: ... 2778 | 2779 | 2780 | @overload 2781 | def combinations(count: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: ... 2782 | 2783 | 2784 | def combinations(count: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: 2785 | return standard_combinations(iterable, count) 2786 | 2787 | 2788 | @overload 2789 | def combinations_with_replacement( 2790 | count: Literal[0], iterable: Iterable[T] 2791 | ) -> Iterator[EmptyTuple]: ... 2792 | 2793 | 2794 | @overload 2795 | def combinations_with_replacement( 2796 | count: Literal[1], iterable: Iterable[T] 2797 | ) -> Iterator[Tuple1[T]]: ... 2798 | 2799 | 2800 | @overload 2801 | def combinations_with_replacement( 2802 | count: Literal[2], iterable: Iterable[T] 2803 | ) -> Iterator[Tuple2[T]]: ... 2804 | 2805 | 2806 | @overload 2807 | def combinations_with_replacement( 2808 | count: Literal[3], iterable: Iterable[T] 2809 | ) -> Iterator[Tuple3[T]]: ... 2810 | 2811 | 2812 | @overload 2813 | def combinations_with_replacement( 2814 | count: Literal[4], iterable: Iterable[T] 2815 | ) -> Iterator[Tuple4[T]]: ... 2816 | 2817 | 2818 | @overload 2819 | def combinations_with_replacement( 2820 | count: Literal[5], iterable: Iterable[T] 2821 | ) -> Iterator[Tuple5[T]]: ... 2822 | 2823 | 2824 | @overload 2825 | def combinations_with_replacement( 2826 | count: Literal[6], iterable: Iterable[T] 2827 | ) -> Iterator[Tuple6[T]]: ... 2828 | 2829 | 2830 | @overload 2831 | def combinations_with_replacement( 2832 | count: Literal[7], iterable: Iterable[T] 2833 | ) -> Iterator[Tuple7[T]]: ... 2834 | 2835 | 2836 | @overload 2837 | def combinations_with_replacement( 2838 | count: Literal[8], iterable: Iterable[T] 2839 | ) -> Iterator[Tuple8[T]]: ... 2840 | 2841 | 2842 | @overload 2843 | def combinations_with_replacement( 2844 | count: int, iterable: Iterable[T] 2845 | ) -> Iterator[DynamicTuple[T]]: ... 2846 | 2847 | 2848 | def combinations_with_replacement(count: int, iterable: Iterable[T]) -> Iterator[DynamicTuple[T]]: 2849 | return standard_combinations_with_replacement(iterable, count) 2850 | -------------------------------------------------------------------------------- /iters/wraps.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Iterator, TypeVar 2 | 3 | from typing_aliases import Binary, Unary 4 | from wraps.primitives.option import NULL, Option, Some 5 | from wraps.primitives.result import Error, Ok, Result 6 | 7 | from iters.types import is_marker, marker 8 | from iters.utils import chain 9 | 10 | S = TypeVar("S") 11 | T = TypeVar("T") 12 | U = TypeVar("U") 13 | 14 | 15 | def scan(state: S, function: Binary[S, T, Option[U]], iterable: Iterable[T]) -> Iterator[U]: 16 | for item in iterable: 17 | option = function(state, item) 18 | 19 | if option.is_some(): 20 | yield option.unwrap() 21 | 22 | else: 23 | break 24 | 25 | 26 | def filter_map_option(function: Unary[T, Option[U]], iterable: Iterable[T]) -> Iterator[U]: 27 | for item in iterable: 28 | option = function(item) 29 | 30 | if option.is_some(): 31 | yield option.unwrap() 32 | 33 | 34 | def exactly_one(iterable: Iterable[T]) -> Result[T, Option[Iterator[T]]]: 35 | iterator = iter(iterable) 36 | 37 | first = next(iterator, marker) 38 | 39 | if is_marker(first): 40 | return Error(NULL) 41 | 42 | second = next(iterator, marker) 43 | 44 | if not is_marker(second): 45 | return Error(Some(chain((first, second), iterator))) 46 | 47 | return Ok(first) 48 | 49 | 50 | def at_most_one(iterable: Iterable[T]) -> Result[Option[T], Iterator[T]]: 51 | iterator = iter(iterable) 52 | 53 | first = next(iterator, marker) 54 | 55 | if is_marker(first): 56 | return Ok(NULL) 57 | 58 | second = next(iterator, marker) 59 | 60 | if not is_marker(second): 61 | return Error(chain((first, second), iterator)) 62 | 63 | return Ok(Some(first)) 64 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: iters 2 | site_author: nekitdev 3 | site_description: Composable external iteration. 4 | 5 | repo_name: nekitdev/iters 6 | repo_url: https://github.com/nekitdev/iters 7 | 8 | remote_branch: github-pages 9 | 10 | nav: 11 | - Index: "index.md" 12 | - Predicates: "predicates.md" 13 | - Reference: 14 | - Iterators: "reference/iters.md" 15 | - Async Iterators: "reference/async_iters.md" 16 | - Ordered Set: "reference/ordered_set.md" 17 | - Mapping View: "reference/mapping_view.md" 18 | - Sequence View: "reference/sequence_view.md" 19 | - Utilities: "reference/utils.md" 20 | - Async Utilities: "reference/async_utils.md" 21 | - Mappings: "reference/mappings.md" 22 | - Typing: "reference/typing.md" 23 | - Changelog: "changelog.md" 24 | - Security: "security.md" 25 | - Code of Conduct: "code_of_conduct.md" 26 | - Contributing: "contributing.md" 27 | 28 | watch: 29 | - docs 30 | - iters 31 | 32 | theme: 33 | name: material 34 | palette: 35 | - media: "(prefers-color-scheme: dark)" 36 | scheme: slate 37 | 38 | primary: deep purple 39 | accent: light blue 40 | 41 | toggle: 42 | icon: material/toggle-switch-off-outline 43 | name: Switch to light mode 44 | 45 | - media: "(prefers-color-scheme: light)" 46 | scheme: default 47 | 48 | primary: light blue 49 | accent: deep purple 50 | 51 | toggle: 52 | icon: material/toggle-switch 53 | name: Switch to dark mode 54 | 55 | plugins: 56 | - search 57 | 58 | - mkdocstrings: 59 | handlers: 60 | python: 61 | options: 62 | members_order: source 63 | show_symbol_type_heading: true 64 | show_symbol_type_toc: true 65 | show_signature_annotations: true 66 | 67 | import: 68 | - https://docs.python.org/3/objects.inv 69 | - https://nekitdev.github.io/orderings/objects.inv 70 | - https://nekitdev.github.io/wraps/objects.inv 71 | 72 | markdown_extensions: 73 | - pymdownx.highlight 74 | - pymdownx.inlinehilite 75 | - pymdownx.snippets 76 | - pymdownx.superfences 77 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "iters" 3 | version = "0.18.0" 4 | description = "Composable external iteration." 5 | authors = ["nekitdev"] 6 | license = "MIT" 7 | 8 | readme = "README.md" 9 | 10 | homepage = "https://github.com/nekitdev/iters" 11 | repository = "https://github.com/nekitdev/iters" 12 | documentation = "https://nekitdev.github.io/iters" 13 | 14 | keywords = ["python", "iter", "iterator"] 15 | 16 | classifiers = [ 17 | "Development Status :: 4 - Beta", 18 | "Intended Audience :: Developers", 19 | "Operating System :: OS Independent", 20 | "Topic :: Utilities", 21 | "Typing :: Typed", 22 | ] 23 | 24 | [tool.poetry.urls] 25 | Chat = "https://nekit.dev/chat" 26 | Issues = "https://github.com/nekitdev/iters/issues" 27 | 28 | [[tool.poetry.packages]] 29 | include = "iters" 30 | 31 | [tool.poetry.dependencies] 32 | python = ">= 3.8" 33 | 34 | typing-aliases = ">= 1.10.1" 35 | typing-extensions = ">= 4.11.0" 36 | 37 | named = ">= 1.4.2" 38 | orderings = ">= 1.4.0" 39 | 40 | solus = ">= 1.2.2" 41 | 42 | async-extensions = ">= 4.0.0" 43 | mixed-methods = ">= 1.1.1" 44 | 45 | funcs = ">= 0.10.1" 46 | wraps = ">= 0.11.0" 47 | 48 | [tool.poetry.group.format.dependencies] 49 | ruff = "0.4.1" 50 | 51 | [tool.poetry.group.check.dependencies] 52 | mypy = "1.9.0" 53 | 54 | [tool.poetry.group.check.dependencies.pre-commit] 55 | version = "3.7.0" 56 | python = ">= 3.9" 57 | 58 | [tool.poetry.group.test.dependencies] 59 | coverage = "7.4.4" 60 | pytest = "8.1.1" 61 | pytest-cov = "5.0.0" 62 | 63 | [tool.poetry.group.docs] 64 | optional = true 65 | 66 | [tool.poetry.group.docs.dependencies] 67 | mkdocs = "1.5.3" 68 | mkdocs-material = "9.5.18" 69 | 70 | [tool.poetry.group.docs.dependencies.mkdocstrings] 71 | version = "0.24.3" 72 | extras = ["python"] 73 | 74 | [tool.poetry.group.release] 75 | optional = true 76 | 77 | [tool.poetry.group.release.dependencies] 78 | changelogging = "1.4.2" 79 | 80 | [tool.ruff] 81 | line-length = 100 82 | 83 | [tool.ruff.lint] 84 | ignore = [ 85 | "E402", # module level import not at top of file (circular import fixes) 86 | ] 87 | 88 | [tool.pytest.ini_options] 89 | addopts = "--cov iters" 90 | testpaths = ["tests"] 91 | 92 | [tool.coverage.run] 93 | source = ["iters"] 94 | 95 | [tool.coverage.report] 96 | ignore_errors = true 97 | exclude_lines = [ 98 | "pragma: never", 99 | "pragma: no cover", 100 | "if TYPE_CHECKING", 101 | "@overload", 102 | "@required", 103 | "raise NotImplementedError", 104 | "raise AssertionError", 105 | "def __repr__", 106 | ] 107 | 108 | [tool.coverage.html] 109 | directory = "coverage" 110 | 111 | [tool.mypy] 112 | strict = true 113 | 114 | [tool.changelogging] 115 | name = "iters" 116 | version = "0.18.0" 117 | url = "https://github.com/nekitdev/iters" 118 | directory = "changes" 119 | output = "CHANGELOG.md" 120 | 121 | start_string = "" 122 | 123 | title_format = "{version} ({date})" 124 | issue_format = "[#{issue}]({url}/pull/{issue})" 125 | 126 | bullet = "-" 127 | wrap = true 128 | wrap_size = 100 129 | 130 | display = ["feature", "change", "fix", "security", "deprecation", "removal", "internal"] 131 | 132 | [build-system] 133 | requires = ["poetry-core >= 1.9.0"] 134 | build-backend = "poetry.core.masonry.api" 135 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nekitdev/iters/07cee611790a7396fb2e74f6698c1cca97f83c03/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_state.py: -------------------------------------------------------------------------------- 1 | from iters.state import stateful 2 | 3 | 4 | def test_stateful() -> None: 5 | value = 13 6 | other = 42 7 | 8 | state = stateful(value) 9 | 10 | assert state.get() is value 11 | assert state.set(other).get() is other 12 | assert state.get() is other 13 | --------------------------------------------------------------------------------