Python 迭代器小節--與C++迭代器的一些思考

最近花了20天才把C++ Primer的“容器與算法”一章看完,過程太長,嚴重打亂了我的讀書計劃,但是看的時間長有時間長的好處,我可以不斷地反思C++容器與算法設計的優劣。結合前一段時間看的《Python cookbook》一書中的迭代器,特做此札記。

1、Python iter()

先說python中的迭代器,特點:簡單易用,方便自定義,還有強大的itertools模塊增強迭代器的功能。

iter(arg)以可迭代對象arg作爲形參生成一個迭代器對象,所謂的迭代器對象其實是實現了python迭代器協議的對象。Python的迭代器協議是指__iter__() 方法返回一個實現了 __next__() 方法的迭代器對象,通過next()方法遍歷這個迭代器對象,最終以StopIteration停止遍歷。

如:

class Node:
    def __init__(self, value):
        self._value = value
        self._children = []

    def __repr__(self):
        return 'Node({!r})'.format(self._value)

    def add_child(self, node):
        self._children.append(node)

    def __iter__(self):
        return iter(self._children)

這裏iter(self._children)只是簡單地調用self._children.__iter__()返回對應的迭代器對象。這是一種較爲簡單的傳遞迭代請求的操作。

如果想創建新的迭代模式(不同於range()、reversed()函數的操作),可以藉助python的生成器來實現:

def frange(start, stop, increment):
    x = start
    while x < stop:
        yield x
        x += increment

你可以用for循環迭代它或者使用其他接受一個可迭代對象的函數(比如 sum() , list() 等)。

>>> for n in frange(0, 4, 0.5):
    ... print(n)

>>> 0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
>>> list(frange(0, 1, 0.125))
>>> [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]

for循環會自動處理迭代終止等細節。

reversed()實現反向迭代:

>>> a = [1, 2, 3, 4]
>>> for x in reversed(a):
...     print(x)
>>> 4
3
2
1

前面看到的迭代器都是簡單的前向迭代(forward iterator),而python中也有實現了類似於C++中的reverse_iterator的功能。程序員可以在自定義類上實現 __reversed__() 方法來實現反向迭代。

class Countdown:
    def __init__(self, start):
        self.start = start

    # Forward iterator
    def __iter__(self):
        n = self.start
        while n > 0:
            yield n
            n -= 1

    # Reverse iterator
    def __reversed__(self):
        n = 1
        while n <= self.start:
            yield n
            n += 1

for rr in reversed(Countdown(30)):
    print(rr)
for rr in Countdown(30):
    print(rr)

Python 迭代器的缺陷在於並不能對其進行隨機訪問,但是itertools模塊實現了類似於C++隨機訪問迭代器(random-access iterator)的功能:

>>> def count(n):
...     while True:
...         yield n
...         n += 1
...
>>> c = count(0)
>>> c[10:20]
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable

>>> # Now using islice()
>>> import itertools
>>> for x in itertools.islice(c, 10, 20):
...     print(x)

函數 islice() 返回一個可以生成指定元素的迭代器,它通過遍歷並丟棄直到切片開始索引位置的所有元素。 然後纔開始一個個的返回元素,並直到切片結束索引位置。

同時,itertools模塊還提供了對輸入序列的排列、組合形式的迭代:itertools.permutations(),itertools.combinations();對多個序列的迭代:itertools.chain()操作等。這些API可以極大地減少冗餘代碼並且提高程序運行效率。

2、C++的迭代器

C++的迭代器與標準庫std中的容器和算法是密不可分的,標準庫的100多個泛型算法是根據迭代器設計的,而容器的大部分操作採用迭代器來實現。同時,有根據容器支持的操作來給迭代器進行分類。

標準庫根據所支持的操作定義了五種迭代器類別:輸入迭代器、輸出迭代器、前向迭代器、雙向迭代器、隨機訪問迭代器。

輸入迭代器是隻能讀不能寫元素的迭代器,python中的迭代器都是輸入迭代器。C++中的輸入迭代器有istream_iterator類型,從輸入流istream對象中讀取數據,這種迭代器支持 == 、!= 、++ 、解引用操作符*和箭頭操作符->,迭代器只能向前遞進指向下一個元素。

輸出迭代器可以向容器寫入元素,如ostream_iterator類型,向輸入流ostream對象寫入數據。

前向迭代器只會以一個方向遍歷序列,可以讀寫序列。

雙向迭代器可以從兩個方向讀寫迭代器,即支持 ++ 和 -- 操作,支持這種類型迭代器的容器有 list、map、set。需要使用雙向迭代器的泛型算法有reverse()。

隨機訪問迭代器提供在常量時間內訪問容器任意位置的功能,它除了支持雙向迭代器的所有功能外,還支持迭代器與整形數值n之間的加減法操作、兩個迭代器之間的減法操作、下表操作符iter[n](等同於*(iter+n))。需要用到隨機訪問迭代器的算法有sort()函數。vector、deque、string的迭代器都是隨機訪問迭代器,內置元素數組的指針也是隨機訪問迭代器。

 

總結python和C++中的迭代器知識,私以爲還是python更勝一籌,結合了生成器的迭代器無論是在效率上還是代碼簡潔之美上都比C++要好一點。C++標準庫算法基於迭代器進行設計,而由於各種順序容器、關聯容器支持的迭代操作不同,標準庫算法並不能完全適用所有的序列類型,因此泛型算法中存在很多重載版本和不同命名規則版本的算法,這給閱讀維護代碼庫都帶來了負擔。

以上是小生個人理解,如有錯誤,望諸君指出!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章