iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 18
2
Software Development

活用python- 路遙知碼力,日久練成精系列 第 18

Day18 - 當我們「鏈」在一起,認識zip()函數

路遙知碼力,日久練成精- 只要在程式之路鑽研的夠深,便能夠充分發揮程式碼的力量; 練習的日子夠久,便能夠練成寫出精簡代碼的能力。

大家好,我是心原一馬。
Day4介紹python的內建函數以來,
我們已經認識蠻多python好用的內建函數了,
今天應該是最後一天會介紹到新的python的內建函數了,
如果想知道所有python的內建函數有哪些可以參考菜鳥教程

好的,首先還是討論一下昨日課後練習的解答:
(還沒看過題目的朋友歡迎點昨日題目傳送門)

def nextPrime(n):
    test = n+1
    while True:
        if isPrime(test):
            return test
        test += 1

不難對不對?
其實要找到第一個大於n的質數想法很簡單,
就跑一個無窮迴圈,從n+1開始測試,
如果是質數的話就返回數字。

以往的課後練習多是漂亮的一行解,
好像python語法就是應該這麼漂亮一樣,
若是跟著小馬一路學了十幾天,
看到這麼樸素的題目可能還有點不知如何下筆呢。
出這一題的用意有點是希望大家不要因為學了太多精簡語法,
反而忘了普通的程式邏輯該怎麼寫。
如有其它寫法或任何問題也歡迎留言討論哦。

zip?拉鏈?

好了,進入今天的主題吧,
今天花整個篇幅只介紹一個函數,
想必這個函數具有一定的份量呢。
zip的英文是「拉鏈」或是「壓縮」,
看到這個詞可能很難去猜說這函數到底是做什麼的。

簡單來說,zip函數可以把多個list的相對應位置起來,
zip可以將多個迭代器相對應位置打包成元組,
返回一個迭代器。
(註: 為何不是說把資料「串」起來,而說把資料「鏈」起來呢?
因為搭配zip有拉鏈的意思,便選用這個看起來較炫的動詞了)

這邊我們就以咯咯的女生朋友為例子,幫助大家快速理解:
(嗯嗯,是女生朋友哦,不是女朋友,別亂想)

範例18-1: 咯咯的女生朋友

咯咯有很多女生朋友,
為了討她們歡心,
咯咯有一本專屬小冊子,
記錄他朋友的星座與喜歡的食物,
以便在適當的時候可以送禮物給他的好朋友,
底下的三個列表便是咯咯的記錄:

friends= ["芸芸", "萱萱", "琳琳", "娜娜"]
stars = ["天秤", "金牛", "雙魚","雙子"]
likes = ["餅乾", "巧克力", "草莓", "蛋糕"]

列表上的相對應位置是同一組資料,
例如('芸芸', '天秤', '餅乾')是同一人的資料,
那麼如何把三個列表合併成一個呢?
這時便可以用zip函數把它們起來:

zipped = zip(friends, stars, likes)
print(list(zipped))

結果為:
[('芸芸', '天秤', '餅乾'), ('萱萱', '金牛', '巧克力'), ('琳琳', '雙魚', '草莓'), ('娜娜', '雙子', '蛋糕')]

注意如果要串起來的列表長度不一樣的話,
會以短的列表為主,忽略長列表多出來的資料,
例如:

friends= ["芸芸", "萱萱", "琳琳", "娜娜"]
stars = ["天秤", "金牛", "雙魚"]
likes = ["餅乾", "巧克力", "草莓", "蛋糕"]
zipped = zip(friends, stars, likes)
print(list(zipped))

那麼結果便為:
[('芸芸', '天秤', '餅乾'), ('萱萱', '金牛', '巧克力'), ('琳琳', '雙魚', '草莓')]

範例18-2: 還原「鏈」起來的資料

在剛剛的範例中,
我們成功的將咯咯的女生朋友的「名字」、「星座」、「喜歡食物」起來,
得到這樣的結果: [('芸芸', '天秤', '餅乾'), ('萱萱', '金牛', '巧克力'), ('琳琳', '雙魚', '草莓'), ('娜娜', '雙子', '蛋糕')]
那今天如果我們想要反向操作,將個人資訊分開各自得到「名字」、「星座」、「喜歡食物」的資料呢?
其實只要把四個人的相對應資料鏈起來就好,
考慮以下程式:

people1 = ('芸芸', '天秤', '餅乾')
people2 = ('萱萱', '金牛', '巧克力')
people3 = ('琳琳', '雙魚', '草莓')
people4 = ('娜娜', '雙子', '蛋糕')
zipped = zip(people1, people2, people3, people4)
print(list(zipped))

結果為[('芸芸', '萱萱', '琳琳', '娜娜'), ('天秤', '金牛', '雙魚', '雙子'), ('餅乾', '巧克力', '草莓', '蛋糕')]
如果能夠取得每個人的資料,再當做參數傳進zip()中,
那麼便可以將相同屬性(「名字」、「星座」、「食物」)的資料在一起了。
然而,有時候我們並不事先知道我們有幾個參數,
而是資料都存在一個列表中,
這時我們便可以用python中的星號「*」來解決這個問題,範例如下:

datas = [('芸芸', '天秤', '餅乾'), ('萱萱', '金牛', '巧克力'), ('琳琳', '雙魚', '草莓'), ('娜娜', '雙子', '蛋糕')]
zipped = zip(*datas)
print(list(zipped))

結果為[('芸芸', '萱萱', '琳琳', '娜娜'), ('天秤', '金牛', '雙魚', '雙子'), ('餅乾', '巧克力', '草莓', '蛋糕')]
順利得到我們要的結果了呢。
在列表datas前面加一顆*
即可把列表拆開,將裡面的元素當做參數傳進函數中。
(明天講解自函數定義技巧時,將更詳細講解python*的效果)

好的,到目前為止已經介紹完zip函數的基礎用法了,
zip函數還有一些不可思議的妙用是較鮮為人知的,
善用zip可以很方便的幫我們處理二維列表(或稱矩陣)的操作,
讓我們繼續看下去。

範例18-3: 選擇題核對答案

文文是即將要考大學的用功向學高中生,
他平常都會做很多學測考古題,
只是寫完題庫要核對答案實在是太麻煩了,
好在題目都是五選一選擇題,
因此我們用程式來做。

實做一個函數check(answer, wenwen)
假設參數answerwenwen都是列表,
分別表示正確答案與文文的答案,
回傳文文總共錯了幾題。
例如:

answer = ['A', 'B', 'B', 'E', 'D', 'C']
wenwen = ['B', 'B', 'B', 'E', 'A', 'C']
output: 2
文文的答案第一題和第五題跟正解不同,答錯2題,
因此函數要回傳 2

普通解法

最普通的方法,大概遍是乖乖跑一個for 迴圈遍歷所有index,
遇到答案不同就把答案加1吧,如下:

def check(answer, wenwen):
    n = 0
    for i in range(len(answer)):
        if answer[i]!= wenwen[i]:
            n += 1
    return n

列表生成式解法

都學過「屠龍刀」了,怎能不拿來用呢?
上述解法可進一步化簡:

def check(answer, wenwen):
    return len([1 for i in range(len(answer)) if answer[i]!= wenwen[i]])

zip函數解法

zip可以把列表相對應位置起來,
華麗解法如下:

def check(answer, wenwen):
    return len([1 for x, y in zip(answer, wenwen) if x != y])

範例18-4: 矩陣行列互換

這是zip函數經典的運用,
我們把範例18-2的資料寫成一個矩陣:

原始資料datas=
[('芸芸', '天秤', '餅乾'),
('萱萱', '金牛', '巧克力'),
('琳琳', '雙魚', '草莓'),
('娜娜', '雙子', '蛋糕')]

經過list(zipped(*datas))轉換後:

轉換後的資料=
[('芸芸', '萱萱', '琳琳', '娜娜'), 
('天秤', '金牛', '雙魚', '雙子'),
('餅乾', '巧克力', '草莓', '蛋糕')]

發現玄機了嗎?
A是一個二維列表,
list(zipped(*A))這個操作可以將A的行、列元素互換。
(若你數學還不錯的話,可想成是得到轉置矩陣)
範例如下:

a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(list((zip(*a))))

結果: [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
不過如果你夠細心的話,會發現zip的結果預設會打包成元組(tuple),
如果想得到[[1, 4, 7], [2, 5, 8], [3, 6, 9]]的結果怎麼辦呢?
賣個關子,範例18-6一併教你。

範例18-5: 字典鍵(key)、值(value)互換

還記得我們在Day11這篇中曾經講解過python的字典(dict)嗎?
python的字典是可以透過「關鍵字」查詢內容的結構,
例如:

>>> D = {'s':"黑桃", 'h':"紅心", 'd':"方塊", 'c':"梅花"}
>>> D['s']
'黑桃'

我們希望進行鍵、值轉換,
使得D變為{'黑桃': 's', '紅心': 'h', '方塊': 'd', '梅花': 'c'}
範例如下:

D = {'s':"黑桃", 'h':"紅心", 'd':"方塊", 'c':"梅花"}
print(dict(zip(D.values(), D.keys())))

結果: {'黑桃': 's', '紅心': 'h', '方塊': 'd', '梅花': 'c'}

範例18-6: 矩陣向右旋轉

我們希望把一個矩陣向右旋轉90度,
例如: 原始矩陣為:

[
  [1,2,3],
  [4,5,6],
  [7,8,9]
]

希望旋轉後變為:

[
  [7,4,1],
  [8,5,2],
  [9,6,3]
]

首先來講講這個操作有什麼用,
給大家一些感覺,
譬如假設你今天拍了一張照片存在電腦上,
對照片「向左旋轉90度」及「向右旋轉90度」都算蠻常見的操作,
而照片可以把它想成是一個二維陣列,
因此,旋轉一張照片也就是旋轉一個矩陣的操作了。

「向右旋轉90度」有蠻多方法可以解的,
小馬想到的其中一種是說,
首先先把圖片上下翻轉,再做行列互換,
是可以達到向右旋轉的效果的,
給個示意圖:

123 (上下翻轉)  789  (行列互換)    741
456     =>     456      =>       852
789            123               963

因此,python程式碼便可以這樣寫:

def rotateRight(arr):
    A = arr[::-1] #利用切片上下翻轉
    return list(map(list,(zip(*A)))) #行列互換,再利用map函數將zip內的元組轉列表

我們測試一下結果:

arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(rotateRight(arr))

結果為[[7, 4, 1], [8, 5, 2], [9, 6, 3]],大功告成。
(注意: 若函數內寫return list(zip(*A))
得到的結果就是[(7, 4, 1), (8, 5, 2), (9, 6, 3)])
今天就先學到這囉。

參考資料

  1. Python 使用 zip 與 for 迴圈同時對多個 List 進行迭代

課後練習

勇者,很高興你跟著小馬的腳步學習到第18天了呢,
做個舉一反三的練習吧。

習題: 矩陣向左旋轉

剛剛教你如何將一個矩陣向右旋轉90度了,
試試舉一反三,想個將矩陣向左旋轉90度的方法吧。
實作下列函數:

def rotateLeft(arr):
    pass

參數arr是一個二維列表,
請回傳它向左旋轉90度的結果,
有問題或想法歡迎於留言區討論哦。


上一篇
Day17- 存在所有人喜歡的文章?認識any(), all()函數
下一篇
Day19- 隨心所欲的自定義函數參數介紹
系列文
活用python- 路遙知碼力,日久練成精30

尚未有邦友留言

立即登入留言