iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 11
3
Software Development

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

Day11- 讓我們優雅泡咖啡般地選擇容器吧,認識int(), float(), str(), list(), tuple(), dict(), set()

  • 分享至 

  • xImage
  •  

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

談談python的資料型態

進入「列表生成式」這個單元之前,
我們先來聊聊python資料型態之間的轉換。
這是python非常方便的地方,
在python中要轉換資料型態,
沒有繁複的語法,
只要把要轉換的資料放進對應的函數裡就行了。
例如有一個字串:

s = "0025"

我們可以轉換成不同型態:

>>> int(s) #轉換成整數
25
>>> float(s) #轉換成浮點數
25.0
>>> list(s) #轉換成列表
['0', '0', '2', '5']
>>> tuple(s) #轉換成元組
('0', '0', '2', '5')
>>> set(s) #轉換成集合
{ '0', '2', '5'}
>>> dict(s) #字串無法直接轉換成字典,會報錯
ValueError

馬克杯是拿來優雅泡咖啡的

你可能會說要轉換成不同資料型態有什麼好處嗎?
看起來不就是資料用小括號、中括號還是大括號來裝的區別嗎?
不不,這樣想就錯了,
Python的容器不光是看起來長的不一樣而已,
而是真的在屬性上有所不同。
拿日常生活中的「容器」打個比方吧,
就像你平常不會拿馬克杯來泡泡麵,
拿大碗公來泡咖啡,
Python內的容器也有它們適合使用的時機。

舉最簡單的例子好了,
像是字串的加號和數字的加號就有所不同。

>>> 12+34
46
>>> '12'+'34'
'1234'

這些不同資料型態上有什麼不同的屬性呢?
我們來一一介紹。

預備知識: python字典介紹

Day7範例7-2撲克花色中,
我們曾見過一次字典的用法

不知道大家小學有沒有查過字典呢?
由於現在網路太發達了,
不一定人人都摸過實體字典,
這邊以線上的教育部國語字典為例(圖示為查詢「字典」這個詞的意思):
https://ithelp.ithome.com.tw/upload/images/20190914/20117114Zk9ebNCcCf.png

當你不知道一個詞語的意思時,
你會輸入關鍵字(key)在字典中查詢,
然後便可以查詢那個詞語的解釋(value)。
這種查詢的概念便類似python的字典(dict),
python的dict由一組key:value組成放在大括號裡,
例如:

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

此時我們便建立了一本字典,
可以想成是「詞語's'的意思為"黑桃"」,「詞語'h'的意思為"紅心"」…。
當我們想要查詢某個字的意思,
便可以用中括號放入關鍵字做查詢,如:

>>> D['s']
'黑桃'

容器屬性介紹

一、可變vs. 不可變

介紹第一個屬性,
什麼是可變物件與不可變物件呢?
不可變物件,該物件所指向的記憶體中的值不能被改變。
例如: str, int, float, tuple屬於不可變物件。
可變物件,該物件所指向的記憶體中的值可以被改變。
例如: list, set, dict都屬於可變物件。
說起來不難,但事實上不可變的屬性可是困惑了很多新手,
到底是什麼不可變呢?

有新手會舉例這樣的程式碼:

a=0
a=1

一開始我把a的值設成0,
現在把a的值改為1了,
應該是可以改變啊?

事實上,改變不可變型態變數的值的時候,
並不是直接去改變那個值,
而是創造一個新的地址,
再把變數指向那個地址。
我比較喜歡用「貼標籤」來做比喻,
一開始做a=0,就好像把a這張標籤貼到0這個物件上,
a=1就只是把a這張標籤撕下來貼到1這個物件上。
對於原來0這個數字來說,
0的頭上並不會突然長角或有什麼改變。

我們看tuple和list的例子更清楚,
我們各自創建一個list和tuple,內容都是1,2,3:

L=[1,2,3] #list
M=(1,2,3) #tuple
L[0]=2 #L會變成[2,2,3]
M[0]=2 #這一行會出現TypeError

上面程式中,L是list而M是tuple,
可以做 L[0]=2去改變L的內容,
但是 M[0]=2的操作是禁止的。

另一個常見的誤解如下:

s="Hello"
s+="World"
print(s) #印出結果HelloWorld

咦?這邊可以做 s+="World"耶?
乍看之下不是改變了這個字串嗎?
怎麼會說字串不可變呢?
其實因為字串是不可變的特性,
s+="World"並不是改變原本的字串,
而是s指向了一個新的物件,叫做"HelloWorld"。

我們再延伸Day3範例3-2,3-3冰箱的例子來幫助理解可變變數:

範例11-1: 偷放臭掉的牛奶到冰箱裡

還記得Day3時我們舉過這個例子嗎?
這回你調皮的室友又要偷動冰箱囉。
令fridge這個變數記錄冰箱存放的東西:

fridge = ['蛋糕', '蘋果', '香蕉']
b = fridge
b.remove('蛋糕')
print(fridge) #['蘋果', '香蕉']

當修改b裡面的值內容時,
fridge的內容也會真的受到影響。
現在我們試著寫兩隻類似的程式出來,
一個是列表版本的,
一個是字串版本的,
實測看看修改可變物件和不可變物件是否有差別。
首先是列表版本的:

#列表版本 - 冰箱
fridge = ['蛋糕', '蘋果', '香蕉']
b = fridge
b+= ['臭掉的牛奶']
print(fridge) #['蛋糕', '蘋果', '香蕉', '臭掉的牛奶']

再來是字串版本的:

#字串版本 - 冰箱
fridge = '蛋糕, 蘋果, 香蕉'
b = fridge
b+= ', 臭掉的牛奶'
print(fridge) # '蛋糕, 蘋果, 香蕉'

在這兩個例子,我們都做了類似的操作,
同樣都使用b = fridge賦值,
也同樣用b+= … 來改變變數b的內容,
但只有列表版本的冰箱受到影響,
字串版本的冰箱則不受影響。
為什麼有這樣的差別呢?

讓我們上個圖示說明:
<列表版>
記得當我們做b = fridge這一行時,
猶如將標籤b 貼到fridge這個冰箱上,
此時這兩個變數指的是同一個冰箱了,如圖:
https://ithelp.ithome.com.tw/upload/images/20190914/20117114jVeYH0WBoi.png

由於列表是可變物件,
執行 b+= ['臭掉的牛奶']時,
便會直接原地修改冰箱的內容,如圖示:
https://ithelp.ithome.com.tw/upload/images/20190914/20117114g4jT0MkZUO.png

<字串版>
但字串為不可變物件,
在做 b+= ', 臭掉的牛奶'這項操作時,
由於無法更改原字串'蛋糕, 蘋果, 香蕉'的內容,
只好先創建一個新的字串叫做 '蛋糕, 蘋果, 香蕉, 臭掉的牛奶'
再把標籤b撕下來貼到新字串上了。
https://ithelp.ithome.com.tw/upload/images/20190915/20117114z70p2A5Gk9.png

二、 可迭代vs. 不可迭代

理解完可變物件與不可變物件後,
剩下的概念就相對簡單了。
迭代是利用迴圈遍歷把元素取出來的過程,
利如str, list, set, tuple, dict都是可迭代物件,
int, float為不可迭代物件。
簡單來說,可迭代物件可以進行for… in … 操作取出元素
舉例來說,
我們可以遍歷一個字串,
印出每個字元:

s= "Hello"
for c in s:
    print(c)

結果為
H
e
l
l
o
再看一例,
我們們這次試著遍歷一個字典:

D={1:'A',2:'B'}
for i in D:
    print(i)

結果為
1
2
(字典的迭代會遍歷key值)

三、 可重複vs. 不可重複

這邊指的可重複性指的是容器裡面是否可以有重複的元素。
list 和tuple 重複,
而set和dict不可重複。
例如我們宣告一個初始值有重複元素的集合S:

S={1,2,2,2,5}
print(S)

結果為 {1, 2, 5}
集合會自動去除重複的元素。

四、 有序vs. 無序

有序與無序指的是元素的順序在容器中不同是否有差別。
list 和tuple 有序,
而set和dict無序。
我們看一個例子:

print([1,2,3]==[2,3,1]) #False
print({1,2,3}=={2,3,1}) #True

我們試著判斷[1,2,3]是否與[2,3,1]相等,
因為列表是有序的,元素順序不同則結果視為不同,
而集合是無序的,不論順序怎麼寫都視為相同。
有了內建型態方便的轉換,我們來看幾個應用吧。

實例演練

範例11-2 數字倒轉

給你一個整數,回傳整數倒轉之後的結果。
例如:

123 -> 321
2300 -> 32 (原來倒轉後變0032,但因為是整數,前面有0需去除)
-250 -> -52(負數倒轉先倒轉數字部分,再補上負號)
0 -> 0

這個問題看似複雜,
又要考慮正負號,又要考慮前面有0需去除。
還記得Day5的時候我們有學過字串倒轉只需一行就解開了嗎?

>>> s="abc"
>>> s[::-1] #倒轉字串
'cba'

數字本身不能做切片運算,
但是如果我們轉換成字串的話,
就可以輕鬆把數字倒轉了:

>>> n= 123
>>> str(n)[::-1]
'321'
>>> n= 2300
>>> str(n)[::-1]
'0032'

最後再把倒轉後的字串轉回數字即可:

>>> n= 2300
>>> int(str(n)[::-1])
32

很方便地,用int()轉換為整數時,連前面的0它都會自動去除。
但是我們需要把正負號考慮進去,
例如n若為 -123,直接轉成字串倒轉將會得到 '321-',
負號會跑到最右邊,這並不是我們要的結果。
因此,我們可以先將正負號取出來:

sign= '-' if n<0 else ''

倒轉數字時,可以統一取絕對值再操作,
完整實作程式碼如下:

def reverseNum(n):
    sign= '-' if n<0 else ''
    return int(sign + str(abs(n))[::-1])

# 底下測試結果
print(reverseNum(123)) #321
print(reverseNum(2300)) #32
print(reverseNum(-250)) #-52
print(reverseNum(0))    #0

課後練習

習題: 聽說好關卡值得一玩再玩

明明買了一款電動遊戲,
總共有100道關卡,編號1~100,
該遊戲可以不必按照順序破關,
也可以重複遊玩。
給你一個列表表示明明的遊玩關卡順序,
你能幫它檢查明明是否玩到重複的關卡嗎?

實作一個函數名稱為 checkRepeat(plays),
讀入參數plays表示遊玩順序,
例如:

1.	input: plays=[80, 11, 86, 45, 54, 31, 46, 11, 61, 42],
第十一關重複玩了兩次,
函數要回傳True。
2.	input: plays= [54, 35, 69, 52, 29, 5, 4, 45, 23, 84],
沒有重複的關卡,
函數要回傳False。

嗯,給你個參考想法,
問題要判斷列表內有沒有重複的元素,
因此,你可能會想去數每個在列表裡面的元素,
如果元素出現超過1次,則表示有重複。
實現程式碼如下:

def checkRepeat(plays):
    for p in plays:
        if plays.count(p)>1:
            return True
    return False

但是這樣還不夠精簡,
你能否使用適當的容器轉換來完成這一題呢?
(提示: 利用set()可以去除重複元素的特性)


上一篇
Day10- 幾個程式經典問題,一亮出第一屠龍刀,彷彿開著坦克車攻打原始人,太逆天啦 (談切片語法的應用)
下一篇
Day12- 第二屠龍刀一式- 列表生成式(一) 精簡的語法,利用一個列表生出另一個
系列文
活用python- 路遙知碼力,日久練成精30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
thisisro
iT邦新手 5 級 ‧ 2020-07-20 17:38:11

list(set(plays)) == plays

不明 檢舉
【**此則訊息已被站方移除**】
0
ambird
iT邦新手 5 級 ‧ 2020-09-01 14:26:23
def checkRepeat(plays):
    return plays != set(plays)

我要留言

立即登入留言