From 4b9557722e90584f6501047ac85fea8cc68a3187 Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Sat, 10 Dec 2022 01:42:21 +0000 Subject: [PATCH 01/11] checkpoint --- boltons/iterutils.py | 94 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/boltons/iterutils.py b/boltons/iterutils.py index 3707566d..5c628b1e 100644 --- a/boltons/iterutils.py +++ b/boltons/iterutils.py @@ -41,12 +41,16 @@ following are based on examples in itertools docs. """ +from __future__ import annotations +import dataclasses import os import math import time import codecs import random import itertools +import operator +from typing import Iterable try: from collections.abc import Mapping, Sequence, Set, ItemsView, Iterable @@ -1510,3 +1514,93 @@ def __lt__(self, other): $ python -m timeit -s "x = [1]" "try: x.split('.') \nexcept AttributeError: pass" 1000000 loops, best of 3: 0.544 usec per loop """ + + + +def _iter_or_num(thing: Union[Iterable[float], float]) -> Iterator[float]: + if isinstance(thing, (int, float)): + return itertools.repeat(thing) + return iter(thing) + + +@dataclasses.dataclass +class NumIterator: + _original: Iterable[float] + + @classmethod + def count(cls): + return cls(itertools.count()) + + @classmethod + def fib(cls): + def inner_fib(): + a, b = 0, 1 + yield a + while True: + yield b + a, b = b, a+b + return cls(inner_fib()) + + @classmethod + def constant(cls, num): + return cls(itertools.repeat(num)) + + def __iter__(self) -> Iterator[float]: + return iter(self._original) + + def apply_operator(self, op: Callable[[float, float], float], other: Union[Iterable[float], float]) -> NumIterator: + return NumIterator(map(op, self._original, _iter_or_num(other))) + + def apply_r_operator(self, op: Callable[[float, float], float], other: Union[Iterable[float], float]) -> NumIterator: + return NumIterator(map(op, _iter_or_num(other), self._original)) + + def __getitem__(self, a_slice: slice)-> NumIterator: + if not isinstance(a_slice, slice): + raise TypeError("no random access, can only slice", a_slice) + new_original = itertools.islice(self._original, a_slice.start, a_slice.stop, a_slice.step) + return NumIterator(new_original) + + def __add__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_operator(operator.add, other) + + def __radd__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_r_operator(operator.add, other) + + def __mul__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_operator(operator.mul, other) + + def __rmul__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_r_operator(operator.mul, other) + + def __floordiv__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_operator(operator.floordiv, other) + + def __rfloordiv__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_r_operator(operator.floordiv, other) + + def __truediv__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_operator(operator.truediv, other) + + def __rtruediv__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_r_operator(operator.truediv, other) + + def __sub__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_operator(operator.sub, other) + + def __rsub__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_r_operator(operator.sub, other) + + def __pow__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_operator(operator.pow, other) + + def __rpow__(self, other: Union[Iterable[float], float]) -> NumIterator: + return self.apply_r_operator(operator.pow, other) + + def __neg__(self, *args) -> NumIterator: + return NumIterator(map(operator.neg, self)) + + def __pos__(self, *args) -> NumIterator: + return NumIterator(map(operator.pos, self)) + + def __round__(self, *args) -> NumIterator: + return NumIterator((round(item, *args) for item in self)) From 17ae1b88ffcc36a4d1280688ab7500d21e066644 Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:08:52 +0000 Subject: [PATCH 02/11] checkpoint --- tests/test_iterutils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_iterutils.py b/tests/test_iterutils.py index 0896f38a..2ade9549 100644 --- a/tests/test_iterutils.py +++ b/tests/test_iterutils.py @@ -9,7 +9,9 @@ research, default_enter, default_exit, - get_path) + get_path, + NumIterator, + ) from boltons.namedutils import namedtuple CUR_PATH = os.path.abspath(__file__) @@ -535,3 +537,9 @@ def test_strip(): assert strip([0,0,0,1,0,2,0,3,0,0,0],0) == [1,0,2,0,3] assert strip([]) == [] +class TestNumIterator: + def test_constant(self): + assert list(NumIterator.constant(5)[:3]) == [5, 5, 5] + + def test_fib(self): + assert list(NumIterator.fib()[:4]) == [0, 1, 1, 2] From c425049c7df73d782a55462bb09cad4d353f812b Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:09:21 +0000 Subject: [PATCH 03/11] checkpoint --- tests/test_iterutils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_iterutils.py b/tests/test_iterutils.py index 2ade9549..98ca2c8c 100644 --- a/tests/test_iterutils.py +++ b/tests/test_iterutils.py @@ -543,3 +543,6 @@ def test_constant(self): def test_fib(self): assert list(NumIterator.fib()[:4]) == [0, 1, 1, 2] + + def test_count(self): + assert list(NumIterator.count()[:4]) == [0, 1, 2, 3] From b10644693e7c30ddbed6922fed634fedd05ed104 Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:12:17 +0000 Subject: [PATCH 04/11] checkpoint --- tests/test_iterutils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_iterutils.py b/tests/test_iterutils.py index 98ca2c8c..8e03c1ec 100644 --- a/tests/test_iterutils.py +++ b/tests/test_iterutils.py @@ -546,3 +546,24 @@ def test_fib(self): def test_count(self): assert list(NumIterator.count()[:4]) == [0, 1, 2, 3] + + def test_slice(self): + assert list(NumIterator.count()[1:4:2]) == [1, 3] + + def test_add(self): + assert list(NumIterator.count()[:3] + 1) == [1, 2, 3] + + def test_radd(self): + assert list(1 + NumIterator.count()[:3]) == [1, 2, 3] + + def test_mul(self): + assert list(NumIterator.count()[:3] * 2) == [0, 2, 4] + + def test_rmul(self): + assert list(2 * NumIterator.count()[:3]) == [0, 2, 4] + + def test_pow(self): + assert list(NumIterator.count()[:3] ** 2) == [0, 1, 4] + + def test_rpow(self): + assert list(2 ** NumIterator.count()[:3]) == [1, 2, 4] From d2ce2c16c843a44f8b5b55c407b70e96ed9f54a4 Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:16:31 +0000 Subject: [PATCH 05/11] checkpoint --- tests/test_iterutils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_iterutils.py b/tests/test_iterutils.py index 8e03c1ec..b7a1316d 100644 --- a/tests/test_iterutils.py +++ b/tests/test_iterutils.py @@ -567,3 +567,15 @@ def test_pow(self): def test_rpow(self): assert list(2 ** NumIterator.count()[:3]) == [1, 2, 4] + + def test_div(self): + assert list(NumIterator.count()[:2] / 2) == [0.0, 0.5] + + def test_floordiv(self): + assert list(NumIterator.count()[:3] // 2) == [0, 0, 1] + + def test_rdiv(self): + assert list(1 / NumIterator.count()[1:3]) == [1.0, 0.5] + + def test_rfloordiv(self): + assert list(2 // NumIterator.count()[1:5]) == [2, 1, 0, 0] From 3351ca3859c8b4bca0025f927323a52aa6c8b263 Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:17:21 +0000 Subject: [PATCH 06/11] checkpoint --- tests/test_iterutils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_iterutils.py b/tests/test_iterutils.py index b7a1316d..a1464f02 100644 --- a/tests/test_iterutils.py +++ b/tests/test_iterutils.py @@ -556,6 +556,12 @@ def test_add(self): def test_radd(self): assert list(1 + NumIterator.count()[:3]) == [1, 2, 3] + def test_sub(self): + assert list(NumIterator.count()[:3] - 1) == [-1, 0, 1] + + def test_rsub(self): + assert list(2 - NumIterator.count()[:3]) == [2, 1, 0] + def test_mul(self): assert list(NumIterator.count()[:3] * 2) == [0, 2, 4] From a21c2de8ec9562b9bb8c8cfb84464c82e259832c Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:20:10 +0000 Subject: [PATCH 07/11] checkpoint --- tests/test_iterutils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_iterutils.py b/tests/test_iterutils.py index a1464f02..782b8d9a 100644 --- a/tests/test_iterutils.py +++ b/tests/test_iterutils.py @@ -585,3 +585,12 @@ def test_rdiv(self): def test_rfloordiv(self): assert list(2 // NumIterator.count()[1:5]) == [2, 1, 0, 0] + + def test_neg(self): + assert list(-NumIterator.count()[:3]) == [0, -1, -2] + + def test_pos(self): + assert list(+NumIterator.count()[:3]) == [0, +1, +2] + + def test_pos(self): + assert list(round(NumIterator.count()[:4] / 3)) == [0, 0, 1, 1] From ff1207c33436b49418454ccea7eb759050b3b0c3 Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:21:14 +0000 Subject: [PATCH 08/11] checkpoint --- tests/test_iterutils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_iterutils.py b/tests/test_iterutils.py index 782b8d9a..de2f8b11 100644 --- a/tests/test_iterutils.py +++ b/tests/test_iterutils.py @@ -550,6 +550,10 @@ def test_count(self): def test_slice(self): assert list(NumIterator.count()[1:4:2]) == [1, 3] + def test_no_access(self): + with pytest.raises(TypeError): + NumIterator.count()[5] + def test_add(self): assert list(NumIterator.count()[:3] + 1) == [1, 2, 3] From 8bfd6727291418881c3c2e2e15d242998a9297c3 Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:23:34 +0000 Subject: [PATCH 09/11] checkpoint --- boltons/iterutils.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/boltons/iterutils.py b/boltons/iterutils.py index 5c628b1e..aea6547d 100644 --- a/boltons/iterutils.py +++ b/boltons/iterutils.py @@ -1516,7 +1516,6 @@ def __lt__(self, other): """ - def _iter_or_num(thing: Union[Iterable[float], float]) -> Iterator[float]: if isinstance(thing, (int, float)): return itertools.repeat(thing) @@ -1526,7 +1525,7 @@ def _iter_or_num(thing: Union[Iterable[float], float]) -> Iterator[float]: @dataclasses.dataclass class NumIterator: _original: Iterable[float] - + @classmethod def count(cls): return cls(itertools.count()) @@ -1540,24 +1539,32 @@ def inner_fib(): yield b a, b = b, a+b return cls(inner_fib()) - + @classmethod def constant(cls, num): return cls(itertools.repeat(num)) - + def __iter__(self) -> Iterator[float]: return iter(self._original) - + def apply_operator(self, op: Callable[[float, float], float], other: Union[Iterable[float], float]) -> NumIterator: return NumIterator(map(op, self._original, _iter_or_num(other))) def apply_r_operator(self, op: Callable[[float, float], float], other: Union[Iterable[float], float]) -> NumIterator: return NumIterator(map(op, _iter_or_num(other), self._original)) - + def __getitem__(self, a_slice: slice)-> NumIterator: if not isinstance(a_slice, slice): - raise TypeError("no random access, can only slice", a_slice) - new_original = itertools.islice(self._original, a_slice.start, a_slice.stop, a_slice.step) + raise TypeError( + "no random access, can only slice", + a_slice, + ) + new_original = itertools.islice( + self._original, + a_slice.start, + a_slice.stop, + a_slice.step, + ) return NumIterator(new_original) def __add__(self, other: Union[Iterable[float], float]) -> NumIterator: @@ -1601,6 +1608,6 @@ def __neg__(self, *args) -> NumIterator: def __pos__(self, *args) -> NumIterator: return NumIterator(map(operator.pos, self)) - + def __round__(self, *args) -> NumIterator: return NumIterator((round(item, *args) for item in self)) From b508d1c79a20fcbf8b9eedd3352d3ac2b407737a Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:37:00 +0000 Subject: [PATCH 10/11] checkpoint --- boltons/iterutils.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/boltons/iterutils.py b/boltons/iterutils.py index aea6547d..a8d52c3c 100644 --- a/boltons/iterutils.py +++ b/boltons/iterutils.py @@ -1524,6 +1524,51 @@ def _iter_or_num(thing: Union[Iterable[float], float]) -> Iterator[float]: @dataclasses.dataclass class NumIterator: + + """An iterator of numbers. + + Supports math operations and slicing. + + >>> list(NumIterator(range(3))) + [0, 1, 2] + >>> list(NumIterator(range(3)) + 1) + [1, 2, 3] + >>> list(NumIterator(range(3)) * 2) + [0, 2, 4] + >>> list(NumIterator(x + 1 for x in range(10))[:3]) + [1, 2, 3] + + There are also a few helper class methods to generate + common iterators. + They all generate *infinite* iterators. + Convert them to finite iterators using slicing. + + Counting: + + >>> list(NumIterator.count()[:5]) + [0, 1, 2, 3, 4] + + Constant: + + >>> list(NumIterator.constant(7)[:2]) + [7, 7] + + Fibonacci sequence: + + >>> list(NumIterator.fib()[:8]) + [0, 1, 1, 2, 3, 5, 8, 13] + + Since the iterators support math operations, + you can combine them to generate more sophisticated + sequences. + + For example, + + >>> fib_pow_of_2 = 2 ** NumIterator.count() + NumIterator.fib() + >>> list(fib_pow_of_2[:5]) + [1, 3, 5, 10, 19] + """ + _original: Iterable[float] @classmethod From b84106d0a648070ef82140f88d8be8941aff91ce Mon Sep 17 00:00:00 2001 From: Moshe Zadka Date: Mon, 12 Dec 2022 00:38:37 +0000 Subject: [PATCH 11/11] checkpoint --- docs/iterutils.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/iterutils.rst b/docs/iterutils.rst index 1cb5c759..0e22e1ab 100644 --- a/docs/iterutils.rst +++ b/docs/iterutils.rst @@ -115,3 +115,12 @@ In the same vein as the feature-checking builtin, :func:`callable`. .. autofunction:: is_iterable .. autofunction:: is_scalar .. autofunction:: is_collection + +Numeric Iterators +----------------- + +A class to wrap iterators producing numbers +to allow it to support arithmetic operations +and slicing. + +.. autoclass:: NumIterator \ No newline at end of file