sain.iter

Composable external iteration. See Iterator for more details.

   1# BSD 3-Clause License
   2#
   3# Copyright (c) 2022-Present, nxtlo
   4# All rights reserved.
   5#
   6# Redistribution and use in source and binary forms, with or without
   7# modification, are permitted provided that the following conditions are met:
   8#
   9# * Redistributions of source code must retain the above copyright notice, this
  10#   list of conditions and the following disclaimer.
  11#
  12# * Redistributions in binary form must reproduce the above copyright notice,
  13#   this list of conditions and the following disclaimer in the documentation
  14#   and/or other materials provided with the distribution.
  15#
  16# * Neither the name of the copyright holder nor the names of its
  17#   contributors may be used to endorse or promote products derived from
  18#   this software without specific prior written permission.
  19#
  20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  24# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  25# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  26# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  27# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  28# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30"""Composable external iteration. See `Iterator` for more details."""
  31
  32from __future__ import annotations
  33
  34__all__ = (
  35    # Core
  36    "Iter",
  37    "Iterator",
  38    "TrustedIter",
  39    # Adapters
  40    "Cloned",
  41    "Copied",
  42    "Take",
  43    "Filter",
  44    "Map",
  45    "Skip",
  46    "Enumerate",
  47    "TakeWhile",
  48    "DropWhile",
  49    "Chunks",
  50    "Empty",
  51    "Repeat",
  52    "Once",
  53    "ExactSizeIterator",
  54    # Functions
  55    "into_iter",
  56    "empty",
  57    "once",
  58    "repeat",
  59)
  60
  61import abc
  62import collections.abc as collections
  63import copy
  64import itertools
  65import typing
  66
  67from . import default as _default
  68from . import futures
  69from . import option as _option
  70from . import result as _result
  71from .macros import rustc_diagnostic_item
  72from .macros import unsafe
  73
  74Item = typing.TypeVar("Item")
  75"""The type of the item that is being yielded."""
  76
  77OtherItem = typing.TypeVar("OtherItem")
  78"""The type of the item that is being mapped into then yielded."""
  79
  80AnyIter = typing.TypeVar("AnyIter", bound="Iterator[typing.Any]")
  81
  82
  83if typing.TYPE_CHECKING:
  84    import _typeshed
  85
  86    from .collections.slice import Slice
  87    from .collections.vec import Vec
  88    from .option import Option
  89
  90    Collector = (
  91        collections.MutableSequence[Item]
  92        | set[Item]
  93        | collections.MutableMapping[int, Item]
  94    )
  95    _typeshed.ConvertibleToInt
  96    Sum: typing.TypeAlias = (
  97        "Iterator[str]"
  98        | "Iterator[typing.SupportsInt]"
  99        | "Iterator[typing.SupportsIndex]"
 100        | "Iterator[_typeshed.ReadableBuffer]"
 101        | "Iterator[_typeshed.SupportsTrunc]"
 102        | "Iterator[float]"
 103    )
 104
 105
 106def unreachable() -> typing.NoReturn:
 107    raise StopIteration(
 108        "No more items exist in this iterator. It has been exhausted."
 109    ) from None
 110
 111
 112def oob() -> typing.NoReturn:
 113    raise IndexError("index is out of bounds.")
 114
 115
 116def diagnostic(cls: type[AnyIter]) -> type[AnyIter]:
 117    def _repr(self: Iterator[typing.Any]) -> str:
 118        return f"{type(self).__name__}(source: Iter<{type(self._it).__name__}>)"  # pyright: ignore
 119
 120    cls.__repr__ = _repr
 121    return cls
 122
 123
 124@rustc_diagnostic_item("Iterator")
 125class Iterator(
 126    typing.Generic[Item],
 127    abc.ABC,
 128    _default.Default["Empty[Item]"],
 129):
 130    """An abstract interface for dealing with iterators.
 131
 132    This is exactly the same trait as `core::iter::Iterator` trait from Rust.
 133
 134    This is the main interface that any type can implement by basically inheriting from it.
 135    The method `__next__` is the only method that needs to be implemented, You get all the other methods for free.
 136
 137    If you want to use a ready iterator for general purposes, Use `Iter`. This interface is only for implementers
 138    and type hints.
 139
 140    Example
 141    -------
 142    ```py
 143    @dataclass
 144    class Counter(Iterator[int]):
 145        start: int = 0
 146        stop: int | None = None
 147
 148        # implement the required method.
 149        def __next__(self) -> int:
 150            result = self.start
 151            self.start += 1
 152
 153            if self.stop is not None and result >= self.stop:
 154                raise StopIteration
 155
 156            return result
 157
 158    counter = Counter(start=0, stop=10)
 159    for i in counter.map(lambda x: x * 2): # multiply each number
 160        ...
 161    ```
 162    """
 163
 164    __slots__ = ()
 165
 166    @abc.abstractmethod
 167    def __next__(self) -> Item:
 168        raise NotImplementedError
 169
 170    ###################
 171    # const functions #
 172    ###################
 173
 174    @staticmethod
 175    @typing.final
 176    def default() -> Empty[Item]:
 177        """Return the default iterator for this type. It returns an empty iterator.
 178
 179        Example
 180        -------
 181        ```py
 182        it: Iterator[int] = Iter.default()
 183        assert t.next().is_none()
 184        ```
 185        """
 186        return Empty()
 187
 188    @typing.overload
 189    def collect(self) -> collections.MutableSequence[Item]: ...
 190
 191    @typing.overload
 192    def collect(
 193        self, *, cast: collections.Callable[[Item], OtherItem]
 194    ) -> collections.MutableSequence[OtherItem]: ...
 195
 196    @typing.final
 197    def collect(
 198        self, *, cast: collections.Callable[[Item], OtherItem] | None = None
 199    ) -> collections.MutableSequence[Item] | collections.MutableSequence[OtherItem]:
 200        """Collects all items in the iterator into a sequence.
 201
 202        Example
 203        -------
 204        ```py
 205        iterator = Iter(range(3))
 206        iterator.collect()
 207        # (0, 1, 2, 3)
 208        iterator.collect(cast=str) # Map each element and collect it.
 209        # ('0', '1', '2', '3')
 210        ```
 211
 212        Parameters
 213        ----------
 214        cast: `T | None`
 215            An optional type to cast the items into.
 216            If not provided the items will be returned as it's original type.
 217        """
 218        if cast is not None:
 219            return list(cast(i) for i in self)
 220
 221        return list(_ for _ in self)
 222
 223    @typing.final
 224    def collect_into(self, collection: Collector[Item]) -> None:
 225        """Consume this iterator, extending all items in the iterator into a mutable `collection`.
 226
 227        Example
 228        -------
 229        ```py
 230        iterator = Iter([1, 1, 2, 3, 4, 2, 6])
 231        uniques = set()
 232        iterator.collect_into(uniques)
 233        # assert uniques == {1, 2, 3, 4, 6}
 234        ```
 235
 236        Parameters
 237        ----------
 238        collection: `MutableSequence[T]` | `set[T]`
 239            The collection to extend the items in this iterator with.
 240        """
 241        if isinstance(collection, collections.MutableSequence):
 242            collection.extend(_ for _ in self)
 243        elif isinstance(collection, collections.MutableSet):
 244            collection.update(_ for _ in self)
 245        else:
 246            for idx, item in enumerate(self):
 247                collection[idx] = item
 248
 249    @typing.final
 250    def to_vec(self) -> Vec[Item]:
 251        """Convert this iterator into `Vec[T]`.
 252
 253        Example
 254        -------
 255        ```py
 256        it = sain.iter.once(0)
 257        vc = it.to_vec()
 258
 259        assert to_vec == [0]
 260        ```
 261        """
 262        from .collections.vec import Vec
 263
 264        return Vec(_ for _ in self)
 265
 266    @typing.final
 267    def sink(self) -> None:
 268        """Consume all elements from this iterator, flushing it into the sink.
 269
 270        Example
 271        -------
 272        ```py
 273        it = Iter((1, 2, 3))
 274        it.sink()
 275        assert it.next().is_none()
 276        ```
 277        """
 278        for _ in self:
 279            pass
 280
 281    @typing.final
 282    def raw_parts(self) -> collections.Generator[Item, None, None]:
 283        """Decompose this iterator into a `Generator[Item]` that yields all of the remaining items.
 284
 285        This mainly used for objects that needs to satisfy its exact type.
 286
 287        ```py
 288        it = Iter("cba")
 289        sort = sorted(it.raw_parts())
 290
 291        assert it.count() == 0
 292        assert sort == ["a", "b", "c"]
 293        ```
 294        """
 295        for item in self:
 296            yield item
 297
 298    ##################
 299    # default impl's #
 300    ##################
 301
 302    def next(self) -> Option[Item]:
 303        """Advance the iterator, Returning the next item, `Some(None)` if all items yielded.
 304
 305        Example
 306        -------
 307        ```py
 308        iterator = Iter(["1", "2"])
 309        assert iterator.next() == Some("1")
 310        assert iterator.next() == Some("2")
 311        assert iterator.next().is_none()
 312        ```
 313        """
 314        try:
 315            return _option.Some(self.__next__())
 316        except StopIteration:
 317            # ! SAFETY: No more items in the iterator.
 318            return _option.NOTHING
 319
 320    def cloned(self) -> Cloned[Item]:
 321        """Creates an iterator which shallow copies its elements by reference.
 322
 323        If you need a copy of the actual iterator and not the elements.
 324        use `Iter.clone()`
 325
 326        .. note::
 327            This method calls [`copy.copy()`](https://docs.python.org/3/library/copy.html)
 328            on each item that is being yielded.
 329
 330        Example
 331        -------
 332        ```py
 333        @dataclass
 334        class User:
 335            users_ids: list[int] = []
 336
 337        # An iterator which elements points to the same user.
 338        user = User()
 339        it = Iter((user, user))
 340
 341        for u in it.cloned():
 342            u.user_ids.append(1)
 343
 344        # We iterated over the same user pointer twice and appended "1"
 345        # since `copy` returns a shallow copy of nested structures.
 346        assert len(user.user_ids) == 2
 347        ```
 348        """
 349        return Cloned(self)
 350
 351    def copied(self) -> Copied[Item]:
 352        """Creates an iterator which copies all of its elements by value.
 353
 354        If you only need a copy of the item reference, Use `.cloned()` instead.
 355
 356        .. note::
 357            This method simply calls [`copy.deepcopy()`](https://docs.python.org/3/library/copy.html)
 358            on each item that is being yielded.
 359
 360        Example
 361        -------
 362        ```py
 363        @dataclass
 364        class User:
 365            users_ids: list[int] = []
 366
 367        # An iterator which elements points to the same user.
 368        user = User()
 369        it = Iter((user, user))
 370
 371        for u in it.copied():
 372            # A new list is created for each item.
 373            u.user_ids.append(1)
 374
 375        # The actual list is untouched since we consumed a deep copy of it.
 376        assert len(user.user_ids) == 0
 377        ```
 378        """
 379        return Copied(self)
 380
 381    def map(self, fn: collections.Callable[[Item], OtherItem]) -> Map[Item, OtherItem]:
 382        """Maps each item in the iterator to another type.
 383
 384        Example
 385        -------
 386        ```py
 387        iterator = Iter(["1", "2", "3"]).map(int)
 388
 389        for item in iterator:
 390            assert isinstance(item, int)
 391        ```
 392
 393        Parameters
 394        ----------
 395        predicate: `Callable[[Item], OtherItem]`
 396            The function to map each item in the iterator to the other type.
 397        """
 398        return Map(self, fn)
 399
 400    def filter(self, predicate: collections.Callable[[Item], bool]) -> Filter[Item]:
 401        """Filters the iterator to only yield items that match the predicate.
 402
 403        Example
 404        -------
 405        ```py
 406        places = Iter(['London', 'Paris', 'Los Angeles'])
 407        for place in places.filter(lambda place: place.startswith('L')):
 408            print(place)
 409
 410        # London
 411        # Los Angeles
 412        ```
 413        """
 414        return Filter(self, predicate)
 415
 416    def take(self, count: int) -> Take[Item]:
 417        """Take the first number of items until the number of items
 418        are yielded or the end of the iterator is exhausted.
 419
 420        Example
 421        -------
 422        ```py
 423        iterator = Iter(['c', 'x', 'y'])
 424
 425        for x in iterator.take(2):
 426            assert x in ('c', 'x')
 427
 428        # <Iter(['c', 'x'])>
 429        ```
 430        """
 431        return Take(self, count)
 432
 433    def skip(self, count: int) -> Skip[Item]:
 434        """Skips the first number of items in the iterator.
 435
 436        Example
 437        -------
 438        ```py
 439        iterator = Iter((1, 2, 3, 4))
 440        for i in iterator.skip(2):
 441            print(i)
 442
 443        # 3
 444        # 4
 445        ```
 446        """
 447        return Skip(self, count)
 448
 449    def enumerate(self, *, start: int = 0) -> Enumerate[Item]:
 450        """Create a new iterator that yields a tuple of the index and item.
 451
 452        Example
 453        -------
 454        ```py
 455        iterator = Iter([1, 2, 3])
 456        for index, item in iterator.enumerate():
 457            print(index, item)
 458
 459        # 0 1
 460        # 1 2
 461        # 2 3
 462        ```
 463        """
 464        return Enumerate(self, start)
 465
 466    def take_while(self, f: collections.Callable[[Item], bool]) -> TakeWhile[Item]:
 467        """yields items from the iterator while predicate returns `True`.
 468
 469        The rest of the items are discarded as soon as the predicate returns `False`
 470
 471        Example
 472        -------
 473        ```py
 474        iterator = Iter(['a', 'ab', 'xd', 'ba'])
 475        for x in iterator.take_while(lambda x: 'a' in x):
 476            print(x)
 477
 478        # a
 479        # ab
 480        ```
 481
 482        Parameters
 483        ----------
 484        predicate: `collections.Callable[[Item], bool]`
 485            The function to predicate each item in the iterator.
 486        """
 487        return TakeWhile(self, f)
 488
 489    def drop_while(self, f: collections.Callable[[Item], bool]) -> DropWhile[Item]:
 490        """Yields items from the iterator while predicate returns `False`.
 491
 492        Example
 493        -------
 494        ```py
 495        iterator = Iter(['a', 'ab', 'xd', 'ba'])
 496        for x in iterator.drop_while(lambda x: 'a' in x):
 497            print(x)
 498
 499        # xd
 500        # ba
 501        ```
 502
 503        Parameters
 504        ----------
 505        predicate: `collections.Callable[[Item], bool]`
 506            The function to predicate each item in the iterator.
 507        """
 508        return DropWhile(self, f)
 509
 510    def chunks(self, chunk_size: int, /) -> Chunks[Item]:
 511        """Returns an iterator over `chunk_size` elements of the iterator at a time,
 512        starting at the beginning of the iterator.
 513
 514        Example
 515        -------
 516        ```py
 517        iter = Iter(['a', 'b', 'c', 'd', 'e'])
 518        chunks = iter.chunks()
 519        assert chunks.next().unwrap() == ['a', 'b']
 520        assert chunks.next().unwrap() == ['c', 'd']
 521        assert chunks.next().unwrap() == ['e']
 522        assert chunks.next().is_none()
 523        ```
 524        """
 525        return Chunks(self, chunk_size)
 526
 527    def all(self, predicate: collections.Callable[[Item], bool]) -> bool:
 528        """Return `True` if all items in the iterator match the predicate.
 529
 530        Example
 531        -------
 532        ```py
 533        iterator = Iter([1, 2, 3])
 534        while iterator.all(lambda item: isinstance(item, int)):
 535            print("Still all integers")
 536            continue
 537            # Still all integers
 538        ```
 539
 540        Parameters
 541        ----------
 542        predicate: `collections.Callable[[Item], bool]`
 543            The function to test each item in the iterator.
 544        """
 545        return all(predicate(item) for item in self)
 546
 547    def any(self, predicate: collections.Callable[[Item], bool]) -> bool:
 548        """`True` if any items in the iterator match the predicate.
 549
 550        Example
 551        -------
 552        ```py
 553        iterator = Iter([1, 2, 3])
 554        if iterator.any(lambda item: isinstance(item, int)):
 555            print("At least one item is an int.")
 556        # At least one item is an int.
 557        ```
 558
 559        Parameters
 560        ----------
 561        predicate: `collections.Callable[[Item], bool]`
 562            The function to test each item in the iterator.
 563        """
 564        return any(predicate(item) for item in self)
 565
 566    def zip(
 567        self, other: collections.Iterable[OtherItem]
 568    ) -> Iter[tuple[Item, OtherItem]]:
 569        """Zips the iterator with another iterable.
 570
 571        Example
 572        -------
 573        ```py
 574        iterator = Iter([1, 2, 3])
 575        for item, other_item in iterator.zip([4, 5, 6]):
 576            assert item == other_item
 577        <Iter([(1, 4), (2, 5), (3, 6)])>
 578        ```
 579
 580        Parameters
 581        ----------
 582        other: `Iter[OtherItem]`
 583            The iterable to zip with.
 584
 585        Returns
 586        -------
 587        `Iter[tuple[Item, OtherItem]]`
 588            The zipped iterator.
 589
 590        """
 591        return Iter(zip(self.raw_parts(), other))
 592
 593    def sort(
 594        self,
 595        *,
 596        key: collections.Callable[[Item], _typeshed.SupportsRichComparison],
 597        reverse: bool = False,
 598    ) -> Iter[Item]:
 599        """Sorts the iterator.
 600
 601        Example
 602        -------
 603        ```py
 604        iterator = Iter([3, 1, 6, 7])
 605        for item in iterator.sort(key=lambda item: item < 3):
 606            print(item)
 607        # 1
 608        # 3
 609        # 6
 610        # 7
 611        ```
 612
 613        Parameters
 614        ----------
 615        key: `collections.Callable[[Item], Any]`
 616            The function to sort by.
 617        reverse: `bool`
 618            Whether to reverse the sort.
 619        """
 620        return Iter(sorted(self.raw_parts(), key=key, reverse=reverse))
 621
 622    def reversed(self) -> Iter[Item]:
 623        """Returns a new iterator that yields the items in the iterator in reverse order.
 624
 625        This consumes this iterator into a sequence and return a new iterator containing all of the elements
 626        in reversed order.
 627
 628        Example
 629        -------
 630        ```py
 631        iterator = Iter([3, 1, 6, 7])
 632        for item in iterator.reversed():
 633            print(item)
 634        # 7
 635        # 6
 636        # 1
 637        # 3
 638        ```
 639        """
 640        # NOTE: In order to reverse the iterator we need to
 641        # first collect it into some collection.
 642        return Iter(reversed(list(_ for _ in self)))
 643
 644    def union(self, other: collections.Iterable[Item]) -> Iter[Item]:
 645        """Returns a new iterator that yields all items from both iterators.
 646
 647        Example
 648        -------
 649        ```py
 650        iterator = Iter([1, 2, 3])
 651        other = [4, 5, 6]
 652
 653        for item in iterator.union(other):
 654            print(item)
 655        # 1
 656        # 2
 657        # 3
 658        # 4
 659        # 5
 660        # 6
 661        ```
 662
 663        Parameters
 664        ----------
 665        other: `Iter[Item]`
 666            The iterable to union with.
 667        """
 668        return Iter(itertools.chain(self.raw_parts(), other))
 669
 670    def first(self) -> Option[Item]:
 671        """Returns the first item in the iterator.
 672
 673        Example
 674        -------
 675        ```py
 676        iterator = Iter([3, 1, 6, 7])
 677        iterator.first().is_some_and(lambda x: x == 3)
 678        ```
 679        """
 680        return self.take(1).next()
 681
 682    def last(self) -> Option[Item]:
 683        """Returns the last item in the iterator.
 684
 685        Example
 686        -------
 687        ```py
 688        iterator = Iter([3, 1, 6, 7])
 689        iterator.last().is_some_and(lambda x: x == 7)
 690        ```
 691        """
 692        return self.reversed().first()
 693
 694    def count(self) -> int:
 695        """Return the count of elements in memory this iterator has.
 696
 697        Example
 698        -------
 699        ```py
 700        it = Iter(range(3))
 701        assert it.count() == 3
 702        ```
 703        """
 704        count = 0
 705        for _ in self:
 706            count += 1
 707
 708        return count
 709
 710    def find(self, predicate: collections.Callable[[Item], bool]) -> Option[Item]:
 711        """Searches for an element of an iterator that satisfies a predicate.
 712
 713        If you want the position of the element, use `Iterator.position` instead.
 714
 715        `find()` takes a lambda that returns true or false. It applies this closure to each element of the iterator,
 716        and if any of them return true, then find() returns `Some(element)`. If they all return false, it returns None.
 717
 718        Example
 719        -------
 720        ```py
 721        it = Iter(range(10))
 722        item = it.find(lambda num: num > 5)
 723        print(item) # 6
 724        ```
 725        """
 726        for item in self:
 727            if predicate(item):
 728                return _option.Some(item)
 729
 730        # no more items
 731        return _option.NOTHING
 732
 733    def position(self, predicate: collections.Callable[[Item], bool]) -> Option[int]:
 734        """Searches for the position of an element in the iterator that satisfies a predicate.
 735
 736        If you want the object itself, use `Iterator.find` instead.
 737
 738        `position()` takes a lambda that returns true or false. It applies this closure to each element of the iterator,
 739        and if any of them return true, then position() returns `Some(position_of_element)`. If they all return false, it returns None.
 740
 741        Example
 742        -------
 743        ```py
 744        it = Iter(range(10))
 745        position = it.find(lambda num: num > 5)
 746        assert position.unwrap() == 6
 747        ```
 748        """
 749        for position, value in self.enumerate():
 750            if predicate(value):
 751                return _option.Some(position)
 752
 753        # no more items
 754        return _option.NOTHING
 755
 756    def fold(
 757        self, init: OtherItem, f: collections.Callable[[OtherItem, Item], OtherItem]
 758    ) -> OtherItem:
 759        """Folds every element into an accumulator by applying an operation, returning the final result.
 760
 761        fold() takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element.
 762        The closure returns the value that the accumulator should have for the next iteration.
 763
 764        The initial value is the value the accumulator will have on the first call.
 765
 766        After applying this closure to every element of the iterator, fold() returns the accumulator.
 767
 768        This operation is sometimes called ‘reduce’ or ‘inject’.
 769
 770        Example
 771        -------
 772        ```py
 773        a = Iter([1, 2, 3, 4])
 774        sum = a.fold(0, lambda acc, elem: acc + elem)
 775        assert sum == 10
 776        ```
 777        """
 778        accum = init
 779        while True:
 780            try:
 781                x = self.__next__()
 782                accum = f(accum, x)
 783            except StopIteration:
 784                break
 785
 786        return accum
 787
 788    def advance_by(self, n: int) -> _result.Result[None, int]:
 789        """Advances the iterator by `n` elements.
 790
 791        Returns `Result[None, int]`, where `Ok(None)` means the iterator
 792        advanced successfully, and `Err(int)` if `None` encountered, where `int`
 793        represents the remaining number of steps that could not be advanced because the iterator ran out.
 794
 795        Example
 796        -------
 797        ```py
 798        it = into_iter([1, 2, 3, 4])
 799        assert it.advance_by(2).is_ok()
 800        assert it.next() == Some(3)
 801        assert it.advance_by(0).is_ok()
 802        assert it.advance_by(100) == Err(99)
 803        ```
 804        """
 805        for i in range(n):
 806            try:
 807                self.__next__()
 808            except StopIteration:
 809                return _result.Err(n - i)
 810
 811        return _result.Ok(None)
 812
 813    def nth(self, n: int) -> Option[Item]:
 814        """Returns the `n`th element of the iterator
 815
 816        Just like normal indexing, the count `n` starts from zero, so `nth(0)` returns the first
 817        value.
 818
 819        Note all elements before `n` will be skipped / consumed.
 820
 821        Example
 822        -------
 823        ```py
 824        a = into_iter([1, 2, 3])
 825        assert a.iter().nth(1) == Some(2)
 826        ```
 827        """
 828        for _ in range(n):
 829            try:
 830                self.__next__()
 831            except StopIteration:
 832                return _option.NOTHING
 833
 834        return self.next()
 835
 836    def sum(self: Sum) -> int:
 837        """Sums an iterator of a possible type `T` that can be converted to an integer.
 838
 839        where `T` is a typeof (`int`, `float`, `str`, `ReadableBuffer`, `SupportsTrunc`, `SupportsIndex`).
 840
 841        Example
 842        -------
 843        ```py
 844        numbers: Iterator[str] = Iter(["1", "2", "3"])
 845        total = numbers.sum()
 846        assert total == 6
 847        ```
 848        """
 849        return sum(int(_) for _ in self)
 850
 851    def for_each(self, func: collections.Callable[[Item], typing.Any]) -> None:
 852        """Calls `func` on each item in the iterator.
 853
 854        Example
 855        -------
 856        ```py
 857        iterator = Iter([1, 2, 3])
 858        iterator.for_each(lambda item: print(item))
 859        # 1
 860        # 2
 861        # 3
 862        ```
 863
 864        Parameters
 865        ----------
 866        func: `collections.Callable[[Item], typing.Any]`
 867            The function to call on each item in the iterator.
 868        """
 869        for item in self:
 870            func(item)
 871
 872    async def async_for_each(
 873        self,
 874        func: collections.Callable[
 875            [Item], collections.Coroutine[typing.Any, typing.Any, OtherItem]
 876        ],
 877    ) -> _result.Result[collections.Sequence[OtherItem], futures.JoinError]:
 878        """Calls the async function on each item in the iterator *concurrently*.
 879
 880        Concurrently meaning that the next item will not wait for other items
 881        to finish to execute, each item gets called in a separate task.
 882
 883        After all the tasks finish, a `Result[list[T], JoinError]` will be returned,
 884        which will need to be handled by the caller.
 885
 886        Example
 887        -------
 888        ```py
 889        async def create_user(username: str) -> None:
 890            await aiohttp.request("POST", f'.../{username}')
 891
 892        async def main():
 893            users = sain.into_iter(["Danny", "Flower"])
 894            match await users.async_for_each(lambda username: create_user(username)):
 895                case Ok(result):
 896                    # all good
 897                case Err(why):
 898                    print(f"couldn't gather all futures, err={why}")
 899        ```
 900
 901        Parameters
 902        ----------
 903        func: `collections.Callable[[Item], Coroutine[None, Any, Any]]`
 904            The async function to call on each item in the iterator.
 905        """
 906        return await futures.join(*(func(item) for item in self))
 907
 908    def __reversed__(self) -> Iter[Item]:
 909        return self.reversed()
 910
 911    def __repr__(self) -> str:
 912        return "<Iterator>"
 913
 914    def __copy__(self) -> Cloned[Item]:
 915        return self.cloned()
 916
 917    def __deepcopy__(
 918        self, memo: collections.MutableMapping[int, typing.Any], /
 919    ) -> Copied[Item]:
 920        return self.copied()
 921
 922    def __len__(self) -> int:
 923        return self.count()
 924
 925    def __iter__(self) -> Iterator[Item]:
 926        return self
 927
 928
 929class ExactSizeIterator(typing.Generic[Item], Iterator[Item], abc.ABC):
 930    """An iterator that knows its exact size.
 931
 932    The implementations of this interface indicates that the iterator knows exactly
 933    how many items it can yield.
 934
 935    however, this is not a requirement for the iterator to implement this trait, as its
 936    only used for iterators that can know their size.
 937
 938    Example
 939    -------
 940    ```py
 941    @dataclass
 942    class Letters(ExactSizeIterator[str]):
 943        letters: list[str]
 944
 945        def __next__(self) -> str:
 946            return self.letters.pop(0)
 947
 948        def __len__(self) -> int:
 949            return len(self.letters)
 950
 951    letters = Letters(['a', 'b', 'c'])
 952    assert letters.count() == 3
 953    assert letters.next() == Some('a')
 954    assert letters.count() == 2
 955    ```
 956    """
 957
 958    __slots__ = ()
 959
 960    @typing.final
 961    def count(self) -> int:
 962        return len(self)
 963
 964    @typing.final
 965    def len(self) -> int:
 966        """Returns the remaining number of items in the iterator.
 967
 968        This doesn't exhaust the iterator.
 969
 970        Example
 971        -------
 972        ```py
 973        it = once(0)
 974        assert it.len() == 1
 975        assert it.len() == 1
 976        it.next()
 977        assert it.len() == 0
 978        ```
 979        """
 980        return len(self)
 981
 982    @typing.final
 983    def is_empty(self) -> bool:
 984        """Return `True` if this iterator has no items left to yield.
 985
 986        Example
 987        -------
 988        ```py
 989        iterator = once(1)
 990        assert not iterator.is_empty()
 991        assert once.next() == Some(1)
 992        assert iterator.is_empty()
 993        ```
 994        """
 995        return len(self) == 0
 996
 997    @abc.abstractmethod
 998    def __len__(self) -> int: ...
 999
1000
1001@rustc_diagnostic_item("Iter")
1002@typing.final
1003@diagnostic
1004class Iter(typing.Generic[Item], Iterator[Item]):
1005    """a lazy iterator that has its items ready in-memory.
1006
1007    This is similar to Rust `std::slice::Iter<T>` item which iterables can build
1008    from this via `.iter()` method.
1009
1010    Example
1011    -------
1012    ```py
1013    iterator = Iter([1, 2, 3])
1014
1015    # Limit the results to 2.
1016    for item in iterator.take(2):
1017        print(item)
1018    # 1
1019    # 2
1020
1021    # Filter the results.
1022    for item in iterator.filter(lambda item: item > 1):
1023        print(item)
1024    # 2
1025    # 3
1026    # 3
1027
1028    # Indexing is supported.
1029    print(iterator[0])
1030    # 1
1031    ```
1032
1033    Parameters
1034    ----------
1035    items: `Iterable[Item]`
1036        The items to iterate over. This can be anything that implements `__iter__` and `__next__`.
1037    """
1038
1039    __slots__ = ("_it",)
1040
1041    def __init__(self, iterable: collections.Iterable[Item]) -> None:
1042        self._it = iter(iterable)
1043
1044    def clone(self) -> Iter[Item]:
1045        """Return a copy of this iterator.
1046
1047        ```py
1048        it = Iterator([1, 2, 3])
1049
1050        for i in it.clone():
1051            ...
1052
1053        # The actual iterator hasn't been exhausted.
1054        assert it.count() == 3
1055        ```
1056        """
1057        return Iter(copy.copy(self._it))
1058
1059    def __next__(self) -> Item:
1060        return next(self._it)
1061
1062    def __getitem__(self, index: int) -> Item:
1063        return self.skip(index).first().unwrap_or_else(oob)
1064
1065    def __contains__(self, item: Item) -> bool:
1066        return item in self._it
1067
1068
1069@typing.final
1070class TrustedIter(typing.Generic[Item], ExactSizeIterator[Item]):
1071    """Similar to `Iter`, but it reports an accurate length using `ExactSizeIterator`.
1072
1073    iterable objects such as `Vec`, `Bytes`, `list` and other `Sized` may be created
1074    using this iterator.
1075
1076    Example
1077    -------
1078    ```py
1079    # we know the size of the iterator.
1080    sized_buf: TrustedIter[int] = into_iter((1, 2, 3, 4))
1081    # this is `Iter[int]` since we don't know when the generator will stop yielding.
1082    unsized_buf: Iter[int] = into_iter((_ for _ in ([1, 2, 3, 4] if cond else [1, 2])))
1083    ```
1084
1085    Parameters
1086    ----------
1087    items: `collections.Collection[Item]`
1088        A sized collection of items to iterate over.
1089    """
1090
1091    __slots__ = ("_it", "_len", "__alive")
1092
1093    def __init__(self, iterable: collections.Sequence[Item]) -> None:
1094        self.__alive = iterable
1095        self._len = len(iterable)
1096        self._it = iter(iterable)
1097
1098    @property
1099    def __slice_checked_get(self) -> collections.Sequence[Item] | None:
1100        try:
1101            return self.__alive
1102        except AttributeError:
1103            return None
1104
1105    def next(self) -> Option[Item]:
1106        if self._len == 0:
1107            # ! SAFETY: len == 0
1108            return _option.NOTHING
1109
1110        return _option.Some(self.__next__())
1111
1112    @unsafe
1113    def next_unchecked(self) -> Item:
1114        """Returns the next item in the iterator without checking if it exists.
1115
1116        This is equivalent to calling `next()` on the iterator directly.
1117
1118        Example
1119        -------
1120        ```py
1121        iterator = Iter([1])
1122        assert iterator.next_unchecked() == 1
1123        iterator.next_unchecked() # raises StopIteration
1124        ```
1125        """
1126        return self.__next__()
1127
1128    @unsafe
1129    def set_len(self, new_len: int) -> None:
1130        """Sets the length of the iterator to `new_len`.
1131
1132        This is unsafe and should only be used if you know what you're doing.
1133
1134        Example
1135        -------
1136        ```py
1137        iterator = Iter([1, 2, 3])
1138        iterator.set_len(2)
1139        assert iterator.len() == 2
1140        ```
1141        """
1142        self._len = new_len
1143
1144    def as_slice(self) -> Slice[Item]:
1145        """Returns an immutable slice of all elements that have not been yielded
1146
1147        Example
1148        -------
1149        ```py
1150        iterator = into_iter([1, 2, 3])
1151        iterator.as_slice() == [1, 2, 3]
1152        iterator.next()
1153        assert iterator.as_slice() == [2, 3]
1154        ```
1155        """
1156        from .collections.slice import Slice
1157
1158        return Slice(self.__slice_checked_get or ())
1159
1160    def __repr__(self) -> str:
1161        # __alive is dropped from `self`.
1162        if (s := self.__slice_checked_get) is None:
1163            return "TrustedIter(<empty>)"
1164
1165        return f"TrustedIter({s[-self._len :]})"
1166
1167    def __next__(self) -> Item:
1168        try:
1169            i = next(self._it)
1170        except StopIteration:
1171            # don't reference this anymore.
1172            del self.__alive
1173            raise
1174
1175        self._len -= 1
1176        return i
1177
1178    def __getitem__(self, index: int) -> Item:
1179        if self._len == 0:
1180            raise IndexError("index out of bounds")
1181
1182        return self.skip(index).first().unwrap_or_else(oob)
1183
1184    def __contains__(self, item: Item) -> bool:
1185        return item in self._it
1186
1187    def __len__(self) -> int:
1188        return self._len
1189
1190
1191@diagnostic
1192class Cloned(typing.Generic[Item], Iterator[Item]):
1193    """An iterator that copies the elements from an underlying iterator.
1194
1195    This iterator is created by the `Iterator.cloned` method.
1196    """
1197
1198    __slots__ = ("_it",)
1199
1200    def __init__(self, it: Iterator[Item]) -> None:
1201        self._it = it
1202
1203    def __next__(self) -> Item:
1204        n = self._it.__next__()
1205
1206        # Avoid useless function call for a list.
1207        if isinstance(n, list):
1208            # SAFETY: We know this is a list.
1209            return n[:]  # pyright: ignore
1210
1211        return copy.copy(n)
1212
1213
1214@diagnostic
1215class Copied(typing.Generic[Item], Iterator[Item]):
1216    """An iterator that deeply-copies the elements from an underlying iterator.
1217
1218    This iterator is created by the `Iterator.copied` method.
1219    """
1220
1221    __slots__ = ("_it",)
1222
1223    def __init__(self, it: Iterator[Item]) -> None:
1224        self._it = it
1225
1226    def __next__(self) -> Item:
1227        return copy.deepcopy(self._it.__next__())
1228
1229
1230@diagnostic
1231class Map(typing.Generic[Item, OtherItem], Iterator[OtherItem]):
1232    """An iterator that maps the elements to a callable.
1233
1234    This iterator is created by the `Iterator.map` method.
1235    """
1236
1237    __slots__ = ("_it", "_call")
1238
1239    def __init__(
1240        self, it: Iterator[Item], call: collections.Callable[[Item], OtherItem]
1241    ) -> None:
1242        self._it = it
1243        self._call = call
1244
1245    def __next__(self) -> OtherItem:
1246        return self._call(self._it.__next__())
1247
1248
1249@diagnostic
1250class Filter(typing.Generic[Item], Iterator[Item]):
1251    """An iterator that filters the elements to a `predicate`.
1252
1253    This iterator is created by the `Iterator.filter` method.
1254    """
1255
1256    __slots__ = ("_it", "_call")
1257
1258    def __init__(
1259        self, it: Iterator[Item], call: collections.Callable[[Item], bool]
1260    ) -> None:
1261        self._it = it
1262        self._call = call
1263
1264    def __next__(self) -> Item:
1265        for item in self._it:
1266            if self._call(item):
1267                return item
1268
1269        unreachable()
1270
1271
1272@diagnostic
1273class Take(typing.Generic[Item], Iterator[Item]):
1274    """An iterator that yields the first `number` of elements and drops the rest.
1275
1276    This iterator is created by the `Iterator.take` method.
1277    """
1278
1279    __slots__ = ("_it", "_taken", "_count")
1280
1281    def __init__(self, it: Iterator[Item], count: int) -> None:
1282        if count <= 0:
1283            raise ValueError("`count` must be non-zero")
1284
1285        self._it = it
1286        self._taken = count
1287        self._count = 0
1288
1289    def __next__(self) -> Item:
1290        if self._count >= self._taken:
1291            unreachable()
1292
1293        item = self._it.__next__()
1294        self._count += 1
1295        return item
1296
1297
1298@diagnostic
1299class Skip(typing.Generic[Item], Iterator[Item]):
1300    """An iterator that skips the first `number` of elements and yields the rest.
1301
1302    This iterator is created by the `Iterator.skip` method.
1303    """
1304
1305    __slots__ = ("_it", "_count", "_skipped")
1306
1307    def __init__(self, it: Iterator[Item], count: int) -> None:
1308        if count <= 0:
1309            raise ValueError("`count` must be non-zero")
1310
1311        self._it = it
1312        self._count = count
1313        self._skipped = 0
1314
1315    def __next__(self) -> Item:
1316        while self._skipped < self._count:
1317            self._skipped += 1
1318            self._it.__next__()
1319
1320        return self._it.__next__()
1321
1322
1323@diagnostic
1324class Enumerate(typing.Generic[Item], Iterator[tuple[int, Item]]):
1325    """An iterator that yields the current count and the element during iteration.
1326
1327    This iterator is created by the `Iterator.enumerate` method.
1328    """
1329
1330    __slots__ = ("_it", "_count")
1331
1332    def __init__(self, it: Iterator[Item], start: int) -> None:
1333        self._it = it
1334        self._count = start
1335
1336    def __next__(self) -> tuple[int, Item]:
1337        a = self._it.__next__()
1338        i = self._count
1339        self._count += 1
1340        return i, a
1341
1342
1343@diagnostic
1344class TakeWhile(typing.Generic[Item], Iterator[Item]):
1345    """An iterator that yields elements while `predicate` returns `True`.
1346
1347    This iterator is created by the `Iterator.take_while` method.
1348    """
1349
1350    __slots__ = ("_it", "_predicate")
1351
1352    def __init__(
1353        self, it: Iterator[Item], predicate: collections.Callable[[Item], bool]
1354    ) -> None:
1355        self._it = it
1356        self._predicate = predicate
1357
1358    def __next__(self) -> Item:
1359        item = self._it.__next__()
1360
1361        if self._predicate(item):
1362            return item
1363
1364        unreachable()
1365
1366
1367@diagnostic
1368class DropWhile(typing.Generic[Item], Iterator[Item]):
1369    """An iterator that yields elements while `predicate` returns `False`.
1370
1371    This iterator is created by the `Iterator.drop_while` method.
1372    """
1373
1374    __slots__ = ("_it", "_predicate", "_dropped")
1375
1376    def __init__(
1377        self, it: Iterator[Item], predicate: collections.Callable[[Item], bool]
1378    ) -> None:
1379        self._it = it
1380        self._predicate = predicate
1381        self._dropped = False
1382
1383    def __next__(self) -> Item:
1384        if not self._dropped:
1385            while not self._predicate(item := self._it.__next__()):
1386                pass
1387
1388            self._dropped = True
1389            return item
1390
1391        unreachable()
1392
1393
1394@diagnostic
1395class Chunks(typing.Generic[Item], Iterator[collections.Sequence[Item]]):
1396    """An iterator that yields elements in chunks.
1397
1398    This iterator is created by the `Iterator.chunks` method.
1399    """
1400
1401    __slots__ = ("chunk_size", "_it")
1402
1403    def __init__(self, it: Iterator[Item], chunk_size: int) -> None:
1404        self.chunk_size = chunk_size
1405        self._it = it
1406
1407    def __next__(self) -> collections.Sequence[Item]:
1408        chunk: list[Item] = []
1409
1410        for item in self._it:
1411            chunk.append(item)
1412
1413            if len(chunk) == self.chunk_size:
1414                break
1415
1416        if chunk:
1417            return chunk
1418
1419        unreachable()
1420
1421
1422@typing.final
1423@diagnostic
1424class Empty(typing.Generic[Item], ExactSizeIterator[Item]):
1425    """An iterator that yields nothing.
1426
1427    This is the default iterator that is created by `Iterator.default()` or `empty()`
1428    """
1429
1430    __slots__ = ()
1431
1432    def __init__(self) -> None:
1433        pass
1434
1435    def next(self) -> Option[Item]:
1436        # SAFETY: an empty iterator always returns None.
1437        # also we avoid calling `nothing_unchecked()` here for fast returns.
1438        return _option.NOTHING
1439
1440    def __len__(self) -> typing.Literal[0]:
1441        return 0
1442
1443    def any(
1444        self, predicate: collections.Callable[[Item], bool]
1445    ) -> typing.Literal[False]:
1446        return False
1447
1448    def all(
1449        self, predicate: collections.Callable[[Item], bool]
1450    ) -> typing.Literal[False]:
1451        return False
1452
1453    def __next__(self) -> Item:
1454        unreachable()
1455
1456
1457@typing.final
1458@diagnostic
1459class Repeat(typing.Generic[Item], ExactSizeIterator[Item]):
1460    """An iterator that repeats a given value an exact number of times.
1461
1462    This iterator is created by calling `repeat()`.
1463    """
1464
1465    __slots__ = ("_count", "_element")
1466
1467    def __init__(self, element: Item, count: int) -> None:
1468        self._count = count
1469        self._element = element
1470
1471    def __next__(self) -> Item:
1472        if self._count > 0:
1473            self._count -= 1
1474            if self._count == 0:
1475                # Return the origin element last
1476                return self._element
1477
1478            return copy.copy(self._element)
1479
1480        unreachable()
1481
1482    def __len__(self) -> int:
1483        return self._count
1484
1485
1486@typing.final
1487@diagnostic
1488class Once(typing.Generic[Item], ExactSizeIterator[Item]):
1489    """An iterator that yields exactly one item.
1490
1491    This iterator is created by calling `once()`.
1492    """
1493
1494    __slots__ = ("_item",)
1495
1496    def __init__(self, item: Item) -> None:
1497        self._item: Item | None = item
1498
1499    def __next__(self) -> Item:
1500        if self._item is None:
1501            unreachable()
1502
1503        i = self._item
1504        self._item = None
1505        return i
1506
1507    def __len__(self) -> int:
1508        return 1 if self._item is not None else 0
1509
1510
1511# a hack to trick the type-checker into thinking that this iterator yield `Item`.
1512@rustc_diagnostic_item("empty")
1513def empty() -> Empty[Item]:  # pyright: ignore
1514    """Create an iterator that yields nothing.
1515
1516    Example
1517    -------
1518    ```py
1519    nope: Iterator[int] = sain.iter.empty()
1520    assert nope.next().is_none()
1521    ```
1522    """
1523    return Empty()
1524
1525
1526@rustc_diagnostic_item("repeat")
1527def repeat(element: Item, count: int) -> Repeat[Item]:
1528    """Returns an iterator that yields the exact same `element` number of `count` times.
1529
1530    The yielded elements is a copy of `element`, but the last element is guaranteed to be the same as the
1531    original `element`.
1532
1533    Example
1534    -------
1535    ```py
1536    nums = [1, 2, 3]
1537    it = sain.iter.repeat(nums, 5)
1538    for i in range(4):
1539        cloned = it.next().unwrap()
1540        assert cloned == [1, 2, 3]
1541
1542    # But the last item is the origin one...
1543    last = it.next().unwrap()
1544    last.append(4)
1545    assert nums == [1, 2, 3, 4]
1546    ```
1547    """
1548    return Repeat(element, count)
1549
1550
1551@rustc_diagnostic_item("once")
1552def once(item: Item) -> Once[Item]:
1553    """Returns an iterator that yields exactly a single item.
1554
1555    Example
1556    -------
1557    ```py
1558    iterator = sain.iter.once(1)
1559    assert iterator.next() == Some(1)
1560    assert iterator.next() == Some(None)
1561    ```
1562    """
1563    return Once(item)
1564
1565
1566@typing.overload
1567def into_iter(
1568    iterable: collections.Sequence[Item],
1569) -> TrustedIter[Item]: ...
1570
1571
1572@typing.overload
1573def into_iter(
1574    iterable: collections.Iterable[Item],
1575    /,
1576) -> Iter[Item]: ...
1577
1578
1579@rustc_diagnostic_item("into_iter")
1580def into_iter(
1581    iterable: collections.Sequence[Item] | collections.Iterable[Item],
1582) -> Iter[Item] | TrustedIter[Item] | TrustedIter[int]:
1583    """Convert any iterable into `Iterator[Item]`.
1584
1585    if the size of the iterable is known, it will return `TrustedIter`,
1586    otherwise it will return `Iter`.
1587
1588    Example
1589    -------
1590    ```py
1591    sequence = [1,2,3]
1592    for item in sain.into_iter(sequence).reversed():
1593        print(item)
1594    # 3
1595    # 2
1596    # 1
1597    ```
1598    """
1599    if isinstance(iterable, collections.Sequence):
1600        return TrustedIter(iterable)
1601    return Iter(iterable)
@rustc_diagnostic_item('Iter')
@typing.final
@diagnostic
class Iter(typing.Generic[~Item], sain.iter.Iterator[~Item]):
1002@rustc_diagnostic_item("Iter")
1003@typing.final
1004@diagnostic
1005class Iter(typing.Generic[Item], Iterator[Item]):
1006    """a lazy iterator that has its items ready in-memory.
1007
1008    This is similar to Rust `std::slice::Iter<T>` item which iterables can build
1009    from this via `.iter()` method.
1010
1011    Example
1012    -------
1013    ```py
1014    iterator = Iter([1, 2, 3])
1015
1016    # Limit the results to 2.
1017    for item in iterator.take(2):
1018        print(item)
1019    # 1
1020    # 2
1021
1022    # Filter the results.
1023    for item in iterator.filter(lambda item: item > 1):
1024        print(item)
1025    # 2
1026    # 3
1027    # 3
1028
1029    # Indexing is supported.
1030    print(iterator[0])
1031    # 1
1032    ```
1033
1034    Parameters
1035    ----------
1036    items: `Iterable[Item]`
1037        The items to iterate over. This can be anything that implements `__iter__` and `__next__`.
1038    """
1039
1040    __slots__ = ("_it",)
1041
1042    def __init__(self, iterable: collections.Iterable[Item]) -> None:
1043        self._it = iter(iterable)
1044
1045    def clone(self) -> Iter[Item]:
1046        """Return a copy of this iterator.
1047
1048        ```py
1049        it = Iterator([1, 2, 3])
1050
1051        for i in it.clone():
1052            ...
1053
1054        # The actual iterator hasn't been exhausted.
1055        assert it.count() == 3
1056        ```
1057        """
1058        return Iter(copy.copy(self._it))
1059
1060    def __next__(self) -> Item:
1061        return next(self._it)
1062
1063    def __getitem__(self, index: int) -> Item:
1064        return self.skip(index).first().unwrap_or_else(oob)
1065
1066    def __contains__(self, item: Item) -> bool:
1067        return item in self._it

a lazy iterator that has its items ready in-memory.

This is similar to Rust std::slice::Iter<T> item which iterables can build from this via sain.iter method.

Example
iterator = Iter([1, 2, 3])

# Limit the results to 2.
for item in iterator.take(2):
    print(item)
# 1
# 2

# Filter the results.
for item in iterator.filter(lambda item: item > 1):
    print(item)
# 2
# 3
# 3

# Indexing is supported.
print(iterator[0])
# 1
Parameters
  • items (Iterable[Item]): The items to iterate over. This can be anything that implements __iter__ and __next__.
  • # Implementations
  • **This class implements Iter:
Iter(iterable: Iterable[~Item])
1042    def __init__(self, iterable: collections.Iterable[Item]) -> None:
1043        self._it = iter(iterable)
def clone(self) -> Iter[~Item]:
1045    def clone(self) -> Iter[Item]:
1046        """Return a copy of this iterator.
1047
1048        ```py
1049        it = Iterator([1, 2, 3])
1050
1051        for i in it.clone():
1052            ...
1053
1054        # The actual iterator hasn't been exhausted.
1055        assert it.count() == 3
1056        ```
1057        """
1058        return Iter(copy.copy(self._it))

Return a copy of this iterator.

it = Iterator([1, 2, 3])

for i in it.clone():
    ...

# The actual iterator hasn't been exhausted.
assert it.count() == 3
@rustc_diagnostic_item('Iterator')
class Iterator(typing.Generic[~Item], abc.ABC, sain.default.Default[ForwardRef('Empty[Item]')]):
125@rustc_diagnostic_item("Iterator")
126class Iterator(
127    typing.Generic[Item],
128    abc.ABC,
129    _default.Default["Empty[Item]"],
130):
131    """An abstract interface for dealing with iterators.
132
133    This is exactly the same trait as `core::iter::Iterator` trait from Rust.
134
135    This is the main interface that any type can implement by basically inheriting from it.
136    The method `__next__` is the only method that needs to be implemented, You get all the other methods for free.
137
138    If you want to use a ready iterator for general purposes, Use `Iter`. This interface is only for implementers
139    and type hints.
140
141    Example
142    -------
143    ```py
144    @dataclass
145    class Counter(Iterator[int]):
146        start: int = 0
147        stop: int | None = None
148
149        # implement the required method.
150        def __next__(self) -> int:
151            result = self.start
152            self.start += 1
153
154            if self.stop is not None and result >= self.stop:
155                raise StopIteration
156
157            return result
158
159    counter = Counter(start=0, stop=10)
160    for i in counter.map(lambda x: x * 2): # multiply each number
161        ...
162    ```
163    """
164
165    __slots__ = ()
166
167    @abc.abstractmethod
168    def __next__(self) -> Item:
169        raise NotImplementedError
170
171    ###################
172    # const functions #
173    ###################
174
175    @staticmethod
176    @typing.final
177    def default() -> Empty[Item]:
178        """Return the default iterator for this type. It returns an empty iterator.
179
180        Example
181        -------
182        ```py
183        it: Iterator[int] = Iter.default()
184        assert t.next().is_none()
185        ```
186        """
187        return Empty()
188
189    @typing.overload
190    def collect(self) -> collections.MutableSequence[Item]: ...
191
192    @typing.overload
193    def collect(
194        self, *, cast: collections.Callable[[Item], OtherItem]
195    ) -> collections.MutableSequence[OtherItem]: ...
196
197    @typing.final
198    def collect(
199        self, *, cast: collections.Callable[[Item], OtherItem] | None = None
200    ) -> collections.MutableSequence[Item] | collections.MutableSequence[OtherItem]:
201        """Collects all items in the iterator into a sequence.
202
203        Example
204        -------
205        ```py
206        iterator = Iter(range(3))
207        iterator.collect()
208        # (0, 1, 2, 3)
209        iterator.collect(cast=str) # Map each element and collect it.
210        # ('0', '1', '2', '3')
211        ```
212
213        Parameters
214        ----------
215        cast: `T | None`
216            An optional type to cast the items into.
217            If not provided the items will be returned as it's original type.
218        """
219        if cast is not None:
220            return list(cast(i) for i in self)
221
222        return list(_ for _ in self)
223
224    @typing.final
225    def collect_into(self, collection: Collector[Item]) -> None:
226        """Consume this iterator, extending all items in the iterator into a mutable `collection`.
227
228        Example
229        -------
230        ```py
231        iterator = Iter([1, 1, 2, 3, 4, 2, 6])
232        uniques = set()
233        iterator.collect_into(uniques)
234        # assert uniques == {1, 2, 3, 4, 6}
235        ```
236
237        Parameters
238        ----------
239        collection: `MutableSequence[T]` | `set[T]`
240            The collection to extend the items in this iterator with.
241        """
242        if isinstance(collection, collections.MutableSequence):
243            collection.extend(_ for _ in self)
244        elif isinstance(collection, collections.MutableSet):
245            collection.update(_ for _ in self)
246        else:
247            for idx, item in enumerate(self):
248                collection[idx] = item
249
250    @typing.final
251    def to_vec(self) -> Vec[Item]:
252        """Convert this iterator into `Vec[T]`.
253
254        Example
255        -------
256        ```py
257        it = sain.iter.once(0)
258        vc = it.to_vec()
259
260        assert to_vec == [0]
261        ```
262        """
263        from .collections.vec import Vec
264
265        return Vec(_ for _ in self)
266
267    @typing.final
268    def sink(self) -> None:
269        """Consume all elements from this iterator, flushing it into the sink.
270
271        Example
272        -------
273        ```py
274        it = Iter((1, 2, 3))
275        it.sink()
276        assert it.next().is_none()
277        ```
278        """
279        for _ in self:
280            pass
281
282    @typing.final
283    def raw_parts(self) -> collections.Generator[Item, None, None]:
284        """Decompose this iterator into a `Generator[Item]` that yields all of the remaining items.
285
286        This mainly used for objects that needs to satisfy its exact type.
287
288        ```py
289        it = Iter("cba")
290        sort = sorted(it.raw_parts())
291
292        assert it.count() == 0
293        assert sort == ["a", "b", "c"]
294        ```
295        """
296        for item in self:
297            yield item
298
299    ##################
300    # default impl's #
301    ##################
302
303    def next(self) -> Option[Item]:
304        """Advance the iterator, Returning the next item, `Some(None)` if all items yielded.
305
306        Example
307        -------
308        ```py
309        iterator = Iter(["1", "2"])
310        assert iterator.next() == Some("1")
311        assert iterator.next() == Some("2")
312        assert iterator.next().is_none()
313        ```
314        """
315        try:
316            return _option.Some(self.__next__())
317        except StopIteration:
318            # ! SAFETY: No more items in the iterator.
319            return _option.NOTHING
320
321    def cloned(self) -> Cloned[Item]:
322        """Creates an iterator which shallow copies its elements by reference.
323
324        If you need a copy of the actual iterator and not the elements.
325        use `Iter.clone()`
326
327        .. note::
328            This method calls [`copy.copy()`](https://docs.python.org/3/library/copy.html)
329            on each item that is being yielded.
330
331        Example
332        -------
333        ```py
334        @dataclass
335        class User:
336            users_ids: list[int] = []
337
338        # An iterator which elements points to the same user.
339        user = User()
340        it = Iter((user, user))
341
342        for u in it.cloned():
343            u.user_ids.append(1)
344
345        # We iterated over the same user pointer twice and appended "1"
346        # since `copy` returns a shallow copy of nested structures.
347        assert len(user.user_ids) == 2
348        ```
349        """
350        return Cloned(self)
351
352    def copied(self) -> Copied[Item]:
353        """Creates an iterator which copies all of its elements by value.
354
355        If you only need a copy of the item reference, Use `.cloned()` instead.
356
357        .. note::
358            This method simply calls [`copy.deepcopy()`](https://docs.python.org/3/library/copy.html)
359            on each item that is being yielded.
360
361        Example
362        -------
363        ```py
364        @dataclass
365        class User:
366            users_ids: list[int] = []
367
368        # An iterator which elements points to the same user.
369        user = User()
370        it = Iter((user, user))
371
372        for u in it.copied():
373            # A new list is created for each item.
374            u.user_ids.append(1)
375
376        # The actual list is untouched since we consumed a deep copy of it.
377        assert len(user.user_ids) == 0
378        ```
379        """
380        return Copied(self)
381
382    def map(self, fn: collections.Callable[[Item], OtherItem]) -> Map[Item, OtherItem]:
383        """Maps each item in the iterator to another type.
384
385        Example
386        -------
387        ```py
388        iterator = Iter(["1", "2", "3"]).map(int)
389
390        for item in iterator:
391            assert isinstance(item, int)
392        ```
393
394        Parameters
395        ----------
396        predicate: `Callable[[Item], OtherItem]`
397            The function to map each item in the iterator to the other type.
398        """
399        return Map(self, fn)
400
401    def filter(self, predicate: collections.Callable[[Item], bool]) -> Filter[Item]:
402        """Filters the iterator to only yield items that match the predicate.
403
404        Example
405        -------
406        ```py
407        places = Iter(['London', 'Paris', 'Los Angeles'])
408        for place in places.filter(lambda place: place.startswith('L')):
409            print(place)
410
411        # London
412        # Los Angeles
413        ```
414        """
415        return Filter(self, predicate)
416
417    def take(self, count: int) -> Take[Item]:
418        """Take the first number of items until the number of items
419        are yielded or the end of the iterator is exhausted.
420
421        Example
422        -------
423        ```py
424        iterator = Iter(['c', 'x', 'y'])
425
426        for x in iterator.take(2):
427            assert x in ('c', 'x')
428
429        # <Iter(['c', 'x'])>
430        ```
431        """
432        return Take(self, count)
433
434    def skip(self, count: int) -> Skip[Item]:
435        """Skips the first number of items in the iterator.
436
437        Example
438        -------
439        ```py
440        iterator = Iter((1, 2, 3, 4))
441        for i in iterator.skip(2):
442            print(i)
443
444        # 3
445        # 4
446        ```
447        """
448        return Skip(self, count)
449
450    def enumerate(self, *, start: int = 0) -> Enumerate[Item]:
451        """Create a new iterator that yields a tuple of the index and item.
452
453        Example
454        -------
455        ```py
456        iterator = Iter([1, 2, 3])
457        for index, item in iterator.enumerate():
458            print(index, item)
459
460        # 0 1
461        # 1 2
462        # 2 3
463        ```
464        """
465        return Enumerate(self, start)
466
467    def take_while(self, f: collections.Callable[[Item], bool]) -> TakeWhile[Item]:
468        """yields items from the iterator while predicate returns `True`.
469
470        The rest of the items are discarded as soon as the predicate returns `False`
471
472        Example
473        -------
474        ```py
475        iterator = Iter(['a', 'ab', 'xd', 'ba'])
476        for x in iterator.take_while(lambda x: 'a' in x):
477            print(x)
478
479        # a
480        # ab
481        ```
482
483        Parameters
484        ----------
485        predicate: `collections.Callable[[Item], bool]`
486            The function to predicate each item in the iterator.
487        """
488        return TakeWhile(self, f)
489
490    def drop_while(self, f: collections.Callable[[Item], bool]) -> DropWhile[Item]:
491        """Yields items from the iterator while predicate returns `False`.
492
493        Example
494        -------
495        ```py
496        iterator = Iter(['a', 'ab', 'xd', 'ba'])
497        for x in iterator.drop_while(lambda x: 'a' in x):
498            print(x)
499
500        # xd
501        # ba
502        ```
503
504        Parameters
505        ----------
506        predicate: `collections.Callable[[Item], bool]`
507            The function to predicate each item in the iterator.
508        """
509        return DropWhile(self, f)
510
511    def chunks(self, chunk_size: int, /) -> Chunks[Item]:
512        """Returns an iterator over `chunk_size` elements of the iterator at a time,
513        starting at the beginning of the iterator.
514
515        Example
516        -------
517        ```py
518        iter = Iter(['a', 'b', 'c', 'd', 'e'])
519        chunks = iter.chunks()
520        assert chunks.next().unwrap() == ['a', 'b']
521        assert chunks.next().unwrap() == ['c', 'd']
522        assert chunks.next().unwrap() == ['e']
523        assert chunks.next().is_none()
524        ```
525        """
526        return Chunks(self, chunk_size)
527
528    def all(self, predicate: collections.Callable[[Item], bool]) -> bool:
529        """Return `True` if all items in the iterator match the predicate.
530
531        Example
532        -------
533        ```py
534        iterator = Iter([1, 2, 3])
535        while iterator.all(lambda item: isinstance(item, int)):
536            print("Still all integers")
537            continue
538            # Still all integers
539        ```
540
541        Parameters
542        ----------
543        predicate: `collections.Callable[[Item], bool]`
544            The function to test each item in the iterator.
545        """
546        return all(predicate(item) for item in self)
547
548    def any(self, predicate: collections.Callable[[Item], bool]) -> bool:
549        """`True` if any items in the iterator match the predicate.
550
551        Example
552        -------
553        ```py
554        iterator = Iter([1, 2, 3])
555        if iterator.any(lambda item: isinstance(item, int)):
556            print("At least one item is an int.")
557        # At least one item is an int.
558        ```
559
560        Parameters
561        ----------
562        predicate: `collections.Callable[[Item], bool]`
563            The function to test each item in the iterator.
564        """
565        return any(predicate(item) for item in self)
566
567    def zip(
568        self, other: collections.Iterable[OtherItem]
569    ) -> Iter[tuple[Item, OtherItem]]:
570        """Zips the iterator with another iterable.
571
572        Example
573        -------
574        ```py
575        iterator = Iter([1, 2, 3])
576        for item, other_item in iterator.zip([4, 5, 6]):
577            assert item == other_item
578        <Iter([(1, 4), (2, 5), (3, 6)])>
579        ```
580
581        Parameters
582        ----------
583        other: `Iter[OtherItem]`
584            The iterable to zip with.
585
586        Returns
587        -------
588        `Iter[tuple[Item, OtherItem]]`
589            The zipped iterator.
590
591        """
592        return Iter(zip(self.raw_parts(), other))
593
594    def sort(
595        self,
596        *,
597        key: collections.Callable[[Item], _typeshed.SupportsRichComparison],
598        reverse: bool = False,
599    ) -> Iter[Item]:
600        """Sorts the iterator.
601
602        Example
603        -------
604        ```py
605        iterator = Iter([3, 1, 6, 7])
606        for item in iterator.sort(key=lambda item: item < 3):
607            print(item)
608        # 1
609        # 3
610        # 6
611        # 7
612        ```
613
614        Parameters
615        ----------
616        key: `collections.Callable[[Item], Any]`
617            The function to sort by.
618        reverse: `bool`
619            Whether to reverse the sort.
620        """
621        return Iter(sorted(self.raw_parts(), key=key, reverse=reverse))
622
623    def reversed(self) -> Iter[Item]:
624        """Returns a new iterator that yields the items in the iterator in reverse order.
625
626        This consumes this iterator into a sequence and return a new iterator containing all of the elements
627        in reversed order.
628
629        Example
630        -------
631        ```py
632        iterator = Iter([3, 1, 6, 7])
633        for item in iterator.reversed():
634            print(item)
635        # 7
636        # 6
637        # 1
638        # 3
639        ```
640        """
641        # NOTE: In order to reverse the iterator we need to
642        # first collect it into some collection.
643        return Iter(reversed(list(_ for _ in self)))
644
645    def union(self, other: collections.Iterable[Item]) -> Iter[Item]:
646        """Returns a new iterator that yields all items from both iterators.
647
648        Example
649        -------
650        ```py
651        iterator = Iter([1, 2, 3])
652        other = [4, 5, 6]
653
654        for item in iterator.union(other):
655            print(item)
656        # 1
657        # 2
658        # 3
659        # 4
660        # 5
661        # 6
662        ```
663
664        Parameters
665        ----------
666        other: `Iter[Item]`
667            The iterable to union with.
668        """
669        return Iter(itertools.chain(self.raw_parts(), other))
670
671    def first(self) -> Option[Item]:
672        """Returns the first item in the iterator.
673
674        Example
675        -------
676        ```py
677        iterator = Iter([3, 1, 6, 7])
678        iterator.first().is_some_and(lambda x: x == 3)
679        ```
680        """
681        return self.take(1).next()
682
683    def last(self) -> Option[Item]:
684        """Returns the last item in the iterator.
685
686        Example
687        -------
688        ```py
689        iterator = Iter([3, 1, 6, 7])
690        iterator.last().is_some_and(lambda x: x == 7)
691        ```
692        """
693        return self.reversed().first()
694
695    def count(self) -> int:
696        """Return the count of elements in memory this iterator has.
697
698        Example
699        -------
700        ```py
701        it = Iter(range(3))
702        assert it.count() == 3
703        ```
704        """
705        count = 0
706        for _ in self:
707            count += 1
708
709        return count
710
711    def find(self, predicate: collections.Callable[[Item], bool]) -> Option[Item]:
712        """Searches for an element of an iterator that satisfies a predicate.
713
714        If you want the position of the element, use `Iterator.position` instead.
715
716        `find()` takes a lambda that returns true or false. It applies this closure to each element of the iterator,
717        and if any of them return true, then find() returns `Some(element)`. If they all return false, it returns None.
718
719        Example
720        -------
721        ```py
722        it = Iter(range(10))
723        item = it.find(lambda num: num > 5)
724        print(item) # 6
725        ```
726        """
727        for item in self:
728            if predicate(item):
729                return _option.Some(item)
730
731        # no more items
732        return _option.NOTHING
733
734    def position(self, predicate: collections.Callable[[Item], bool]) -> Option[int]:
735        """Searches for the position of an element in the iterator that satisfies a predicate.
736
737        If you want the object itself, use `Iterator.find` instead.
738
739        `position()` takes a lambda that returns true or false. It applies this closure to each element of the iterator,
740        and if any of them return true, then position() returns `Some(position_of_element)`. If they all return false, it returns None.
741
742        Example
743        -------
744        ```py
745        it = Iter(range(10))
746        position = it.find(lambda num: num > 5)
747        assert position.unwrap() == 6
748        ```
749        """
750        for position, value in self.enumerate():
751            if predicate(value):
752                return _option.Some(position)
753
754        # no more items
755        return _option.NOTHING
756
757    def fold(
758        self, init: OtherItem, f: collections.Callable[[OtherItem, Item], OtherItem]
759    ) -> OtherItem:
760        """Folds every element into an accumulator by applying an operation, returning the final result.
761
762        fold() takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element.
763        The closure returns the value that the accumulator should have for the next iteration.
764
765        The initial value is the value the accumulator will have on the first call.
766
767        After applying this closure to every element of the iterator, fold() returns the accumulator.
768
769        This operation is sometimes called ‘reduce’ or ‘inject’.
770
771        Example
772        -------
773        ```py
774        a = Iter([1, 2, 3, 4])
775        sum = a.fold(0, lambda acc, elem: acc + elem)
776        assert sum == 10
777        ```
778        """
779        accum = init
780        while True:
781            try:
782                x = self.__next__()
783                accum = f(accum, x)
784            except StopIteration:
785                break
786
787        return accum
788
789    def advance_by(self, n: int) -> _result.Result[None, int]:
790        """Advances the iterator by `n` elements.
791
792        Returns `Result[None, int]`, where `Ok(None)` means the iterator
793        advanced successfully, and `Err(int)` if `None` encountered, where `int`
794        represents the remaining number of steps that could not be advanced because the iterator ran out.
795
796        Example
797        -------
798        ```py
799        it = into_iter([1, 2, 3, 4])
800        assert it.advance_by(2).is_ok()
801        assert it.next() == Some(3)
802        assert it.advance_by(0).is_ok()
803        assert it.advance_by(100) == Err(99)
804        ```
805        """
806        for i in range(n):
807            try:
808                self.__next__()
809            except StopIteration:
810                return _result.Err(n - i)
811
812        return _result.Ok(None)
813
814    def nth(self, n: int) -> Option[Item]:
815        """Returns the `n`th element of the iterator
816
817        Just like normal indexing, the count `n` starts from zero, so `nth(0)` returns the first
818        value.
819
820        Note all elements before `n` will be skipped / consumed.
821
822        Example
823        -------
824        ```py
825        a = into_iter([1, 2, 3])
826        assert a.iter().nth(1) == Some(2)
827        ```
828        """
829        for _ in range(n):
830            try:
831                self.__next__()
832            except StopIteration:
833                return _option.NOTHING
834
835        return self.next()
836
837    def sum(self: Sum) -> int:
838        """Sums an iterator of a possible type `T` that can be converted to an integer.
839
840        where `T` is a typeof (`int`, `float`, `str`, `ReadableBuffer`, `SupportsTrunc`, `SupportsIndex`).
841
842        Example
843        -------
844        ```py
845        numbers: Iterator[str] = Iter(["1", "2", "3"])
846        total = numbers.sum()
847        assert total == 6
848        ```
849        """
850        return sum(int(_) for _ in self)
851
852    def for_each(self, func: collections.Callable[[Item], typing.Any]) -> None:
853        """Calls `func` on each item in the iterator.
854
855        Example
856        -------
857        ```py
858        iterator = Iter([1, 2, 3])
859        iterator.for_each(lambda item: print(item))
860        # 1
861        # 2
862        # 3
863        ```
864
865        Parameters
866        ----------
867        func: `collections.Callable[[Item], typing.Any]`
868            The function to call on each item in the iterator.
869        """
870        for item in self:
871            func(item)
872
873    async def async_for_each(
874        self,
875        func: collections.Callable[
876            [Item], collections.Coroutine[typing.Any, typing.Any, OtherItem]
877        ],
878    ) -> _result.Result[collections.Sequence[OtherItem], futures.JoinError]:
879        """Calls the async function on each item in the iterator *concurrently*.
880
881        Concurrently meaning that the next item will not wait for other items
882        to finish to execute, each item gets called in a separate task.
883
884        After all the tasks finish, a `Result[list[T], JoinError]` will be returned,
885        which will need to be handled by the caller.
886
887        Example
888        -------
889        ```py
890        async def create_user(username: str) -> None:
891            await aiohttp.request("POST", f'.../{username}')
892
893        async def main():
894            users = sain.into_iter(["Danny", "Flower"])
895            match await users.async_for_each(lambda username: create_user(username)):
896                case Ok(result):
897                    # all good
898                case Err(why):
899                    print(f"couldn't gather all futures, err={why}")
900        ```
901
902        Parameters
903        ----------
904        func: `collections.Callable[[Item], Coroutine[None, Any, Any]]`
905            The async function to call on each item in the iterator.
906        """
907        return await futures.join(*(func(item) for item in self))
908
909    def __reversed__(self) -> Iter[Item]:
910        return self.reversed()
911
912    def __repr__(self) -> str:
913        return "<Iterator>"
914
915    def __copy__(self) -> Cloned[Item]:
916        return self.cloned()
917
918    def __deepcopy__(
919        self, memo: collections.MutableMapping[int, typing.Any], /
920    ) -> Copied[Item]:
921        return self.copied()
922
923    def __len__(self) -> int:
924        return self.count()
925
926    def __iter__(self) -> Iterator[Item]:
927        return self

An abstract interface for dealing with iterators.

This is exactly the same trait as core::iter::Iterator trait from Rust.

This is the main interface that any type can implement by basically inheriting from it. The method __next__ is the only method that needs to be implemented, You get all the other methods for free.

If you want to use a ready iterator for general purposes, Use Iter. This interface is only for implementers and type hints.

Example
@dataclass
class Counter(Iterator[int]):
    start: int = 0
    stop: int | None = None

    # implement the required method.
    def __next__(self) -> int:
        result = self.start
        self.start += 1

        if self.stop is not None and result >= self.stop:
            raise StopIteration

        return result

counter = Counter(start=0, stop=10)
for i in counter.map(lambda x: x * 2): # multiply each number
    ...

Implementations

This class implements Iterator in Rust.

@staticmethod
@typing.final
def default() -> Empty[~Item]:
175    @staticmethod
176    @typing.final
177    def default() -> Empty[Item]:
178        """Return the default iterator for this type. It returns an empty iterator.
179
180        Example
181        -------
182        ```py
183        it: Iterator[int] = Iter.default()
184        assert t.next().is_none()
185        ```
186        """
187        return Empty()

Return the default iterator for this type. It returns an empty iterator.

Example
it: Iterator[int] = Iter.default()
assert t.next().is_none()
@typing.final
def collect( self, *, cast: Callable[[~Item], ~OtherItem] | None = None) -> MutableSequence[~Item] | MutableSequence[~OtherItem]:
197    @typing.final
198    def collect(
199        self, *, cast: collections.Callable[[Item], OtherItem] | None = None
200    ) -> collections.MutableSequence[Item] | collections.MutableSequence[OtherItem]:
201        """Collects all items in the iterator into a sequence.
202
203        Example
204        -------
205        ```py
206        iterator = Iter(range(3))
207        iterator.collect()
208        # (0, 1, 2, 3)
209        iterator.collect(cast=str) # Map each element and collect it.
210        # ('0', '1', '2', '3')
211        ```
212
213        Parameters
214        ----------
215        cast: `T | None`
216            An optional type to cast the items into.
217            If not provided the items will be returned as it's original type.
218        """
219        if cast is not None:
220            return list(cast(i) for i in self)
221
222        return list(_ for _ in self)

Collects all items in the iterator into a sequence.

Example
iterator = Iter(range(3))
iterator.collect()
# (0, 1, 2, 3)
iterator.collect(cast=str) # Map each element and collect it.
# ('0', '1', '2', '3')
Parameters
  • cast (T | None): An optional type to cast the items into. If not provided the items will be returned as it's original type.
@typing.final
def collect_into(self, collection: 'Collector[Item]') -> None:
224    @typing.final
225    def collect_into(self, collection: Collector[Item]) -> None:
226        """Consume this iterator, extending all items in the iterator into a mutable `collection`.
227
228        Example
229        -------
230        ```py
231        iterator = Iter([1, 1, 2, 3, 4, 2, 6])
232        uniques = set()
233        iterator.collect_into(uniques)
234        # assert uniques == {1, 2, 3, 4, 6}
235        ```
236
237        Parameters
238        ----------
239        collection: `MutableSequence[T]` | `set[T]`
240            The collection to extend the items in this iterator with.
241        """
242        if isinstance(collection, collections.MutableSequence):
243            collection.extend(_ for _ in self)
244        elif isinstance(collection, collections.MutableSet):
245            collection.update(_ for _ in self)
246        else:
247            for idx, item in enumerate(self):
248                collection[idx] = item

Consume this iterator, extending all items in the iterator into a mutable collection.

Example
iterator = Iter([1, 1, 2, 3, 4, 2, 6])
uniques = set()
iterator.collect_into(uniques)
# assert uniques == {1, 2, 3, 4, 6}
Parameters
  • collection (MutableSequence[T] | set[T]): The collection to extend the items in this iterator with.
@typing.final
def to_vec(self) -> 'Vec[Item]':
250    @typing.final
251    def to_vec(self) -> Vec[Item]:
252        """Convert this iterator into `Vec[T]`.
253
254        Example
255        -------
256        ```py
257        it = sain.iter.once(0)
258        vc = it.to_vec()
259
260        assert to_vec == [0]
261        ```
262        """
263        from .collections.vec import Vec
264
265        return Vec(_ for _ in self)

Convert this iterator into Vec[T].

Example
it = sain.iter.once(0)
vc = it.to_vec()

assert to_vec == [0]
@typing.final
def sink(self) -> None:
267    @typing.final
268    def sink(self) -> None:
269        """Consume all elements from this iterator, flushing it into the sink.
270
271        Example
272        -------
273        ```py
274        it = Iter((1, 2, 3))
275        it.sink()
276        assert it.next().is_none()
277        ```
278        """
279        for _ in self:
280            pass

Consume all elements from this iterator, flushing it into the sink.

Example
it = Iter((1, 2, 3))
it.sink()
assert it.next().is_none()
@typing.final
def raw_parts(self) -> Generator[~Item, None, None]:
282    @typing.final
283    def raw_parts(self) -> collections.Generator[Item, None, None]:
284        """Decompose this iterator into a `Generator[Item]` that yields all of the remaining items.
285
286        This mainly used for objects that needs to satisfy its exact type.
287
288        ```py
289        it = Iter("cba")
290        sort = sorted(it.raw_parts())
291
292        assert it.count() == 0
293        assert sort == ["a", "b", "c"]
294        ```
295        """
296        for item in self:
297            yield item

Decompose this iterator into a Generator[Item] that yields all of the remaining items.

This mainly used for objects that needs to satisfy its exact type.

it = Iter("cba")
sort = sorted(it.raw_parts())

assert it.count() == 0
assert sort == ["a", "b", "c"]
def next(self) -> 'Option[Item]':
303    def next(self) -> Option[Item]:
304        """Advance the iterator, Returning the next item, `Some(None)` if all items yielded.
305
306        Example
307        -------
308        ```py
309        iterator = Iter(["1", "2"])
310        assert iterator.next() == Some("1")
311        assert iterator.next() == Some("2")
312        assert iterator.next().is_none()
313        ```
314        """
315        try:
316            return _option.Some(self.__next__())
317        except StopIteration:
318            # ! SAFETY: No more items in the iterator.
319            return _option.NOTHING

Advance the iterator, Returning the next item, Some(None) if all items yielded.

Example
iterator = Iter(["1", "2"])
assert iterator.next() == Some("1")
assert iterator.next() == Some("2")
assert iterator.next().is_none()
def cloned(self) -> Cloned[~Item]:
321    def cloned(self) -> Cloned[Item]:
322        """Creates an iterator which shallow copies its elements by reference.
323
324        If you need a copy of the actual iterator and not the elements.
325        use `Iter.clone()`
326
327        .. note::
328            This method calls [`copy.copy()`](https://docs.python.org/3/library/copy.html)
329            on each item that is being yielded.
330
331        Example
332        -------
333        ```py
334        @dataclass
335        class User:
336            users_ids: list[int] = []
337
338        # An iterator which elements points to the same user.
339        user = User()
340        it = Iter((user, user))
341
342        for u in it.cloned():
343            u.user_ids.append(1)
344
345        # We iterated over the same user pointer twice and appended "1"
346        # since `copy` returns a shallow copy of nested structures.
347        assert len(user.user_ids) == 2
348        ```
349        """
350        return Cloned(self)

Creates an iterator which shallow copies its elements by reference.

If you need a copy of the actual iterator and not the elements. use Iter.clone()

This method calls copy.copy() on each item that is being yielded.

Example
@dataclass
class User:
    users_ids: list[int] = []

# An iterator which elements points to the same user.
user = User()
it = Iter((user, user))

for u in it.cloned():
    u.user_ids.append(1)

# We iterated over the same user pointer twice and appended "1"
# since `copy` returns a shallow copy of nested structures.
assert len(user.user_ids) == 2
def copied(self) -> Copied[~Item]:
352    def copied(self) -> Copied[Item]:
353        """Creates an iterator which copies all of its elements by value.
354
355        If you only need a copy of the item reference, Use `.cloned()` instead.
356
357        .. note::
358            This method simply calls [`copy.deepcopy()`](https://docs.python.org/3/library/copy.html)
359            on each item that is being yielded.
360
361        Example
362        -------
363        ```py
364        @dataclass
365        class User:
366            users_ids: list[int] = []
367
368        # An iterator which elements points to the same user.
369        user = User()
370        it = Iter((user, user))
371
372        for u in it.copied():
373            # A new list is created for each item.
374            u.user_ids.append(1)
375
376        # The actual list is untouched since we consumed a deep copy of it.
377        assert len(user.user_ids) == 0
378        ```
379        """
380        return Copied(self)

Creates an iterator which copies all of its elements by value.

If you only need a copy of the item reference, Use .cloned() instead.

This method simply calls copy.deepcopy() on each item that is being yielded.

Example
@dataclass
class User:
    users_ids: list[int] = []

# An iterator which elements points to the same user.
user = User()
it = Iter((user, user))

for u in it.copied():
    # A new list is created for each item.
    u.user_ids.append(1)

# The actual list is untouched since we consumed a deep copy of it.
assert len(user.user_ids) == 0
def map( self, fn: Callable[[~Item], ~OtherItem]) -> Map[~Item, ~OtherItem]:
382    def map(self, fn: collections.Callable[[Item], OtherItem]) -> Map[Item, OtherItem]:
383        """Maps each item in the iterator to another type.
384
385        Example
386        -------
387        ```py
388        iterator = Iter(["1", "2", "3"]).map(int)
389
390        for item in iterator:
391            assert isinstance(item, int)
392        ```
393
394        Parameters
395        ----------
396        predicate: `Callable[[Item], OtherItem]`
397            The function to map each item in the iterator to the other type.
398        """
399        return Map(self, fn)

Maps each item in the iterator to another type.

Example
iterator = Iter(["1", "2", "3"]).map(int)

for item in iterator:
    assert isinstance(item, int)
Parameters
  • predicate (Callable[[Item], OtherItem]): The function to map each item in the iterator to the other type.
def filter(self, predicate: Callable[[~Item], bool]) -> Filter[~Item]:
401    def filter(self, predicate: collections.Callable[[Item], bool]) -> Filter[Item]:
402        """Filters the iterator to only yield items that match the predicate.
403
404        Example
405        -------
406        ```py
407        places = Iter(['London', 'Paris', 'Los Angeles'])
408        for place in places.filter(lambda place: place.startswith('L')):
409            print(place)
410
411        # London
412        # Los Angeles
413        ```
414        """
415        return Filter(self, predicate)

Filters the iterator to only yield items that match the predicate.

Example
places = Iter(['London', 'Paris', 'Los Angeles'])
for place in places.filter(lambda place: place.startswith('L')):
    print(place)

# London
# Los Angeles
def take(self, count: int) -> Take[~Item]:
417    def take(self, count: int) -> Take[Item]:
418        """Take the first number of items until the number of items
419        are yielded or the end of the iterator is exhausted.
420
421        Example
422        -------
423        ```py
424        iterator = Iter(['c', 'x', 'y'])
425
426        for x in iterator.take(2):
427            assert x in ('c', 'x')
428
429        # <Iter(['c', 'x'])>
430        ```
431        """
432        return Take(self, count)

Take the first number of items until the number of items are yielded or the end of the iterator is exhausted.

Example
iterator = Iter(['c', 'x', 'y'])

for x in iterator.take(2):
    assert x in ('c', 'x')

# <Iter(['c', 'x'])>
def skip(self, count: int) -> Skip[~Item]:
434    def skip(self, count: int) -> Skip[Item]:
435        """Skips the first number of items in the iterator.
436
437        Example
438        -------
439        ```py
440        iterator = Iter((1, 2, 3, 4))
441        for i in iterator.skip(2):
442            print(i)
443
444        # 3
445        # 4
446        ```
447        """
448        return Skip(self, count)

Skips the first number of items in the iterator.

Example
iterator = Iter((1, 2, 3, 4))
for i in iterator.skip(2):
    print(i)

# 3
# 4
def enumerate(self, *, start: int = 0) -> Enumerate[~Item]:
450    def enumerate(self, *, start: int = 0) -> Enumerate[Item]:
451        """Create a new iterator that yields a tuple of the index and item.
452
453        Example
454        -------
455        ```py
456        iterator = Iter([1, 2, 3])
457        for index, item in iterator.enumerate():
458            print(index, item)
459
460        # 0 1
461        # 1 2
462        # 2 3
463        ```
464        """
465        return Enumerate(self, start)

Create a new iterator that yields a tuple of the index and item.

Example
iterator = Iter([1, 2, 3])
for index, item in iterator.enumerate():
    print(index, item)

# 0 1
# 1 2
# 2 3
def take_while(self, f: Callable[[~Item], bool]) -> TakeWhile[~Item]:
467    def take_while(self, f: collections.Callable[[Item], bool]) -> TakeWhile[Item]:
468        """yields items from the iterator while predicate returns `True`.
469
470        The rest of the items are discarded as soon as the predicate returns `False`
471
472        Example
473        -------
474        ```py
475        iterator = Iter(['a', 'ab', 'xd', 'ba'])
476        for x in iterator.take_while(lambda x: 'a' in x):
477            print(x)
478
479        # a
480        # ab
481        ```
482
483        Parameters
484        ----------
485        predicate: `collections.Callable[[Item], bool]`
486            The function to predicate each item in the iterator.
487        """
488        return TakeWhile(self, f)

yields items from the iterator while predicate returns True.

The rest of the items are discarded as soon as the predicate returns False

Example
iterator = Iter(['a', 'ab', 'xd', 'ba'])
for x in iterator.take_while(lambda x: 'a' in x):
    print(x)

# a
# ab
Parameters
  • predicate (collections.Callable[[Item], bool]): The function to predicate each item in the iterator.
def drop_while(self, f: Callable[[~Item], bool]) -> DropWhile[~Item]:
490    def drop_while(self, f: collections.Callable[[Item], bool]) -> DropWhile[Item]:
491        """Yields items from the iterator while predicate returns `False`.
492
493        Example
494        -------
495        ```py
496        iterator = Iter(['a', 'ab', 'xd', 'ba'])
497        for x in iterator.drop_while(lambda x: 'a' in x):
498            print(x)
499
500        # xd
501        # ba
502        ```
503
504        Parameters
505        ----------
506        predicate: `collections.Callable[[Item], bool]`
507            The function to predicate each item in the iterator.
508        """
509        return DropWhile(self, f)

Yields items from the iterator while predicate returns False.

Example
iterator = Iter(['a', 'ab', 'xd', 'ba'])
for x in iterator.drop_while(lambda x: 'a' in x):
    print(x)

# xd
# ba
Parameters
  • predicate (collections.Callable[[Item], bool]): The function to predicate each item in the iterator.
def chunks(self, chunk_size: int, /) -> Chunks[~Item]:
511    def chunks(self, chunk_size: int, /) -> Chunks[Item]:
512        """Returns an iterator over `chunk_size` elements of the iterator at a time,
513        starting at the beginning of the iterator.
514
515        Example
516        -------
517        ```py
518        iter = Iter(['a', 'b', 'c', 'd', 'e'])
519        chunks = iter.chunks()
520        assert chunks.next().unwrap() == ['a', 'b']
521        assert chunks.next().unwrap() == ['c', 'd']
522        assert chunks.next().unwrap() == ['e']
523        assert chunks.next().is_none()
524        ```
525        """
526        return Chunks(self, chunk_size)

Returns an iterator over chunk_size elements of the iterator at a time, starting at the beginning of the iterator.

Example
iter = Iter(['a', 'b', 'c', 'd', 'e'])
chunks = iter.chunks()
assert chunks.next().unwrap() == ['a', 'b']
assert chunks.next().unwrap() == ['c', 'd']
assert chunks.next().unwrap() == ['e']
assert chunks.next().is_none()
def all(self, predicate: Callable[[~Item], bool]) -> bool:
528    def all(self, predicate: collections.Callable[[Item], bool]) -> bool:
529        """Return `True` if all items in the iterator match the predicate.
530
531        Example
532        -------
533        ```py
534        iterator = Iter([1, 2, 3])
535        while iterator.all(lambda item: isinstance(item, int)):
536            print("Still all integers")
537            continue
538            # Still all integers
539        ```
540
541        Parameters
542        ----------
543        predicate: `collections.Callable[[Item], bool]`
544            The function to test each item in the iterator.
545        """
546        return all(predicate(item) for item in self)

Return True if all items in the iterator match the predicate.

Example
iterator = Iter([1, 2, 3])
while iterator.all(lambda item: isinstance(item, int)):
    print("Still all integers")
    continue
    # Still all integers
Parameters
  • predicate (collections.Callable[[Item], bool]): The function to test each item in the iterator.
def any(self, predicate: Callable[[~Item], bool]) -> bool:
548    def any(self, predicate: collections.Callable[[Item], bool]) -> bool:
549        """`True` if any items in the iterator match the predicate.
550
551        Example
552        -------
553        ```py
554        iterator = Iter([1, 2, 3])
555        if iterator.any(lambda item: isinstance(item, int)):
556            print("At least one item is an int.")
557        # At least one item is an int.
558        ```
559
560        Parameters
561        ----------
562        predicate: `collections.Callable[[Item], bool]`
563            The function to test each item in the iterator.
564        """
565        return any(predicate(item) for item in self)

True if any items in the iterator match the predicate.

Example
iterator = Iter([1, 2, 3])
if iterator.any(lambda item: isinstance(item, int)):
    print("At least one item is an int.")
# At least one item is an int.
Parameters
  • predicate (collections.Callable[[Item], bool]): The function to test each item in the iterator.
def zip( self, other: Iterable[~OtherItem]) -> Iter[tuple[~Item, ~OtherItem]]:
567    def zip(
568        self, other: collections.Iterable[OtherItem]
569    ) -> Iter[tuple[Item, OtherItem]]:
570        """Zips the iterator with another iterable.
571
572        Example
573        -------
574        ```py
575        iterator = Iter([1, 2, 3])
576        for item, other_item in iterator.zip([4, 5, 6]):
577            assert item == other_item
578        <Iter([(1, 4), (2, 5), (3, 6)])>
579        ```
580
581        Parameters
582        ----------
583        other: `Iter[OtherItem]`
584            The iterable to zip with.
585
586        Returns
587        -------
588        `Iter[tuple[Item, OtherItem]]`
589            The zipped iterator.
590
591        """
592        return Iter(zip(self.raw_parts(), other))

Zips the iterator with another iterable.

Example
iterator = Iter([1, 2, 3])
for item, other_item in iterator.zip([4, 5, 6]):
    assert item == other_item
<Iter([(1, 4), (2, 5), (3, 6)])>
Parameters
  • other (Iter[OtherItem]): The iterable to zip with.
Returns
  • Iter[tuple[Item, OtherItem]]: The zipped iterator.
def sort( self, *, key: 'collections.Callable[[Item], _typeshed.SupportsRichComparison]', reverse: bool = False) -> Iter[~Item]:
594    def sort(
595        self,
596        *,
597        key: collections.Callable[[Item], _typeshed.SupportsRichComparison],
598        reverse: bool = False,
599    ) -> Iter[Item]:
600        """Sorts the iterator.
601
602        Example
603        -------
604        ```py
605        iterator = Iter([3, 1, 6, 7])
606        for item in iterator.sort(key=lambda item: item < 3):
607            print(item)
608        # 1
609        # 3
610        # 6
611        # 7
612        ```
613
614        Parameters
615        ----------
616        key: `collections.Callable[[Item], Any]`
617            The function to sort by.
618        reverse: `bool`
619            Whether to reverse the sort.
620        """
621        return Iter(sorted(self.raw_parts(), key=key, reverse=reverse))

Sorts the iterator.

Example
iterator = Iter([3, 1, 6, 7])
for item in iterator.sort(key=lambda item: item < 3):
    print(item)
# 1
# 3
# 6
# 7
Parameters
  • key (collections.Callable[[Item], Any]): The function to sort by.
  • reverse (bool): Whether to reverse the sort.
def reversed(self) -> Iter[~Item]:
623    def reversed(self) -> Iter[Item]:
624        """Returns a new iterator that yields the items in the iterator in reverse order.
625
626        This consumes this iterator into a sequence and return a new iterator containing all of the elements
627        in reversed order.
628
629        Example
630        -------
631        ```py
632        iterator = Iter([3, 1, 6, 7])
633        for item in iterator.reversed():
634            print(item)
635        # 7
636        # 6
637        # 1
638        # 3
639        ```
640        """
641        # NOTE: In order to reverse the iterator we need to
642        # first collect it into some collection.
643        return Iter(reversed(list(_ for _ in self)))

Returns a new iterator that yields the items in the iterator in reverse order.

This consumes this iterator into a sequence and return a new iterator containing all of the elements in reversed order.

Example
iterator = Iter([3, 1, 6, 7])
for item in iterator.reversed():
    print(item)
# 7
# 6
# 1
# 3
def union(self, other: Iterable[~Item]) -> Iter[~Item]:
645    def union(self, other: collections.Iterable[Item]) -> Iter[Item]:
646        """Returns a new iterator that yields all items from both iterators.
647
648        Example
649        -------
650        ```py
651        iterator = Iter([1, 2, 3])
652        other = [4, 5, 6]
653
654        for item in iterator.union(other):
655            print(item)
656        # 1
657        # 2
658        # 3
659        # 4
660        # 5
661        # 6
662        ```
663
664        Parameters
665        ----------
666        other: `Iter[Item]`
667            The iterable to union with.
668        """
669        return Iter(itertools.chain(self.raw_parts(), other))

Returns a new iterator that yields all items from both iterators.

Example
iterator = Iter([1, 2, 3])
other = [4, 5, 6]

for item in iterator.union(other):
    print(item)
# 1
# 2
# 3
# 4
# 5
# 6
Parameters
  • other (Iter[Item]): The iterable to union with.
def first(self) -> 'Option[Item]':
671    def first(self) -> Option[Item]:
672        """Returns the first item in the iterator.
673
674        Example
675        -------
676        ```py
677        iterator = Iter([3, 1, 6, 7])
678        iterator.first().is_some_and(lambda x: x == 3)
679        ```
680        """
681        return self.take(1).next()

Returns the first item in the iterator.

Example
iterator = Iter([3, 1, 6, 7])
iterator.first().is_some_and(lambda x: x == 3)
def last(self) -> 'Option[Item]':
683    def last(self) -> Option[Item]:
684        """Returns the last item in the iterator.
685
686        Example
687        -------
688        ```py
689        iterator = Iter([3, 1, 6, 7])
690        iterator.last().is_some_and(lambda x: x == 7)
691        ```
692        """
693        return self.reversed().first()

Returns the last item in the iterator.

Example
iterator = Iter([3, 1, 6, 7])
iterator.last().is_some_and(lambda x: x == 7)
def count(self) -> int:
695    def count(self) -> int:
696        """Return the count of elements in memory this iterator has.
697
698        Example
699        -------
700        ```py
701        it = Iter(range(3))
702        assert it.count() == 3
703        ```
704        """
705        count = 0
706        for _ in self:
707            count += 1
708
709        return count

Return the count of elements in memory this iterator has.

Example
it = Iter(range(3))
assert it.count() == 3
def find(self, predicate: Callable[[~Item], bool]) -> 'Option[Item]':
711    def find(self, predicate: collections.Callable[[Item], bool]) -> Option[Item]:
712        """Searches for an element of an iterator that satisfies a predicate.
713
714        If you want the position of the element, use `Iterator.position` instead.
715
716        `find()` takes a lambda that returns true or false. It applies this closure to each element of the iterator,
717        and if any of them return true, then find() returns `Some(element)`. If they all return false, it returns None.
718
719        Example
720        -------
721        ```py
722        it = Iter(range(10))
723        item = it.find(lambda num: num > 5)
724        print(item) # 6
725        ```
726        """
727        for item in self:
728            if predicate(item):
729                return _option.Some(item)
730
731        # no more items
732        return _option.NOTHING

Searches for an element of an iterator that satisfies a predicate.

If you want the position of the element, use Iterator.position instead.

find() takes a lambda that returns true or false. It applies this closure to each element of the iterator, and if any of them return true, then find() returns Some(element). If they all return false, it returns None.

Example
it = Iter(range(10))
item = it.find(lambda num: num > 5)
print(item) # 6
def position(self, predicate: Callable[[~Item], bool]) -> 'Option[int]':
734    def position(self, predicate: collections.Callable[[Item], bool]) -> Option[int]:
735        """Searches for the position of an element in the iterator that satisfies a predicate.
736
737        If you want the object itself, use `Iterator.find` instead.
738
739        `position()` takes a lambda that returns true or false. It applies this closure to each element of the iterator,
740        and if any of them return true, then position() returns `Some(position_of_element)`. If they all return false, it returns None.
741
742        Example
743        -------
744        ```py
745        it = Iter(range(10))
746        position = it.find(lambda num: num > 5)
747        assert position.unwrap() == 6
748        ```
749        """
750        for position, value in self.enumerate():
751            if predicate(value):
752                return _option.Some(position)
753
754        # no more items
755        return _option.NOTHING

Searches for the position of an element in the iterator that satisfies a predicate.

If you want the object itself, use Iterator.find instead.

position() takes a lambda that returns true or false. It applies this closure to each element of the iterator, and if any of them return true, then position() returns Some(position_of_element). If they all return false, it returns None.

Example
it = Iter(range(10))
position = it.find(lambda num: num > 5)
assert position.unwrap() == 6
def fold( self, init: ~OtherItem, f: Callable[[~OtherItem, ~Item], ~OtherItem]) -> ~OtherItem:
757    def fold(
758        self, init: OtherItem, f: collections.Callable[[OtherItem, Item], OtherItem]
759    ) -> OtherItem:
760        """Folds every element into an accumulator by applying an operation, returning the final result.
761
762        fold() takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element.
763        The closure returns the value that the accumulator should have for the next iteration.
764
765        The initial value is the value the accumulator will have on the first call.
766
767        After applying this closure to every element of the iterator, fold() returns the accumulator.
768
769        This operation is sometimes called ‘reduce’ or ‘inject’.
770
771        Example
772        -------
773        ```py
774        a = Iter([1, 2, 3, 4])
775        sum = a.fold(0, lambda acc, elem: acc + elem)
776        assert sum == 10
777        ```
778        """
779        accum = init
780        while True:
781            try:
782                x = self.__next__()
783                accum = f(accum, x)
784            except StopIteration:
785                break
786
787        return accum

Folds every element into an accumulator by applying an operation, returning the final result.

fold() takes two arguments: an initial value, and a closure with two arguments: an ‘accumulator’, and an element. The closure returns the value that the accumulator should have for the next iteration.

The initial value is the value the accumulator will have on the first call.

After applying this closure to every element of the iterator, fold() returns the accumulator.

This operation is sometimes called ‘reduce’ or ‘inject’.

Example
a = Iter([1, 2, 3, 4])
sum = a.fold(0, lambda acc, elem: acc + elem)
assert sum == 10
def advance_by(self, n: int) -> '_result.Result[None, int]':
789    def advance_by(self, n: int) -> _result.Result[None, int]:
790        """Advances the iterator by `n` elements.
791
792        Returns `Result[None, int]`, where `Ok(None)` means the iterator
793        advanced successfully, and `Err(int)` if `None` encountered, where `int`
794        represents the remaining number of steps that could not be advanced because the iterator ran out.
795
796        Example
797        -------
798        ```py
799        it = into_iter([1, 2, 3, 4])
800        assert it.advance_by(2).is_ok()
801        assert it.next() == Some(3)
802        assert it.advance_by(0).is_ok()
803        assert it.advance_by(100) == Err(99)
804        ```
805        """
806        for i in range(n):
807            try:
808                self.__next__()
809            except StopIteration:
810                return _result.Err(n - i)
811
812        return _result.Ok(None)

Advances the iterator by n elements.

Returns Result[None, int], where Ok(None) means the iterator advanced successfully, and Err(int) if None encountered, where int represents the remaining number of steps that could not be advanced because the iterator ran out.

Example
it = into_iter([1, 2, 3, 4])
assert it.advance_by(2).is_ok()
assert it.next() == Some(3)
assert it.advance_by(0).is_ok()
assert it.advance_by(100) == Err(99)
def nth(self, n: int) -> 'Option[Item]':
814    def nth(self, n: int) -> Option[Item]:
815        """Returns the `n`th element of the iterator
816
817        Just like normal indexing, the count `n` starts from zero, so `nth(0)` returns the first
818        value.
819
820        Note all elements before `n` will be skipped / consumed.
821
822        Example
823        -------
824        ```py
825        a = into_iter([1, 2, 3])
826        assert a.iter().nth(1) == Some(2)
827        ```
828        """
829        for _ in range(n):
830            try:
831                self.__next__()
832            except StopIteration:
833                return _option.NOTHING
834
835        return self.next()

Returns the nth element of the iterator

Just like normal indexing, the count n starts from zero, so nth(0) returns the first value.

Note all elements before n will be skipped / consumed.

Example
a = into_iter([1, 2, 3])
assert a.iter().nth(1) == Some(2)
def sum(self: 'Sum') -> int:
837    def sum(self: Sum) -> int:
838        """Sums an iterator of a possible type `T` that can be converted to an integer.
839
840        where `T` is a typeof (`int`, `float`, `str`, `ReadableBuffer`, `SupportsTrunc`, `SupportsIndex`).
841
842        Example
843        -------
844        ```py
845        numbers: Iterator[str] = Iter(["1", "2", "3"])
846        total = numbers.sum()
847        assert total == 6
848        ```
849        """
850        return sum(int(_) for _ in self)

Sums an iterator of a possible type T that can be converted to an integer.

where T is a typeof (int, float, str, ReadableBuffer, SupportsTrunc, SupportsIndex).

Example
numbers: Iterator[str] = Iter(["1", "2", "3"])
total = numbers.sum()
assert total == 6
def for_each(self, func: Callable[[~Item], typing.Any]) -> None:
852    def for_each(self, func: collections.Callable[[Item], typing.Any]) -> None:
853        """Calls `func` on each item in the iterator.
854
855        Example
856        -------
857        ```py
858        iterator = Iter([1, 2, 3])
859        iterator.for_each(lambda item: print(item))
860        # 1
861        # 2
862        # 3
863        ```
864
865        Parameters
866        ----------
867        func: `collections.Callable[[Item], typing.Any]`
868            The function to call on each item in the iterator.
869        """
870        for item in self:
871            func(item)

Calls func on each item in the iterator.

Example
iterator = Iter([1, 2, 3])
iterator.for_each(lambda item: print(item))
# 1
# 2
# 3
Parameters
  • func (collections.Callable[[Item], typing.Any]): The function to call on each item in the iterator.
async def async_for_each( self, func: Callable[[~Item], Coroutine[typing.Any, typing.Any, ~OtherItem]]) -> '_result.Result[collections.Sequence[OtherItem], futures.JoinError]':
873    async def async_for_each(
874        self,
875        func: collections.Callable[
876            [Item], collections.Coroutine[typing.Any, typing.Any, OtherItem]
877        ],
878    ) -> _result.Result[collections.Sequence[OtherItem], futures.JoinError]:
879        """Calls the async function on each item in the iterator *concurrently*.
880
881        Concurrently meaning that the next item will not wait for other items
882        to finish to execute, each item gets called in a separate task.
883
884        After all the tasks finish, a `Result[list[T], JoinError]` will be returned,
885        which will need to be handled by the caller.
886
887        Example
888        -------
889        ```py
890        async def create_user(username: str) -> None:
891            await aiohttp.request("POST", f'.../{username}')
892
893        async def main():
894            users = sain.into_iter(["Danny", "Flower"])
895            match await users.async_for_each(lambda username: create_user(username)):
896                case Ok(result):
897                    # all good
898                case Err(why):
899                    print(f"couldn't gather all futures, err={why}")
900        ```
901
902        Parameters
903        ----------
904        func: `collections.Callable[[Item], Coroutine[None, Any, Any]]`
905            The async function to call on each item in the iterator.
906        """
907        return await futures.join(*(func(item) for item in self))

Calls the async function on each item in the iterator concurrently.

Concurrently meaning that the next item will not wait for other items to finish to execute, each item gets called in a separate task.

After all the tasks finish, a Result[list[T], JoinError] will be returned, which will need to be handled by the caller.

Example
async def create_user(username: str) -> None:
    await aiohttp.request("POST", f'.../{username}')

async def main():
    users = sain.into_iter(["Danny", "Flower"])
    match await users.async_for_each(lambda username: create_user(username)):
        case Ok(result):
            # all good
        case Err(why):
            print(f"couldn't gather all futures, err={why}")
Parameters
  • func (collections.Callable[[Item], Coroutine[None, Any, Any]]): The async function to call on each item in the iterator.
@typing.final
class TrustedIter(typing.Generic[~Item], sain.iter.ExactSizeIterator[~Item]):
1070@typing.final
1071class TrustedIter(typing.Generic[Item], ExactSizeIterator[Item]):
1072    """Similar to `Iter`, but it reports an accurate length using `ExactSizeIterator`.
1073
1074    iterable objects such as `Vec`, `Bytes`, `list` and other `Sized` may be created
1075    using this iterator.
1076
1077    Example
1078    -------
1079    ```py
1080    # we know the size of the iterator.
1081    sized_buf: TrustedIter[int] = into_iter((1, 2, 3, 4))
1082    # this is `Iter[int]` since we don't know when the generator will stop yielding.
1083    unsized_buf: Iter[int] = into_iter((_ for _ in ([1, 2, 3, 4] if cond else [1, 2])))
1084    ```
1085
1086    Parameters
1087    ----------
1088    items: `collections.Collection[Item]`
1089        A sized collection of items to iterate over.
1090    """
1091
1092    __slots__ = ("_it", "_len", "__alive")
1093
1094    def __init__(self, iterable: collections.Sequence[Item]) -> None:
1095        self.__alive = iterable
1096        self._len = len(iterable)
1097        self._it = iter(iterable)
1098
1099    @property
1100    def __slice_checked_get(self) -> collections.Sequence[Item] | None:
1101        try:
1102            return self.__alive
1103        except AttributeError:
1104            return None
1105
1106    def next(self) -> Option[Item]:
1107        if self._len == 0:
1108            # ! SAFETY: len == 0
1109            return _option.NOTHING
1110
1111        return _option.Some(self.__next__())
1112
1113    @unsafe
1114    def next_unchecked(self) -> Item:
1115        """Returns the next item in the iterator without checking if it exists.
1116
1117        This is equivalent to calling `next()` on the iterator directly.
1118
1119        Example
1120        -------
1121        ```py
1122        iterator = Iter([1])
1123        assert iterator.next_unchecked() == 1
1124        iterator.next_unchecked() # raises StopIteration
1125        ```
1126        """
1127        return self.__next__()
1128
1129    @unsafe
1130    def set_len(self, new_len: int) -> None:
1131        """Sets the length of the iterator to `new_len`.
1132
1133        This is unsafe and should only be used if you know what you're doing.
1134
1135        Example
1136        -------
1137        ```py
1138        iterator = Iter([1, 2, 3])
1139        iterator.set_len(2)
1140        assert iterator.len() == 2
1141        ```
1142        """
1143        self._len = new_len
1144
1145    def as_slice(self) -> Slice[Item]:
1146        """Returns an immutable slice of all elements that have not been yielded
1147
1148        Example
1149        -------
1150        ```py
1151        iterator = into_iter([1, 2, 3])
1152        iterator.as_slice() == [1, 2, 3]
1153        iterator.next()
1154        assert iterator.as_slice() == [2, 3]
1155        ```
1156        """
1157        from .collections.slice import Slice
1158
1159        return Slice(self.__slice_checked_get or ())
1160
1161    def __repr__(self) -> str:
1162        # __alive is dropped from `self`.
1163        if (s := self.__slice_checked_get) is None:
1164            return "TrustedIter(<empty>)"
1165
1166        return f"TrustedIter({s[-self._len :]})"
1167
1168    def __next__(self) -> Item:
1169        try:
1170            i = next(self._it)
1171        except StopIteration:
1172            # don't reference this anymore.
1173            del self.__alive
1174            raise
1175
1176        self._len -= 1
1177        return i
1178
1179    def __getitem__(self, index: int) -> Item:
1180        if self._len == 0:
1181            raise IndexError("index out of bounds")
1182
1183        return self.skip(index).first().unwrap_or_else(oob)
1184
1185    def __contains__(self, item: Item) -> bool:
1186        return item in self._it
1187
1188    def __len__(self) -> int:
1189        return self._len

Similar to Iter, but it reports an accurate length using ExactSizeIterator.

iterable objects such as Vec, Bytes, list and other Sized may be created using this iterator.

Example
# we know the size of the iterator.
sized_buf: TrustedIter[int] = into_iter((1, 2, 3, 4))
# this is `Iter[int]` since we don't know when the generator will stop yielding.
unsized_buf: Iter[int] = into_iter((_ for _ in ([1, 2, 3, 4] if cond else [1, 2])))
Parameters
  • items (collections.Collection[Item]): A sized collection of items to iterate over.
TrustedIter(iterable: Sequence[~Item])
1094    def __init__(self, iterable: collections.Sequence[Item]) -> None:
1095        self.__alive = iterable
1096        self._len = len(iterable)
1097        self._it = iter(iterable)
def next(self) -> 'Option[Item]':
1106    def next(self) -> Option[Item]:
1107        if self._len == 0:
1108            # ! SAFETY: len == 0
1109            return _option.NOTHING
1110
1111        return _option.Some(self.__next__())

Advance the iterator, Returning the next item, Some(None) if all items yielded.

Example
iterator = Iter(["1", "2"])
assert iterator.next() == Some("1")
assert iterator.next() == Some("2")
assert iterator.next().is_none()
@unsafe
def next_unchecked(self) -> ~Item:
1113    @unsafe
1114    def next_unchecked(self) -> Item:
1115        """Returns the next item in the iterator without checking if it exists.
1116
1117        This is equivalent to calling `next()` on the iterator directly.
1118
1119        Example
1120        -------
1121        ```py
1122        iterator = Iter([1])
1123        assert iterator.next_unchecked() == 1
1124        iterator.next_unchecked() # raises StopIteration
1125        ```
1126        """
1127        return self.__next__()

Returns the next item in the iterator without checking if it exists.

This is equivalent to calling next() on the iterator directly.

Example
iterator = Iter([1])
assert iterator.next_unchecked() == 1
iterator.next_unchecked() # raises StopIteration

Safety ⚠️

Calling this method without knowing the output is considered undefined behavior.

@unsafe
def set_len(self, new_len: int) -> None:
1129    @unsafe
1130    def set_len(self, new_len: int) -> None:
1131        """Sets the length of the iterator to `new_len`.
1132
1133        This is unsafe and should only be used if you know what you're doing.
1134
1135        Example
1136        -------
1137        ```py
1138        iterator = Iter([1, 2, 3])
1139        iterator.set_len(2)
1140        assert iterator.len() == 2
1141        ```
1142        """
1143        self._len = new_len

Sets the length of the iterator to new_len.

This is unsafe and should only be used if you know what you're doing.

Example
iterator = Iter([1, 2, 3])
iterator.set_len(2)
assert iterator.len() == 2

Safety ⚠️

Calling this method without knowing the output is considered undefined behavior.

def as_slice(self) -> 'Slice[Item]':
1145    def as_slice(self) -> Slice[Item]:
1146        """Returns an immutable slice of all elements that have not been yielded
1147
1148        Example
1149        -------
1150        ```py
1151        iterator = into_iter([1, 2, 3])
1152        iterator.as_slice() == [1, 2, 3]
1153        iterator.next()
1154        assert iterator.as_slice() == [2, 3]
1155        ```
1156        """
1157        from .collections.slice import Slice
1158
1159        return Slice(self.__slice_checked_get or ())

Returns an immutable slice of all elements that have not been yielded

Example
iterator = into_iter([1, 2, 3])
iterator.as_slice() == [1, 2, 3]
iterator.next()
assert iterator.as_slice() == [2, 3]
@diagnostic
class Cloned(typing.Generic[~Item], sain.iter.Iterator[~Item]):
1192@diagnostic
1193class Cloned(typing.Generic[Item], Iterator[Item]):
1194    """An iterator that copies the elements from an underlying iterator.
1195
1196    This iterator is created by the `Iterator.cloned` method.
1197    """
1198
1199    __slots__ = ("_it",)
1200
1201    def __init__(self, it: Iterator[Item]) -> None:
1202        self._it = it
1203
1204    def __next__(self) -> Item:
1205        n = self._it.__next__()
1206
1207        # Avoid useless function call for a list.
1208        if isinstance(n, list):
1209            # SAFETY: We know this is a list.
1210            return n[:]  # pyright: ignore
1211
1212        return copy.copy(n)

An iterator that copies the elements from an underlying iterator.

This iterator is created by the Iterator.cloned method.

Cloned(it: Iterator[~Item])
1201    def __init__(self, it: Iterator[Item]) -> None:
1202        self._it = it
@diagnostic
class Copied(typing.Generic[~Item], sain.iter.Iterator[~Item]):
1215@diagnostic
1216class Copied(typing.Generic[Item], Iterator[Item]):
1217    """An iterator that deeply-copies the elements from an underlying iterator.
1218
1219    This iterator is created by the `Iterator.copied` method.
1220    """
1221
1222    __slots__ = ("_it",)
1223
1224    def __init__(self, it: Iterator[Item]) -> None:
1225        self._it = it
1226
1227    def __next__(self) -> Item:
1228        return copy.deepcopy(self._it.__next__())

An iterator that deeply-copies the elements from an underlying iterator.

This iterator is created by the Iterator.copied method.

Copied(it: Iterator[~Item])
1224    def __init__(self, it: Iterator[Item]) -> None:
1225        self._it = it
@diagnostic
class Take(typing.Generic[~Item], sain.iter.Iterator[~Item]):
1273@diagnostic
1274class Take(typing.Generic[Item], Iterator[Item]):
1275    """An iterator that yields the first `number` of elements and drops the rest.
1276
1277    This iterator is created by the `Iterator.take` method.
1278    """
1279
1280    __slots__ = ("_it", "_taken", "_count")
1281
1282    def __init__(self, it: Iterator[Item], count: int) -> None:
1283        if count <= 0:
1284            raise ValueError("`count` must be non-zero")
1285
1286        self._it = it
1287        self._taken = count
1288        self._count = 0
1289
1290    def __next__(self) -> Item:
1291        if self._count >= self._taken:
1292            unreachable()
1293
1294        item = self._it.__next__()
1295        self._count += 1
1296        return item

An iterator that yields the first number of elements and drops the rest.

This iterator is created by the Iterator.take method.

Take(it: Iterator[~Item], count: int)
1282    def __init__(self, it: Iterator[Item], count: int) -> None:
1283        if count <= 0:
1284            raise ValueError("`count` must be non-zero")
1285
1286        self._it = it
1287        self._taken = count
1288        self._count = 0
@diagnostic
class Filter(typing.Generic[~Item], sain.iter.Iterator[~Item]):
1250@diagnostic
1251class Filter(typing.Generic[Item], Iterator[Item]):
1252    """An iterator that filters the elements to a `predicate`.
1253
1254    This iterator is created by the `Iterator.filter` method.
1255    """
1256
1257    __slots__ = ("_it", "_call")
1258
1259    def __init__(
1260        self, it: Iterator[Item], call: collections.Callable[[Item], bool]
1261    ) -> None:
1262        self._it = it
1263        self._call = call
1264
1265    def __next__(self) -> Item:
1266        for item in self._it:
1267            if self._call(item):
1268                return item
1269
1270        unreachable()

An iterator that filters the elements to a predicate.

This iterator is created by the Iterator.filter method.

Filter(it: Iterator[~Item], call: Callable[[~Item], bool])
1259    def __init__(
1260        self, it: Iterator[Item], call: collections.Callable[[Item], bool]
1261    ) -> None:
1262        self._it = it
1263        self._call = call
@diagnostic
class Map(typing.Generic[~Item, ~OtherItem], sain.iter.Iterator[~OtherItem]):
1231@diagnostic
1232class Map(typing.Generic[Item, OtherItem], Iterator[OtherItem]):
1233    """An iterator that maps the elements to a callable.
1234
1235    This iterator is created by the `Iterator.map` method.
1236    """
1237
1238    __slots__ = ("_it", "_call")
1239
1240    def __init__(
1241        self, it: Iterator[Item], call: collections.Callable[[Item], OtherItem]
1242    ) -> None:
1243        self._it = it
1244        self._call = call
1245
1246    def __next__(self) -> OtherItem:
1247        return self._call(self._it.__next__())

An iterator that maps the elements to a callable.

This iterator is created by the Iterator.map method.

Map(it: Iterator[~Item], call: Callable[[~Item], ~OtherItem])
1240    def __init__(
1241        self, it: Iterator[Item], call: collections.Callable[[Item], OtherItem]
1242    ) -> None:
1243        self._it = it
1244        self._call = call
@diagnostic
class Skip(typing.Generic[~Item], sain.iter.Iterator[~Item]):
1299@diagnostic
1300class Skip(typing.Generic[Item], Iterator[Item]):
1301    """An iterator that skips the first `number` of elements and yields the rest.
1302
1303    This iterator is created by the `Iterator.skip` method.
1304    """
1305
1306    __slots__ = ("_it", "_count", "_skipped")
1307
1308    def __init__(self, it: Iterator[Item], count: int) -> None:
1309        if count <= 0:
1310            raise ValueError("`count` must be non-zero")
1311
1312        self._it = it
1313        self._count = count
1314        self._skipped = 0
1315
1316    def __next__(self) -> Item:
1317        while self._skipped < self._count:
1318            self._skipped += 1
1319            self._it.__next__()
1320
1321        return self._it.__next__()

An iterator that skips the first number of elements and yields the rest.

This iterator is created by the Iterator.skip method.

Skip(it: Iterator[~Item], count: int)
1308    def __init__(self, it: Iterator[Item], count: int) -> None:
1309        if count <= 0:
1310            raise ValueError("`count` must be non-zero")
1311
1312        self._it = it
1313        self._count = count
1314        self._skipped = 0
@diagnostic
class Enumerate(typing.Generic[~Item], sain.iter.Iterator[tuple[int, ~Item]]):
1324@diagnostic
1325class Enumerate(typing.Generic[Item], Iterator[tuple[int, Item]]):
1326    """An iterator that yields the current count and the element during iteration.
1327
1328    This iterator is created by the `Iterator.enumerate` method.
1329    """
1330
1331    __slots__ = ("_it", "_count")
1332
1333    def __init__(self, it: Iterator[Item], start: int) -> None:
1334        self._it = it
1335        self._count = start
1336
1337    def __next__(self) -> tuple[int, Item]:
1338        a = self._it.__next__()
1339        i = self._count
1340        self._count += 1
1341        return i, a

An iterator that yields the current count and the element during iteration.

This iterator is created by the Iterator.enumerate method.

Enumerate(it: Iterator[~Item], start: int)
1333    def __init__(self, it: Iterator[Item], start: int) -> None:
1334        self._it = it
1335        self._count = start
@diagnostic
class TakeWhile(typing.Generic[~Item], sain.iter.Iterator[~Item]):
1344@diagnostic
1345class TakeWhile(typing.Generic[Item], Iterator[Item]):
1346    """An iterator that yields elements while `predicate` returns `True`.
1347
1348    This iterator is created by the `Iterator.take_while` method.
1349    """
1350
1351    __slots__ = ("_it", "_predicate")
1352
1353    def __init__(
1354        self, it: Iterator[Item], predicate: collections.Callable[[Item], bool]
1355    ) -> None:
1356        self._it = it
1357        self._predicate = predicate
1358
1359    def __next__(self) -> Item:
1360        item = self._it.__next__()
1361
1362        if self._predicate(item):
1363            return item
1364
1365        unreachable()

An iterator that yields elements while predicate returns True.

This iterator is created by the Iterator.take_while method.

TakeWhile(it: Iterator[~Item], predicate: Callable[[~Item], bool])
1353    def __init__(
1354        self, it: Iterator[Item], predicate: collections.Callable[[Item], bool]
1355    ) -> None:
1356        self._it = it
1357        self._predicate = predicate
@diagnostic
class DropWhile(typing.Generic[~Item], sain.iter.Iterator[~Item]):
1368@diagnostic
1369class DropWhile(typing.Generic[Item], Iterator[Item]):
1370    """An iterator that yields elements while `predicate` returns `False`.
1371
1372    This iterator is created by the `Iterator.drop_while` method.
1373    """
1374
1375    __slots__ = ("_it", "_predicate", "_dropped")
1376
1377    def __init__(
1378        self, it: Iterator[Item], predicate: collections.Callable[[Item], bool]
1379    ) -> None:
1380        self._it = it
1381        self._predicate = predicate
1382        self._dropped = False
1383
1384    def __next__(self) -> Item:
1385        if not self._dropped:
1386            while not self._predicate(item := self._it.__next__()):
1387                pass
1388
1389            self._dropped = True
1390            return item
1391
1392        unreachable()

An iterator that yields elements while predicate returns False.

This iterator is created by the Iterator.drop_while method.

DropWhile(it: Iterator[~Item], predicate: Callable[[~Item], bool])
1377    def __init__(
1378        self, it: Iterator[Item], predicate: collections.Callable[[Item], bool]
1379    ) -> None:
1380        self._it = it
1381        self._predicate = predicate
1382        self._dropped = False
@diagnostic
class Chunks(typing.Generic[~Item], sain.iter.Iterator[collections.abc.Sequence[~Item]]):
1395@diagnostic
1396class Chunks(typing.Generic[Item], Iterator[collections.Sequence[Item]]):
1397    """An iterator that yields elements in chunks.
1398
1399    This iterator is created by the `Iterator.chunks` method.
1400    """
1401
1402    __slots__ = ("chunk_size", "_it")
1403
1404    def __init__(self, it: Iterator[Item], chunk_size: int) -> None:
1405        self.chunk_size = chunk_size
1406        self._it = it
1407
1408    def __next__(self) -> collections.Sequence[Item]:
1409        chunk: list[Item] = []
1410
1411        for item in self._it:
1412            chunk.append(item)
1413
1414            if len(chunk) == self.chunk_size:
1415                break
1416
1417        if chunk:
1418            return chunk
1419
1420        unreachable()

An iterator that yields elements in chunks.

This iterator is created by the Iterator.chunks method.

Chunks(it: Iterator[~Item], chunk_size: int)
1404    def __init__(self, it: Iterator[Item], chunk_size: int) -> None:
1405        self.chunk_size = chunk_size
1406        self._it = it
chunk_size
@typing.final
@diagnostic
class Empty(typing.Generic[~Item], sain.iter.ExactSizeIterator[~Item]):
1423@typing.final
1424@diagnostic
1425class Empty(typing.Generic[Item], ExactSizeIterator[Item]):
1426    """An iterator that yields nothing.
1427
1428    This is the default iterator that is created by `Iterator.default()` or `empty()`
1429    """
1430
1431    __slots__ = ()
1432
1433    def __init__(self) -> None:
1434        pass
1435
1436    def next(self) -> Option[Item]:
1437        # SAFETY: an empty iterator always returns None.
1438        # also we avoid calling `nothing_unchecked()` here for fast returns.
1439        return _option.NOTHING
1440
1441    def __len__(self) -> typing.Literal[0]:
1442        return 0
1443
1444    def any(
1445        self, predicate: collections.Callable[[Item], bool]
1446    ) -> typing.Literal[False]:
1447        return False
1448
1449    def all(
1450        self, predicate: collections.Callable[[Item], bool]
1451    ) -> typing.Literal[False]:
1452        return False
1453
1454    def __next__(self) -> Item:
1455        unreachable()

An iterator that yields nothing.

This is the default iterator that is created by Iterator.default() or empty()

def next(self) -> 'Option[Item]':
1436    def next(self) -> Option[Item]:
1437        # SAFETY: an empty iterator always returns None.
1438        # also we avoid calling `nothing_unchecked()` here for fast returns.
1439        return _option.NOTHING

Advance the iterator, Returning the next item, Some(None) if all items yielded.

Example
iterator = Iter(["1", "2"])
assert iterator.next() == Some("1")
assert iterator.next() == Some("2")
assert iterator.next().is_none()
def any(self, predicate: Callable[[~Item], bool]) -> Literal[False]:
1444    def any(
1445        self, predicate: collections.Callable[[Item], bool]
1446    ) -> typing.Literal[False]:
1447        return False

True if any items in the iterator match the predicate.

Example
iterator = Iter([1, 2, 3])
if iterator.any(lambda item: isinstance(item, int)):
    print("At least one item is an int.")
# At least one item is an int.
Parameters
  • predicate (collections.Callable[[Item], bool]): The function to test each item in the iterator.
def all(self, predicate: Callable[[~Item], bool]) -> Literal[False]:
1449    def all(
1450        self, predicate: collections.Callable[[Item], bool]
1451    ) -> typing.Literal[False]:
1452        return False

Return True if all items in the iterator match the predicate.

Example
iterator = Iter([1, 2, 3])
while iterator.all(lambda item: isinstance(item, int)):
    print("Still all integers")
    continue
    # Still all integers
Parameters
  • predicate (collections.Callable[[Item], bool]): The function to test each item in the iterator.
@typing.final
@diagnostic
class Repeat(typing.Generic[~Item], sain.iter.ExactSizeIterator[~Item]):
1458@typing.final
1459@diagnostic
1460class Repeat(typing.Generic[Item], ExactSizeIterator[Item]):
1461    """An iterator that repeats a given value an exact number of times.
1462
1463    This iterator is created by calling `repeat()`.
1464    """
1465
1466    __slots__ = ("_count", "_element")
1467
1468    def __init__(self, element: Item, count: int) -> None:
1469        self._count = count
1470        self._element = element
1471
1472    def __next__(self) -> Item:
1473        if self._count > 0:
1474            self._count -= 1
1475            if self._count == 0:
1476                # Return the origin element last
1477                return self._element
1478
1479            return copy.copy(self._element)
1480
1481        unreachable()
1482
1483    def __len__(self) -> int:
1484        return self._count

An iterator that repeats a given value an exact number of times.

This iterator is created by calling repeat().

Repeat(element: ~Item, count: int)
1468    def __init__(self, element: Item, count: int) -> None:
1469        self._count = count
1470        self._element = element
@typing.final
@diagnostic
class Once(typing.Generic[~Item], sain.iter.ExactSizeIterator[~Item]):
1487@typing.final
1488@diagnostic
1489class Once(typing.Generic[Item], ExactSizeIterator[Item]):
1490    """An iterator that yields exactly one item.
1491
1492    This iterator is created by calling `once()`.
1493    """
1494
1495    __slots__ = ("_item",)
1496
1497    def __init__(self, item: Item) -> None:
1498        self._item: Item | None = item
1499
1500    def __next__(self) -> Item:
1501        if self._item is None:
1502            unreachable()
1503
1504        i = self._item
1505        self._item = None
1506        return i
1507
1508    def __len__(self) -> int:
1509        return 1 if self._item is not None else 0

An iterator that yields exactly one item.

This iterator is created by calling once().

Once(item: ~Item)
1497    def __init__(self, item: Item) -> None:
1498        self._item: Item | None = item
class ExactSizeIterator(typing.Generic[~Item], sain.iter.Iterator[~Item], abc.ABC):
930class ExactSizeIterator(typing.Generic[Item], Iterator[Item], abc.ABC):
931    """An iterator that knows its exact size.
932
933    The implementations of this interface indicates that the iterator knows exactly
934    how many items it can yield.
935
936    however, this is not a requirement for the iterator to implement this trait, as its
937    only used for iterators that can know their size.
938
939    Example
940    -------
941    ```py
942    @dataclass
943    class Letters(ExactSizeIterator[str]):
944        letters: list[str]
945
946        def __next__(self) -> str:
947            return self.letters.pop(0)
948
949        def __len__(self) -> int:
950            return len(self.letters)
951
952    letters = Letters(['a', 'b', 'c'])
953    assert letters.count() == 3
954    assert letters.next() == Some('a')
955    assert letters.count() == 2
956    ```
957    """
958
959    __slots__ = ()
960
961    @typing.final
962    def count(self) -> int:
963        return len(self)
964
965    @typing.final
966    def len(self) -> int:
967        """Returns the remaining number of items in the iterator.
968
969        This doesn't exhaust the iterator.
970
971        Example
972        -------
973        ```py
974        it = once(0)
975        assert it.len() == 1
976        assert it.len() == 1
977        it.next()
978        assert it.len() == 0
979        ```
980        """
981        return len(self)
982
983    @typing.final
984    def is_empty(self) -> bool:
985        """Return `True` if this iterator has no items left to yield.
986
987        Example
988        -------
989        ```py
990        iterator = once(1)
991        assert not iterator.is_empty()
992        assert once.next() == Some(1)
993        assert iterator.is_empty()
994        ```
995        """
996        return len(self) == 0
997
998    @abc.abstractmethod
999    def __len__(self) -> int: ...

An iterator that knows its exact size.

The implementations of this interface indicates that the iterator knows exactly how many items it can yield.

however, this is not a requirement for the iterator to implement this trait, as its only used for iterators that can know their size.

Example
@dataclass
class Letters(ExactSizeIterator[str]):
    letters: list[str]

    def __next__(self) -> str:
        return self.letters.pop(0)

    def __len__(self) -> int:
        return len(self.letters)

letters = Letters(['a', 'b', 'c'])
assert letters.count() == 3
assert letters.next() == Some('a')
assert letters.count() == 2
@typing.final
def count(self) -> int:
961    @typing.final
962    def count(self) -> int:
963        return len(self)

Return the count of elements in memory this iterator has.

Example
it = Iter(range(3))
assert it.count() == 3
@typing.final
def len(self) -> int:
965    @typing.final
966    def len(self) -> int:
967        """Returns the remaining number of items in the iterator.
968
969        This doesn't exhaust the iterator.
970
971        Example
972        -------
973        ```py
974        it = once(0)
975        assert it.len() == 1
976        assert it.len() == 1
977        it.next()
978        assert it.len() == 0
979        ```
980        """
981        return len(self)

Returns the remaining number of items in the iterator.

This doesn't exhaust the iterator.

Example
it = once(0)
assert it.len() == 1
assert it.len() == 1
it.next()
assert it.len() == 0
@typing.final
def is_empty(self) -> bool:
983    @typing.final
984    def is_empty(self) -> bool:
985        """Return `True` if this iterator has no items left to yield.
986
987        Example
988        -------
989        ```py
990        iterator = once(1)
991        assert not iterator.is_empty()
992        assert once.next() == Some(1)
993        assert iterator.is_empty()
994        ```
995        """
996        return len(self) == 0

Return True if this iterator has no items left to yield.

Example
iterator = once(1)
assert not iterator.is_empty()
assert once.next() == Some(1)
assert iterator.is_empty()
@rustc_diagnostic_item('into_iter')
def into_iter( iterable: Sequence[~Item] | Iterable[~Item]) -> Union[Iter[~Item], TrustedIter[~Item], TrustedIter[int]]:
1580@rustc_diagnostic_item("into_iter")
1581def into_iter(
1582    iterable: collections.Sequence[Item] | collections.Iterable[Item],
1583) -> Iter[Item] | TrustedIter[Item] | TrustedIter[int]:
1584    """Convert any iterable into `Iterator[Item]`.
1585
1586    if the size of the iterable is known, it will return `TrustedIter`,
1587    otherwise it will return `Iter`.
1588
1589    Example
1590    -------
1591    ```py
1592    sequence = [1,2,3]
1593    for item in sain.into_iter(sequence).reversed():
1594        print(item)
1595    # 3
1596    # 2
1597    # 1
1598    ```
1599    """
1600    if isinstance(iterable, collections.Sequence):
1601        return TrustedIter(iterable)
1602    return Iter(iterable)

Convert any iterable into Iterator[Item].

if the size of the iterable is known, it will return TrustedIter, otherwise it will return Iter.

Example
sequence = [1,2,3]
for item in sain.into_iter(sequence).reversed():
    print(item)
# 3
# 2
# 1

Implementations

This function implements .into_iter">into_iter in Rust.

@rustc_diagnostic_item('empty')
def empty() -> Empty[~Item]:
1513@rustc_diagnostic_item("empty")
1514def empty() -> Empty[Item]:  # pyright: ignore
1515    """Create an iterator that yields nothing.
1516
1517    Example
1518    -------
1519    ```py
1520    nope: Iterator[int] = sain.iter.empty()
1521    assert nope.next().is_none()
1522    ```
1523    """
1524    return Empty()

Create an iterator that yields nothing.

Example
nope: Iterator[int] = sain.iter.empty()
assert nope.next().is_none()

Implementations

This function implements empty in Rust.

@rustc_diagnostic_item('once')
def once(item: ~Item) -> Once[~Item]:
1552@rustc_diagnostic_item("once")
1553def once(item: Item) -> Once[Item]:
1554    """Returns an iterator that yields exactly a single item.
1555
1556    Example
1557    -------
1558    ```py
1559    iterator = sain.iter.once(1)
1560    assert iterator.next() == Some(1)
1561    assert iterator.next() == Some(None)
1562    ```
1563    """
1564    return Once(item)

Returns an iterator that yields exactly a single item.

Example
iterator = sain.iter.once(1)
assert iterator.next() == Some(1)
assert iterator.next() == Some(None)

Implementations

This function implements once in Rust.

@rustc_diagnostic_item('repeat')
def repeat(element: ~Item, count: int) -> Repeat[~Item]:
1527@rustc_diagnostic_item("repeat")
1528def repeat(element: Item, count: int) -> Repeat[Item]:
1529    """Returns an iterator that yields the exact same `element` number of `count` times.
1530
1531    The yielded elements is a copy of `element`, but the last element is guaranteed to be the same as the
1532    original `element`.
1533
1534    Example
1535    -------
1536    ```py
1537    nums = [1, 2, 3]
1538    it = sain.iter.repeat(nums, 5)
1539    for i in range(4):
1540        cloned = it.next().unwrap()
1541        assert cloned == [1, 2, 3]
1542
1543    # But the last item is the origin one...
1544    last = it.next().unwrap()
1545    last.append(4)
1546    assert nums == [1, 2, 3, 4]
1547    ```
1548    """
1549    return Repeat(element, count)

Returns an iterator that yields the exact same element number of count times.

The yielded elements is a copy of element, but the last element is guaranteed to be the same as the original element.

Example
nums = [1, 2, 3]
it = sain.iter.repeat(nums, 5)
for i in range(4):
    cloned = it.next().unwrap()
    assert cloned == [1, 2, 3]

# But the last item is the origin one...
last = it.next().unwrap()
last.append(4)
assert nums == [1, 2, 3, 4]

Implementations

This function implements repeat in Rust.