Add more_itertools module with `take` and `chunked`

This commit is contained in:
Zsolt Dollenstein 2020-03-26 22:21:50 +00:00
parent 4ee12ea16f
commit bfc15b2ee6
No known key found for this signature in database
GPG Key ID: 09F75E762C27AAD9
5 changed files with 109 additions and 3 deletions

View File

@ -72,8 +72,8 @@ async for value in islice(generator1(), 2, None, 2):
```
See [builtins.py][] and [itertools.py][] for full documentation
of functions and abilities.
See [builtins.py][], [itertools.py][], and [more_itertools.py][] for full
documentation of functions and abilities.
License

View File

@ -340,7 +340,7 @@ async def islice(itr: AnyIterable[T], *args: Optional[int]) -> AsyncIterator[T]:
if not args:
raise ValueError("must pass stop index")
if len(args) == 1:
stop, = args
(stop,) = args
elif len(args) == 2:
start, stop = args # type: ignore
elif len(args) == 3:

View File

@ -0,0 +1,46 @@
# Copyright 2020 John Reese
# Licensed under the MIT license
from typing import AsyncIterable, List, TypeVar
from .builtins import iter
from .itertools import islice
from .types import AnyIterable
T = TypeVar("T")
async def take(n: int, iterable: AnyIterable[T]) -> List[T]:
"""
Return the first n items of iterable as a list.
If there are too few items in iterable, all of them are returned.
n needs to be at least 0. If it is 0, an empty list is returned.
Example:
first_two = await take(2, [1, 2, 3, 4, 5])
"""
if n < 0:
raise ValueError("take's first parameter can't be negative")
return [item async for item in islice(iterable, n)]
async def chunked(iterable: AnyIterable[T], n: int) -> AsyncIterable[List[T]]:
"""
Break iterable into chunks of length n.
The last chunk will be shorter if the total number of items is not
divisible by n.
Example:
async for chunk in chunked([1, 2, 3, 4, 5], n=2):
... # first iteration: chunk == [1, 2]; last one: chunk == [5]
"""
it = iter(iterable)
chunk = await take(n, it)
while chunk != []:
yield chunk
chunk = await take(n, it)

View File

@ -5,3 +5,4 @@ from .asyncio import AsyncioTest
from .builtins import BuiltinsTest
from .helpers import HelpersTest
from .itertools import ItertoolsTest
from .more_itertools import MoreItertoolsTest

View File

@ -0,0 +1,59 @@
# Copyright 2020 John Reese
# Licensed under the MIT license
from typing import AsyncIterable
from unittest import TestCase
import aioitertools.more_itertools as mit
from .helpers import async_test
async def _gen() -> AsyncIterable[int]:
for i in range(5):
yield i
async def _empty() -> AsyncIterable[int]:
return
yield 0 # pylint: disable=unreachable
class MoreItertoolsTest(TestCase):
@async_test
async def test_take(self) -> None:
self.assertEqual(await mit.take(2, _gen()), [0, 1])
self.assertEqual(await mit.take(2, range(5)), [0, 1])
@async_test
async def test_take_zero(self) -> None:
self.assertEqual(await mit.take(0, _gen()), [])
@async_test
async def test_take_negative(self) -> None:
with self.assertRaises(ValueError):
await mit.take(-1, _gen())
@async_test
async def test_take_more_than_iterable(self) -> None:
self.assertEqual(await mit.take(10, _gen()), list(range(5)))
@async_test
async def test_take_empty(self) -> None:
it = _gen()
self.assertEqual(len(await mit.take(5, it)), 5)
self.assertEqual(await mit.take(1, it), [])
self.assertEqual(await mit.take(1, _empty()), [])
@async_test
async def test_chunked(self) -> None:
self.assertEqual(
[chunk async for chunk in mit.chunked(_gen(), 2)], [[0, 1], [2, 3], [4]]
)
self.assertEqual(
[chunk async for chunk in mit.chunked(range(5), 2)], [[0, 1], [2, 3], [4]]
)
@async_test
async def test_chunked_empty(self) -> None:
self.assertEqual([], [chunk async for chunk in mit.chunked(_empty(), 2)])