Python標準庫筆記(10) — itertools模塊

itertools 用於更高效地建立迭代器的函數工具。python

itertools 提供的功能受Clojure,Haskell,APL和SML等函數式編程語言的相似功能的啓發。它們的目的是快速有效地使用內存,而且將它們關聯在一塊兒以表示更復雜的基於迭代的算法。算法

基於迭代器的代碼比使用列表的代碼提供了更好的內存消耗特性。由於直到數據須要使用時才從迭代器中生成,全部數據不須要同時存儲在內存中。這種 「惰性」 的處理模式能夠減小大型數據集的交換和其餘反作用,從而提升性能。編程

除了 itertools 中定義的函數以外,本文中的示例還使用了一些內置函數進行迭代。編程語言

1.合併和分割

chain() 函數將多個迭代器做爲參數,並返回一個迭代器,這樣它生成全部輸入的內容,就像來自於單個迭代器同樣。ide

from itertools import chain

for i in chain([1, 2, 3], ['a', 'b', 'c']):
    print(i, end=' ')

使用 chain() 能夠輕鬆地處理多個序列,而不須要生成一個更大的序列。函數式編程

# OutPut
1 2 3 a b c

若是要組合的迭代不是所有預先顯式聲明的,或者須要惰性計算的,則可使用 chain.from_iterable() 來替換 chain()函數

from itertools import chain

def make_iterables_to_chain():
    yield [1, 2, 3]
    yield ['a', 'b', 'c']

for i in chain.from_iterable(make_iterables_to_chain()):
    print(i, end=' ')
# OutPut
1 2 3 a b c

Python內置函數 zip() 也是返回一個迭代器,但它是將幾個迭代器的元素組合成元組。工具

for i in zip([1, 2, 3], ['a', 'b', 'c']):
    print(i)

zip() 和本模塊中的其餘函數同樣,返回一個可迭代的對象,每次迭代產生一個值。性能

# OutPut
(1, 'a')
(2, 'b')
(3, 'c')

可是, 使用 zip() 時當第一個輸入迭代器耗盡時,zip() 就會中止。若是要處理全部的輸入,即便迭代器產生不一樣數量的值,那麼可使用 zip_longest()測試

from itertools import zip_longest

r1 = range(3)
r2 = range(2)

print('使用zip會提早結果迭代:')
print(list(zip(r1, r2)))
print()
print('zip_longest會處理完全部值:')
print(list(zip_longest(r1, r2)))

默認狀況下,zip_longest() 會使用 None 來填充缺失位置的值。使用 fillvalue 參數來設置不一樣的替代值。

# OutPut
使用zip會提早結果迭代:
[(0, 0), (1, 1)]

zip_longest會處理完全部值:
[(0, 0), (1, 1), (2, None)]

islice() 函數返回一個迭代器,用於經過索引返回輸入迭代器的指定項。

from itertools import islice

print('Stop at 5:')
for i in islice(range(100), 5):
    print(i, end=' ')
print()

print('Start at 5, Stop at 10:')
for i in islice(range(100), 5, 10):
    print(i, end=' ')
print()

print('By tens to 100:')
for i in islice(range(100), 0, 100, 10):
    print(i, end=' ')
print()

islice() 接收和列表切片相同的參數:startstopstepstartstep 參數是可選的。

# OutPut
Stop at 5:
0 1 2 3 4 
Start at 5, Stop at 10:
5 6 7 8 9 
By tens to 100:
0 10 20 30 40 50 60 70 80 90

tee() 函數做用是根據單個原始輸入返回多個獨立的迭代器(默認爲兩個)。

from itertools import islice, tee

r = islice(range(10), 5)
i1, i2 = tee(r)

print('i1:', list(i1))
print('i2:', list(i2))

tee() 具備與Unix tee 實用程序相似的語義,它從它的輸入中重複地讀取的值並將它們寫入一個命名文件和標準輸出。 經過 tee() 函數能夠將同一組數據提供給多個算法並行處理。

# OutPut
i1: [0, 1, 2, 3, 4]
i2: [0, 1, 2, 3, 4]

須要注意,由 tee() 建立的新迭代器將共享它們的輸入,所以在建立新迭代器後不要再使用輸入的迭代器。

from itertools import islice, tee

r = islice(range(10), 5)
i1, i2 = tee(r)

print('迭代原始:', end=' ')
for i in r:
    print(i, end=' ')
    if i > 1:
        break
print()

print('i1:', list(i1))
print('i2:', list(i2))

若是原始迭代器已經消耗了一些值,那麼新的迭代器將不會生成這些值。

# OutPut
迭代原始: 0 1 2
i1: [3, 4]
i2: [3, 4]

2.計算輸入

Python內置的 map() 函數返回一個迭代器。 該迭代器根據輸入迭代器中的值調用函數,並返回結果。當任意一個輸入迭代器耗盡時它就馬上中止。

def times_two(x):
    return 2 * x

def multiply(x, y):
    return (x, y, x * y)

print('單個輸入:')
for i in map(times_two, range(5)):
    print(i, end=' ')

print('\n多個輸入:')
r1 = range(5)
r2 = range(5, 10)
for i in map(multiply, r1, r2):
    print('{:d} * {:d} = {:d}'.format(*i))

print('\n迭代中止:')
r1 = range(5)
r2 = range(2)
for i in map(multiply, r1, r2):
    print(i)

在第一個例子中,函數將全部輸入值乘以2。在第二個例子中,函數將從兩個單獨的迭代器中獲取的兩個參數相乘,並返回一個包含原始參數和計算值的元組。第三個例子中,在生成了兩個元組以後便中止了,由於第二個輸入已經耗盡。

# OutPut
單個輸入:
0 2 4 6 8
多個輸入:
0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36

迭代中止:
(0, 0, 0)
(1, 1, 1)

starmap() 函數與 map() 相似,但不是從多個迭代器構造元組,而是使用 * 語法將單個迭代器中的項做爲參數解包給map函數。

from itertools import starmap

values = [(0, 5), (1, 6), (2, 7), (3, 8), (4, 9)]

for i in starmap(lambda x, y: (x, y, x * y), values):
    print('{} * {} = {}'.format(*i))

若是使用 map() 函數將是這種調用 f(i1,i2) ,而使用 starmap() 直接是 f(*i)

#OutPut
0 * 5 = 0
1 * 6 = 6
2 * 7 = 14
3 * 8 = 24
4 * 9 = 36

3.產生新值

count() 函數會返回一個能夠無限地產生連續整數的迭代器。第一個數字能夠做爲參數傳遞(默認值爲0)。沒有上限參數(有關對結果集的更多控制,請參閱內置的 range())。

from itertools import count

for i in zip(count(1), ['a', 'b', 'c']):
    print(i)

此示例由於使用了 zip() 和有限長度列表參數因此才中止。

# OutPut
(1, 'a')
(2, 'b')
(3, 'c')

count() 的start和step參數能夠是任何能夠加在一塊兒的數字值。

import fractions
from itertools import count

start = fractions.Fraction(1, 3)
step = fractions.Fraction(1, 3)

for i in zip(count(start, step), ['a', 'b', 'c']):
    print('{}: {}'.format(*i))

本例中,起始點和步長來自 Fraction (分數)模塊的 fraction 對象。

# OutPut
1/3: a
2/3: b
1: c

cycle() 函數的做用是:返回一個迭代器,該迭代器重複無限地給出的參數的內容。由於它必須記住輸入迭代器的所有內容,因此若是迭代器很長,它可能會消耗至關多的內存。

from itertools import cycle

for i in cycle(['a', 'b', 'c']):
    print(i)

若是沒有打斷,它會無限循環下去。

# OutPut
a
b
c
a
b
...

repeat() 函數的做用是:返回一個迭代器,該迭代器每次訪問時都會產生相同的值。

from itertools import repeat

for i in repeat('over-and-over', times=5):
    print(i)

repeat() 返回的迭代器將不斷返回數據,除非提供可選的times參數來限制次數。

# OutPut
over-and-over
over-and-over
over-and-over
over-and-over
over-and-over

當須要將某個固定值包含在其餘迭代器的值中時,使用 repeat()zip()map() 組合會頗有用。

from itertools import repeat, count

for i, s in zip(count(), repeat('over-and-over', 5)):
    print(i, s)

在本例中,count值與 repeat() 返回的常量組合在一塊兒。

此示例使用 map() 將從0到4的數字乘以2。

from itertools import repeat

for i in map(lambda x, y: (x, y, x * y), repeat(2), range(5)):
    print('{:d} * {:d} = {:d}'.format(*i))

本例中 repeat() 不須要顯式限制迭代次數,由於 range() 只返回五個元素, map() 在其任意輸入結束時會中止處理。

# OutPut
2 * 0 = 0
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8

4.過濾

dropwhile() 函數的做用是:返回一個迭代器,直到條件第一次爲false時,該迭代器開始才產生輸入迭代器的元素。

from itertools import dropwhile

def should_drop(x):
    print('輸入:', x)
    return x < 1

for i in dropwhile(should_drop, [-1, 0, 1, 2, -2]):
    print('產出:', i)

dropwhile() 不會過濾每一個輸入項; 當第一次條件爲假後,便直接返回輸入中的全部剩餘項目。

# OutPut
輸入: -1
輸入: 0
輸入: 1
產出: 1
產出: 2
產出: -2

dropwhile() 相反的是 takewhile() 。它返回一個迭代器,只要測試函數返回true, 該迭代器就返回輸入迭代器中的項目。

from itertools import takewhile

def should_take(x):
    print('輸入:', x)
    return x < 1

for i in takewhile(should_take, [-1, 0, 1, 2, -2]):
    print('產生:', i)

一旦should_take()返回 False, takewhile()就中止處理輸入。

# OutPut
輸入: -1
產生: -1
輸入: 0
產生: 0
輸入: 1

Python內置函數 filter() 是返回一個包含測試函數返回true的全部項的迭代器。

def check_item(x):
    print('輸入:', x)
    return x < 1

for i in filter(check_item, [-1, 0, 1, 2, -2]):
    print('產出:', i)

filter() 不一樣於 dropwhile()takewhile() 的是,filter() 每一個項目在返回以前都代入測試函數。

# OutPut
輸入: -1
產出: -1
輸入: 0
產出: 0
輸入: 1
輸入: 2
輸入: -2
產出: -2

filterfalse() 返回一個迭代器,該迭代器只包含測試函數返回false的項。

from itertools import filterfalse

def check_item(x):
    print('輸入:', x)
    return x < 1

for i in filterfalse(check_item, [-1, 0, 1, 2, -2]):
    print('產出:', i)

測試函數 check_item() 和上例中的同樣,可是返回的結果正好和 filter() 相反。

# OutPut
輸入: -1
輸入: 0
輸入: 1
產出: 1
輸入: 2
產出: 2
輸入: -2

compress() 提供了另外一種過濾可迭代內容的方法。它再也不是調用函數,而是使用另外一個迭代中的值來指示什麼時候接受值什麼時候忽略值。

from itertools import compress, cycle

every_third = cycle([False, False, True])
data = range(1, 10)

for i in compress(data, every_third):
    print(i, end=' ')

compress() 的第一個參數是須要進行處理的可迭代數據,第二個參數是可迭代的生成的布爾值選擇器,指示從數據輸入中取出哪些元素(True產生值,False忽略)。

# OutPut
3 6 9

5.聚合

groupby() 函數返回一個迭代器,該迭代器生成由公共鍵聚合的值集。下面例子展現基於屬性對相關值進行分組。

from itertools import groupby
import functools
import operator
import pprint


@functools.total_ordering
class Point:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return '({}, {})'.format(self.x, self.y)

    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

    def __gt__(self, other):
        return (self.x, self.y) > (other.x, other.y)


# 生成Point實例的數據集
data = list(map(Point, [1, 2, 3, 1, 2], range(5)))
print('Data:')
pprint.pprint(data, width=35)
print()

# 對無序的data基於屬性x聚合
print('聚合, 無序data:')
for k, g in groupby(data, operator.attrgetter('x')):
    print(k, list(g))
print()

# 對data排序
data.sort()
print('排序後:')
pprint.pprint(data, width=35)
print()

# 對排序後的data基於屬性X聚合
print('聚合, 有序data:')
for k, g in groupby(data, operator.attrgetter('x')):
    print(k, list(g))

輸入序列須要根據鍵值進行排序處理後才輸出預期的聚合結果。

# OutPut
Data:
[(1, 0),
 (2, 1),
 (3, 2),
 (1, 3),
 (2, 4)]

聚合, 無序data:
1 [(1, 0)]
2 [(2, 1)]
3 [(3, 2)]
1 [(1, 3)]
2 [(2, 4)]

排序後:
[(1, 0),
 (1, 3),
 (2, 1),
 (2, 4),
 (3, 2)]

聚合, 有序data:
1 [(1, 0), (1, 3)]
2 [(2, 1), (2, 4)]
3 [(3, 2)]

6.組合

accumulate() 函數的做用是:處理可迭代的輸入,將第n和n+1項傳遞給目標函數,生成返回值,而不是直接返回輸入。默認函數功能是將兩個值相加,所以可使用 accumulate() 來生成一系列數值輸入的累積和。

from itertools import accumulate

print(list(accumulate(range(5))))
print(list(accumulate('abcde')))

若是輸入序列是非整數值時,結果取決於將兩個項「相加」在一塊兒的含義。好比上面例子中的第二項 accumulate() 接收的是一個字符串,返回則是將字符串逐個拼接在一塊兒。

# OutPut
[0, 1, 3, 6, 10]
['a', 'ab', 'abc', 'abcd', 'abcde']

同時 accumulate() 也接受自定義的帶有兩個輸入項的函數。

from itertools import accumulate

def f(a, b):
    print(a, b)
    return b + a

print(list(accumulate('abcde', f)))
# OutPut
a b
ba c
cba d
dcba e
['a', 'ba', 'cba', 'dcba', 'edcba']

若是嵌套for循環遍歷多個序列可使用 product() ,它會生成一個迭代器,其值是該組輸入值的笛卡爾乘積。

from itertools import product

char = ['a', 'b', 'c']
integer = [1, 2, 3]

for each in product(char, integer):
    print(each)

product() 產生的值是元組,由每一個迭代中取出的成員按照它們傳遞的順序做爲參數傳入。

# OutPut
('a', 1)
('a', 2)
('a', 3)
('b', 1)
('b', 2)
('b', 3)
('c', 1)
('c', 2)
('c', 3)

若是要計算序列與其自己的笛卡爾積,則須要指定 repeat 參數。

from itertools import product

char = ['a', 'b']

for each in product(char, repeat=2):
    print(each)

for each in product(char, repeat=2):
    print(each)
# OutPut
('a', 'a', 'a')
('a', 'a', 'b')
('a', 'b', 'a')
('a', 'b', 'b')
('b', 'a', 'a')
('b', 'a', 'b')
('b', 'b', 'a')
('b', 'b', 'b')

permutation() 函數從輸入的迭代的組合中生成指定長度的排列。它默認生成全部排列的完整集合。

from itertools import permutations

for each in permutations('abc'):
    print(each)
print()

for each in permutations('abc', r=2):
    print(each)

使用 r 參數來限制返回的單個排列的長度。

# OutPut
('a', 'b', 'c')
('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
('c', 'a', 'b')
('c', 'b', 'a')

('a', 'b')
('a', 'c')
('b', 'a')
('b', 'c')
('c', 'a')
('c', 'b')

若是輸出要保證惟一, 即須要組合而不是排列,請使用 combination() 。只要輸入的成員是惟一的,輸出就不會包含任何重複的值。

from itertools import combinations

for each in combinations('abc', r=2):
    print(each)

permutations() 不一樣的是, combination() 必須傳入 r 參數。

# OutPut
('a', 'b')
('a', 'c')
('b', 'c')

由於 combination() 不重複單個輸入元素,但考慮有時須要包含重複元素的組合。對於這些狀況,可使用 combinations_with_replacement()

from itertools import combinations_with_replacement

for each in combinations_with_replacement('abc', r=2):
    print(each)

在此輸出中,每一個輸入項都與其自身以及輸入序列的全部其餘成員組合。

('a', 'a')
('a', 'b')
('a', 'c')
('b', 'b')
('b', 'c')
('c', 'c')

轉載請註明來源: Python標準庫筆記(10) — itertools模塊

相關文章
相關標籤/搜索