itertools應用之一

在上一篇文章中(《迭代器案例1:zip》中,已經使用過了Python標準庫itertools,本文將繼續探討這個標準庫中的幾個有意思的函數,從而理解迭代器的應用。python

組合與排列

數學中的「組合」與「排列」,若是用程序來實現,使用Python中的itertools模塊的函數,是很是簡單實用的。單純來說排列組合問題,沒有什麼意思,能夠看一個經典的問題。算法

題目:假設有這樣一些RMB:3張20元;5張10元;2張5元;5張1元。問:如何組合出100元。編程

首先,能夠用一個列表,把已知的RMB表示出來。bash

rmb =  [20, 20, 20, 10, 10, 10, 10, 10, 5, 5, 1, 1, 1, 1, 1]
複製代碼

剛纔這個問題,是一個組合問題,好比,能夠這麼思考:先找出一張20的,而後逐個嘗試,其它各類組合可能,直到找到和爲100爲止。這樣就獲得了一種組合方式。app

Python標準庫中的itertools就爲咱們提供了這樣一個函數,實現這種組合。機器學習

>>> import itertools
>>> com = itertools.combinations([1,2,3], 2)
>>> list(com)
[(1, 2), (1, 3), (2, 3)]
複製代碼

itertools.combinations([1,2,3], 2)中的第一個參數是待組合的對象,第二個參數是組合結果中的元素個數。若是把這個函數用於rmb這個列表,就是這樣的結果:函數

>>> list(itertools.combinations(rmb, 3))
[(20, 20, 20), (20, 20, 10), (20, 20, 10), (20, 20, 10), (20, 20, 10), (20, 20, 10), (20, 20, 5), (20, 20, 5), (20, 20, 1), (20, 20, 1), (20, 20, 1), (20, 20, 1), (20, 20, 1), (20, 20, 10), (20, 20, 10), (20, 20, 10), (20, 20, 10), (20, 20, 10), (20, 20, 5), (20, 20, 5), (20, 20, 1), (20, 20, 1), (20, 20, 1), (20, 20, 1), (20, 20, 1), (20, 10, 10), (20, 10, 10), (20, 10, 10), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 10), (20, 10, 10), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 10), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 5, 5), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 20, 10), (20, 20, 10), (20, 20, 10), (20, 20, 10), (20, 20, 10), (20, 20, 5), (20, 20, 5), (20, 20, 1), (20, 20, 1), (20, 20, 1), (20, 20, 1), (20, 20, 1), (20, 10, 10), (20, 10, 10), (20, 10, 10), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 10), (20, 10, 10), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 10), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 5, 5), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 10, 10), (20, 10, 10), (20, 10, 10), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 10), (20, 10, 10), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 10), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 10), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 5), (20, 10, 5), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 10, 1), (20, 5, 5), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 5, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (20, 1, 1), (10, 10, 10), (10, 10, 10), (10, 10, 10), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 10), (10, 10, 10), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 10), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 5, 5), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 10, 10), (10, 10, 10), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 10), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 5, 5), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 10, 10), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 5, 5), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 10, 5), (10, 10, 5), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 10, 1), (10, 5, 5), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 5, 5), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 5, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (10, 1, 1), (5, 5, 1), (5, 5, 1), (5, 5, 1), (5, 5, 1), (5, 5, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (5, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1)]
複製代碼

而後,就從這些組合中挑出那些可以符合某些條件的,就是本問題的解了。不過,上面僅僅以組合3個元素爲例。實際上多是從1開始一直到rmb列表的長度爲止。固然,咱們一眼就能夠看出來,只取得1個、2個,不管怎麼組合,也達不到100。或者說,先從咱們的分析角度出發考慮,最小的可能組合應該是(20, 20, 20, 10, 10, 10, 10),即至少要有7張鈔票才能組合出100元。post

>>> makes_100 = []
>>> for n in range(7, len(rmb)+1):
...     for comb in itertools.combinations(rmb, n):
...         if sum(comb) == 100:
...             makes_100.append(comb)
...
複製代碼

注意,這時候獲得的makes_100,並非最終結果。由於裏面有不少重複的,之因此這樣,是由於rmb中的元素就有重複的。因此,還要進一步去重。學習

>>> set(makes_100)
{(20, 20, 20, 10, 10, 10, 5, 5), (20, 20, 20, 10, 10, 10, 5, 1, 1, 1, 1, 1), (20, 20, 10, 10, 10, 10, 10, 5, 5), (20, 20, 10, 10, 10, 10, 10, 5, 1, 1, 1, 1, 1), (20, 20, 20, 10, 10, 10, 10)}
複製代碼

獲得了最終結果,能夠有5中組合方案,實現100元的目標。測試

按照Python的編程習慣,上面的程序能夠寫成:

>>> makes_100 = [comb for n in range(7, len(rmb)+1) for comb in itertools.combinations(rmb, n)  if sum(comb)==100]
>>> set(makes_100)
{(20, 20, 20, 10, 10, 10, 5, 5), (20, 20, 20, 10, 10, 10, 5, 1, 1, 1, 1, 1), (20, 20, 10, 10, 10, 10, 10, 5, 5), (20, 20, 10, 10, 10, 10, 10, 5, 1, 1, 1, 1, 1), (20, 20, 20, 10, 10, 10, 10)}
複製代碼

是否是顯示出了Python的簡潔。

這個問題還能夠這樣變化一下:

題目:有若干張面額爲50、20、十、五、1元的RMB,問有多少種方式,可以組合出100元。

問題變化了,所用函數也要變化。由於combinations()的做用是從已知數據集中選擇若干個元素組合,可是,並不會實現如本題中所要求的。例如問題中最直接的一種組合就是100個1元的。而這種組合是combinations函數沒法提供的。

itertools中還有另一個函數,它能幫助咱們解決這個問題。

>>> list(itertools.combinations_with_replacement([1, 2], 2))
[(1, 1), (1, 2), (2, 2)]
>>> list(itertools.combinations([1,2], 2))    #注意比較二者的結果
[(1, 2)]
複製代碼

從上述操做中,就能夠看出combinations_with_replacementcombinations的區別了。combinations_with_replacement就是用來解決這個問題的函數。

>>> rmbs = [50, 20, 10, 5, 1]
>>> makes_100_2 = []
>>> for n in range(2, 101):
...     for comb in itertools.combinations_with_replacement(rmbs, n):
...         if sum(comb) == 100:
...             makes_100_2.append(comb)
...
#開始執行上面的程序,須要一段時間,你能夠此時完成以下活動:喝水、上廁所、想一想人生。
>>> len(makes_100_2)
343
複製代碼

儘管上面的程序比較耗費時間,畢竟可以用暴力的方式解決了上述問題——須要執行96,560,645此組合。

對於「錢幣組合」問題,是算法中的一個經典問題,在這裏咱們其實使用了「暴力窮舉」的方法,不算是最好的方法,只是演示itertools中的兩個函數的使用方法。

在itertools中,除了提供上述實現組合的函數以外,還提供了實現排列功能的函數:permutations()

>>> import itertools
>>> list(itertools.permutations(['a', 'b', 'c']))
[('a', 'b', 'c'), ('a', 'c', 'b'), ('b', 'a', 'c'), ('b', 'c', 'a'), ('c', 'a', 'b'), ('c', 'b', 'a')]
複製代碼

數列

itertools模塊中,還提供了生成包含無限項的迭代器對象——注意,雖說包含了無限項,可是由於它們並無被讀入內存,因此不用擔憂。這就是迭代器的優點。

奇數和偶數

從數學的角度來講,奇數和偶數,都有無限項。那麼,在Python中如何表示這樣的集合?這就要建立一個迭代器——其實下面所建立是生成器,生成器也是迭代器。

>>> def evens():
...     n = 0
...     while True:
...         yield n
...         n += 2
...
複製代碼

這個函數,就可以生成偶數集合,它實際上是返回了一個生成器對象——《跟老齊學Python:輕鬆入門》中有對生成器的詳細講述。

>>> e = evens()    #①
>>> list(next(e) for _ in range(6))    #②
[0, 2, 4, 6, 8, 10]
複製代碼

①獲得了偶數集合的生成器對象,在②中,經過循環語句,生成6個偶數,這時候,這些偶數才被讀入內存。

一樣的道理,奇數的生成器函數,也是相似的。

>>> def odds():
...     n = 1
...     while True:
...         yield n
...         n += 2
...
複製代碼

無限多個數

在itertools中,還提供了另一個函數,實現對某種特定規律的數的集合的迭代器對象的建立。

例如天然數:

>>> counter = itertools.count()
>>> list(next(counter) for _ in range(9))
[0, 1, 2, 3, 4, 5, 6, 7, 8]
複製代碼

跟前面偶數和奇數集合的生成器對象相似,這裏獲得的counter對象也是隻有在讀取數值的時候,才根據設定的個數將相應的天然數讀入內存——0是否是天然數?這裏姑且認爲是吧。

若是使用help()查看這個函數的幫助信息,能夠看到這種表述:

count(start=0, step=1) --> count object

counter = itertools.count()中沒有設置參數step的值,意味着採用了默認的step=1,而且另一個參數start=0。從這兩個參數的設置可知,咱們能夠設置從某個數值開始直到無限大,且步驟爲某個值的天然數集合。那麼前面演示過的偶數和奇數集合,就有另一種方式實現了。

#偶數的迭代器
>>> evens = itertools.count(step=2)
>>> list(next(evens) for _ in range(9))
[0, 2, 4, 6, 8, 10, 12, 14, 16]

#奇數的迭代器
>>> odds = itertools.count(start=1, step=2)
>>> list(next(odds) for _ in range(9))
[1, 3, 5, 7, 9, 11, 13, 15, 17]
複製代碼

其實,不只能夠針對天然數,還能夠針對浮點數進行操做。注意:要Python3.1以後的版本。

>>> counter_float = itertools.count(start=0.5, step=0.25)
>>> list(next(counter_float) for _ in range(9))
[0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5]
複製代碼

負數也能夠。

>>> counter_negative = itertools.count(start=-1, step=-0.3)
>>> list(next(counter_negative) for _ in range(9))
[-1, -1.3, -1.6, -1.9000000000000001, -2.2, -2.5, -2.8, -3.0999999999999996, -3.3999999999999995]
複製代碼

上面的結果中出現了一些特別的浮點數,這是什麼緣由——這個問題我早就在《跟老齊學Python:輕鬆入門》一書中講得很清楚了,欲知詳情,請參考。

經過上述操做,itertools.count()range()的區別也很是明顯了。在針對天然數部分,二者功能相似,而且range也返回迭代器對象。可是,range返回的對象都是在有限範圍以內,count則是無限的。另外的差異在於,count不只能夠處理天然數範圍的數字,還能處理負數、浮點數。

前面提到了生成器,雖說有無限個數的集合有不少,而且前面列舉了幾個,多數經過itertools.count能夠實現,對於它不能實現的,還能夠寫一個生成器(迭代器)函數來實現。下面這個例子,是在這時候必定要舉出來的,那就是編寫斐波那契數列的生成器函數——關於斐波那契數列的其它更多寫法,推薦閱讀拙做《跟老齊學Python:輕鬆入門》。

>>> def fibs():
...     a, b = 0, 1
...     while True:
...         yield a
...         a, b = b, a + b
...
複製代碼

無限多項的斐波那契數列,就能夠經過這個生成器函數得到。

>>> f = fibs()
>>> list(next(f) for _ in range(9))
[0, 1, 1, 2, 3, 5, 8, 13, 21]
複製代碼

獲得了9項斐波那契數列。

對於無限項的集合,要取出其部分有限個數的數字,除了用上面的循環方式next(f) for _ in range(9)以外,還可使用itertools中的一個函數。例如:

>>> counter = itertools.count()
>>> list(itertools.islice(counter, 9))
[0, 1, 2, 3, 4, 5, 6, 7, 8]
複製代碼

itertools真不辜負它的名字。

遞推關係式

在數學中,遞歸或者遞推關係( recurrence relation)在數列中常見,好比等差數列、等比數列等,都是遞歸關係式。從這點上來看,前面使用itertools.count獲得的數列,都是一種遞歸關係的數列。

前面列舉的斐波那契數列數列,其實也是一些具備遞歸關係的數。或者說,也能夠經過「遞歸」實現斐波那契數列函數。

不過,還有一種遞歸關係的數列,不能用itertools.count獲得,那就是全部的數字都是同樣的,好比都是由1組成的,或者是由某幾個數字組成,好比都是由1,3,5組成。

對此,itertools模塊提供了別的函數。

>>> ones = itertools.repeat(1)
>>> list(itertools.islice(ones, 9))
[1, 1, 1, 1, 1, 1, 1, 1, 1]
複製代碼

itertools.repeat函數專門生成由某個數字組成的無限長數列。注意:其參數除了數字以外,還能夠是其它對象,好比字符串、列表等。

固然,建立有限的也能夠,那就增長一個標識元素個數的參數。

>>> five_ones = itertools.repeat(1, 5)
>>> [i for i in five_ones]
[1, 1, 1, 1, 1]
複製代碼

另一種狀況,則可使用itertools.cycle函數實現。先不用看示例演示,看函數名稱,就能猜想到結果應該是什麼樣子的——[1,3,5,1,3,5,...],應該如此,不然就不能是cycle了。

>>> otf = itertools.cycle([1, 3, 5])
>>> list(itertools.islice(otf, 9))
[1, 3, 5, 1, 3, 5, 1, 3, 5, 1, 3, 5, 1, 3, 5]
複製代碼

上面的演示中,只取出數列的前15項看看,果真不出所料。

以上演示的都是遞推關係中的特例,下面要用數學形式說明一下遞推關係中的所謂一階和二階遞推關係。

a_n = Pa_{n-1} + h, (n\in N^+, P,h爲常數)

數列中相鄰兩項符合此式子的,就是一階遞歸關係數列。

  • P=1,爲等差數列
  • P=1,且h=0,爲常數列
  • P≠0,且h=0,爲等比數列。

還有另一種遞推關係,它表示的是某一項和相鄰的兩項之間的關係,好比前面的斐波那契數列,第三項等於前兩項和。這種關係的通項式能夠表示爲:

a_n = Pa_{n-1} + Qa_{n-2} + h, (n\in N^+, P,Q,h爲常數)

對於此通項式,若是P=Q=1,且h=0,那麼它就稱爲了斐波那契數列。

若是根據上述通項式,可知前面用countrepeatcycle等所實現的數列,都是特殊數列。有沒有比它們更通常的呢?

必須有。

>>> list(range(9))
[0, 1, 2, 3, 4, 5, 6, 7, 8]
>>> list(itertools.accumulate(range(9)))
[0, 1, 3, 6, 10, 15, 21, 28, 36]
複製代碼

注意,上面實現的不是斐波那契數列,而是按照下圖所示方式獲得了一個新的數列。

看到這個函數,你應該想起來另一個函數,若是沒有想起來,說明你的基礎知識尚有發展的空間。

>>> import functools
>>> functools.reduce(lambda x,y :x+y, range(9))
36
複製代碼

functools.reduce相比,itertools.accumulate返回的不是一個數,而是數列,若是嚴格地說,是由數列元素做爲潛在對象的迭代器對象。

已經看到了itertools.accumulate(range(9))的結果了。而對於accumulate()而言,其完整的參數形式是這樣的:

accumulate(iterable[, func]) --> accumulate object

默認狀況,後面的func是加法函數。因此,像下面的方式與前述示例是同樣的。

>>> import operator
>>> list(itertools.accumulate(range(9), operator.add))
[0, 1, 3, 6, 10, 15, 21, 28, 36]
複製代碼

根據這個原理,就能夠對不少數列進行操做,依據函數關係獲得另一個數列了。下面依次列舉若干示例。

>>> list(itertools.accumulate([9, 21, 17, 5, 11, 12, 2, 6], min))
[9, 9, 9, 5, 5, 5, 2, 2]

>>> list(itertools.accumulate([1, 2, 3, 4, 5], lambda x, y: (x + y) / 2))
[1, 1.5, 2.25, 3.125, 4.0625]
複製代碼

還記得前面的一階遞歸關係式嗎?若是寫一個函數,實現那個關係式?

>>> def first_order(p, h, initial_val):
...     return itertools.accumulate(itertools.repeat(initial_val), lambda a, _: p*a + h)
...
複製代碼

這個函數就是一階遞歸通項式,經過它可以獲得一階遞歸的數列。

在函數中綜合運用了學習過的兩個函數。下面測試一下。

>>> evens = first_order(p=1, h=2, initial_val=0)    #獲得偶數數列
>>> list(itertools.islice(evens, 5))
[0, 2, 4, 6, 8]

#能被3整除的數
>>> count_by_threes = first_order(p=1, h=3, initial_val=0)
>>> list(itertools.islice(count_by_threes, 5))
[0, 3, 6, 9, 12]

#由1,-1組成的數列
>>> alternating_ones = first_order(p=-1, h=0, initial_val=1)
>>> list(itertools.islice(alternating_ones, 5))
[1, -1, 1, -1, 1]

#都是1組成的數列
>>> all_ones = first_order(p=1, h=0, initial_val=1)
>>> list(itertools.islice(all_ones, 5))
[1, 1, 1, 1, 1]
複製代碼

經過上面的示例不難看出,當獲得了一階遞推關係式通項以後,全部一階遞推數列,均可以通吃了。

厲害。

一樣,也能夠寫出二階遞歸關係的通項函數。

>>> def second_order(p, q, h, initial_val):
...     itermediate = itertools.accumulate(
                              itertools.repeat(initial_val), 
                              lambda a, _:(a[1], p*a[1] + q*a[0] + h))
...     return map(lambda x: x[0], itermediate)
...
複製代碼

二階遞歸關係通項函數貌似複雜。由於二階遞推通項式中,初始值是前兩項,因此,若是直接實現數學函數值,返回的是兩個值,就不得不使用map函數,把其中的值一個一個取出來。

寫好以後,用它來生成斐波那契數列。

>>> fibs = second_order(p=1, q=1, h=0, initial_val=(0, 1))
>>> list(itertools.islice(fibs, 9))
[0, 1, 1, 2, 3, 5, 8, 13, 21]
複製代碼

對照前面斐波那契數列數列。感覺到二階遞推關係的通項函數魅力了吧。

典型的二階遞推關係除了斐波那契數列以外,還有佩爾數(Pell number)和盧卡斯數(Lucas number)。

Pell number:

P_n = \begin{cases} 0, & \text {if n=0} \\ 1, & \text{if $n=1$ } \\2P_{n-1} + P_{n-2}, &\text{otherwise} \end{cases}

Lucas Number:

L_n = \begin{cases}2, & \text{if n=0;}\\1, & \text{if n=1;} \\L_{n-1} + L_{n-2}, & \text{if n>1.} \end{cases}

下面就用上面的二階遞推關係通項實現這兩個數列。

>>> pell = second_order(p=2, q=1, h=0, initial_val=(0, 1))
>>> list(itertools.islice(pell, 6))
[0, 1, 2, 5, 12, 29]

>>> lucas = second_order(p=1, q=1, h=0, initial_val=(2, 1))
>>> list(itertools.islice(lucas, 6))
[2, 1, 3, 4, 7, 11]
複製代碼

迭代器的確是個好東西,itertools還有更多好內容。


系列圖書:

  • 《跟老齊學Python:輕鬆入門》:面向Python語言入門學習者,本書基於Python3.x
  • 《跟老齊學Python:Django實戰》:面向學習Web開發的讀者,本書的初版是基於Django1.10,第二版基於Django2.x
  • 《跟老齊學Python:數據分析》:面向數據科學(數據分析、機器學習)的入門學習者
相關文章
相關標籤/搜索