@@ -1587,27 +1587,169 @@ def batched_recipe(iterable, n):
15871587 self .assertEqual (r1 , r2 )
15881588 self .assertEqual (e1 , e2 )
15891589
1590+
1591+ def test_groupby_recipe (self ):
1592+
1593+ # Begin groupby() recipe #######################################
1594+
1595+ def groupby (iterable , key = None ):
1596+ # [k for k, g in groupby('AAAABBBCCDAABBB')] → A B C D A B
1597+ # [list(g) for k, g in groupby('AAAABBBCCD')] → AAAA BBB CC D
1598+
1599+ keyfunc = (lambda x : x ) if key is None else key
1600+ iterator = iter (iterable )
1601+ exhausted = False
1602+
1603+ def _grouper (target_key ):
1604+ nonlocal curr_value , curr_key , exhausted
1605+ yield curr_value
1606+ for curr_value in iterator :
1607+ curr_key = keyfunc (curr_value )
1608+ if curr_key != target_key :
1609+ return
1610+ yield curr_value
1611+ exhausted = True
1612+
1613+ try :
1614+ curr_value = next (iterator )
1615+ except StopIteration :
1616+ return
1617+ curr_key = keyfunc (curr_value )
1618+
1619+ while not exhausted :
1620+ target_key = curr_key
1621+ curr_group = _grouper (target_key )
1622+ yield curr_key , curr_group
1623+ if curr_key == target_key :
1624+ for _ in curr_group :
1625+ pass
1626+
1627+ # End groupby() recipe #########################################
1628+
1629+ # Check whether it accepts arguments correctly
1630+ self .assertEqual ([], list (groupby ([])))
1631+ self .assertEqual ([], list (groupby ([], key = id )))
1632+ self .assertRaises (TypeError , list , groupby ('abc' , []))
1633+ if False :
1634+ # Test not applicable to the recipe
1635+ self .assertRaises (TypeError , list , groupby ('abc' , None ))
1636+ self .assertRaises (TypeError , groupby , 'abc' , lambda x :x , 10 )
1637+
1638+ # Check normal input
1639+ s = [(0 , 10 , 20 ), (0 , 11 ,21 ), (0 ,12 ,21 ), (1 ,13 ,21 ), (1 ,14 ,22 ),
1640+ (2 ,15 ,22 ), (3 ,16 ,23 ), (3 ,17 ,23 )]
1641+ dup = []
1642+ for k , g in groupby (s , lambda r :r [0 ]):
1643+ for elem in g :
1644+ self .assertEqual (k , elem [0 ])
1645+ dup .append (elem )
1646+ self .assertEqual (s , dup )
1647+
1648+ # Check nested case
1649+ dup = []
1650+ for k , g in groupby (s , testR ):
1651+ for ik , ig in groupby (g , testR2 ):
1652+ for elem in ig :
1653+ self .assertEqual (k , elem [0 ])
1654+ self .assertEqual (ik , elem [2 ])
1655+ dup .append (elem )
1656+ self .assertEqual (s , dup )
1657+
1658+ # Check case where inner iterator is not used
1659+ keys = [k for k , g in groupby (s , testR )]
1660+ expectedkeys = set ([r [0 ] for r in s ])
1661+ self .assertEqual (set (keys ), expectedkeys )
1662+ self .assertEqual (len (keys ), len (expectedkeys ))
1663+
1664+ # Check case where inner iterator is used after advancing the groupby
1665+ # iterator
1666+ s = list (zip ('AABBBAAAA' , range (9 )))
1667+ it = groupby (s , testR )
1668+ _ , g1 = next (it )
1669+ _ , g2 = next (it )
1670+ _ , g3 = next (it )
1671+ self .assertEqual (list (g1 ), [])
1672+ self .assertEqual (list (g2 ), [])
1673+ self .assertEqual (next (g3 ), ('A' , 5 ))
1674+ list (it ) # exhaust the groupby iterator
1675+ self .assertEqual (list (g3 ), [])
1676+
1677+ # Exercise pipes and filters style
1678+ s = 'abracadabra'
1679+ # sort s | uniq
1680+ r = [k for k , g in groupby (sorted (s ))]
1681+ self .assertEqual (r , ['a' , 'b' , 'c' , 'd' , 'r' ])
1682+ # sort s | uniq -d
1683+ r = [k for k , g in groupby (sorted (s )) if list (islice (g ,1 ,2 ))]
1684+ self .assertEqual (r , ['a' , 'b' , 'r' ])
1685+ # sort s | uniq -c
1686+ r = [(len (list (g )), k ) for k , g in groupby (sorted (s ))]
1687+ self .assertEqual (r , [(5 , 'a' ), (2 , 'b' ), (1 , 'c' ), (1 , 'd' ), (2 , 'r' )])
1688+ # sort s | uniq -c | sort -rn | head -3
1689+ r = sorted ([(len (list (g )) , k ) for k , g in groupby (sorted (s ))], reverse = True )[:3 ]
1690+ self .assertEqual (r , [(5 , 'a' ), (2 , 'r' ), (2 , 'b' )])
1691+
1692+ # iter.__next__ failure
1693+ class ExpectedError (Exception ):
1694+ pass
1695+ def delayed_raise (n = 0 ):
1696+ for i in range (n ):
1697+ yield 'yo'
1698+ raise ExpectedError
1699+ def gulp (iterable , keyp = None , func = list ):
1700+ return [func (g ) for k , g in groupby (iterable , keyp )]
1701+
1702+ # iter.__next__ failure on outer object
1703+ self .assertRaises (ExpectedError , gulp , delayed_raise (0 ))
1704+ # iter.__next__ failure on inner object
1705+ self .assertRaises (ExpectedError , gulp , delayed_raise (1 ))
1706+
1707+ # __eq__ failure
1708+ class DummyCmp :
1709+ def __eq__ (self , dst ):
1710+ raise ExpectedError
1711+ s = [DummyCmp (), DummyCmp (), None ]
1712+
1713+ # __eq__ failure on outer object
1714+ self .assertRaises (ExpectedError , gulp , s , func = id )
1715+ # __eq__ failure on inner object
1716+ self .assertRaises (ExpectedError , gulp , s )
1717+
1718+ # keyfunc failure
1719+ def keyfunc (obj ):
1720+ if keyfunc .skip > 0 :
1721+ keyfunc .skip -= 1
1722+ return obj
1723+ else :
1724+ raise ExpectedError
1725+
1726+ # keyfunc failure on outer object
1727+ keyfunc .skip = 0
1728+ self .assertRaises (ExpectedError , gulp , [None ], keyfunc )
1729+ keyfunc .skip = 1
1730+ self .assertRaises (ExpectedError , gulp , [None , None ], keyfunc )
1731+
1732+
15901733 @staticmethod
15911734 def islice (iterable , * args ):
1735+ # islice('ABCDEFG', 2) → A B
1736+ # islice('ABCDEFG', 2, 4) → C D
1737+ # islice('ABCDEFG', 2, None) → C D E F G
1738+ # islice('ABCDEFG', 0, None, 2) → A C E G
1739+
15921740 s = slice (* args )
1593- start , stop , step = s .start or 0 , s .stop or sys .maxsize , s .step or 1
1594- it = iter (range (start , stop , step ))
1595- try :
1596- nexti = next (it )
1597- except StopIteration :
1598- # Consume *iterable* up to the *start* position.
1599- for i , element in zip (range (start ), iterable ):
1600- pass
1601- return
1602- try :
1603- for i , element in enumerate (iterable ):
1604- if i == nexti :
1605- yield element
1606- nexti = next (it )
1607- except StopIteration :
1608- # Consume to *stop*.
1609- for i , element in zip (range (i + 1 , stop ), iterable ):
1610- pass
1741+ start = 0 if s .start is None else s .start
1742+ stop = s .stop
1743+ step = 1 if s .step is None else s .step
1744+ if start < 0 or (stop is not None and stop < 0 ) or step <= 0 :
1745+ raise ValueError
1746+
1747+ indices = count () if stop is None else range (max (start , stop ))
1748+ next_i = start
1749+ for i , element in zip (indices , iterable ):
1750+ if i == next_i :
1751+ yield element
1752+ next_i += step
16111753
16121754 def test_islice_recipe (self ):
16131755 self .assertEqual (list (self .islice ('ABCDEFG' , 2 )), list ('AB' ))
@@ -1627,6 +1769,161 @@ def test_islice_recipe(self):
16271769 self .assertEqual (next (c ), 3 )
16281770
16291771
1772+ def test_tee_recipe (self ):
1773+
1774+ # Begin tee() recipe ###########################################
1775+
1776+ def tee (iterable , n = 2 ):
1777+ iterator = iter (iterable )
1778+ shared_link = [None , None ]
1779+ return tuple (_tee (iterator , shared_link ) for _ in range (n ))
1780+
1781+ def _tee (iterator , link ):
1782+ try :
1783+ while True :
1784+ if link [1 ] is None :
1785+ link [0 ] = next (iterator )
1786+ link [1 ] = [None , None ]
1787+ value , link = link
1788+ yield value
1789+ except StopIteration :
1790+ return
1791+
1792+ # End tee() recipe #############################################
1793+
1794+ n = 200
1795+
1796+ a , b = tee ([]) # test empty iterator
1797+ self .assertEqual (list (a ), [])
1798+ self .assertEqual (list (b ), [])
1799+
1800+ a , b = tee (irange (n )) # test 100% interleaved
1801+ self .assertEqual (lzip (a ,b ), lzip (range (n ), range (n )))
1802+
1803+ a , b = tee (irange (n )) # test 0% interleaved
1804+ self .assertEqual (list (a ), list (range (n )))
1805+ self .assertEqual (list (b ), list (range (n )))
1806+
1807+ a , b = tee (irange (n )) # test dealloc of leading iterator
1808+ for i in range (100 ):
1809+ self .assertEqual (next (a ), i )
1810+ del a
1811+ self .assertEqual (list (b ), list (range (n )))
1812+
1813+ a , b = tee (irange (n )) # test dealloc of trailing iterator
1814+ for i in range (100 ):
1815+ self .assertEqual (next (a ), i )
1816+ del b
1817+ self .assertEqual (list (a ), list (range (100 , n )))
1818+
1819+ for j in range (5 ): # test randomly interleaved
1820+ order = [0 ]* n + [1 ]* n
1821+ random .shuffle (order )
1822+ lists = ([], [])
1823+ its = tee (irange (n ))
1824+ for i in order :
1825+ value = next (its [i ])
1826+ lists [i ].append (value )
1827+ self .assertEqual (lists [0 ], list (range (n )))
1828+ self .assertEqual (lists [1 ], list (range (n )))
1829+
1830+ # test argument format checking
1831+ self .assertRaises (TypeError , tee )
1832+ self .assertRaises (TypeError , tee , 3 )
1833+ self .assertRaises (TypeError , tee , [1 ,2 ], 'x' )
1834+ self .assertRaises (TypeError , tee , [1 ,2 ], 3 , 'x' )
1835+
1836+ # Tests not applicable to the tee() recipe
1837+ if False :
1838+ # tee object should be instantiable
1839+ a , b = tee ('abc' )
1840+ c = type (a )('def' )
1841+ self .assertEqual (list (c ), list ('def' ))
1842+
1843+ # test long-lagged and multi-way split
1844+ a , b , c = tee (range (2000 ), 3 )
1845+ for i in range (100 ):
1846+ self .assertEqual (next (a ), i )
1847+ self .assertEqual (list (b ), list (range (2000 )))
1848+ self .assertEqual ([next (c ), next (c )], list (range (2 )))
1849+ self .assertEqual (list (a ), list (range (100 ,2000 )))
1850+ self .assertEqual (list (c ), list (range (2 ,2000 )))
1851+
1852+ # Tests not applicable to the tee() recipe
1853+ if False :
1854+ # test invalid values of n
1855+ self .assertRaises (TypeError , tee , 'abc' , 'invalid' )
1856+ self .assertRaises (ValueError , tee , [], - 1 )
1857+
1858+ for n in range (5 ):
1859+ result = tee ('abc' , n )
1860+ self .assertEqual (type (result ), tuple )
1861+ self .assertEqual (len (result ), n )
1862+ self .assertEqual ([list (x ) for x in result ], [list ('abc' )]* n )
1863+
1864+
1865+ # Tests not applicable to the tee() recipe
1866+ if False :
1867+ # tee pass-through to copyable iterator
1868+ a , b = tee ('abc' )
1869+ c , d = tee (a )
1870+ self .assertTrue (a is c )
1871+
1872+ # test tee_new
1873+ t1 , t2 = tee ('abc' )
1874+ tnew = type (t1 )
1875+ self .assertRaises (TypeError , tnew )
1876+ self .assertRaises (TypeError , tnew , 10 )
1877+ t3 = tnew (t1 )
1878+ self .assertTrue (list (t1 ) == list (t2 ) == list (t3 ) == list ('abc' ))
1879+
1880+ # test that tee objects are weak referencable
1881+ a , b = tee (range (10 ))
1882+ p = weakref .proxy (a )
1883+ self .assertEqual (getattr (p , '__class__' ), type (b ))
1884+ del a
1885+ gc .collect () # For PyPy or other GCs.
1886+ self .assertRaises (ReferenceError , getattr , p , '__class__' )
1887+
1888+ ans = list ('abc' )
1889+ long_ans = list (range (10000 ))
1890+
1891+ # Tests not applicable to the tee() recipe
1892+ if False :
1893+ # check copy
1894+ a , b = tee ('abc' )
1895+ self .assertEqual (list (copy .copy (a )), ans )
1896+ self .assertEqual (list (copy .copy (b )), ans )
1897+ a , b = tee (list (range (10000 )))
1898+ self .assertEqual (list (copy .copy (a )), long_ans )
1899+ self .assertEqual (list (copy .copy (b )), long_ans )
1900+
1901+ # check partially consumed copy
1902+ a , b = tee ('abc' )
1903+ take (2 , a )
1904+ take (1 , b )
1905+ self .assertEqual (list (copy .copy (a )), ans [2 :])
1906+ self .assertEqual (list (copy .copy (b )), ans [1 :])
1907+ self .assertEqual (list (a ), ans [2 :])
1908+ self .assertEqual (list (b ), ans [1 :])
1909+ a , b = tee (range (10000 ))
1910+ take (100 , a )
1911+ take (60 , b )
1912+ self .assertEqual (list (copy .copy (a )), long_ans [100 :])
1913+ self .assertEqual (list (copy .copy (b )), long_ans [60 :])
1914+ self .assertEqual (list (a ), long_ans [100 :])
1915+ self .assertEqual (list (b ), long_ans [60 :])
1916+
1917+ # Issue 13454: Crash when deleting backward iterator from tee()
1918+ forward , backward = tee (repeat (None , 2000 )) # 20000000
1919+ try :
1920+ any (forward ) # exhaust the iterator
1921+ del backward
1922+ except :
1923+ del forward , backward
1924+ raise
1925+
1926+
16301927class TestGC (unittest .TestCase ):
16311928
16321929 def makecycle (self , iterator , container ):
0 commit comments