Metadata-Version: 2.4
Name: iterquarry
Version: 2.1.1
Summary: Python iterable tools: pairwise, chunked batches, sliding windows, flatten, sampling, and combinatorics.
Author: iterquarry contributors
License-Expression: MIT
Project-URL: Homepage, https://pypi.org/project/iterquarry/
Keywords: iterable,iterator,itertools,pairwise,chunked,sliding window,flatten,windowed,sampling,combinatorics,permutations,batches
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Dynamic: license-file

# iterquarry

`iterquarry` is a Python library of extra tools for working with iterables. It sits beside the standard-library `itertools` module and covers the jobs that come up once a loop is no longer enough: fixed-size batches, sliding windows, lookahead, flattening nested data, sampling, combinatorics, and small numeric helpers.

The public functions are plain callables. Import the names you need:

```python
from iterquarry import pairwise, chunked, windowed

list(pairwise("ABCDE"))
# [('A', 'B'), ('B', 'C'), ('C', 'D'), ('D', 'E')]
```

Version 2.1.1 requires Python 3.10 or newer and has no third-party dependencies. The license is MIT.

## Installation

Install the release from PyPI:

```bash
pip install iterquarry
```

For a local checkout, install the package in editable mode together with the test extra:

```bash
pip install -e ".[test]"
```

## What it is for

Most of the library yields values lazily. You can pass a generator, a file, or any other iterable, and work starts only when you iterate the result. A few tools need random access or a known length, and those accept a sequence or build a list internally. Each section below says when that happens.

Typical uses:

- Walk a sequence in pairs, triples, or fixed-width windows.
- Split a stream into batches, or split it where a predicate changes.
- Peek at the next item, or step back through items you have already seen.
- Flatten, interleave, zip, and sort several iterables together.
- Keep the first item, the last item, or exactly one item, and fail clearly when the count is wrong.
- Sample, count, group, and reduce without writing the bookkeeping by hand.
- Generate combinations, permutations, partitions, and prime-related sequences.

## Quick start

```python
from iterquarry import chunked, flatten, windowed

list(chunked(range(10), 3))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

list(windowed(range(1, 6), 3))
# [(1, 2, 3), (2, 3, 4), (3, 4, 5)]

list(flatten([[0, 1], [2, 3]]))
# [0, 1, 2, 3]
```

`chunked` collects each batch into a list. `windowed` yields tuples and pads a short final window when you pass `fillvalue`. `flatten` removes one level of nesting.

## Grouping and splitting

`chunked(iterable, n)` cuts an iterable into lists of length `n`. The last list is shorter when the length does not divide evenly. Pass `strict=True` to raise `ValueError` on a short final batch. `chunked(iterable, None)` returns one list of every item.

`chunked_even` keeps the batch sizes as close as possible, with the longer batches first:

```python
from iterquarry import chunked_even

list(chunked_even(range(7), 3))
# [[0, 1, 2], [3, 4], [5, 6]]
```

`batched` yields tuples and matches the strictness rules of `itertools.batched` on newer Python versions. `grouper` also builds fixed-width tuples. Its `incomplete` argument is `"fill"`, `"ignore"`, or `"strict"`:

```python
from iterquarry import grouper

list(grouper("ABCDEFG", 3, incomplete="fill", fillvalue="x"))
# [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')]
```

`distribute` deals items out one by one, the way you deal cards. `divide` cuts the iterable into contiguous blocks. `divide` reads the whole iterable first.

```python
from iterquarry import distribute, divide

[list(part) for part in distribute(3, range(8))]
# [[0, 3, 6], [1, 4, 7], [2, 5]]

[list(part) for part in divide(3, range(8))]
# [[0, 1, 2], [3, 4, 5], [6, 7]]
```

`split_before`, `split_after`, `split_at`, and `split_when` break an iterable wherever a predicate says so. `split_into` uses an explicit list of sizes.

```python
from iterquarry import split_before

[list(group) for group in split_before("abcdef", lambda char: char in "ce")]
# [['a', 'b'], ['c', 'd'], ['e', 'f']]
```

`bucket` files each item under a key and lets you read one key at a time:

```python
from iterquarry import bucket

grouped = bucket("AbBa", key=str.lower)
list(grouped["a"])
# ['A', 'a']
list(grouped["b"])
# ['b', 'B']
```

`unzip` is the inverse of `zip`: one iterable of tuples becomes one iterator per column.

Other grouping tools: `ichunked`, `sliced`, `constrained_batches`, `groupby_transform`, `consecutive_groups`.

## Windows

`pairwise` yields overlapping pairs. `triplewise` yields overlapping triples. `sliding_window` is the general form and yields a tuple of length `n` on every step.

```python
from iterquarry import sliding_window, triplewise

list(triplewise(range(5)))
# [(0, 1, 2), (1, 2, 3), (2, 3, 4)]

list(sliding_window(range(5), 3))
# [(0, 1, 2), (1, 2, 3), (2, 3, 4)]
```

`windowed` is the padded variant. Set `step` to move ahead by more than one item, and set `fillvalue` when the tail is shorter than the window. `windowed_complete` yields `(before, window, after)` so you can see the items on both sides of each window.

`stagger` zips an iterable with offset copies of itself:

```python
from iterquarry import stagger

list(stagger([1, 2, 3, 4, 5], offsets=(-1, 0, 1), longest=True))
# [(None, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, None), (5, None, None)]
```

`substrings` and `subslices` yield every non-empty contiguous slice. `substrings_indexes` yields `(start, end)` pairs for a sequence. `adjacent` marks each item when it, or a neighbor within `distance`, satisfies a predicate.

## Lookahead

`spy` copies the first few items and returns both that copy and an iterator that still starts at the beginning:

```python
from iterquarry import spy

head, remaining = spy("abcdef", 2)
head
# ['a', 'b']
list(remaining)
# ['a', 'b', 'c', 'd', 'e', 'f']
```

`peekable` wraps an iterator so you can inspect the next item without consuming it, and put items back with `prepend`:

```python
from iterquarry import peekable

items = peekable("abc")
items.peek()
# 'a'
next(items)
# 'a'
list(items)
# ['b', 'c']
```

Indexing a `peekable` reads ahead as far as the index requires. A negative index reads the rest of the source.

`seekable` remembers the items it has produced and can return to an earlier position with `seek`. A `maxlen` limits how far back that cache reaches. `relative_seek` moves from the current position.

## Combining iterables

`flatten` removes one level of nesting. `collapse` keeps going into nested iterables. Strings and bytes stay intact unless you change `base_type`. `levels` stops the collapse after a fixed depth.

```python
from iterquarry import collapse

list(collapse([1, [2, [3, [4]]]]))
# [1, 2, 3, 4]
```

`interleave` takes one item from each iterable in turn and stops at the shortest. `interleave_longest` continues until every iterable is exhausted. `roundrobin` does the same with a different internal schedule. `interleave_evenly` spaces shorter iterables through the longest one. `interleave_randomly` picks among the iterables that still have items.

```python
from iterquarry import interleave, roundrobin

list(interleave([1, 2, 3], [4, 5], [6]))
# [1, 4, 6]

list(roundrobin("ABC", "D", "EF"))
# ['A', 'D', 'E', 'B', 'F', 'C']
```

`zip_offset` shifts each iterable by a given offset before zipping. `zip_broadcast` repeats strings, bytes, and other scalars across the items of the real iterables. `sort_together` sorts several sequences by one or more columns and returns them in the new order:

```python
from iterquarry import sort_together, zip_offset

list(zip_offset("0123", "abcdef", offsets=(0, 1)))
# [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e')]

sort_together([["a", "b", "c"], [3, 1, 2]], key_list=(1,))
# (('b', 'c', 'a'), (1, 2, 3))
```

`prepend` adds one value at the front. `value_chain` yields its arguments, iterating anything that is not a string or bytes. `partial_product` walks a product space one coordinate at a time. `dotproduct` and `transpose` cover the small linear-algebra cases, and `matmul` multiplies two matrices given as iterables of rows.

## Selecting items

`first` and `last` return one end of an iterable. `take` and `tail` return several items. `nth` returns the item at an index, or a default when the iterable ends first. `nth_or_last` returns the last item when the index runs past the end.

```python
from iterquarry import first, last, tail, take

first([1, 2, 3])
# 1
last([1, 2, 3])
# 3
take(2, "abcde")
# ['a', 'b']
list(tail(2, "abcde"))
# ['d', 'e']
```

`one` requires exactly one item and raises `ValueError` when the iterable is empty or longer. `only` returns the single item, or a default when the iterable is empty, and raises when there is a second item. `strictly_n` yields exactly `n` items and calls `too_short` or `too_long` when the length differs.

`unique_everseen` drops items that have already appeared. `unique_justseen` drops only adjacent duplicates. `unique` sorts, then drops duplicates, so it accepts unhashable items. `duplicates_everseen` and `duplicates_justseen` yield the copies that the unique helpers would drop. `unique_in_window` treats an item as new when it has not appeared in the last `n` items.

`locate` yields indexes where a predicate is true. `rlocate` yields those indexes from the right. `replace` substitutes matching items, or matching windows when `window_size` is greater than one.

`strip`, `lstrip`, and `rstrip` drop leading or trailing items that match a predicate, the way `str.strip` drops characters. `before_and_after` splits once, at the first failing item, and returns both sides:

```python
from iterquarry import before_and_after

head, rest = before_and_after(lambda value: value < 3, range(6))
list(head), list(rest)
# ([0, 1, 2], [3, 4, 5])
```

`partition` splits into the items that fail a predicate and the items that pass. `filter_except` and `map_except` skip items that raise one of the given exceptions. `iter_suppress` does the same while iterating. `filter_map` keeps values that are true after a function runs. `extract` pulls items at selected indexes. `takewhile_inclusive` keeps the first item that fails the predicate, then stops. `longest_common_prefix` walks several iterables until they disagree.

## Summaries

These helpers reduce an iterable to a count, a flag, or a small aggregate.

```python
from iterquarry import all_equal, ilen, quantify

ilen(range(5))
# 5
quantify([1, 0, 2, 0, 3])
# 3
all_equal([2, 2, 2])
# True
```

`all_unique` reports whether every item is distinct. `is_sorted` checks order, with `strict=True` when equal neighbors should fail. `exactly_n` is true when exactly `n` items satisfy a predicate. `iequals` compares several iterables item by item. `minmax` returns the smallest and largest value in one pass. `argmin` and `argmax` return the index of the extreme value.

`sample` draws `k` items. It supports `weights`, `counts`, and `strict`. The draw uses the `random` module, so successive calls differ. `run_length.encode` compresses adjacent repeats, and `run_length.decode` expands them:

```python
from iterquarry import run_length

list(run_length.encode("aaabbca"))
# [('a', 3), ('b', 2), ('c', 1), ('a', 1)]
```

`consecutive_groups` groups values that increase by one. `map_reduce` groups by a key, maps each item, and optionally reduces each group. `join_mappings` merges dictionaries that share keys. `unique_to_each` lists the items that appear in only one of several iterables.

## Adding information to a stream

`mark_ends` tags the first and last items:

```python
from iterquarry import mark_ends

list(mark_ends("ABC"))
# [(True, False, 'A'), (False, False, 'B'), (False, True, 'C')]
```

`count_cycle` pairs each item with a cycle counter. `intersperse` inserts a separator every `n` items. `padded` extends an iterable out to a length, or out to the next multiple of a length. `repeat_each` repeats every item. `repeat_last` yields the final item forever after the source ends. `ncycles` repeats the whole iterable `n` times. `pad_none` is an endless tail of `None`.

```python
from iterquarry import intersperse, padded, repeat_each

list(intersperse("|", "abc"))
# ['a', '|', 'b', '|', 'c']
list(padded([1, 2], n=4, fillvalue=0))
# [1, 2, 0, 0]
list(repeat_each("AB", 2))
# ['A', 'A', 'B', 'B']
```

`classify_unique` yields `(item, unique_just_seen, unique_ever_seen)` so a single pass can answer both duplicate questions.

## Combinatorics

`powerset` yields every subset, shortest first. `distinct_permutations` and `distinct_combinations` skip results that look the same because of repeated values. `circular_shifts` rotates a sequence. `partitions` splits a sequence into contiguous blocks. `set_partitions` splits it into unordered groups.

```python
from iterquarry import circular_shifts, distinct_permutations, powerset, set_partitions

list(powerset("AB"))
# [(), ('A',), ('B',), ('A', 'B')]

list(distinct_permutations("AAB"))
# [('A', 'A', 'B'), ('A', 'B', 'A'), ('B', 'A', 'A')]

list(circular_shifts("ABCD"))
# [('A', 'B', 'C', 'D'), ('B', 'C', 'D', 'A'), ('C', 'D', 'A', 'B'), ('D', 'A', 'B', 'C')]

list(set_partitions("abc", 2))
# [[['a'], ['b', 'c']], [['a', 'b'], ['c']], [['b'], ['a', 'c']]]
```

`gray_product` walks a product so that only one coordinate changes at each step. `derangements` yields permutations with no item left in its original place. `outer_product` applies a function to every pair from two iterables. `powerset_of_sets` builds the power set with set objects.

When you need one result by index, use `nth_combination`, `nth_combination_with_replacement`, `nth_permutation`, or `nth_product`. The matching `combination_index`, `combination_with_replacement_index`, `permutation_index`, and `product_index` functions go the other way. `random_product`, `random_permutation`, `random_combination`, `random_combination_with_replacement`, and `random_derangement` each return one result.

```python
from iterquarry import gray_product, nth_combination

list(gray_product("AB", "CD"))
# [('A', 'C'), ('B', 'C'), ('B', 'D'), ('A', 'D')]

nth_combination(range(5), 3, 5)
# (0, 3, 4)
```

## Numeric helpers

`numeric_range` follows the same start, stop, and step pattern as `range`, for any numeric type that supports addition and comparison. That includes `Decimal`, `Fraction`, and `datetime` values. A datetime range needs an explicit `timedelta` step.

```python
import datetime
from decimal import Decimal
from iterquarry import numeric_range

list(numeric_range(Decimal("1.0"), Decimal("2.0"), Decimal("0.3")))
# [Decimal('1.0'), Decimal('1.3'), Decimal('1.6'), Decimal('1.9')]

start = datetime.date(2026, 1, 1)
step = datetime.timedelta(days=1)
list(numeric_range(start, start + datetime.timedelta(days=3), step))
# [datetime.date(2026, 1, 1), datetime.date(2026, 1, 2), datetime.date(2026, 1, 3)]
```

`running_min`, `running_max`, `running_mean`, and `running_median` yield the statistic of the items seen so far. Pass `maxlen` to keep a sliding window. `running_statistics` yields a frozen `Stats` value with `size`, `minimum`, `median`, `maximum`, and `mean`.

```python
from iterquarry import running_mean

list(running_mean([1, 2, 3, 4]))
# [1.0, 1.5, 2.0, 2.5]
```

`difference` yields the first value, then the gap from each item to the one before it. Pass `func` to use a function other than subtraction, or `initial` to set the starting previous value.

```python
from iterquarry import difference

list(difference([10, 20, 30, 45]))
# [10, 10, 10, 15]
```

`is_prime`, `factor`, `sieve`, `nth_prime`, and `totient` cover common integer-number tasks. `nth_prime` is zero-based, so `nth_prime(0)` is 2 and `nth_prime(10)` is 31. `multinomial` computes a multinomial coefficient. `convolve` combines a signal with a kernel. `dft` and `idft` are the discrete Fourier transform and its inverse.

```python
from iterquarry import convolve, factor, is_prime, sieve, totient

is_prime(97)
# True
list(factor(84))
# [2, 2, 3, 7]
list(sieve(20))
# [2, 3, 5, 7, 11, 13, 17, 19]
totient(10)
# 4
list(convolve([1, -1, -20], [1, -3]))
# [1, -4, -17, 60]
```

`sum_of_squares`, `polynomial_from_roots`, `polynomial_derivative`, and `polynomial_eval` are small polynomial helpers. `reshape` rearranges a nested iterable into a new shape.

## Wrappers

`always_iterable` returns an iterator for any object. Strings and bytes are treated as single values. `None` becomes an empty iterator.

```python
from iterquarry import always_iterable

list(always_iterable("hi"))
# ['hi']
list(always_iterable(None))
# []
```

`always_reversible` caches a one-shot iterable so it can be reversed. `countable` counts the items that have been consumed in `items_seen`. `sized_iterator` reports a length that shrinks as you iterate. `consumer` primes a generator that receives values with `send`. `with_iter` iterates a context manager and closes it when iteration finishes. `callback_iter` runs a callback in a background thread and exposes the calls through an iterator. `AbortThread` is the exception used to stop that worker. `SequenceView` is a read-only view of a sequence.

## Other tools

`side_effect` calls a function for each item, or for each chunk, and still yields the original items. Optional `before` and `after` hooks run around the iteration. `iterate` yields `start`, `func(start)`, `func(func(start))`, and so on. `time_limited` stops a slow iterable after a number of seconds and records whether it timed out. `make_decorator` turns an iterable tool into a decorator. `map_if` applies one function when a predicate is true and another when it is false. `doublestarmap` calls a function with each mapping unpacked as keyword arguments. `raise_` raises an exception from a point in an iterator pipeline where a `raise` statement is awkward. `consume` advances an iterator and discards the items. `tabulate`, `repeatfunc`, `iter_except`, `iter_index`, and `loops` cover function tables, repeated calls, and exception-driven iteration.

`seekable` and `peekable` are the right tools when a pipeline needs to look backward or forward. `concurrent_tee` copies an iterable out to several threads. `serialize` and `synchronized` take a lock around iteration or around a function.

## API reference

| Area | Names |
| --- | --- |
| Grouping | `chunked`, `chunked_even`, `batched`, `ichunked`, `sliced`, `constrained_batches`, `distribute`, `divide`, `split_at`, `split_before`, `split_after`, `split_into`, `split_when`, `bucket`, `unzip`, `grouper`, `groupby_transform` |
| Windows | `pairwise`, `windowed`, `sliding_window`, `triplewise`, `stagger`, `windowed_complete`, `substrings`, `substrings_indexes`, `subslices`, `adjacent` |
| Lookahead | `spy`, `peekable`, `seekable` |
| Combining | `flatten`, `collapse`, `interleave`, `interleave_longest`, `interleave_evenly`, `interleave_randomly`, `roundrobin`, `zip_offset`, `zip_broadcast`, `sort_together`, `prepend`, `partial_product`, `value_chain`, `dotproduct`, `transpose`, `matmul` |
| Selecting | `first`, `last`, `one`, `only`, `nth`, `nth_or_last`, `take`, `tail`, `strip`, `lstrip`, `rstrip`, `unique_everseen`, `unique_justseen`, `unique`, `duplicates_everseen`, `duplicates_justseen`, `unique_in_window`, `locate`, `rlocate`, `replace`, `filter_except`, `map_except`, `filter_map`, `iter_suppress`, `before_and_after`, `takewhile_inclusive`, `strictly_n`, `partition`, `extract`, `longest_common_prefix`, `classify_unique` |
| Summaries | `ilen`, `sample`, `consecutive_groups`, `run_length`, `map_reduce`, `join_mappings`, `all_equal`, `all_unique`, `quantify`, `minmax`, `argmin`, `argmax`, `is_sorted`, `exactly_n`, `iequals`, `unique_to_each`, `first_true` |
| Augmenting | `count_cycle`, `intersperse`, `padded`, `pad_none`, `padnone`, `mark_ends`, `repeat_each`, `repeat_last`, `ncycles` |
| Combinatorics | `powerset`, `powerset_of_sets`, `distinct_permutations`, `distinct_combinations`, `partitions`, `set_partitions`, `circular_shifts`, `derangements`, `gray_product`, `outer_product`, `nth_combination`, `nth_combination_with_replacement`, `nth_permutation`, `nth_product`, `combination_index`, `combination_with_replacement_index`, `permutation_index`, `product_index`, `random_product`, `random_permutation`, `random_combination`, `random_combination_with_replacement`, `random_derangement` |
| Numbers | `numeric_range`, `difference`, `running_min`, `running_max`, `running_mean`, `running_median`, `running_statistics`, `Stats`, `is_prime`, `factor`, `sieve`, `nth_prime`, `totient`, `multinomial`, `convolve`, `dft`, `idft`, `sum_of_squares`, `polynomial_from_roots`, `polynomial_derivative`, `polynomial_eval`, `reshape` |
| Wrapping | `always_iterable`, `always_reversible`, `countable`, `sized_iterator`, `consumer`, `with_iter`, `callback_iter`, `AbortThread`, `SequenceView` |
| Other | `side_effect`, `iterate`, `time_limited`, `make_decorator`, `map_if`, `doublestarmap`, `raise_`, `consume`, `tabulate`, `repeatfunc`, `iter_except`, `iter_index`, `loops`, `concurrent_tee`, `serialize`, `synchronized` |

Every name in that table is exported from the top-level package:

```python
import iterquarry

iterquarry.__version__
# '2.1.1'
```

## Development

Clone the repository, then install it in editable mode and run the test suite:

```bash
pip install -e ".[test]"
pytest
```

Build source and wheel archives with:

```bash
python -m build
```

The archives are written to `dist/`.
