Iterator#
The apfel.core.iter module.
The abstraction for an iterator, alternative to Python's vanilla built-in iterator ABC collections.abc.Iterator.
It provides a large number of methods that are commonly found in Rust Iterator and Python itertools.
To create or use this abstraction of iterator:
- Use
itrtto wrap any iterable or Python vanilla iterator, - Use
Iteratormethods directly on any Python vanilla iterator.
Iterator and itrt are are exposed in the package namespace.
Implementation#
Iterators have a large number of methods. Missing methods will be added gradually over time.
Iterator
Reference Iterator |
Counterpart |
|---|---|
advance_by |
|
all |
|
any |
|
array_chunks |
|
by_ref |
|
chain |
|
cloned |
|
cmp |
|
cmp_by |
|
collect |
|
collect_into |
|
copied |
|
count |
|
cycle |
|
enumerate |
|
eq |
|
eq_by |
|
filter |
|
filter_map |
|
find |
|
find_map |
|
flat_map |
|
flatten |
|
fold |
|
for_each |
|
fuse |
|
ge |
|
gt |
|
inspect |
tap |
intersperse |
|
intersperse_with |
|
is_partitioned |
|
is_sorted |
|
is_sorted_by |
|
is_sorted_by_key |
|
last |
|
le |
|
lt |
|
map |
|
map_while |
|
map_windows |
|
max |
|
max_by |
|
max_by_key |
|
min |
|
min_by |
|
min_by_key |
|
ne |
|
next |
|
next_chunk |
|
nth |
|
partial_cmp |
|
partial_cmp_by |
|
partition |
|
partition_in_place |
|
peekable |
|
position |
|
product |
pipe(math.product) |
reduce |
|
rev |
|
rposition |
|
scan |
|
size_hint |
|
skip |
|
skip_while |
|
step_by |
|
sum |
pipe(sum) |
take |
|
take_while |
|
try_collect |
|
try_find |
|
try_fold |
|
try_for_each |
|
try_reduce |
|
unzip |
|
zip |
itertools
This table tracks named Iterator counterparts in itertools.
Standalone itertools functions can still be used through pipe when their first argument is an iterable.
Reference itertools |
Counterpart |
|---|---|
accumulate |
accumulate |
batched |
|
chain |
|
chain.from_iterable |
flatten |
compress |
|
count |
|
cycle |
|
dropwhile |
skip_while |
filterfalse |
filter |
groupby |
|
islice |
take / skip / step_by |
pairwise |
|
repeat |
|
starmap |
map |
takewhile |
take_while |
tee |
|
zip_longest |
|
product |
|
permutations |
|
combinations |
|
combinations_with_replacement |
itrt(iterable)
#
Create an Iterator from a standard Python Iterator or Iterable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
iterable
|
A Python standard |
required |
Returns:
| Type | Description |
|---|---|
|
An iterator that guarantees the apfel |
Raises:
| Type | Description |
|---|---|
TypeError
|
If the input is neither an |
Iterator
#
The interface for an iterator. See module level documentation for more information.
__next__()
abstractmethod
#
Return the next item of the iterator.
If the iterator is exhausted, raise StopIteration.
Any implementor of collections.abc.Iterator should be directly compatible with this interface.
accumulate(state, func)
#
Creates a new iterator that yields the accumulated state after applying func
to each element, starting with state. Unlike fold,
this yields each intermediate state rather than consuming the iterator.
Compared to itertools.accumulate,
this method enforces an explicit initial state and binary function;
it also does not yield the initial state before consuming any element.
This is also consistent with the numpy.ufunc.accumulate behavior.
Also check Iterator.scan for a method that provides more generalized state control.
advance_by(n)
#
Advance the iterator by n steps.
If the iterator is exhausted before advancing n steps,
return an Err containing the number of remaining steps.
Otherwise, return Ok(()).
all(pred)
#
Returns True if all elements of the iterator satisfy the predicate f.
Otherwise, returns False.
The iteration short-circuits if an element is found that does not satisfy the predicate.
An empty iterator returns True.
any(pred)
#
Returns True if any element of the iterator satisfies the predicate f.
Otherwise, returns False.
The iteration short-circuits if an element is found that satisfies the predicate.
An empty iterator returns False.
chain(*others)
#
Creates a new iterator that yields elements from this iterator until it is exhausted,
then yields elements from the other iterator.
iterator1 = itrt([1, 2])
iterator2 = itrt([3, 4])
iterator3 = itrt([5, 6])
chained_iterator = iterator1.chain(iterator2, iterator3)
assert chained_iterator.next().unwrap() == 1
assert chained_iterator.next().unwrap() == 2
assert iterator1.next().is_nothing() # original iterator1 is also exhausted
assert chained_iterator.next().unwrap() == 3
assert chained_iterator.next().unwrap() == 4
assert iterator2.next().is_nothing() # original iterator2 is also exhausted
assert chained_iterator.next().unwrap() == 5
assert chained_iterator.next().unwrap() == 6
assert chained_iterator.next().is_nothing()
count()
#
enumerate(init=0)
#
Creates a new iterator that yields tuples of (index, element) pairs,
where index starts from 0 or the specified init value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
init
|
int
|
The starting index for enumeration. Default is 0. |
0
|
filter(pred)
#
Creates a new iterator that yields only the elements of the original iterator
that satisfy the predicate pred.
iterator = itrt([1, 2, 3, 4, 5])
filtered_iterator = iterator.filter(lambda x: x % 2 == 0)
assert filtered_iterator.next().unwrap() == 2
assert filtered_iterator.next().unwrap() == 4
assert filtered_iterator.next().is_nothing()
assert iterator.next().is_nothing() # original iterator is also exhausted
filter_map(pred)
#
Creates a new iterator that applies pred to each element and yields the unwrapped
value for each result that is a Just, skipping Nothing.
from apfel.container.maybe import Maybe
def try_parse(s):
try:
return Maybe.make_just(int(s))
except ValueError:
return Maybe.make_nothing()
iterator = itrt(["1", "two", "3", "four"])
filtered = iterator.filter_map(try_parse)
assert filtered.next().unwrap() == 1
assert filtered.next().unwrap() == 3
assert filtered.next().is_nothing()
find(pred)
#
Returns the first element in the iterator that satisfies the predicate f,
wrapped in a Maybe.
If no such element is found, returns Nothing.
iterator = itrt([1, 2, 3, 4, 5])
assert iterator.find(lambda x: x % 2 == 0).unwrap() == 2
assert iterator.find(lambda x: x <= 3).unwrap() == 3 # 1, 2 have been consumed
assert iterator.find(lambda x: x > 10).is_nothing() # no such element exists
assert iterator.next().is_nothing() # iterator is exhausted
find_map(pred)
#
Applies the function pred to each element of the iterator and returns the first
result that is a Just, unwrapped.
If no element produces a Just, returns Nothing.
flat_map(func)
#
Creates a new iterator that applies func to each element and flattens the results.
Equivalent to .map(func).flatten().
iterator = itrt([1, 2, 3])
result = iterator.flat_map(lambda x: itrt([x, x * 10]))
assert result.next().unwrap() == 1
assert result.next().unwrap() == 10
assert result.next().unwrap() == 2
assert result.next().unwrap() == 20
assert result.next().unwrap() == 3
assert result.next().unwrap() == 30
assert result.next().is_nothing()
flatten()
#
Flattens an iterable of iterators into a single iterator by yielding all elements from each inner iterator in sequence.
iterator = itrt([itrt([1, 2]), itrt([3, 4]), itrt([5])])
flattened_iterator = iterator.flatten()
assert flattened_iterator.next().unwrap() == 1
assert flattened_iterator.next().unwrap() == 2
assert flattened_iterator.next().unwrap() == 3
assert flattened_iterator.next().unwrap() == 4
assert flattened_iterator.next().unwrap() == 5
assert flattened_iterator.next().is_nothing()
assert iterator.next().is_nothing() # original iterator is also exhausted
fold(init, func)
#
Fold (reduce) the elements of the iterator using the accumulation function func,
starting with the initial value init.
See also reduce if the first element of the iterator should be used as the initial accumulator value.
Tip
The default implementation of this method uses functools.reduce
under the hood, but allows keyword arguments for both init and func.
for_each(func)
#
Applies the function func to each element of the iterator.
Compare to map, the result of each function application is discarded,
and the iterator is consumed eagerly.
hint(hint)
#
intersperse(separator)
#
Creates a new iterator that places separator between adjacent elements.
iterator = itrt([1, 2, 3])
result = iterator.intersperse(0)
assert result.next().unwrap() == 1
assert result.next().unwrap() == 0
assert result.next().unwrap() == 2
assert result.next().unwrap() == 0
assert result.next().unwrap() == 3
assert result.next().is_nothing()
assert itrt([]).intersperse(0).next().is_nothing()
assert itrt([1]).intersperse(0).next().unwrap() == 1
intersperse_with(sep_fn)
#
Creates a new iterator that places the value returned by sep_fn between adjacent elements.
sep_fn is called once for each separator inserted.
iterator = itrt([1, 2, 3])
result = iterator.intersperse_with(lambda: 0)
assert result.next().unwrap() == 1
assert result.next().unwrap() == 0
assert result.next().unwrap() == 2
assert result.next().unwrap() == 0
assert result.next().unwrap() == 3
assert result.next().is_nothing()
n = 0
def counter():
nonlocal n
n += 1
return n
result = itrt(['a', 'b', 'c']).intersperse_with(counter)
assert list(result) == ['a', 1, 'b', 2, 'c']
last()
#
Returns the last element of the iterator, wrapped in a Maybe.
If the iterator is empty, returns Nothing.
map(func)
#
Creates a new iterator that applies the function func to each element of the original iterator.
iterator = itrt([1, 2, 3])
mapped_iterator = iterator.map(lambda x: x * 2)
assert mapped_iterator.next().unwrap() == 2
assert mapped_iterator.next().unwrap() == 4
assert mapped_iterator.next().unwrap() == 6
assert mapped_iterator.next().is_nothing()
assert iterator.next().is_nothing() # original iterator is also exhausted
map_while(pred)
#
Creates a new iterator that applies pred to each element and yields the unwrapped
value while the result is a Just.
Once pred returns Nothing, iteration stops immediately.
from apfel.container.maybe import just, nothing
def checked_double(x):
if x < 4:
return just(x * 2)
return nothing()
iterator = itrt([1, 2, 3, 4, 5])
result = iterator.map_while(checked_double)
assert result.next().unwrap() == 2
assert result.next().unwrap() == 4
assert result.next().unwrap() == 6
assert result.next().is_nothing()
nth(n)
#
Returns the n-th element of the iterator (0-indexed), wrapped in a Maybe.
If the iterator has fewer than n + 1 elements, returns Nothing.
Note
This method consumes the first n + 1 elements of the iterator.
pipe(func, *args, **kwargs)
#
Pipes the iterator into the function func, passing any additional positional and keyword arguments.
This allows for chaining operations in a functional style.
position(pred)
#
Returns the index of the first element in the iterator that satisfies the predicate pred,
wrapped in a Maybe.
If no such element is found, returns Nothing.
reduce(func)
#
scan(state, func)
#
Creates a new iterator that holds internal state, applying func to each element.
func receives state and an element, and returns a
Maybe. Yields the unwrapped value while func
returns Just; stops on Nothing.
state is passed directly to func each iteration — pass a mutable container
such as Value if func needs to update it across iterations.
from apfel.container.maybe import just, nothing
from apfel.container.value import Value
from apfel.core.common import imperative
state = Value(1)
iterator = itrt([1, 2, 3, 4])
result = iterator.scan(state, lambda s, x: imperative(
s.update(lambda v: v * x),
nothing() if s.done() > 6 else just(-s.done()),
))
assert result.next().unwrap() == -1
assert result.next().unwrap() == -2
assert result.next().unwrap() == -6
assert result.next().is_nothing()
skip(n)
#
Creates a new iterator that skips the first n elements of the original iterator.
If the original iterator has fewer than n elements, all elements are skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
The number of elements to skip from the start of the iterator. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
iterator = itrt([1, 2, 3, 4, 5])
skipped_iterator = iterator.skip(2)
assert skipped_iterator.next().unwrap() == 3
assert skipped_iterator.next().unwrap() == 4
assert skipped_iterator.next().unwrap() == 5
assert skipped_iterator.next().is_nothing()
assert iterator.next().is_nothing() # original iterator is also exhausted
skip_while(pred)
#
Creates a new iterator that skips elements while the predicate pred returns True.
Once the predicate returns False, all remaining elements are yielded.
step_by(n)
#
Creates a new iterator that yields every n-th element of the original iterator.
The first element (index 0) is always yielded.
It does not guarantee the skipped elements are consumed before or after yielding the next element.
iterator = itrt([1, 2, 3, 4, 5, 6, 7, 8])
stepped_iterator = iterator.step_by(2)
assert stepped_iterator.next().unwrap() == 1
assert stepped_iterator.next().unwrap() == 3
assert stepped_iterator.next().unwrap() == 5
assert stepped_iterator.next().unwrap() == 7
assert stepped_iterator.next().is_nothing()
take(n)
#
Creates a new iterator that stops after the first n elements of the original iterator.
If the original iterator has fewer than n elements, all elements are yielded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
The number of elements to take from the start of the iterator. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
iterator = itrt([1, 2, 3, 4, 5])
taken_iterator = iterator.take(3)
assert taken_iterator.next().unwrap() == 1
assert taken_iterator.next().unwrap() == 2
assert taken_iterator.next().unwrap() == 3
assert taken_iterator.next().is_nothing()
assert iterator.next().unwrap() == 4 # original iterator continues from where take stopped
take_while(pred)
#
Creates a new iterator that yields elements while the predicate pred returns True.
Once the predicate returns False, iteration stops.
iterator = itrt([1, 2, 3, 4, 1, 2])
taken_iterator = iterator.take_while(lambda x: x < 4)
assert taken_iterator.next().unwrap() == 1
assert taken_iterator.next().unwrap() == 2
assert taken_iterator.next().unwrap() == 3
assert taken_iterator.next().is_nothing()
assert iterator.next().unwrap() == 1 # elements after the predicate failed
tap(func)
#
Creates a new iterator that calls func on each element for side effects,
passing the element through unchanged.
zip(*others)
#
zip(other1: Iterable[U1], other2: Iterable[U2], other3: Iterable[U3]) -> Iterator[tuple[I, U1, U2, U3]]
Zips up this iterator with one or more other iterables into a single iterator of tuples. Iteration stops whenever an iterator or iterable is exhausted.
The iterators are guaranteed to be consumed in the order they are passed in.
Warning
The original iterators should not be pulled after being zipped together, as some of their elements may have been consumed and discarded during the zipping process.
iterator1 = itrt([1, 2, 3])
iterator2 = itrt([4, 5, 6])
zipped = iterator1.zip(iterator2)
assert zipped.next().unwrap() == (1, 4)
assert zipped.next().unwrap() == (2, 5)
assert zipped.next().unwrap() == (3, 6)
assert zipped.next().is_nothing()
iterator1 = itrt([1, 2, 3])
iterator2 = itrt(['a', 'b'])
zipped = iterator1.zip(iterator2)
assert zipped.next().unwrap() == (1, 'a')
assert zipped.next().unwrap() == (2, 'b')
assert zipped.next().is_nothing()
assert iterator1.next().is_nothing() # 3 is consumed and discarded during zipping
iterator = itrt([1, 2, 3])
zipped = iterator.zip([4, 5, 6])
assert zipped.next().unwrap() == (1, 4)
assert zipped.next().unwrap() == (2, 5)
assert zipped.next().unwrap() == (3, 6)
assert zipped.next().is_nothing()