Python 有 zip,那麼有沒有 unzip?

___

我們都知道 Pyhton 內建的 zip 函式可以將多個 lists 合成,這在需要同時 iterate 多個 lists 時很好用:

    foo = (1, 2, 3, 4)
    bar = (5, 6, 7, 8)
    for f, b in zip(foo, bar):
        print f, b

會得到

1, 5
2, 6
3, 7
4, 8

可是如果我們今天想做的事情相反,是要把一個 list 裡的每個項目拆開,要怎麼辦?也就是說,如果

   baz = ((1, 2), (3, 4), (5, 6), (7, 8))

有沒有 unzip 使得

    rex, blah = unzip(baz)
    print rex
    print blah

能得到

(1, 3, 5, 7)
(2, 4, 6, 8)

的結果?

答案是有,而且很容易就能找到,如果你 RTFM

zip() in conjunction with the * operator can be used to unzip a list

所以只要這樣就能搞定了!

    baz = ((1, 2), (3, 4), (5, 6), (7, 8))
    rex, blah = zip(*baz)

如果仔細想想,這其實很合理。官方文件對 zip 的解釋如下:

zip([iterable, …]) This function returns a list of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables.

(Emphasize mine.)

所以我們可以導出下面的規則:令 ab 各為一 tuple,且 b = zip(*a),則 a[i][j] == b[j][i],其中 ij 為整數, i <= len(a),且對於 0 <= i < len(a)j <= len(a[i])

如果你把 ab 想成矩陣,事實上這個運算就等同於轉置(transpose)的效果。在一開始的那個例子(一般的使用狀況)中,a 即為 ((1, 2, 3, 4), (5, 6, 7, 8)),其轉置為 ((1, 5), (2, 6), (3, 7), (4, 8)),拿來 iterate 後就會得到上面的結果。回憶高中數學,若 A 為矩陣,則

\[ (A^T)^T = A \]

所以當然 zip 會是自己的 inverse!

註:參數展開運算子(* operator)需要將傳入的 sequence/tuple 完全展開,才能進行運算,因此如果傳入值體積太大,可能會造成效能瓶頸。如果有這種大量運算需求,請考慮使用其他 iterator-based 的方案,甚至直接使用 numpy 的矩陣運算為佳。


comments powered by Disqus

Blog Posts

___

Scroll top