路遙知碼力,日久練成精-只要在程式之路鑽研的夠深,便能夠充分發揮程式碼的力量; 練習的日子夠久,便能夠練成寫出精簡代碼的能力。
首先先來討論一下昨日課後練習的解答,
(還沒看過題目的朋友歡迎點昨日題目傳送門)
昨天談的是一個排序問題,
要將字串依照指定的規則重排:
其實這不就是有優先次序的排序嗎?
優先次序: 數字(偶數>奇數)>字母(大寫>小寫)>位置
因此我們可以按照這樣的順序,
將要比較的值映射到一個元組,
我想到最精簡的程式如下:
s = input() #測試範例輸入 Sorting1234
L=sorted(s, key= lambda x: (x.isdigit(), x.isdigit() and int(x) % 2 == 0, x.isupper(), s.index(x)))
print("".join(L)) #預期結果: ortingS1324
元組中將字串中的每個字元依序映射出四個值:
「是否為數字」、「若是數字是否為偶數」、「是否為大寫字母」、「在原字串的位置」。
你可能會問說為何不用判斷是否為小寫字母?
因為原題目有說input只有字母和數字,
若不是數字也不是大寫字母,
那一定是小寫字母了,
便不必再特別判斷。
如有問題或其它想法也歡迎留言討論哦。
其實這一篇像是Day11-讓我們優雅泡咖啡般地選擇容器吧的延伸,
正所謂工欲善其事,必先利其器,
選擇好的工具往往可以達到更好的效果,
可能是你所需要的程式碼數量減少,
也可能是選擇合適的工具使得程式效能提升了。
本篇要簡介的是python內建模組- collections
,
此模組實現了提供特定目標的容器。
首先,其實對初學者來說,
內建的list
, set
, tuple
, dict
,已經算非常夠用了,
若你對於一般狀況如何選擇這些容器已經很困惑了,
可以先略過本篇不看。
不過若是你對於list
, set
, tuple
, dict
這些基本容器夠了解了,
有時選擇更客製化的容器效果更佳。
欲知collections
模組全部有哪些容器,
可以參考官方文檔。
例如我們在昨天Day24的範例24-2中,
我們用元組表示一個人的國、英、數三科的分數,
例如說(9, 6, 11)
表示國文9級分,英文6級分,數學11級分,
然而當我們開發大型程式時,
可能看到(9, 6, 11)
具體並不記得這是什麼東西,
這時,使用namedtuple()
便可以讓我們為每個分量做命名了。
範例如下:
from collections import namedtuple
Score = namedtuple('Score', ['Chinese', 'English', 'math'])
s1 = Score(9, 6, 11)
print(s1)
print(s1.Chinese)
結果為:Score(Chinese=9, English=6, math=11)
9
可以看到此時我們可以用s1.Chinese
這樣的語法取得國文成績了,
當程式規模很大時可增加程式的可讀性。
deque是個類似列表(list)的容器,
它實現了高效率的在容器的左、右兩端新增或刪除元素,
因此適合用來做為資料結構上的stack或queue來使用。
(若你不懂資料結構的話也沒關係,這並非本系列文討論的重點)
以下是deque()提供的幾個方法:
添加x到右端
添加x到左端
移除並返回deque最右端的元素
移除並返回deque最右端的元素
究竟deque的效率跟list相比如何呢?
直接以程式碼測試給大家看:
我們同樣在list和deque的右端插入一百萬個元素,
用time模組測量時間,程式如下:
from collections import deque
import time
# 測試list的插入效能
tStart = time.time()#計時開始
myList = []
for i in range(10**6):
myList.append(i)
tEnd = time.time()#計時結束
print("Total time= %f seconds" % (tEnd - tStart))
# 測試deque的插入效能
tStart = time.time()#計時開始
myDeque = deque()
for i in range(10**6):
myDeque.append(i)
tEnd = time.time()#計時結束
print("Total time= %f seconds" % (tEnd - tStart))
結果為:Total time= 0.180000 seconds
Total time= 0.150000 seconds
你會覺得說感覺兩者之間好像也沒差多少嘛,
我們再比較左插入的狀況看看。
這次我們在list和deque的左端插入十萬個元素,
一樣用time模組測量時間,程式如下:
from collections import deque
import time
# 測試list的插入效能
tStart = time.time()#計時開始
myList = []
for i in range(10**5):
myList.insert(0,i)
tEnd = time.time()#計時結束
print("Total time= %f seconds" % (tEnd - tStart))
# 測試deque的插入效能
tStart = time.time()#計時開始
myDeque = deque()
for i in range(10**5):
myDeque.appendleft(i)
tEnd = time.time()#計時結束
print("Total time= %f seconds" % (tEnd - tStart))
結果為:Total time= 2.180000 seconds
Total time= 0.030000 seconds
哇,效率相差將近百倍呢,驚不驚人?
由此可知在適當的時機選擇適合的容器相當重要呢。
那為什麼對於list來說,
在左邊插入元素和在右邊插入元素效率差這麼多呢?
我試著給個直觀的解釋:
把list想成一個排隊的隊伍,
右側是隊伍的尾巴,
如果在右側插入元素的話,
就相當於在隊伍後面多排一個人,
只有加入隊伍的人動,前面的人都不動。
可是若在左側插入元素的話,
則是硬在「插隊」排在隊伍的第一個,
其它所有人都要移動位置往後一格,
因此相當耗時。
我們可能會想到說可以用一個字典(dict)來統計列表中每個元素的個數,
譬如想這樣寫:
myList = ['A','B','A','A','B','B','A','C','C','C']
count = {} #{}表示一個空字典
for element in a_list:
count[element] += 1
用for迴圈遍歷列表的每個元素,
然後把計數加一,
看起來蠻合理的,
但是直接執行程式的話會出現KeyError
,
因為字典的鍵值(key)必需要先初始化才能用。
因此,我們每次都要先檢查key值存不存在才行,
正確程式如下:
myList = ['A','B','A','A','B','B','A','C','C','C']
count = {} #{}表示一個空字典
for element in myList:
if element not in count:
count[element] = 1
else:
count[element] += 1
print(count)
結果為: {'A': 4, 'B': 3, 'C': 3}
但是這樣又有點失去python的簡潔性了。
能不能我們把沒用過的key值都設一個默認值(譬如:0
),
這樣只要每次都把count[element]
加一就好,
不用先去判斷key值是否存在。
這時便可以用defaultdict()
-可以默認值的字典來幫忙了,
程式修改如下:
from collections import defaultdict
myList = ['A','B','A','A','B','B','A','C','C','C']
count = defaultdict(lambda: 0)
for element in myList:
count[element] += 1
print(count)
結果為:defaultdict(<function <lambda> at 0x000000000B8E99D8>, {'A': 4, 'B': 3, 'C': 3})
defaultdict裡面可以傳入一個函數回傳默認值。
說到計數功能的話,還是使用Counter
最直接了。Counter
也可以把它想成是字典的一種,
不過跟defaultdict
比起來,Counter
多了一些專為計數而設的功能,
因此若是要統計個數的話Counter
比defaultdict
好用太多啦,
範例程式如下:
from collections import Counter
myList = ['A','B','A','A','B','B','A','C','C','C']
count = Counter(myList)
print(count)
print(count['A'])
print(count['D']) #對於不存在的值默認為0
print(count.most_common(2)) #找出出現次數最高的兩個元素
結果為:Counter({'A': 4, 'B': 3, 'C': 3})
4
0
[('A', 4), ('B', 3)]
有時候我們會碰到這樣的問題,我們拿到一篇英文的文章,
要統計說每個單字出現了幾次。
比如說底下是我隨意打的幾個英文句字:
article = "How are you? I am fine, thank you. And you you you you you? You are so smart."
像這一類跟「計數」相關的問題,
就很適合用Counter來解決。
不過,首先我們要先想辦法把每個單字取出來(提示: 字串的split
函數),
並忽略大小寫的區別和標點符號。
(關於字串操作技巧,可複習Day6- 超完整python字串函數用法統整。這篇)
底下給你統計文章各單字次數的程式架構:
def trans(article):
return #寫你要回傳的值
article = "How are you? I am fine, thank you. And you you you you you? You are so smart."
article = trans(article)
print(article)
count = Counter(article.split())
print(count)
其中,我們定義一個轉換函數trans(article)
,
效果是把article
的字母全部變成小寫,
然後把不是英文字母的字(如標點符號)替換成空白,
函數回傳一個字串。
預期結果:
how are you i am fine thank you and you you you you you you are so smart
Counter({'you': 8, 'are': 2, 'how': 1, 'i': 1, 'am': 1, 'fine': 1, 'thank': 1, 'and': 1, 'so': 1, 'smart': 1})
這題難度有點難,不過解答只要寫一行程式即可完成。
from collections import Counter
from re import sub as reSub
def trans(article):
return reSub(r'[^A-Za-z]+', ' ', article.lower())
一馬老師的教學愈來愈有深度了,
我知道上面的應該不是標準答案,但是我就是喜歡用RegEXP...
請問一下
教學範例
from collections import namedtuple
Score = namedtuple('Score', ['Chinese', 'English', 'math'])
s1 = Score(9, 6, 11)
print(s1)
print(s1.Chinese)
但如果改成
Score = namedtuple('Score', ['1', '2', '3'])
s1 = Score(9, 6, 11)
....
也就說Chinese可否換成數字...
謝謝!
跑了一下,不行
namedtuple是用賦值,把分數(資料)指給變數,變數名稱不能用數字開頭,是物件的屬性
def trans(article):
import re
return re.sub(r'[^a-z]','',article.lower())
換成空字串,不換成空白是因為,之後如果要用split(),標點符號後面已經有空白,兩空白之間會切出空字串