itertools
--- 建立產生高效率迴圈之疊代器的函式¶
這個模組實作了許多疊代器 (iterator) 構建塊 (building block),其靈感來自 APL、Haskell 和 SML 的結構。每個構建塊都以適合 Python 的形式來重新設計。
這個模組標準化了快速且高效率利用記憶體的核心工具集,這些工具本身或組合使用都很有用。它們共同構成了一個「疊代器代數 (iterator algebra)」,使得在純 Python 中簡潔且高效地建構專用工具成為可能。
例如,SML 提供了一個造表工具:tabulate(f)
,它產生一個序列f(0),f(1),...
。在 Python 中,可以透過結合map()
和count()
組成map(f,count())
以達到同樣的效果。
無限疊代器:
疊代器 | 引數 | 結果 | 範例 |
---|---|---|---|
[start[, step]] | start, start+step, start+2*step, ... |
| |
p | p0, p1, ... plast, p0, p1, ... |
| |
elem [,n] | elem, elem, elem,... 重複無限次或 n 次 |
|
在最短輸入序列 (shortest input sequence) 處終止的疊代器:
疊代器 | 引數 | 結果 | 範例 |
---|---|---|---|
p [,func] | p0, p0+p1, p0+p1+p2, ... |
| |
p, n | (p0, p1, ..., p_n-1), ... |
| |
p, q, ... | p0, p1, ... plast, q0, q1, ... |
| |
可疊代物件 | p0, p1, ... plast, q0, q1, ... |
| |
data, selectors | (d[0] if s[0]), (d[1] if s[1]), ... |
| |
predicate, seq | seq[n], seq[n+1],當 predicate 失敗時開始 |
| |
predicate, seq | 當 predicate(elem) 失敗時 seq 的元素 |
| |
iterable[, key] | 根據 key(v) 的值分組的子疊代器 |
| |
seq, [start,] stop [, step] | seq[start:stop:step] 的元素 |
| |
可疊代物件 | (p[0], p[1]), (p[1], p[2]) |
| |
func, seq | func(*seq[0]), func(*seq[1]), ... |
| |
predicate, seq | seq[0], seq[1],直到 predicate 失敗 |
| |
it, n | it1, it2, ... itn,將一個疊代器分成 n 個 |
| |
p, q, ... | (p[0], q[0]), (p[1], q[1]), ... |
|
組合疊代器:
疊代器 | 引數 | 結果 |
---|---|---|
p, q, ... [repeat=1] | 笛卡爾乘積 (cartesian product),相當於巢狀的 for 迴圈 | |
p[, r] | 長度為 r 的元組,所有可能的定序,無重複元素 | |
p, r | 長度為 r 的元組,按照排序過後的定序,無重複元素 | |
p, r | 長度為 r 的元組,按照排序過後的定序,有重複元素 |
範例 | 結果 |
---|---|
|
|
|
|
|
|
|
|
Itertool 函式¶
以下的函式都會建構並回傳疊代器。一些函式提供無限長度的串流 (stream),因此應僅由截斷串流的函式或迴圈來存取它們。
- itertools.accumulate(iterable[,function,*,initial=None])¶
建立一個回傳累積和的疊代器,或其他二進位函式的累積結果。
function 預設為加法。function 應接受兩個引數,即累積總和和來自iterable 的值。
如果提供了initial 值,則累積將從該值開始,並且輸出的元素數將比輸入的可疊代物件多一個。
大致等價於:
defaccumulate(iterable,function=operator.add,*,initial=None):'Return running totals'# accumulate([1,2,3,4,5]) → 1 3 6 10 15# accumulate([1,2,3,4,5], initial=100) → 100 101 103 106 110 115# accumulate([1,2,3,4,5], operator.mul) → 1 2 6 24 120iterator=iter(iterable)total=initialifinitialisNone:try:total=next(iterator)exceptStopIteration:returnyieldtotalforelementiniterator:total=function(total,element)yieldtotal
function 引數可以被設定為
min()
以得到連續的最小值,設定為max()
以得到連續的最大值,或者設定為operator.mul()
以得到連續的乘積。也可以透過累積利息和付款來建立攤銷表 (Amortization tables) :>>>data=[3,4,6,2,1,9,0,7,5,8]>>>list(accumulate(data,max))# 運行最大值[3, 4, 6, 6, 6, 9, 9, 9, 9, 9]>>>list(accumulate(data,operator.mul))# 運行乘積[3, 12, 72, 144, 144, 1296, 0, 0, 0, 0]# 攤銷一筆 1000 的 5% 貸款,分 10 年、每年支付 90>>>update=lambdabalance,payment:round(balance*1.05)-payment>>>list(accumulate(repeat(90,10),update,initial=1_000))[1000, 960, 918, 874, 828, 779, 728, 674, 618, 559, 497]
可參見
functools.reduce()
,其是個類似的函式,但僅回傳最終的累積值。在 3.2 版被加入.
在 3.3 版的變更:新增可選的function 參數。
在 3.8 版的變更:新增可選的initial 參數。
- itertools.batched(iterable,n,*,strict=False)¶
將來自iterable 的資料分批為長度為n 的元組。最後一個批次可能比n 短。
如果strict 為真,則當最後一個批次比n 短時,會引發
ValueError
。對輸入的可疊代物件進行迴圈,並將資料累積到大小為n 的元組中。輸入是惰性地被消耗 (consumed lazily) 的,會剛好足夠填充一批的資料。一旦批次填滿或輸入的可疊代物件耗盡,就會 yield 出結果:
>>>flattened_data=['roses','red','violets','blue','sugar','sweet']>>>unflattened=list(batched(flattened_data,2))>>>unflattened[('roses', 'red'), ('violets', 'blue'), ('sugar', 'sweet')]
大致等價於:
defbatched(iterable,n,*,strict=False):# batched('ABCDEFG', 2) → AB CD EF Gifn<1:raiseValueError('n must be at least one')iterator=iter(iterable)whilebatch:=tuple(islice(iterator,n)):ifstrictandlen(batch)!=n:raiseValueError('batched(): incomplete batch')yieldbatch
在 3.12 版被加入.
在 3.13 版的變更:新增strict 選項。
- itertools.chain(*iterables)¶
建立一個疊代器,從第一個可疊代物件回傳元素直到其耗盡,然後繼續處理下一個可疊代物件,直到所有可疊代物件都耗盡。這將多個資料來源結合為單一個疊代器。大致等價於:
defchain(*iterables):# chain('ABC', 'DEF') → A B C D E Fforiterableiniterables:yield fromiterable
- classmethodchain.from_iterable(iterable)¶
chain()
的另一個建構函式。從單個可疊代的引數中得到鏈接的輸入,該引數是惰性計算的。大致等價於:deffrom_iterable(iterables):# chain.from_iterable(['ABC', 'DEF']) → A B C D E Fforiterableiniterables:yield fromiterable
- itertools.combinations(iterable,r)¶
從輸入iterable 中回傳長度為r 的元素的子序列。
輸出是
product()
的子序列,僅保留作為iterable 子序列的條目。輸出的長度由math.comb()
給定,當0≤r≤n
時,長度為n!/r!/(n-r)!
,當r>n
時為零。根據輸入值iterable 的順序,組合的元組會按照字典順序輸出。如果輸入的iterable 已經排序,則輸出的元組也將按排序的順序產生。
元素是根據它們的位置(而非值)來決定其唯一性。如果輸入的元素都是獨特的,則每個組合內將不會有重複的值。
大致等價於:
defcombinations(iterable,r):# combinations('ABCD', 2) → AB AC AD BC BD CD# combinations(range(4), 3) → 012 013 023 123pool=tuple(iterable)n=len(pool)ifr>n:returnindices=list(range(r))yieldtuple(pool[i]foriinindices)whileTrue:foriinreversed(range(r)):ifindices[i]!=i+n-r:breakelse:returnindices[i]+=1forjinrange(i+1,r):indices[j]=indices[j-1]+1yieldtuple(pool[i]foriinindices)
- itertools.combinations_with_replacement(iterable,r)¶
回傳來自輸入iterable 的長度為r 的子序列,且允許個別元素重複多次。
其輸出是一個
product()
的子序列,僅保留作為iterable 子序列(可能有重複元素)的條目。當n>0
時,回傳的子序列數量為(n+r-1)!/r!/(n-1)!
。根據輸入值iterable 的順序,組合的元組會按照字典順序輸出。如果輸入的iterable 已經排序,則輸出的元組也將按排序的順序產生。
元素是根據它們的位置(而非值)來決定其唯一性。如果輸入的元素都是獨特的,生成的組合也將是獨特的。
大致等價於:
defcombinations_with_replacement(iterable,r):# combinations_with_replacement('ABC', 2) → AA AB AC BB BC CCpool=tuple(iterable)n=len(pool)ifnotnandr:returnindices=[0]*ryieldtuple(pool[i]foriinindices)whileTrue:foriinreversed(range(r)):ifindices[i]!=n-1:breakelse:returnindices[i:]=[indices[i]+1]*(r-i)yieldtuple(pool[i]foriinindices)
在 3.1 版被加入.
- itertools.compress(data,selectors)¶
建立一個疊代器,回傳data 中對應selectors 的元素為 true 的元素。當data 或selectors 可疊代物件耗盡時停止。大致等價於:
defcompress(data,selectors):# compress('ABCDEF', [1,0,1,0,1,1]) → A C E Freturn(datumfordatum,selectorinzip(data,selectors)ifselector)
在 3.1 版被加入.
- itertools.count(start=0,step=1)¶
建立一個疊代器,回傳從start 開始的等差的值。可以與
map()
一起使用來產生連續的資料點,或與zip()
一起使用來增加序列號。大致等價於:defcount(start=0,step=1):# count(10) → 10 11 12 13 14 ...# count(2.5, 0.5) → 2.5 3.0 3.5 ...n=startwhileTrue:yieldnn+=step
當用浮點數計數時,將上述程式碼替換為乘法有時可以獲得更好的精確度,例如:
(start+step*iforiincount())
。在 3.1 版的變更:新增step 引數並允許非整數引數。
- itertools.cycle(iterable)¶
建立一個疊代器,回傳iterable 中的元素並保存每個元素的副本。當可疊代物件耗盡時,從保存的副本中回傳元素。會無限次的重複。大致等價於:
defcycle(iterable):# cycle('ABCD') → A B C D A B C D A B C D ...saved=[]forelementiniterable:yieldelementsaved.append(element)whilesaved:forelementinsaved:yieldelement
此 itertool 可能需要大量的輔助儲存空間(取決於可疊代物件的長度)。
- itertools.dropwhile(predicate,iterable)¶
建立一個疊代器,在predicate 為 true 時丟棄iterable 中的元素,之後回傳每個元素。大致等價於:
defdropwhile(predicate,iterable):# dropwhile(lambda x: x<5, [1,4,6,3,8]) → 6 3 8iterator=iter(iterable)forxiniterator:ifnotpredicate(x):yieldxbreakforxiniterator:yieldx
注意,在 predicate 首次變為 False 之前,這不會產生任何輸出,所以此 itertool 可能會有較長的啟動時間。
- itertools.filterfalse(predicate,iterable)¶
建立一個疊代器,過濾iterable 中的元素,僅回傳predicate 為 False 值的元素。如果predicate 是
None
,則回傳為 False 的項目。大致等價於:deffilterfalse(predicate,iterable):# filterfalse(lambda x: x<5, [1,4,6,3,8]) → 6 8ifpredicateisNone:predicate=boolforxiniterable:ifnotpredicate(x):yieldx
- itertools.groupby(iterable,key=None)¶
建立一個疊代器,回傳iterable 中連續的鍵和群組。key 是一個為每個元素計算鍵值的函式。如果其未指定或為
None
,則key 預設為一個識別性函式 (identity function),並回傳未被更改的元素。一般來說,可疊代物件需要已經用相同的鍵函式進行排序。groupby()
的操作類似於 Unix 中的uniq
過濾器。每當鍵函式的值發生變化時,它會產生一個 break 或新的群組(這就是為什麼通常需要使用相同的鍵函式對資料進行排序)。這種行為不同於 SQL 的 GROUP BY,其無論輸入順序如何都會聚合相同的元素。回傳的群組本身是一個與
groupby()
共享底層可疊代物件的疊代器。由於來源是共享的,當groupby()
物件前進時,前一個群組將不再可見。因此,如果之後需要該資料,應將其儲存為串列:groups=[]uniquekeys=[]data=sorted(data,key=keyfunc)fork,gingroupby(data,keyfunc):groups.append(list(g))# 將群組疊代器儲存為串列uniquekeys.append(k)
groupby()
大致等價於:defgroupby(iterable,key=None):# [k for k, g in groupby('AAAABBBCCDAABBB')] → A B C D A B# [list(g) for k, g in groupby('AAAABBBCCD')] → AAAA BBB CC Dkeyfunc=(lambdax:x)ifkeyisNoneelsekeyiterator=iter(iterable)exhausted=Falsedef_grouper(target_key):nonlocalcurr_value,curr_key,exhaustedyieldcurr_valueforcurr_valueiniterator:curr_key=keyfunc(curr_value)ifcurr_key!=target_key:returnyieldcurr_valueexhausted=Truetry:curr_value=next(iterator)exceptStopIteration:returncurr_key=keyfunc(curr_value)whilenotexhausted:target_key=curr_keycurr_group=_grouper(target_key)yieldcurr_key,curr_groupifcurr_key==target_key:for_incurr_group:pass
- itertools.islice(iterable,stop)¶
- itertools.islice(iterable,start,stop[,step])
建立一個疊代器,回傳從 iterable 中選取的元素。其作用類似於序列切片 (sequence slicing),但不支援負數的start、stop 或step 的值。
如果start 為零或
None
,則從零開始疊代。否則在達到start 之前,會跳過 iterable 中的元素。如果stop 為
None
,則疊代將繼續前進直到輸入耗盡。如果指定了stop,則在達到指定位置時停止。如果step 為
None
,則步長 (step) 預設為一。元素會連續回傳,除非將step 設定為大於一,這會導致一些項目被跳過。大致等價於:
defislice(iterable,*args):# islice('ABCDEFG', 2) → A B# islice('ABCDEFG', 2, 4) → C D# islice('ABCDEFG', 2, None) → C D E F G# islice('ABCDEFG', 0, None, 2) → A C E Gs=slice(*args)start=0ifs.startisNoneelses.startstop=s.stopstep=1ifs.stepisNoneelses.stepifstart<0or(stopisnotNoneandstop<0)orstep<=0:raiseValueErrorindices=count()ifstopisNoneelserange(max(start,stop))next_i=startfori,elementinzip(indices,iterable):ifi==next_i:yieldelementnext_i+=step
若輸入為疊代器,則完整耗盡islice 會使輸入的疊代器向前移動
max(start,stop)
步,與step 的值無關。
- itertools.pairwise(iterable)¶
回傳從輸入的iterable 中提取的連續重疊對。
輸出疊代器中的 2 元組數量將比輸入少一個。如果輸入的可疊代物件中的值少於兩個,則輸出將為空值。
大致等價於:
defpairwise(iterable):# pairwise('ABCDEFG') → AB BC CD DE EF FGiterator=iter(iterable)a=next(iterator,None)forbiniterator:yielda,ba=b
在 3.10 版被加入.
- itertools.permutations(iterable,r=None)¶
回傳iterable 中連續且長度為r 的元素排列 。
如果未指定r 或其值為
None
,則r 預設為iterable 的長度,並產生所有可能的完整長度的排列。輸出是
product()
的子序列,其中重複元素的條目已被濾除。輸出的長度由math.perm()
給定,當0≤r≤n
時,長度為n!/(n-r)!
,當r>n
時為零。根據輸入值iterable 的順序,排列的元組會按照字典順序輸出。如果輸入的iterable 已排序,則輸出的元組也將按排序的順序產生。
元素是根據它們的位置(而非值)來決定其唯一性。如果輸入的元素都是獨特的,則排列中將不會有重複的值。
大致等價於:
defpermutations(iterable,r=None):# permutations('ABCD', 2) → AB AC AD BA BC BD CA CB CD DA DB DC# permutations(range(3)) → 012 021 102 120 201 210pool=tuple(iterable)n=len(pool)r=nifrisNoneelserifr>n:returnindices=list(range(n))cycles=list(range(n,n-r,-1))yieldtuple(pool[i]foriinindices[:r])whilen:foriinreversed(range(r)):cycles[i]-=1ifcycles[i]==0:indices[i:]=indices[i+1:]+indices[i:i+1]cycles[i]=n-ielse:j=cycles[i]indices[i],indices[-j]=indices[-j],indices[i]yieldtuple(pool[i]foriinindices[:r])breakelse:return
- itertools.product(*iterables,repeat=1)¶
輸入可疊代物的笛卡爾乘積
大致等價於產生器運算式中的巢狀 for 迴圈。例如,
product(A,B)
的回傳結果與((x,y)forxinAforyinB)
相同。巢狀迴圈的循環類似於里程表,最右邊的元素在每次疊代時前進。這種模式會建立字典順序,因此如果輸入的 iterables 已排序,則輸出的乘積元組也將按排序的順序產生。
要計算可疊代物件自身的乘積,可以使用可選的repeat 關鍵字引數來指定重複次數。例如,
product(A,repeat=4)
與product(A,A,A,A)
相同。此函式大致等價於以下的程式碼,不同之處在於真正的實作不會在記憶體中建立中間結果:
defproduct(*iterables,repeat=1):# product('ABCD', 'xy') → Ax Ay Bx By Cx Cy Dx Dy# product(range(2), repeat=3) → 000 001 010 011 100 101 110 111ifrepeat<0:raiseValueError('repeat argument cannot be negative')pools=[tuple(pool)forpooliniterables]*repeatresult=[[]]forpoolinpools:result=[x+[y]forxinresultforyinpool]forprodinresult:yieldtuple(prod)
在
product()
執行之前,它會完全消耗輸入的 iterables,並將值的池 (pools of values) 保存在記憶體中以產生乘積。因此,它僅對有限的輸入有用。
- itertools.repeat(object[,times])¶
建立一個疊代器,反覆回傳object。除非指定了times 引數,否則會執行無限次。
大致等價於:
defrepeat(object,times=None):# repeat(10, 3) → 10 10 10iftimesisNone:whileTrue:yieldobjectelse:foriinrange(times):yieldobject
repeat 的常見用途是為map 或zip 提供定值的串流:
>>>list(map(pow,range(10),repeat(2)))[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- itertools.starmap(function,iterable)¶
建立一個疊代器,使用從iterable 取得的引數計算function 。當引數參數已經被「預先壓縮 (pre-zipped)」成元組時,使用此方法代替
map()
。map()
和starmap()
之間的區別類似於function(a,b)
和function(*c)
之間的區別。大致等價於:defstarmap(function,iterable):# starmap(pow, [(2,5), (3,2), (10,3)]) → 32 9 1000forargsiniterable:yieldfunction(*args)
- itertools.takewhile(predicate,iterable)¶
建立一個疊代器,只在predicate 為 true 時回傳iterable 中的元素。大致等價於:
deftakewhile(predicate,iterable):# takewhile(lambda x: x<5, [1,4,6,3,8]) → 1 4forxiniterable:ifnotpredicate(x):breakyieldx
注意,第一個不符合條件判斷的元素將從輸入疊代器中被消耗,且無法再存取它。如果應用程式希望在takewhile 耗盡後進一步消耗輸入疊代器,這可能會是個問題。為了解決這個問題,可以考慮使用more-itertools 中的 before_and_after() 作為替代。
- itertools.tee(iterable,n=2)¶
從一個 iterable 中回傳n 個獨立的疊代器。
大致等價於:
deftee(iterable,n=2):ifn<0:raiseValueErrorifn==0:return()iterator=_tee(iterable)result=[iterator]for_inrange(n-1):result.append(_tee(iterator))returntuple(result)class_tee:def__init__(self,iterable):it=iter(iterable)ifisinstance(it,_tee):self.iterator=it.iteratorself.link=it.linkelse:self.iterator=itself.link=[None,None]def__iter__(self):returnselfdef__next__(self):link=self.linkiflink[1]isNone:link[0]=next(self.iterator)link[1]=[None,None]value,self.link=linkreturnvalue
當輸入的iterable 已經是一個 tee 疊代物件時,回傳的 tuple(元組)中所有成員都會被建立,就如同它們是由上游的
tee()
呼叫所產生的一樣。這個「展平步驟 (flattening step)」讓巢狀的tee()
呼叫能共享相同的底層資料鏈,並以單一的更新步驟取代一連串的呼叫。展平特性讓 tee 疊代器具備高效的預覽能力:
deflookahead(tee_iterator):"回傳下一個值,但不推進輸入"[forked_iterator]=tee(tee_iterator,1)returnnext(forked_iterator)
>>>iterator=iter('abcdef')>>>[iterator]=tee(iterator,1)# 使得輸入可預覽>>>next(iterator)# 往前移動疊代器'a'>>>lookahead(iterator)# 檢查下一個值'b'>>>next(iterator)# 繼續往前移動'b'
tee
疊代器不是執行緒安全 (threadsafe) 的。當同時使用由同一個tee()
呼叫所回傳的疊代器時,即使原始的iterable 是執行緒安全的,也可能引發RuntimeError
。此 itertool 可能需要大量的輔助儲存空間(取決於需要儲存多少臨時資料)。通常如果一個疊代器在另一個疊代器開始之前使用了大部分或全部的資料,使用
list()
會比tee()
更快。
- itertools.zip_longest(*iterables,fillvalue=None)¶
建立一個疊代器,聚合來自每個iterables 中的元素。
如果 iterables 的長度不一,則使用fillvalue 填充缺少的值。如果未指定,fillvalue 會預設為
None
。疊代將持續直到最長的可疊代物件耗盡為止。
大致等價於:
defzip_longest(*iterables,fillvalue=None):# zip_longest('ABCD', 'xy', fillvalue='-') → Ax By C- D-iterators=list(map(iter,iterables))num_active=len(iterators)ifnotnum_active:returnwhileTrue:values=[]fori,iteratorinenumerate(iterators):try:value=next(iterator)exceptStopIteration:num_active-=1ifnotnum_active:returniterators[i]=repeat(fillvalue)value=fillvaluevalues.append(value)yieldtuple(values)
如果其中一個 iterables 可能是無限的,那麼應該用別的可以限制呼叫次數的方法來包裝
zip_longest()
函式(例如islice()
或takewhile()
)。
Itertools 應用技巧¶
此段落展示了使用現有的 itertools 作為構建塊來建立擴展工具集的應用技巧。
itertools 應用技巧的主要目的是教學。這些應用技巧展示了對單個工具進行思考的各種方式 —— 例如,chain.from_iterable
與攤平 (flattening) 的概念相關。這些應用技巧還提供了組合使用工具的想法 —— 例如,starmap()
和repeat()
如何一起工作。另外還展示了將 itertools 與operator
和collections
模組一同使用以及與內建 itertools(如map()
、filter()
、reversed()
和enumerate()
)一同使用的模式。
應用技巧的次要目的是作為 itertools 的孵化器。accumulate()
,compress()
和pairwise()
itertools 最初都是作為應用技巧出現的。目前,sliding_window()
、iter_index()
和sieve()
的應用技巧正在被測試,以確定它們是否有價值被收錄到內建的 itertools 中。
幾乎所有這些應用技巧以及許多其他應用技巧都可以從 Python Package Index 上的more-itertools 專案中安裝:
python-mpipinstallmore-itertools
許多應用技巧提供了與底層工具集相同的高性能。透過一次處理一個元素而不是將整個可疊代物件一次性引入記憶體,能保持優異的記憶體性能。以函式風格 (functional style) 將工具連接在一起,能將程式碼的數量維持在較少的情況。透過優先使用「向量化 (vectorized)」的構建塊而不是使用會造成直譯器負擔的 for 迴圈和產生器,則能保持高速度。
fromcollectionsimportCounter,dequefromcontextlibimportsuppressfromfunctoolsimportreducefrommathimportcomb,prod,sumprod,isqrtfromoperatorimportitemgetter,getitem,mul,negdeftake(n,iterable):"回傳可疊代物件的前 n 個元素為串列。"returnlist(islice(iterable,n))defprepend(value,iterable):"在可疊代物件開頭插入單一值。"# prepend(1, [2, 3, 4]) → 1 2 3 4returnchain([value],iterable)deftabulate(function,start=0):"回傳 function(0), function(1), ..."returnmap(function,count(start))defrepeatfunc(function,times=None,*args):"重複呼叫一個帶指定引數的函式。"iftimesisNone:returnstarmap(function,repeat(args))returnstarmap(function,repeat(args,times))defflatten(list_of_lists):"將巢狀結構攤平一層。"returnchain.from_iterable(list_of_lists)defncycles(iterable,n):"回傳序列的元素重複 n 次。"returnchain.from_iterable(repeat(tuple(iterable),n))defloops(n):"執行 n 次的迴圈。類似 range(n) 但不建立整數序列。"# for _ in loops(100): ...returnrepeat(None,n)deftail(n,iterable):"回傳一個疊代器,疊代最後 n 個元素。"# tail(3, 'ABCDEFG') → E F Greturniter(deque(iterable,maxlen=n))defconsume(iterator,n=None):"將疊代器往前推進 n 步。如果 n 為 None,則完全消耗。"# 使用以 C 語言的速度消耗疊代器的函式。ifnisNone:deque(iterator,maxlen=0)else:next(islice(iterator,n,n),None)defnth(iterable,n,default=None):"回傳第 n 個元素或預設值。"returnnext(islice(iterable,n,None),default)defquantify(iterable,predicate=bool):"給定一個回傳 True 或 False 的判斷函式,計算為 True 的結果。"returnsum(map(predicate,iterable))deffirst_true(iterable,default=False,predicate=None):"回傳第一個為 true 的值,若無則回傳*預設值*。"# first_true([a,b,c], x) → a or b or c or x# first_true([a,b], x, f) → a if f(a) else b if f(b) else xreturnnext(filter(predicate,iterable),default)defall_equal(iterable,key=None):"回傳 True,如果所有元素兩兩相等。"# all_equal('4٤௪౪໔', key=int) → Truereturnlen(take(2,groupby(iterable,key)))<=1defunique_justseen(iterable,key=None):"產生唯一的元素,並保留原始順序。只記住剛看見的元素。"# unique_justseen('AAAABBBCCDAABBB') → A B C D A B# unique_justseen('ABBcCAD', str.casefold) → A B c A DifkeyisNone:returnmap(itemgetter(0),groupby(iterable))returnmap(next,map(itemgetter(1),groupby(iterable,key)))defunique_everseen(iterable,key=None):"產生唯一的元素,並保留原始順序。記住所有曾見過的元素。"# unique_everseen('AAAABBBCCDAABBB') → A B C D# unique_everseen('ABBcCAD', str.casefold) → A B c Dseen=set()ifkeyisNone:forelementinfilterfalse(seen.__contains__,iterable):seen.add(element)yieldelementelse:forelementiniterable:k=key(element)ifknotinseen:seen.add(k)yieldelementdefunique(iterable,key=None,reverse=False):"產生排序後的不重複元素。支援不可雜湊的輸入。"# unique([[1, 2], [3, 4], [1, 2]]) → [1, 2] [3, 4]sequenced=sorted(iterable,key=key,reverse=reverse)returnunique_justseen(sequenced,key=key)defsliding_window(iterable,n):"將資料收集成重疊的固定長度區段或區塊。"# sliding_window('ABCDEFG', 4) → ABCD BCDE CDEF DEFGiterator=iter(iterable)window=deque(islice(iterator,n-1),maxlen=n)forxiniterator:window.append(x)yieldtuple(window)defgrouper(iterable,n,*,incomplete='fill',fillvalue=None):"將資料收集成不重疊的固定長度區段或區塊。"# grouper('ABCDEFG', 3, fillvalue='x') → ABC DEF Gxx# grouper('ABCDEFG', 3, incomplete='strict') → ABC DEF ValueError# grouper('ABCDEFG', 3, incomplete='ignore') → ABC DEFiterators=[iter(iterable)]*nmatchincomplete:case'fill':returnzip_longest(*iterators,fillvalue=fillvalue)case'strict':returnzip(*iterators,strict=True)case'ignore':returnzip(*iterators)case_:raiseValueError('Expected fill, strict, or ignore')defroundrobin(*iterables):"以循環方式依序輸入可疊代物件,直到全部耗盡。"# roundrobin('ABC', 'D', 'EF') → A D E B F C# 演算法出自 George Sakkisiterators=map(iter,iterables)fornum_activeinrange(len(iterables),0,-1):iterators=cycle(islice(iterators,num_active))yield frommap(next,iterators)defsubslices(seq):"回傳序列的所有連續非空子切片。"# subslices('ABCD') → A AB ABC ABCD B BC BCD C CD Dslices=starmap(slice,combinations(range(len(seq)+1),2))returnmap(getitem,repeat(seq),slices)defiter_index(iterable,value,start=0,stop=None):"回傳在序列或可疊代物件中某值出現的索引位置。"# iter_index('AABCADEAF', 'A') → 0 1 4 7seq_index=getattr(iterable,'index',None)ifseq_indexisNone:iterator=islice(iterable,start,stop)fori,elementinenumerate(iterator,start):ifelementisvalueorelement==value:yieldielse:stop=len(iterable)ifstopisNoneelsestopi=startwithsuppress(ValueError):whileTrue:yield(i:=seq_index(value,i,stop))i+=1defiter_except(function,exception,first=None):"將一個 call-until-exception 轉換為疊代器介面。"# iter_except(d.popitem, KeyError) → 非阻塞的字典疊代器withsuppress(exception):iffirstisnotNone:yieldfirst()whileTrue:yieldfunction()
以下的應用技巧具有更多的數學風格:
defmultinomial(*counts):"多重集合的不同排列數。"# Counter('abracadabra').values() → 5 2 2 1 1# multinomial(5, 2, 2, 1, 1) → 83160returnprod(map(comb,accumulate(counts),counts))defpowerset(iterable):"來自可疊代物件的子序列,從最短到最長。"# powerset([1,2,3]) → () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)s=list(iterable)returnchain.from_iterable(combinations(s,r)forrinrange(len(s)+1))defsum_of_squares(iterable):"將輸入值的平方加總。"# sum_of_squares([10, 20, 30]) → 1400returnsumprod(*tee(iterable))defreshape(matrix,columns):"將 2 維矩陣重新塑形為指定的行數。"# reshape([(0, 1), (2, 3), (4, 5)], 3) → (0, 1, 2), (3, 4, 5)returnbatched(chain.from_iterable(matrix),columns,strict=True)deftranspose(matrix):"交換 2 維矩陣的列和行。"# transpose([(1, 2, 3), (11, 22, 33)]) → (1, 11) (2, 22) (3, 33)returnzip(*matrix,strict=True)defmatmul(m1,m2):"矩陣相乘。"# matmul([(7, 5), (3, 5)], [(2, 5), (7, 9)]) → (49, 80), (41, 60)n=len(m2[0])returnbatched(starmap(sumprod,product(m1,transpose(m2))),n)defconvolve(signal,kernel):"""兩個可疊代物件的離散線性捲積。 等同於多項式相乘。 在數學上捲積是可交換的;但輸入的處理方式不同。 訊號以惰性方式被讀取,且可以是無限;核心會在計算開始前被全部讀取。 文章:https://betterexplained.com/articles/intuitive-convolution/ 影片:https://www.youtube.com/watch?v=KuXjwB4LzSA """# convolve([1, -1, -20], [1, -3]) → 1 -4 -17 60# convolve(data, [0.25, 0.25, 0.25, 0.25]) → 移動平均(模糊)# convolve(data, [1/2, 0, -1/2]) → 一階導數估計# convolve(data, [1, -2, 1]) → 二階導數估計kernel=tuple(kernel)[::-1]n=len(kernel)padded_signal=chain(repeat(0,n-1),signal,repeat(0,n-1))windowed_signal=sliding_window(padded_signal,n)returnmap(sumprod,repeat(kernel),windowed_signal)defpolynomial_from_roots(roots):"""由多項式的根計算其係數。 (x - 5) (x + 4) (x - 3) 展開為: x³ -4x² -17x + 60 """# polynomial_from_roots([5, -4, 3]) → [1, -4, -17, 60]factors=zip(repeat(1),map(neg,roots))returnlist(reduce(convolve,factors,[1]))defpolynomial_eval(coefficients,x):"""在指定值計算多項式的值。 此方法在數值穩定性上比 Horner 方法更好。 """# 計算 x³ -4x² -17x + 60 在 x = 5 時的值# polynomial_eval([1, -4, -17, 60], x=5) → 0n=len(coefficients)ifnotn:returntype(x)(0)powers=map(pow,repeat(x),reversed(range(n)))returnsumprod(coefficients,powers)defpolynomial_derivative(coefficients):"""計算多項式的一階導數。 f(x) = x³ -4x² -17x + 60 f'(x) = 3x² -8x -17 """# polynomial_derivative([1, -4, -17, 60]) → [3, -8, -17]n=len(coefficients)powers=reversed(range(1,n))returnlist(map(mul,coefficients,powers))defsieve(n):"小於 n 的質數。"# sieve(30) → 2 3 5 7 11 13 17 19 23 29ifn>2:yield2data=bytearray((0,1))*(n//2)forpiniter_index(data,1,start=3,stop=isqrt(n)+1):data[p*p:n:p+p]=bytes(len(range(p*p,n,p+p)))yield fromiter_index(data,1,start=3)deffactor(n):"n 的質因數。"# factor(99) → 3 3 11# factor(1_000_000_000_000_007) → 47 59 360620266859# factor(1_000_000_000_000_403) → 1000000000000403forprimeinsieve(isqrt(n)+1):whilenotn%prime:yieldprimen//=primeifn==1:returnifn>1:yieldndefis_prime(n):"回傳 True,若 n 為質數。"# is_prime(1_000_000_000_000_403) → Truereturnn>1andnext(factor(n))==ndeftotient(n):"計算不大於 n 且與 n 互質的自然數個數。"# https://mathworld.wolfram.com/TotientFunction.html# totient(12) → 4 因爲 len([1, 5, 7, 11]) == 4forprimeinset(factor(n)):n-=n//primereturnn