9 函式
微分方程式數值求解 (一)
33
此公式需設定起始條件才能求解,若讓 = c,c 要一開始就給定,
整個數值求解才能開始。以上的數值求解法稱為 Euler method,這是最
簡單的數值法用來計算起始值問題(initial value problem),缺點為
計算精度偏低,若要得到較好的結果,∆x 要越小越好。
本程式利用 c 迴圈來設定起始值,c 分別為 0、5、10,對相同微分
方程式,三個不同起始值代表三個不同的微分方程式,可產生三個數值解。
一般來說,所有起始值微分方程的數值方法,離起始點越遠,誤差越大。
9 函式
微分方程式數值求解 (二)
34
9 函式
微分方程式數值求解 (三)
35
import pylab
#----------------------------------------
# y’ = x**(1/3) sin(x) + 0.2
#
# i.c. y(0) = val val = range(0,11,5)
#----------------------------------------
def fn(x) :
return x**(1/3) * pylab.sin(x) + 0.2
# 設定周邊空白為白色
pylab.figure(facecolor=’white’)
a , b , n = 0 , 20*pylab.pi , 501
dx = (b-a)/(n-1)
# 設定 xs , ys
xs = [ a + i*dx for i in range(n) ]
ys = [None] * n
# c :起始值,在此分別為 0 5 10 三數
# 以下計算相同微分方程式但不同起始值的解答
for c in range(0,11,5) :
9 函式
微分方程式數值求解 (四)
36
ys[0] = c
for i in range(1,n) :
ys[i] = ys[i-1] + dx * fn(xs[i-1])
sym = ’y(0) = ’ + str(ys[0])
pylab.plot(xs,ys,label=sym)
# 設定圖形標頭文字
pylab.title(r”$y’ = sqrt[3]{x}, sin(x) + 0.2$",fontsize=20)
# 設定 X 軸與 Y 軸文字
pylab.xlabel(’X’)
pylab.ylabel(’Y’)
# 設定各線條圖例位置
pylab.legend(loc=’upper left’)
pylab.show()
9 函式
大樂透對獎 (一)
 用程式產生大樂透中獎號碼與十組彩券號碼,印出中獎的
號碼個數與號碼,輸出如下:
37
9 函式
大樂透對獎 (二)
本題使用集合物件儲存中獎號碼與彩券號碼,利用集合
物件的交集函式找出兩組樂透號碼的相同號碼。在列印集合
的樂透號碼時,為便於比對號碼利用 sorted 排序使得號
碼是由小到大排列。本範例程式特別使用函式來撰寫程式,
使得相同程式片段不需重複撰寫。
對小程式來說,以函式來撰寫程式或許不需要。但對龐
大的程式問題來說,將程式步驟以功能切割成一個個函式區
塊,整個程式設計問題就轉變為許多小型函式區塊設計,設
計規模由大縮小,適度的簡化了程式設計難度,使得程式設
計較容易駕馭。這種程式設計方式為由上而下的程式設計
(Top-Down design),這是透過函式設計來達到的。
38
9 函式
大樂透對獎 (三)
39
from random import *
def main() :
# 樂透號碼數
num = 6
fset , cset = set() , set()
# 產生 num 數字的中獎號碼
lottery( num , fset )
# 樂透中獎號碼
wset = frozenset(fset)
# 由小到大印出中獎號碼
print( " ".join(map(lambda x : ”{:>2}”.format(str(x)),sorted(wset))) )
# 彩券號碼
for i in range(10) :
fset = set()
# 產生 num 數字彩券號碼
lottery( 6 , fset )
9 函式
大樂透對獎 (四)
40
# 找出中獎號碼與彩券號碼相同數字
cset = check_num(wset,fset)
print(" ".join( map( lambda x : "{:>2}".format(str(x)),
sorted(fset))) ,
’:’, len(cset) , ’-->’ ,
" ".join(map(str,sorted(cset))) )
# 產生 n 個號碼的樂透號碼
def lottery( n , bset ) :
while True :
bset.add( randint(1,49) )
if len(bset) == n : return
#找出兩組樂透號碼的相同號碼
def check_num( aset , bset ) :
return aset.intersection(bset)
# 執行主函式
main()
9 函式
印製年曆 (一)
 印製年曆首先要應用由日期推算星期幾的公式,此公式如下:
( Y+[Y/4]−[Y/100]+[Y/400]+[2.6 × M−0.2]+D ) mod 7
以上公式中的年(Y)、月(M)兩數字與實際日期的年月是有所差異。在
公式中,每年的三月被當成公式中的一月,四月為二月,次年的一、二月為
公式中當年的十一、十二月。例如:西元日期 2000 年 1 月 1 日,在
公式中的 Y = 1999,M = 11,D = 1。又如西元1999 年 12 月 31
日在公式中 Y = 1999,M = 10,D = 31。公式中的 [x] 為 x 的整
數部份,mod 為餘數運算子。公式回傳 [0,6] 之間整數,分別代表星期
日到星期六。
本範例程式總共有四個函式,除 main 函式外,其他三個函式作用分
別是 (1)計算某年是否為閏年? (2)求得某年月的天數 (3)求得某日是
星期幾。有了這三個函式協同運作,列印年曆過程就變得很直接,程式由兩
層迴圈組成,外迴圈為月份迴圈,內迴圈為日期迴圈。此外程式有兩個小細
節,分別為每月初一的前面空格算法,與要記得每印完星期六後隨即要跳列
。整體來說,年曆列印問題是一個簡單且直接的函式應用問題。
41
9 函式
印製年曆 (二)
42
def main() :
wstrs = ( "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" )
w = 4
fmt = "{:>" + str(w) + "}"
while True :
y = int( input("輸入西元年> ") )
for m in range(1,13) :
print( " "*12 , m , "月" )
for s in wstrs : print( fmt.format(s) , end="" )
print()
wday , mdays = weekday(y,m,1) , mondays(y,m)
print( " "*int(w*wday) , end="" )
for d in range(mdays) :
print( fmt.format(d+1) , end= ("" if wday<6 else "n" ) )
wday = ( wday + 1 if wday < 6 else 0 )
print("n")
9 函式
印製年曆 (三)
43
# 某年是否為閏年
def isleap( y ) :
return True if y%400 == 0 or ( y%100 and y%4 == 0 ) else False
# 某年某月的日數
def mondays( y , m ) :
days = [ 31 , 28 , 31 , 30 , 31 , 30 ,
31 , 31 , 30 , 31 , 30 , 31 ]
if m == 2 :
return 29 if isleap(y) else 28
else :
return days[m-1]
# 計算某年月日星期幾
def weekday( y , m , d ) :
( y , m ) = ( y-1 , m+10 ) if m < 3 else ( y , m - 2 )
return ( y + y//4 - y//100 + y//400 + int(2.6*m-0.2) + d )%7
# 執行主函式
main()
9 函式
印製年曆 (四)
44
輸出:
9 函式
修課時間排序 (一)
讀入一個修課時間檔案,依課程每周第一次上課時間排序,將結果
印出來。以下左側為修課時間,每列包含課程名稱與授課時間,右
側則為排序後的結果。例如:化學在星期一的第三節是一周中最早
上課時間,其次是經濟學在星期二的第五節,其他依此類推。
45
schedule.dat@web
9 函式
修課時間排序 (二)
本範例是一個排序問題,但排序的條件卻有些複雜,困難點來自要找出
每門課於一周的最早上課時間,這要透過比較該門課的所有上課時間才能找
到。此外各門課的最早上課時間也不一定排在最前面,例如:化學課的最早
上課時間是星期一的第三節,資料排在末尾。在這種情況下,往常的排序函
式僅使用 key=lambda ... 的方式是無法勝任的,在此就需使用特殊的
排序規則函式來處理。
觀察輸出結果,可知其與課程檔各列的資料一樣,僅是順序不同而已。
如此,在由課程檔讀入各列資料後,整列的課程名稱與上課時間就以原字串
直接存入串列,不需作任何處理,所有的資料截取與比較動作都在排序規則
函式中進行。由於課程時間是以「中文數字:若干個英文數字」方式設定,
例如:化學上課時間為 三:8 一:34 共三個小時,各小時需要將其轉為一
個數字,用來比較該節課在一周上課的前後。在這裡使用一個簡單方式,即
將漢字數字一到五變為十位數的 0 到 4 ,節數變為個位數。如此一來化
學課的三個上課時段,就變為 28、3、4 。3 為最小,就用來代表化學的
最早上課時間與其他門課的同等數字比較。
46
9 函式
修課時間排序 (三)
在程式中的排序規則函式,by_weekly_earlier_hr ,首先分解各
列取出上課時間,在迴圈中將各個上課時間再度依冒號分解為中文數字與英
文數字字串,由此利用前述方法將各個上課鐘點轉為數字,回傳最小值。在
程式中,中文數字所對應的數字是先在主函式處理存成字典,名稱為 c2n
,再透過 global 方式給排序規則函式使用,這是不得已的作法,因為排
序規則函式僅接受由排序函式 sort 傳入的串列元素參數。在正常的情況
下程式設計要避免使用 global 變數,在下個範例會詳加說明使用
global 的缺點。
47
9 函式
修課時間排序 (四)
48
def main() :
global c2n
cnum = ’一二三四五’
c2n = dict( [ ( b , a ) for a , b in enumerate(cnum) ] )
with open("schedule.dat") as infile :
schedules = infile.readlines()
#依據課程在一周內最早上課時間排序
schedules.sort( key=by_weekly_earlier_time )
for s in schedules : print( s.rstrip() )
# 設定排序標準
def by_weekly_earlier_time( schedule ) :
global c2n
course , *csect = schedule.split()
all = []
for p in csect :
9 函式
修課時間排序 (五)
49
# 拆解上課時間
a , b = p.split(’:’)
w = c2n[a]
# 將此門課所有上課時間以整數表示
for c in b :
s = int(c)
all.append(w*10+s)
# 回傳該門課在一周最早上課時間所代表的整數
return sorted(all)[0]
# 執行主函式
main()
本題另有一種作法,即在讀入課程檔時,立即計算該門課的最早上課時間
對應數字,將課名與數字存入字典 snum,其資料為 {’化學’:3, ’國文
’:25 , ..., }。在排序時,只要用 split() 取得第一筆字串(即課名)
,馬上可使用 snum 得到該課最早上課時間的對應數字,以此數字當成排序
標準。這種程式寫法避免了之前程式需要不斷地執行排序規則函式,會讓程式
執行更有效率。
9 函式
修課時間排序 (六)
50
def main() :
global snum
cnum = ’一二三四五’
c2n = dict( [ ( b , a ) for a , b in enumerate(cnum) ] )
# 課程與上課時間
schedules = []
# 字典,儲存課名與一周最早上課時間比較數字
snum = {}
# 讀檔
with open("schedule.dat") as infile :
for line in infile :
schedule = line.strip()
schedules += [ schedule ]
# 回傳課程與其一周最早上課時間比較數字
course , num = course_eariler_number(schedule,c2n)
# 存入字典
snum[course] = num
9 函式
修課時間排序 (七)
51
# 依據課程在一周內最早上課時間排序
schedules.sort( key = lambda s : snum[s.split()[0]] )
# 列印
for s in schedules :
print( s.strip() )
# 尋找最早上課時間代表數字
def course_earlier_number( schedule , c2n ) :
# 分解課名與上課時間
course , *csect = schedule.split()
all = []
for p in csect :
# 拆解上課時間
a , b = p.split(’:’)
w = c2n[a]
# 將此門課所有上課時間以整數表示
for c in b :
s = int(c)
all.append(w*10+s)
return ( course , min(all) )
# 執行主函式
main()
9 函式
中文成語筆劃排序 (一)
52
 一些中文書末尾的索引通常依照各字的筆劃數由筆劃少排到筆劃多,如
果第一個字筆劃一樣,則依次比之後的字。本題讀入筆劃檔與成語檔,
依筆劃數將成語由筆劃少排到筆劃多,輸出時連同成語各字的筆劃數一
起顯示以資比對,有關筆劃檔格式詳見第八章筆劃數範例。
strokes.dat@web idioms.dat@web
9 函式
中文成語筆劃排序 (二)
53
9 函式
中文成語筆劃排序 (三)
54
以上的輸出看似複雜,但這僅是成語資料的列印程序而已。整個問題若以程
式設計的角度來看卻是很簡單,程式流程大概可區分為以下幾個連串步驟:
1. 讀取筆劃檔取得所有漢字筆劃:使用字典儲存每個漢字所對應的筆劃數
2. 讀取成語檔取得所有的成語:直接存入串列
3. 對成語串列排序:依照成語各字的筆劃數
4. 列印排序後的成語串列
以上幾個步驟各自獨立,可分別設計函式替代,如此一來,主函式
main 就乾淨許多,由主函式就可清楚的看到各個程式步驟。整個程式設計
就是許多功用各異的函式組合而成,撰寫程式規模較小的函式就變得簡單許
多。
9 函式
中文成語筆劃排序 (四)
55
在程式設計中,主函式與其他函式的資料交流最好是透過參數傳遞,避
免使用 global 於函式間分享資料。在此程式中,為了讓排序函式能取用
筆劃字典,由於無法將筆劃字典當成參數直接傳入排序函式,只好讓筆劃字
典當成 global 物件在 main 與 by_strokes 兩函式共用,這是一種
特殊情況。正常撰寫函式的過程中,資料於函式間傳遞最好都透過參數或是
函式的回傳來更動資料,這比較容易掌握資料的正確性,使用 global 變
數很容易忘記 global 資料在某個函式被更動,使得程式執行發生問題而
不知出錯根源,這種情況對越大型的程式越容易發生。
本範例僅是個小程式,程式規模不過數十列,對許多實際的應用問題,
程式通常以千行起跳,此時若不使用函式區塊切割程式,整個程式就會變得
複雜難以駕馭,不管是在開發或是維修程式,程式設計將會是場夢魘,修改
程式變得動轍得咎,處處動彈不得,這也說明使用函式於程式設計中的重要
性。
9 函式
中文成語筆劃排序 (五)
56
def main() :
global sdict
# 1:讀入筆畫檔,設定 sdict 字典(由 字-->筆劃)
sdict = {}
read_strokes( sdict )
# 2:讀入成語檔,設定 idioms 成語串列
idioms = []
read_idioms( idioms )
# 3:依各字筆劃數排序
idioms.sort( key = by_strokes )
# 4:列印排序後的成語
print_idioms( idioms , sdict )
# 讀取筆劃檔,設定 sdict 字典(由 字-->筆劃)
def read_strokes( sdict ) :
with open( "strokes.dat" ) as infile :
for line in infile :
ucode , strokes = line.split()
ch = chr(int(ucode[2:],16))
sdict[ch] = int(strokes)
9 函式
中文成語筆劃排序 (六)
57
# 讀入成語檔,設定 idioms 成語串列
def read_idioms( idioms ) :
with open("idioms.dat") as infile :
for line in infile :
idioms += [ line.strip() ]
# 排序標準:依各字筆劃數排序
def by_strokes( idiom ) :
global sdict
return [ sdict[c] for c in idiom ]
# 列印成語
def print_idioms( idioms , sdict ) :
s1 = 0
for ws in idioms :
s2 = sdict[ws[0]]
if s1 != s2 :
if s1 : print()
print( s2 , "劃:" )
print( ws , "-".join( map( lambda c : str(sdict[c]) , ws ) ) )
s1 = s2
# 執行主函式
main()

Ch9 範例

  • 1.
    9 函式 微分方程式數值求解 (一) 33 此公式需設定起始條件才能求解,若讓= c,c 要一開始就給定, 整個數值求解才能開始。以上的數值求解法稱為 Euler method,這是最 簡單的數值法用來計算起始值問題(initial value problem),缺點為 計算精度偏低,若要得到較好的結果,∆x 要越小越好。 本程式利用 c 迴圈來設定起始值,c 分別為 0、5、10,對相同微分 方程式,三個不同起始值代表三個不同的微分方程式,可產生三個數值解。 一般來說,所有起始值微分方程的數值方法,離起始點越遠,誤差越大。
  • 2.
  • 3.
    9 函式 微分方程式數值求解 (三) 35 importpylab #---------------------------------------- # y’ = x**(1/3) sin(x) + 0.2 # # i.c. y(0) = val val = range(0,11,5) #---------------------------------------- def fn(x) : return x**(1/3) * pylab.sin(x) + 0.2 # 設定周邊空白為白色 pylab.figure(facecolor=’white’) a , b , n = 0 , 20*pylab.pi , 501 dx = (b-a)/(n-1) # 設定 xs , ys xs = [ a + i*dx for i in range(n) ] ys = [None] * n # c :起始值,在此分別為 0 5 10 三數 # 以下計算相同微分方程式但不同起始值的解答 for c in range(0,11,5) :
  • 4.
    9 函式 微分方程式數值求解 (四) 36 ys[0]= c for i in range(1,n) : ys[i] = ys[i-1] + dx * fn(xs[i-1]) sym = ’y(0) = ’ + str(ys[0]) pylab.plot(xs,ys,label=sym) # 設定圖形標頭文字 pylab.title(r”$y’ = sqrt[3]{x}, sin(x) + 0.2$",fontsize=20) # 設定 X 軸與 Y 軸文字 pylab.xlabel(’X’) pylab.ylabel(’Y’) # 設定各線條圖例位置 pylab.legend(loc=’upper left’) pylab.show()
  • 5.
    9 函式 大樂透對獎 (一) 用程式產生大樂透中獎號碼與十組彩券號碼,印出中獎的 號碼個數與號碼,輸出如下: 37
  • 6.
    9 函式 大樂透對獎 (二) 本題使用集合物件儲存中獎號碼與彩券號碼,利用集合 物件的交集函式找出兩組樂透號碼的相同號碼。在列印集合 的樂透號碼時,為便於比對號碼利用sorted 排序使得號 碼是由小到大排列。本範例程式特別使用函式來撰寫程式, 使得相同程式片段不需重複撰寫。 對小程式來說,以函式來撰寫程式或許不需要。但對龐 大的程式問題來說,將程式步驟以功能切割成一個個函式區 塊,整個程式設計問題就轉變為許多小型函式區塊設計,設 計規模由大縮小,適度的簡化了程式設計難度,使得程式設 計較容易駕馭。這種程式設計方式為由上而下的程式設計 (Top-Down design),這是透過函式設計來達到的。 38
  • 7.
    9 函式 大樂透對獎 (三) 39 fromrandom import * def main() : # 樂透號碼數 num = 6 fset , cset = set() , set() # 產生 num 數字的中獎號碼 lottery( num , fset ) # 樂透中獎號碼 wset = frozenset(fset) # 由小到大印出中獎號碼 print( " ".join(map(lambda x : ”{:>2}”.format(str(x)),sorted(wset))) ) # 彩券號碼 for i in range(10) : fset = set() # 產生 num 數字彩券號碼 lottery( 6 , fset )
  • 8.
    9 函式 大樂透對獎 (四) 40 #找出中獎號碼與彩券號碼相同數字 cset = check_num(wset,fset) print(" ".join( map( lambda x : "{:>2}".format(str(x)), sorted(fset))) , ’:’, len(cset) , ’-->’ , " ".join(map(str,sorted(cset))) ) # 產生 n 個號碼的樂透號碼 def lottery( n , bset ) : while True : bset.add( randint(1,49) ) if len(bset) == n : return #找出兩組樂透號碼的相同號碼 def check_num( aset , bset ) : return aset.intersection(bset) # 執行主函式 main()
  • 9.
    9 函式 印製年曆 (一) 印製年曆首先要應用由日期推算星期幾的公式,此公式如下: ( Y+[Y/4]−[Y/100]+[Y/400]+[2.6 × M−0.2]+D ) mod 7 以上公式中的年(Y)、月(M)兩數字與實際日期的年月是有所差異。在 公式中,每年的三月被當成公式中的一月,四月為二月,次年的一、二月為 公式中當年的十一、十二月。例如:西元日期 2000 年 1 月 1 日,在 公式中的 Y = 1999,M = 11,D = 1。又如西元1999 年 12 月 31 日在公式中 Y = 1999,M = 10,D = 31。公式中的 [x] 為 x 的整 數部份,mod 為餘數運算子。公式回傳 [0,6] 之間整數,分別代表星期 日到星期六。 本範例程式總共有四個函式,除 main 函式外,其他三個函式作用分 別是 (1)計算某年是否為閏年? (2)求得某年月的天數 (3)求得某日是 星期幾。有了這三個函式協同運作,列印年曆過程就變得很直接,程式由兩 層迴圈組成,外迴圈為月份迴圈,內迴圈為日期迴圈。此外程式有兩個小細 節,分別為每月初一的前面空格算法,與要記得每印完星期六後隨即要跳列 。整體來說,年曆列印問題是一個簡單且直接的函式應用問題。 41
  • 10.
    9 函式 印製年曆 (二) 42 defmain() : wstrs = ( "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ) w = 4 fmt = "{:>" + str(w) + "}" while True : y = int( input("輸入西元年> ") ) for m in range(1,13) : print( " "*12 , m , "月" ) for s in wstrs : print( fmt.format(s) , end="" ) print() wday , mdays = weekday(y,m,1) , mondays(y,m) print( " "*int(w*wday) , end="" ) for d in range(mdays) : print( fmt.format(d+1) , end= ("" if wday<6 else "n" ) ) wday = ( wday + 1 if wday < 6 else 0 ) print("n")
  • 11.
    9 函式 印製年曆 (三) 43 #某年是否為閏年 def isleap( y ) : return True if y%400 == 0 or ( y%100 and y%4 == 0 ) else False # 某年某月的日數 def mondays( y , m ) : days = [ 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ] if m == 2 : return 29 if isleap(y) else 28 else : return days[m-1] # 計算某年月日星期幾 def weekday( y , m , d ) : ( y , m ) = ( y-1 , m+10 ) if m < 3 else ( y , m - 2 ) return ( y + y//4 - y//100 + y//400 + int(2.6*m-0.2) + d )%7 # 執行主函式 main()
  • 12.
  • 13.
  • 14.
    9 函式 修課時間排序 (二) 本範例是一個排序問題,但排序的條件卻有些複雜,困難點來自要找出 每門課於一周的最早上課時間,這要透過比較該門課的所有上課時間才能找 到。此外各門課的最早上課時間也不一定排在最前面,例如:化學課的最早 上課時間是星期一的第三節,資料排在末尾。在這種情況下,往常的排序函 式僅使用key=lambda ... 的方式是無法勝任的,在此就需使用特殊的 排序規則函式來處理。 觀察輸出結果,可知其與課程檔各列的資料一樣,僅是順序不同而已。 如此,在由課程檔讀入各列資料後,整列的課程名稱與上課時間就以原字串 直接存入串列,不需作任何處理,所有的資料截取與比較動作都在排序規則 函式中進行。由於課程時間是以「中文數字:若干個英文數字」方式設定, 例如:化學上課時間為 三:8 一:34 共三個小時,各小時需要將其轉為一 個數字,用來比較該節課在一周上課的前後。在這裡使用一個簡單方式,即 將漢字數字一到五變為十位數的 0 到 4 ,節數變為個位數。如此一來化 學課的三個上課時段,就變為 28、3、4 。3 為最小,就用來代表化學的 最早上課時間與其他門課的同等數字比較。 46
  • 15.
    9 函式 修課時間排序 (三) 在程式中的排序規則函式,by_weekly_earlier_hr,首先分解各 列取出上課時間,在迴圈中將各個上課時間再度依冒號分解為中文數字與英 文數字字串,由此利用前述方法將各個上課鐘點轉為數字,回傳最小值。在 程式中,中文數字所對應的數字是先在主函式處理存成字典,名稱為 c2n ,再透過 global 方式給排序規則函式使用,這是不得已的作法,因為排 序規則函式僅接受由排序函式 sort 傳入的串列元素參數。在正常的情況 下程式設計要避免使用 global 變數,在下個範例會詳加說明使用 global 的缺點。 47
  • 16.
    9 函式 修課時間排序 (四) 48 defmain() : global c2n cnum = ’一二三四五’ c2n = dict( [ ( b , a ) for a , b in enumerate(cnum) ] ) with open("schedule.dat") as infile : schedules = infile.readlines() #依據課程在一周內最早上課時間排序 schedules.sort( key=by_weekly_earlier_time ) for s in schedules : print( s.rstrip() ) # 設定排序標準 def by_weekly_earlier_time( schedule ) : global c2n course , *csect = schedule.split() all = [] for p in csect :
  • 17.
    9 函式 修課時間排序 (五) 49 #拆解上課時間 a , b = p.split(’:’) w = c2n[a] # 將此門課所有上課時間以整數表示 for c in b : s = int(c) all.append(w*10+s) # 回傳該門課在一周最早上課時間所代表的整數 return sorted(all)[0] # 執行主函式 main() 本題另有一種作法,即在讀入課程檔時,立即計算該門課的最早上課時間 對應數字,將課名與數字存入字典 snum,其資料為 {’化學’:3, ’國文 ’:25 , ..., }。在排序時,只要用 split() 取得第一筆字串(即課名) ,馬上可使用 snum 得到該課最早上課時間的對應數字,以此數字當成排序 標準。這種程式寫法避免了之前程式需要不斷地執行排序規則函式,會讓程式 執行更有效率。
  • 18.
    9 函式 修課時間排序 (六) 50 defmain() : global snum cnum = ’一二三四五’ c2n = dict( [ ( b , a ) for a , b in enumerate(cnum) ] ) # 課程與上課時間 schedules = [] # 字典,儲存課名與一周最早上課時間比較數字 snum = {} # 讀檔 with open("schedule.dat") as infile : for line in infile : schedule = line.strip() schedules += [ schedule ] # 回傳課程與其一周最早上課時間比較數字 course , num = course_eariler_number(schedule,c2n) # 存入字典 snum[course] = num
  • 19.
    9 函式 修課時間排序 (七) 51 #依據課程在一周內最早上課時間排序 schedules.sort( key = lambda s : snum[s.split()[0]] ) # 列印 for s in schedules : print( s.strip() ) # 尋找最早上課時間代表數字 def course_earlier_number( schedule , c2n ) : # 分解課名與上課時間 course , *csect = schedule.split() all = [] for p in csect : # 拆解上課時間 a , b = p.split(’:’) w = c2n[a] # 將此門課所有上課時間以整數表示 for c in b : s = int(c) all.append(w*10+s) return ( course , min(all) ) # 執行主函式 main()
  • 20.
    9 函式 中文成語筆劃排序 (一) 52 一些中文書末尾的索引通常依照各字的筆劃數由筆劃少排到筆劃多,如 果第一個字筆劃一樣,則依次比之後的字。本題讀入筆劃檔與成語檔, 依筆劃數將成語由筆劃少排到筆劃多,輸出時連同成語各字的筆劃數一 起顯示以資比對,有關筆劃檔格式詳見第八章筆劃數範例。 strokes.dat@web idioms.dat@web
  • 21.
  • 22.
    9 函式 中文成語筆劃排序 (三) 54 以上的輸出看似複雜,但這僅是成語資料的列印程序而已。整個問題若以程 式設計的角度來看卻是很簡單,程式流程大概可區分為以下幾個連串步驟: 1.讀取筆劃檔取得所有漢字筆劃:使用字典儲存每個漢字所對應的筆劃數 2. 讀取成語檔取得所有的成語:直接存入串列 3. 對成語串列排序:依照成語各字的筆劃數 4. 列印排序後的成語串列 以上幾個步驟各自獨立,可分別設計函式替代,如此一來,主函式 main 就乾淨許多,由主函式就可清楚的看到各個程式步驟。整個程式設計 就是許多功用各異的函式組合而成,撰寫程式規模較小的函式就變得簡單許 多。
  • 23.
    9 函式 中文成語筆劃排序 (四) 55 在程式設計中,主函式與其他函式的資料交流最好是透過參數傳遞,避 免使用global 於函式間分享資料。在此程式中,為了讓排序函式能取用 筆劃字典,由於無法將筆劃字典當成參數直接傳入排序函式,只好讓筆劃字 典當成 global 物件在 main 與 by_strokes 兩函式共用,這是一種 特殊情況。正常撰寫函式的過程中,資料於函式間傳遞最好都透過參數或是 函式的回傳來更動資料,這比較容易掌握資料的正確性,使用 global 變 數很容易忘記 global 資料在某個函式被更動,使得程式執行發生問題而 不知出錯根源,這種情況對越大型的程式越容易發生。 本範例僅是個小程式,程式規模不過數十列,對許多實際的應用問題, 程式通常以千行起跳,此時若不使用函式區塊切割程式,整個程式就會變得 複雜難以駕馭,不管是在開發或是維修程式,程式設計將會是場夢魘,修改 程式變得動轍得咎,處處動彈不得,這也說明使用函式於程式設計中的重要 性。
  • 24.
    9 函式 中文成語筆劃排序 (五) 56 defmain() : global sdict # 1:讀入筆畫檔,設定 sdict 字典(由 字-->筆劃) sdict = {} read_strokes( sdict ) # 2:讀入成語檔,設定 idioms 成語串列 idioms = [] read_idioms( idioms ) # 3:依各字筆劃數排序 idioms.sort( key = by_strokes ) # 4:列印排序後的成語 print_idioms( idioms , sdict ) # 讀取筆劃檔,設定 sdict 字典(由 字-->筆劃) def read_strokes( sdict ) : with open( "strokes.dat" ) as infile : for line in infile : ucode , strokes = line.split() ch = chr(int(ucode[2:],16)) sdict[ch] = int(strokes)
  • 25.
    9 函式 中文成語筆劃排序 (六) 57 #讀入成語檔,設定 idioms 成語串列 def read_idioms( idioms ) : with open("idioms.dat") as infile : for line in infile : idioms += [ line.strip() ] # 排序標準:依各字筆劃數排序 def by_strokes( idiom ) : global sdict return [ sdict[c] for c in idiom ] # 列印成語 def print_idioms( idioms , sdict ) : s1 = 0 for ws in idioms : s2 = sdict[ws[0]] if s1 != s2 : if s1 : print() print( s2 , "劃:" ) print( ws , "-".join( map( lambda c : str(sdict[c]) , ws ) ) ) s1 = s2 # 執行主函式 main()