Skip to content

Commit 1ababfd

Browse files
committed
reimplement itertools.groupby based on PyPy and add test
1 parent e2cc576 commit 1ababfd

File tree

2 files changed

+128
-15
lines changed

2 files changed

+128
-15
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_itertools.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import unittest
77
from itertools import *
88

9+
910
class CombinationsTests(unittest.TestCase):
1011

1112
def test_combinations_with_replacement(self):
@@ -24,3 +25,76 @@ def test_combinations_with_replacement(self):
2425
compare += a;
2526
self.assertEqual(result,compare)
2627

28+
29+
def test_groupby(self):
30+
# Check whether it accepts arguments correctly
31+
self.assertEqual([], list(groupby([])))
32+
self.assertEqual([], list(groupby([], key=id)))
33+
self.assertRaises(TypeError, list, groupby('abc', []))
34+
self.assertRaises(TypeError, groupby, None)
35+
self.assertRaises(TypeError, groupby, 'abc', lambda x:x, 10)
36+
37+
# Check normal input
38+
s = [(0, 10, 20), (0, 11,21), (0,12,21), (1,13,21), (1,14,22),
39+
(2,15,22), (3,16,23), (3,17,23)]
40+
dup = []
41+
for k, g in groupby(s, lambda r:r[0]):
42+
for elem in g:
43+
self.assertEqual(k, elem[0])
44+
dup.append(elem)
45+
self.assertEqual(s, dup)
46+
47+
# Exercise pipes and filters style
48+
s = 'abracadabra'
49+
# sort s | uniq
50+
r = [k for k, g in groupby(sorted(s))]
51+
self.assertEqual(r, ['a', 'b', 'c', 'd', 'r'])
52+
# sort s | uniq -d
53+
r = [k for k, g in groupby(sorted(s)) if list(islice(g,1,2))]
54+
self.assertEqual(r, ['a', 'b', 'r'])
55+
# sort s | uniq -c
56+
r = [(len(list(g)), k) for k, g in groupby(sorted(s))]
57+
self.assertEqual(r, [(5, 'a'), (2, 'b'), (1, 'c'), (1, 'd'), (2, 'r')])
58+
# sort s | uniq -c | sort -rn | head -3
59+
r = sorted([(len(list(g)) , k) for k, g in groupby(sorted(s))], reverse=True)[:3]
60+
self.assertEqual(r, [(5, 'a'), (2, 'r'), (2, 'b')])
61+
62+
# iter.__next__ failure
63+
class ExpectedError(Exception):
64+
pass
65+
def delayed_raise(n=0):
66+
for i in range(n):
67+
yield 'yo'
68+
raise ExpectedError
69+
def gulp(iterable, keyp=None, func=list):
70+
return [func(g) for k, g in groupby(iterable, keyp)]
71+
72+
# iter.__next__ failure on outer object
73+
self.assertRaises(ExpectedError, gulp, delayed_raise(0))
74+
# iter.__next__ failure on inner object
75+
self.assertRaises(ExpectedError, gulp, delayed_raise(1))
76+
77+
# __eq__ failure
78+
class DummyCmp:
79+
def __eq__(self, dst):
80+
raise ExpectedError
81+
s = [DummyCmp(), DummyCmp(), None]
82+
83+
# __eq__ failure on outer object
84+
self.assertRaises(ExpectedError, gulp, s, func=id)
85+
# __eq__ failure on inner object
86+
self.assertRaises(ExpectedError, gulp, s)
87+
88+
# keyfunc failure
89+
def keyfunc(obj):
90+
if keyfunc.skip > 0:
91+
keyfunc.skip -= 1
92+
return obj
93+
else:
94+
raise ExpectedError
95+
96+
# keyfunc failure on outer object
97+
keyfunc.skip = 0
98+
self.assertRaises(ExpectedError, gulp, [None], keyfunc)
99+
keyfunc.skip = 1
100+
self.assertRaises(ExpectedError, gulp, [None, None], keyfunc)

graalpython/lib-graalpython/itertools.py

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -489,27 +489,66 @@ class groupby(object):
489489
uniquekeys.append(k)
490490
"""
491491
def __init__(self, iterable, key=None):
492-
if key is None:
493-
key = lambda x: x
492+
self._iterator = iter(iterable)
494493
self._keyfunc = key
495-
self._iter = iter(iterable)
496-
self._tgtkey = self._currkey = self._currvalue = range(0)
494+
self._tgtkey = self._currkey = self._currvalue = None
497495

498496
def __iter__(self):
499497
return self
500498

501499
def __next__(self):
502-
while self._currkey == self._tgtkey:
503-
self._currvalue = next(self._iter) # Exit on StopIteration
504-
self._currkey = self._keyfunc(self._currvalue)
505-
self._tgtkey = self._currkey
506-
return (self._currkey, self._grouper(self._tgtkey))
507-
508-
def _grouper(self, tgtkey):
509-
while self._currkey == tgtkey:
510-
yield self._currvalue
511-
self._currvalue = next(self._iter) # Exit on StopIteration
512-
self._currkey = self._keyfunc(self._currvalue)
500+
self._skip_to_next_iteration_group()
501+
key = self._tgtkey = self._currkey
502+
grouper = _groupby(self, key)
503+
return (key, grouper)
504+
505+
def _skip_to_next_iteration_group(self):
506+
while True:
507+
if self._currkey is None:
508+
pass
509+
elif self._tgtkey is None:
510+
break
511+
else:
512+
if not self._tgtkey == self._currkey:
513+
break
514+
515+
newvalue = next(self._iterator)
516+
if self._keyfunc is None:
517+
newkey = newvalue
518+
else:
519+
newkey = self._keyfunc(newvalue)
520+
521+
self._currkey = newkey
522+
self._currvalue = newvalue
523+
524+
525+
class _groupby():
526+
def __init__(self, groupby, tgtkey):
527+
self.groupby = groupby
528+
self.tgtkey = tgtkey
529+
530+
def __iter__(self):
531+
return self
532+
533+
def __next__(self):
534+
groupby = self.groupby
535+
if groupby._currvalue is None:
536+
newvalue = next(groupby._iterator)
537+
if groupby._keyfunc is None:
538+
newkey = newvalue
539+
else:
540+
newkey = groupby._keyfunc(newvalue)
541+
assert groupby._currvalue is None
542+
groupby._currkey = newkey
543+
groupby._currvalue = newvalue
544+
545+
assert groupby._currkey is not None
546+
if not self.tgtkey == groupby._currkey:
547+
raise StopIteration(None)
548+
result = groupby._currvalue
549+
groupby._currvalue = None
550+
groupby._currkey = None
551+
return result
513552

514553

515554
class combinations():

0 commit comments

Comments
 (0)