forked from more-itertools/more-itertools
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
collate, peekable, and chunked are here. chunked has no tests yet.
- Loading branch information
Erik Rose
committed
Apr 26, 2012
0 parents
commit c980534
Showing
6 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (c) 2012 Erik Rose | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
this software and associated documentation files (the "Software"), to deal in | ||
the Software without restriction, including without limitation the rights to | ||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
of the Software, and to permit persons to whom the Software is furnished to do | ||
so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
include README.rst | ||
include LICENSE |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
============== | ||
More Itertools | ||
============== | ||
|
||
I love itertools; it's one of the most beautiful, composable standard libs. | ||
"Aha! I have an iteration problem here; I'm sure there is an itertools routine | ||
that fits it perfectly" oft passes my lips. Often, my confidence is | ||
well-placed, but sometimes, neither itertools nor the recipes included in its | ||
docs do quite what I need. | ||
|
||
Here I've collected several routines I've reached for but not found. Since | ||
these are deceptively tricky to get right, I thought I'd wrap them up into a | ||
library. Enjoy! Any additions are welcome; just file a pull request. | ||
|
||
|
||
The Routines | ||
============ | ||
|
||
``chunked(iterable, n)`` | ||
Break an iterable into tuples of a given length. | ||
|
||
chunked([1, 2, 3, 4, 5, 6, 7], 3) --> [(1, 2, 3), (4, 5, 6), (7,)] | ||
|
||
If the length of ``iterable`` is not evenly divisible by ``n``, the last | ||
returned tuple will be shorter. | ||
|
||
``peekable(iterable)`` | ||
Wrapper for an iterator to allow 1-item lookahead | ||
|
||
``peekable(iterator).peek()`` returns the value that will next pop out of | ||
``next()``. | ||
|
||
``collate(*iterables[, key=<key function>, reverse=<bool>])`` | ||
Return an iterable ordered collation of the already-sorted items | ||
from each of ``iterables``, compared by kwarg ``key``. | ||
|
||
If ``reverse=True`` is passed, iterables must return their results in | ||
descending order rather than ascending. | ||
|
||
|
||
License | ||
======= | ||
|
||
More Itertools is under the MIT License. See the LICENSE file. | ||
|
||
|
||
Version History | ||
=============== | ||
|
||
1.0 | ||
* Initial release, with ``collate``, ``peekable``, and ``chunked`` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from itertools import izip_longest | ||
|
||
|
||
_marker = object() | ||
def chunked(iterable, n): | ||
"""Break an iterable into tuples of a given length. | ||
chunked([1, 2, 3, 4, 5, 6, 7], 3) --> [(1, 2, 3), (4, 5, 6), (7,)] | ||
If the length of ``iterable`` is not evenly divisible by ``n``, the last | ||
returned tuple will be shorter. | ||
""" | ||
# Doesn't seem to run into any number-of-args limits. | ||
for group in izip_longest(*[iter(iterable)] * n, fillvalue=_marker): | ||
if group[-1] is _marker: | ||
# If this is the last group, shuck off the padding: | ||
group = tuple(x for x in group if x is not _marker) | ||
yield group | ||
|
||
|
||
class peekable(object): | ||
"""Wrapper for an iterator to allow 1-item lookahead | ||
Just call ``peek()`` on me to get the value that will next pop out of | ||
``next()``. | ||
""" | ||
# Lowercase to blend in with itertools. The fact that it's a class is an | ||
# implementation detail. | ||
|
||
def __init__(self, iterable): | ||
self._it = iter(iterable) | ||
|
||
def __iter__(self): | ||
return self | ||
|
||
def __nonzero__(self): | ||
try: | ||
self.peek() | ||
except StopIteration: | ||
return False | ||
return True | ||
|
||
def peek(self): | ||
"""Return the item that will be next returned from ``next()``. | ||
Raise ``StopIteration`` if there are no items left. | ||
""" | ||
# TODO: Give peek a default arg. Raise StopIteration only when it isn't | ||
# provided. If it is, return the arg. Just like get('key', object()) | ||
if not hasattr(self, '_peek'): | ||
self._peek = self._it.next() | ||
return self._peek | ||
|
||
def next(self): | ||
ret = self.peek() | ||
del self._peek | ||
return ret | ||
|
||
|
||
def collate(*iterables, **kwargs): | ||
"""Return an iterable ordered collation of the already-sorted items | ||
from each of ``iterables``, compared by kwarg ``key``. | ||
If ``reverse=True`` is passed, iterables must return their results in | ||
descending order rather than ascending. | ||
""" | ||
key = kwargs.pop('key', lambda a: a) | ||
reverse = kwargs.pop('reverse', False) | ||
|
||
min_or_max = max if reverse else min | ||
peekables = [peekable(it) for it in iterables] | ||
peekables = [p for p in peekables if p] # Kill empties. | ||
while peekables: | ||
_, p = min_or_max((key(p.peek()), p) for p in peekables) | ||
yield p.next() | ||
peekables = [p for p in peekables if p] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from unittest import TestCase | ||
|
||
from nose.tools import eq_ | ||
|
||
from more_itertools import collate | ||
|
||
|
||
class CollateTests(TestCase): | ||
"""Unit tests for collate()""" | ||
# Also accidentally tests peekable, though that could use its own tests | ||
|
||
def test_default(self): | ||
"""Test with the default `key` function.""" | ||
iterables = [xrange(4), xrange(7), xrange(3, 6)] | ||
eq_(sorted(reduce(list.__add__, [list(it) for it in iterables])), | ||
list(collate(*iterables))) | ||
|
||
def test_key(self): | ||
"""Test using a custom `key` function.""" | ||
iterables = [xrange(5, 0, -1), xrange(4, 0, -1)] | ||
eq_(list(sorted(reduce(list.__add__, | ||
[list(it) for it in iterables]), | ||
reverse=True)), | ||
list(collate(*iterables, key=lambda x: -x))) | ||
|
||
def test_empty(self): | ||
"""Be nice if passed an empty list of iterables.""" | ||
eq_([], list(collate())) | ||
|
||
def test_one(self): | ||
"""Work when only 1 iterable is passed.""" | ||
eq_([0, 1], list(collate(xrange(2)))) | ||
|
||
def test_reverse(self): | ||
"""Test the `reverse` kwarg.""" | ||
iterables = [xrange(4, 0, -1), xrange(7, 0, -1), xrange(3, 6, -1)] | ||
eq_(sorted(reduce(list.__add__, [list(it) for it in iterables]), | ||
reverse=True), | ||
list(collate(*iterables, reverse=True))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from setuptools import setup, find_packages | ||
|
||
|
||
setup( | ||
name='more_itertools', | ||
version='1.0', | ||
description='More routines for operating on iterables, beyond itertools', | ||
long_description=open('README.rst').read(), | ||
author='Erik Rose', | ||
author_email='erikrose@grinchcentral.com', | ||
license='MIT', | ||
packages=find_packages(exclude=['ez_setup']), | ||
tests_require=['nose'], | ||
test_suite='nose.collector', | ||
url='https://github.com/erikrose/more-itertools', | ||
include_package_data=True, | ||
classifiers=[ | ||
'Development Status :: 5 - Production/Stable', | ||
'Intended Audience :: Developers', | ||
'Natural Language :: English', | ||
'License :: OSI Approved :: MIT License', | ||
'Programming Language :: Python :: 2', | ||
'Programming Language :: Python :: 2.6', | ||
'Programming Language :: Python :: 2.7', | ||
'Topic :: Software Development :: Libraries', | ||
], | ||
keywords=['itertools', 'iterator', 'iteration', 'filter'] | ||
) |