Functional Programming in Python Ch3: Lazy Evaluation, Note

這一章主要在講iterator。對一個可以用迴圈取的集合來說,如果你直接將整個集合傳入,勢必要evaluate每個值。若是這集合會被另一程式來增加元素,或是每個元素其實是一個statement(需要做點運算),在放入迴圈取值時,就evaluate會不合理。所以就需要lazy evaluation。函數式編成的語言比較會有直接下lazy的初始化,一般語言好像沒有。
This chapter is mainly about iterator. For a collection that can be traversed, if you pass the whole collection into somewhere else, in Python, it will evaluate every element. It seems unreasonable if every statement needs some calculation, so that is why lazy evaluation comes in. I found out that functional language tends have lazy tag in initialization, but not for normal(OOP) language.

If you initialize a list

a = [fib(0), fib(1), …, fib(35),fib(36)]

裡面費波納契數列非常耗時(用遞迴的情況下),你希望程式啟動時不要浪費時間在上面的話,必須改用generator(Scala的 話,應該會用Stream)
The fibonacci methods cost a lot. If you don’t want these calculation delay your program initialization, you have to use "generator".(In Scala, that would be "Stream”)

In this example, you could use

b = gen(36)

In this way, the program would return immediately. It would do the calculation until real usage.

Iterator protocol: 若要讓一個物件iterable, 主要你要實作__iter__()跟__next__()。 第一個回傳本身,第二個傳回下一個元素。__iter__()這函數是idempotent的意思是,iter(A)==A==iter(iter(A)),(線性代數裡的矩陣運算有提過)
Iterator protocol: To make an object iterable, you have to implement __iter__() and __next__(). The former one should return itself, and for the latter one, return the next element. By the way, __iter__() should be idempotent, which means iter(A)==A==iter(iter(A)), (This term should be mentioned in matrix computaion in Linear Algerbra)

這邊書上用了itertools的5個函數,分別是 tee, accumulate,chain, chain.from_iterable, count。
In the following, the book illustrated the built-in lib: itertools which is used commonly. Normally, programmer use this lib instead of inventing a new wheel. This chapter talked about 5 methods, which are tee, accumulate,chain, chain.from_iterable, count

tee主要接收一個iterable,並將它換成n個iterables。像是[[1,2], [3,4]],本來iterable取第一第二個list變成取兩個int的iterable。
tee takes an iterable as argument and will return n iterables. Like [[1,2], [3,4]], the original iterable would return the first and second list, after tee, you would get two iterables that return Int from two list.

accumlate: 很像Scala的scan,只是不需要初始值。回傳同樣長度的list,第一個元素是你給的運算式跟初始值,第二個是前一個與原本資料的第二個做運算式裡面的運算,以此類推
accumlate: It resemble “scan” in Scala, but without initial value. It would return the list with same length. The first element would be the operation you give on the initial value, the second one would be the operation on former element and second element from original list, and so on.

>>> data = [3, 4, 6, 2, 1, 9, 0, 7, 5, 8]>>> list(accumulate(data, operator.mul))     # running product[3, 12, 72, 144, 144, 1296, 0, 0, 0, 0]

As for Scala

val d = List(3, 4, 6, 2, 1, 9, 0, 7, 5, 8)println(d.scan(1)(_*_).tail)//List(3, 12, 72, 144, 144, 1296, 0, 0, 0, 0)//res1: Unit = ()

“chain” is much easier to understand by example. It would return an iterator to traverse all elements from all iterables you give.

b = chain([1,2],[3,4])for i in b:    print i    1234

from_iterable is like chain. It is only in python3.

a = chain.from_iterable([[1,2],[3,4]])for i in a:    print(i)     1234

count,給定初始值跟差值,可以給出序列,像是generator 一樣
count: Given initial value and increment, it would return a list if you traverse. Like generator.

像是count(10),會回傳一個iterator給你,一值往下一個call會得到遞增1的元素,比起range(10, n)這種直接evaluate的list,既是lazy也不用給最大值。
Like count(10), it would return an iterator. If you call __next__(), you would get element 1 larger that previous element. Compared to range(10, n) which evaluates every element immediately, it is lazy and you don’t need to give the maximum value.

# count(10) --> 10 11 12 13 14 ...# count(2.5, 0.5) -> 2.5 3.0 3.5 ...

The examples on the book are less easier to understand, so I would not put them here.


Like my work?
Don't forget to support or like, so I know you are with me..