本次課程,接續前面三次課程的內容,繼續講編程實現——第三步。
前三次課:
前面的課程中,提出了一個簡單的編程需求案例:選擇一組數據中的最大值。我們在上一次課程中針對這道例題設計了兩種編程模型,一種是普通的遍歷;一種是Map-Reduce模型。現在我們把它具體寫成代碼,看看其中有哪些地方值得我們的注意。
先看在遍歷模型的代碼:
def getValue(item): cm= item['chinese']+ item['math'] ret= cm*200+ item['chinese'] return retdef getMax(dat): ret= [] ref= 0 for item in dat: a= getValue(item) if a> ref: ref= a # ret=[item] ret=[data.index(item)] elif a==ref: # ret.append(item) ret.append(data.index(item)) else: # 小於分支可以忽略 pass return retif __name__=='__main__': data=[{'name': '馮章君', 'chinese': 92, 'math': 100}, {'name': '王郡瑤', 'chinese': 62, 'math': 76}, {'name': '陶而', 'chinese': 77, 'math': 78}, {'name': '鄭洪淺', 'chinese': 85, 'math': 68}, {'name': '鄭而', 'chinese': 95, 'math': 74}, {'name': '朱豫', 'chinese': 76, 'math': 78}, {'name': '周都', 'chinese': 82, 'math': 78}, {'name': '王衡', 'chinese': 73, 'math': 85}, {'name': '魏接', 'chinese': 92, 'math': 73}, {'name': '錢地綾', 'chinese': 97, 'math': 91}, {'name': '李廬綾', 'chinese': 96, 'math': 92}, {'name': '蔣衡瑛', 'chinese': 82, 'math': 66}, {'name': '陳新', 'chinese': 88, 'math': 63}, {'name': '秦分', 'chinese': 74, 'math': 91}, {'name': '吳而綾', 'chinese': 72, 'math': 93}, {'name': '許洪', 'chinese': 91, 'math': 87}, {'name': '沈星若', 'chinese': 60, 'math': 87}, {'name': '沈江', 'chinese': 69, 'math': 79}, {'name': '華府', 'chinese': 74, 'math': 92}, {'name': '衛翼', 'chinese': 97, 'math': 67}, {'name': '華地', 'chinese': 66, 'math': 89}, {'name': '許接', 'chinese': 77, 'math': 70}, {'name': '何廬', 'chinese': 92, 'math': 96}, {'name': '馮故', 'chinese': 87, 'math': 65}, {'name': '何章清', 'chinese': 94, 'math': 70}, {'name': '沈故綾', 'chinese': 97, 'math': 92}, {'name': '沈章', 'chinese': 78, 'math': 86}, {'name': '魏故君', 'chinese': 92, 'math': 100}, {'name': '韓翼綾', 'chinese': 93, 'math': 64}, {'name': '王江染', 'chinese': 83, 'math': 67}, {'name': '施翼熙', 'chinese': 79, 'math': 71}, {'name': '趙湖輕', 'chinese': 87, 'math': 98}, {'name': '陳星', 'chinese': 67, 'math': 85}, {'name': '曹府', 'chinese': 83, 'math': 77}, {'name': '張而', 'chinese': 65, 'math': 67}, {'name': '錢章', 'chinese': 69, 'math': 73}, {'name': '尤地倩', 'chinese': 89, 'math': 96}, {'name': '陶故', 'chinese': 92, 'math': 83}, {'name': '鄭軫綾', 'chinese': 60, 'math': 96}, {'name': '吳翼然', 'chinese': 86, 'math': 69}, {'name': '秦江君', 'chinese': 68, 'math': 67}, {'name': '周地', 'chinese': 96, 'math': 66}, {'name': '金郡欣', 'chinese': 60, 'math': 64}, {'name': '楊新', 'chinese': 87, 'math': 91}, {'name': '趙洪', 'chinese': 98, 'math': 85}, {'name': '吳而', 'chinese': 79, 'math': 93}, {'name': '秦襟熙', 'chinese': 79, 'math': 84}, {'name': '楊故淺', 'chinese': 64, 'math': 71}, {'name': '鄭江可', 'chinese': 72, 'math': 63}, {'name': '嚴府', 'chinese': 90, 'math': 61}] ma= getMax(data) print(ma)
這段代碼,分為3個部分
工具函數與功能函數,很難說有嚴格明確的界限,我們可以這樣來區分。凡是返回結果的內容(形式可以再調整)可以輸出給用戶的函數,都可以算一種功能函數。而一個函數的返回值只能給其他函數作為中間結果使用,就叫做工具函數。
本段程序比較簡短,只有兩個函數,放在一個文件中。代碼內容較多的時候,可以將工具函數統一放在一個文件中,而將功能函數集中或分類放的組織方法。在專項的編程課中會遇到。
函數的簡單測試,可以寫在
if __name__=='__main__':
語句的後面。完整的測試,一般會另外寫測試代碼,這裡暫時忽略。
注意代碼中被注釋的兩行語句
ret=[item]
和
ret.append(item)
這是為了調試時能夠看到數據的值(而不僅僅是編號),從而便於判斷結果是否正確。
本段代碼還補充了一個列表函數,index,用於獲取一個列表元素在列表中的編號。
Map-Reduce模型有幾種表現方法,
1、先看原汁原味的:
from functools import reduce# 單值化函數def getValue(item): cm= item['chinese']+ item['math'] ret= cm*200+ item['chinese'] return ret# 原始map-reduce函數的操作def getMax(dat): ref= reduce(lambda x,y:max(x,getValue(y)), dat, 0) lst= filter(lambda x:getValue(x)==ref, dat) ret= map(lambda x:dat.index(x), lst) return list(ret)if __name__=='__main__': data=[{'name': '馮章君', 'chinese': 92, 'math': 100}, {'name': '王郡瑤', 'chinese': 62, 'math': 76}, {'name': '陶而', 'chinese': 77, 'math': 78}, {'name': '鄭洪淺', 'chinese': 85, 'math': 68}, {'name': '鄭而', 'chinese': 95, 'math': 74}, {'name': '朱豫', 'chinese': 76, 'math': 78}, {'name': '周都', 'chinese': 82, 'math': 78}, {'name': '王衡', 'chinese': 73, 'math': 85}, {'name': '魏接', 'chinese': 92, 'math': 73}, {'name': '錢地綾', 'chinese': 97, 'math': 91}, {'name': '李廬綾', 'chinese': 96, 'math': 92}, {'name': '蔣衡瑛', 'chinese': 82, 'math': 66}, {'name': '陳新', 'chinese': 88, 'math': 63}, {'name': '秦分', 'chinese': 74, 'math': 91}, {'name': '吳而綾', 'chinese': 72, 'math': 93}, {'name': '許洪', 'chinese': 91, 'math': 87}, {'name': '沈星若', 'chinese': 60, 'math': 87}, {'name': '沈江', 'chinese': 69, 'math': 79}, {'name': '華府', 'chinese': 74, 'math': 92}, {'name': '衛翼', 'chinese': 97, 'math': 67}, {'name': '華地', 'chinese': 66, 'math': 89}, {'name': '許接', 'chinese': 77, 'math': 70}, {'name': '何廬', 'chinese': 92, 'math': 96}, {'name': '馮故', 'chinese': 87, 'math': 65}, {'name': '何章清', 'chinese': 94, 'math': 70}, {'name': '沈故綾', 'chinese': 97, 'math': 92}, {'name': '沈章', 'chinese': 78, 'math': 86}, {'name': '魏故君', 'chinese': 92, 'math': 100}, {'name': '韓翼綾', 'chinese': 93, 'math': 64}, {'name': '王江染', 'chinese': 83, 'math': 67}, {'name': '施翼熙', 'chinese': 79, 'math': 71}, {'name': '趙湖輕', 'chinese': 87, 'math': 98}, {'name': '陳星', 'chinese': 67, 'math': 85}, {'name': '曹府', 'chinese': 83, 'math': 77}, {'name': '張而', 'chinese': 65, 'math': 67}, {'name': '錢章', 'chinese': 69, 'math': 73}, {'name': '尤地倩', 'chinese': 89, 'math': 96}, {'name': '陶故', 'chinese': 92, 'math': 83}, {'name': '鄭軫綾', 'chinese': 60, 'math': 96}, {'name': '吳翼然', 'chinese': 86, 'math': 69}, {'name': '秦江君', 'chinese': 68, 'math': 67}, {'name': '周地', 'chinese': 96, 'math': 66}, {'name': '金郡欣', 'chinese': 60, 'math': 64}, {'name': '楊新', 'chinese': 87, 'math': 91}, {'name': '趙洪', 'chinese': 98, 'math': 85}, {'name': '吳而', 'chinese': 79, 'math': 93}, {'name': '秦襟熙', 'chinese': 79, 'math': 84}, {'name': '楊故淺', 'chinese': 64, 'math': 71}, {'name': '鄭江可', 'chinese': 72, 'math': 63}, {'name': '嚴府', 'chinese': 90, 'math': 61}] ma= getMax(data) print(ma)
reduce功能由於用法比較複雜,在python3中不再是默認函數,必須引用functools才能使用。代碼的整體結構和前一個相似,只是getMax函數的算法改變了。
reduce接收三個參數,第一個參數是一個雙參數函數;第二個參數是數據列表;第三個參數是初始值。它的工作原理是:首先將初始值插入列表中成為第一個元素,原來列表的一個元素成為第二個,然後把列表的前兩個元素傳給這個雙參數函數。再刪除這兩個元素,把函數的返回值作為新的第一個元素,原來列表的第二個元素提升為新的第二個元素。這樣持續疊加計算,直到得到最後的返回值。具體到本段代碼,這個雙參數函數實際做的事情就是依次比較成績值,並始終保留最大的一個。
filter函數比較明確,就是只保留後面列表中所有滿足前面函數條件的元素。
map函數就是一一映射,將元素換成它對應的編號。
2、再看python推薦風格的
map-reduce在python中不受重用,從reduce被排擠出默認函數就可以看出來。我們再看一種更加python風格的表現形式,用了map-reduce的含義,而沒有使用這幾個函數名。
# 單值化函數def getValue(item): cm= item['chinese']+ item['math'] ret= cm*200+ item['chinese'] return ret# 使用Python語法表達def getMax(dat): refitem=max(dat, key=getValue) ret= [dat.index(x) for x in dat if getValue(x)==getValue(refitem)] return list(ret)if __name__=='__main__': data=[{'name': '馮章君', 'chinese': 92, 'math': 100}, {'name': '王郡瑤', 'chinese': 62, 'math': 76}, {'name': '陶而', 'chinese': 77, 'math': 78}, {'name': '鄭洪淺', 'chinese': 85, 'math': 68}, {'name': '鄭而', 'chinese': 95, 'math': 74}, {'name': '朱豫', 'chinese': 76, 'math': 78}, {'name': '周都', 'chinese': 82, 'math': 78}, {'name': '王衡', 'chinese': 73, 'math': 85}, {'name': '魏接', 'chinese': 92, 'math': 73}, {'name': '錢地綾', 'chinese': 97, 'math': 91}, {'name': '李廬綾', 'chinese': 96, 'math': 92}, {'name': '蔣衡瑛', 'chinese': 82, 'math': 66}, {'name': '陳新', 'chinese': 88, 'math': 63}, {'name': '秦分', 'chinese': 74, 'math': 91}, {'name': '吳而綾', 'chinese': 72, 'math': 93}, {'name': '許洪', 'chinese': 91, 'math': 87}, {'name': '沈星若', 'chinese': 60, 'math': 87}, {'name': '沈江', 'chinese': 69, 'math': 79}, {'name': '華府', 'chinese': 74, 'math': 92}, {'name': '衛翼', 'chinese': 97, 'math': 67}, {'name': '華地', 'chinese': 66, 'math': 89}, {'name': '許接', 'chinese': 77, 'math': 70}, {'name': '何廬', 'chinese': 92, 'math': 96}, {'name': '馮故', 'chinese': 87, 'math': 65}, {'name': '何章清', 'chinese': 94, 'math': 70}, {'name': '沈故綾', 'chinese': 97, 'math': 92}, {'name': '沈章', 'chinese': 78, 'math': 86}, {'name': '魏故君', 'chinese': 92, 'math': 100}, {'name': '韓翼綾', 'chinese': 93, 'math': 64}, {'name': '王江染', 'chinese': 83, 'math': 67}, {'name': '施翼熙', 'chinese': 79, 'math': 71}, {'name': '趙湖輕', 'chinese': 87, 'math': 98}, {'name': '陳星', 'chinese': 67, 'math': 85}, {'name': '曹府', 'chinese': 83, 'math': 77}, {'name': '張而', 'chinese': 65, 'math': 67}, {'name': '錢章', 'chinese': 69, 'math': 73}, {'name': '尤地倩', 'chinese': 89, 'math': 96}, {'name': '陶故', 'chinese': 92, 'math': 83}, {'name': '鄭軫綾', 'chinese': 60, 'math': 96}, {'name': '吳翼然', 'chinese': 86, 'math': 69}, {'name': '秦江君', 'chinese': 68, 'math': 67}, {'name': '周地', 'chinese': 96, 'math': 66}, {'name': '金郡欣', 'chinese': 60, 'math': 64}, {'name': '楊新', 'chinese': 87, 'math': 91}, {'name': '趙洪', 'chinese': 98, 'math': 85}, {'name': '吳而', 'chinese': 79, 'math': 93}, {'name': '秦襟熙', 'chinese': 79, 'math': 84}, {'name': '楊故淺', 'chinese': 64, 'math': 71}, {'name': '鄭江可', 'chinese': 72, 'math': 63}, {'name': '嚴府', 'chinese': 90, 'math': 61}] ma= getMax(data) print(ma)
這是代碼最短、依賴最少的一個代碼,也是最符合python風格的。其他的部分都一樣,只有getMax不同。代碼沒有直接用reduce函數,因此省略的reduce的引用;並且用python的列表生成式合併了map和filter的功能,一句頂兩句。
第一句,是max函數,獲得一個成績最大的學生refitem。然後再通過getValue來獲得最大的成績值。列表生成式是python列表的又一個功能,它的格式是:[func(x) for x in 列表 if 條件];功能是從列表中選取所有滿足條件的元素,並把每個函數用func函數計算。實際就是合併了map和filter兩個函數的功能。
3、不用單值化技巧
剛學會編程的人,喜歡使用技巧。反而是編程的時間長了,更願意追求不使用技巧的方法,就是所謂的大巧不工。能夠把一個條件轉化為一個值的情況,畢竟是有限的,我們必須學會怎麼對任意情況進行處理。我們這樣一個 版本:
from functools import cmp_to_key# 普通的比較函數def cmp(item1, item2): if item1['chinese']+item1['math']> item2['chinese']+item2['math']: return 1 elif item1['chinese']+item1['math']== item2['chinese']+item2['math']: if item1['chinese']> item2['chinese']: return 1 elif item1['chinese']== item2['chinese']: return 0 else: return -1 else: return -1# 基於比較函數的流程def getMax(dat): refitem= max(dat, key=cmp_to_key(cmp)) ret= [dat.index(x) for x in dat if cmp(x,refitem)==0] # ret= [x for x in dat if cmp(x,refitem)==0] return list(ret)if __name__=='__main__': data=[{'name': '馮章君', 'chinese': 92, 'math': 100}, {'name': '王郡瑤', 'chinese': 62, 'math': 76}, {'name': '陶而', 'chinese': 77, 'math': 78}, {'name': '鄭洪淺', 'chinese': 85, 'math': 68}, {'name': '鄭而', 'chinese': 95, 'math': 74}, {'name': '朱豫', 'chinese': 76, 'math': 78}, {'name': '周都', 'chinese': 82, 'math': 78}, {'name': '王衡', 'chinese': 73, 'math': 85}, {'name': '魏接', 'chinese': 92, 'math': 73}, {'name': '錢地綾', 'chinese': 97, 'math': 91}, {'name': '李廬綾', 'chinese': 96, 'math': 92}, {'name': '蔣衡瑛', 'chinese': 82, 'math': 66}, {'name': '陳新', 'chinese': 88, 'math': 63}, {'name': '秦分', 'chinese': 74, 'math': 91}, {'name': '吳而綾', 'chinese': 72, 'math': 93}, {'name': '許洪', 'chinese': 91, 'math': 87}, {'name': '沈星若', 'chinese': 60, 'math': 87}, {'name': '沈江', 'chinese': 69, 'math': 79}, {'name': '華府', 'chinese': 74, 'math': 92}, {'name': '衛翼', 'chinese': 97, 'math': 67}, {'name': '華地', 'chinese': 66, 'math': 89}, {'name': '許接', 'chinese': 77, 'math': 70}, {'name': '何廬', 'chinese': 92, 'math': 96}, {'name': '馮故', 'chinese': 87, 'math': 65}, {'name': '何章清', 'chinese': 94, 'math': 70}, {'name': '沈故綾', 'chinese': 97, 'math': 92}, {'name': '沈章', 'chinese': 78, 'math': 86}, {'name': '魏故君', 'chinese': 92, 'math': 100}, {'name': '韓翼綾', 'chinese': 93, 'math': 64}, {'name': '王江染', 'chinese': 83, 'math': 67}, {'name': '施翼熙', 'chinese': 79, 'math': 71}, {'name': '趙湖輕', 'chinese': 87, 'math': 98}, {'name': '陳星', 'chinese': 67, 'math': 85}, {'name': '曹府', 'chinese': 83, 'math': 77}, {'name': '張而', 'chinese': 65, 'math': 67}, {'name': '錢章', 'chinese': 69, 'math': 73}, {'name': '尤地倩', 'chinese': 89, 'math': 96}, {'name': '陶故', 'chinese': 92, 'math': 83}, {'name': '鄭軫綾', 'chinese': 60, 'math': 96}, {'name': '吳翼然', 'chinese': 86, 'math': 69}, {'name': '秦江君', 'chinese': 68, 'math': 67}, {'name': '周地', 'chinese': 96, 'math': 66}, {'name': '金郡欣', 'chinese': 60, 'math': 64}, {'name': '楊新', 'chinese': 87, 'math': 91}, {'name': '趙洪', 'chinese': 98, 'math': 85}, {'name': '吳而', 'chinese': 79, 'math': 93}, {'name': '秦襟熙', 'chinese': 79, 'math': 84}, {'name': '楊故淺', 'chinese': 64, 'math': 71}, {'name': '鄭江可', 'chinese': 72, 'math': 63}, {'name': '嚴府', 'chinese': 90, 'math': 61}] ma= getMax(data) print(ma)
函數cmp是比較大小的函數,輸入兩個參數,根據規則進行大小比較,這是肯定可以做到的。返回值的規則是:
這段代碼展示了在使用cmp函數的情況下,使用functools中的cmp_to_key將cmp轉換為key的方法。這裡與使用reduce一樣,必須增加一個import。cmp_to_key使用了python面向對象的一個技巧(不用那個技巧,就必須用這個技巧,只是這個技巧更通用),讓比較函數看起來有單值化一樣的效果。
1、引入兩個新的列表功能
一個是index,一個是列表生成式。我們可以用這樣一條語句來記住這兩個功能。
ret= [dat.index(x) for x in dat if cmp(x,refitem)==0]
請朋友們把這兩個功能合併到我們的列表相關知識庫中。
隨時總結,是一個很好的學習方法。
2、參數key的用法
在max函數中,我們使用了key功能,朋友們或許還記得前面課程中講過列表的sort函數,也有類似的key功能。這種用法,在稍微複雜的編程實踐中經常遇到,我們應該熟記並理解。
至於functools中的cmp_to_key函數,是對此key功能的一個輔助和補充,我們應該對它有一個印象,必須使用時,能夠想起來去查找即可。
面對同一個需求,不同編程者實現的代碼邏輯可以差別很大。更別說代碼風格上的差異了。歸根結底,編程的手段(語句)是科學的,但產品是藝術的;正如繪畫使用的手段(顏料)是科學的,但作品是藝術的。