การเขียนโปรแกรมย่อย
หัวข้อ องค์ประกอบของฟังก์ชัน การปรับโปรแกรมโดยการแยกกลุ่มคำสั่งเป็นฟังก์ชัน ฟังก์ชันแบบเวียนเกิด
mean, median, mode n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) s = 0 for e in d: s += e mean = s/n d.sort() median=(d[(n-1)//2]+d[n//2])/2 maxcount = 0 c = 0 for j in range(n): if d[i] == d[j]: c += 1 if c > maxcount: maxcount = c mode = d[i] print(mean,median,mode) def read_data(): ... def get_median(d): ... def get_mean(d) : ... def get_mode(d): ...
แบ่งโปรแกรมเป็นส่วนย่อยๆ เรียกส่วนย่อยเหล่านี้ว่า subroutine, subprogram, function หรือ method แต่ละ function มีหน้าที่ในตัวเองอย่างเด่นชัด โปรแกรมอ่านง่าย function เรียกใช้ได้หลายครั้ง function เรียกใช้ได้หลายที่ def read_data(): ... def get_median(d): ... def get_mean(d) : ... def get_mode(d): ...
mean, median, mode n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) s = 0 for e in d: s += e mean = s/n d.sort() median=(d[(n-1)//2]+d[n//2])/2 maxcount = 0 c = 0 for j in range(n): if d[i] == d[j]: c += 1 if c > maxcount: maxcount = c mode = d[i] print(mean,median,mode) def read_data(): n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) return d def get_mean(d): s = 0 for e in d: s += e mean = s/len(d) return mean def get_median(d): sorted_d = sorted(d); n = len(d) median = (sorted_d[(n-1)//2] + \ sorted_d[n//2] ) / 2 return median
mean, median, mode n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) s = 0 for e in d: s += e mean = s/n d.sort() median=(d[(n-1)//2]+d[n//2])/2 maxcount = 0 c = 0 for j in range(n): if d[i] == d[j]: c += 1 if c > maxcount: maxcount = c mode = d[i] print(mean,median,mode) def get_mode(d): maxcount = 0 for i in range(n): c = 0 for j in range(n): if d[i] == d[j]: c += 1 if c > maxcount: maxcount = c mode = d[i] return mode
mean, median, mode def read_data(): n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) return d def get_median(d): sorted_d = sorted(d); n = len(d) median = (sorted_d[(n-1)//2] + \ sorted_d[n//2] ) / 2 return median def get_mean(d): s = 0 for e in d: s += e mean = s/len(d) return mean def get_mode(d): maxcount = 0 n = len(d) for i in range(n): c = 0 for j in range(n): if d[i] == d[j]: c += 1 if c > maxcount: maxcount = c mode = d[i] return mode x = read_data() mean = get_mean(x) median = get_median(x) mode = get_mode(x) print(mean, median, mode)
mean, median, mode def read_data(): n = int(input()) d = [int(input()) for i in range(n)] return d #---------------------------------------------------------- def get_mean(d): return sum(d)/len(d) def get_median(d): sorted_d = sorted(d) n = len(d) return (sorted_d[(n-1)//2]+sorted_d[n//2])/2 def get_mode(d): c = [d.count(e) for e in d] maxcount = max(c) return d[c.index(maxcount)] x = read_data() mean = get_mean(x) median = get_median(x) mode = get_mode(x) print(mean, median, mode)
องค์ประกอบของฟังก์ชัน ชื่อฟังก์ชัน รายการพารามิเตอร์ def is_overlap( x1,y1,r1,x2,y2,r2 ) : distance = ( (x1-x2)**2 + (y1-y2)**2 ) ** 0.5 return distance <= (r1+r2) ต้องเยื้องคำสั่งทั้งหลายในฟังก์ชันให้ตรงกัน คืนผลจากฟังก์ชัน
ส่วนหัว : ชื่อฟังก์ชัน ใช้กฎการตั้งชื่อเหมือนกับตัวแปร มักตั้งชื่อเมท็อดขึ้นต้นด้วยตัวอังกฤษเล็ก มักตั้งชื่อเมท็อดให้เป็นกริยา input print read_data get_median max sin random is_overlap
ส่วนหัว : รายการของพารามิเตอร์ parameter คือตัวแปรสำหรับรับข้อมูลจากผู้เรียก รายการของ parameters อยู่ภายในวงเล็บ (ถ้าไม่รับ parameter ใดๆ ก็ไม่ต้องใส่อะไรในวงเล็บ) def is_overlap( x1, y1, r1, x2, y2, r2 ) : distance = ( (x1-x2)**2 + (y1-y2)**2 ) ** 0.5 return distance <= (r1+r2) def read_data() : n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) return d
ส่วนตัวของฟังก์ชัน คำสั่งต่าง ๆ ที่ทำงานตามข้อกำหนด ถ้าต้องการคืนผลการทำงานให้ผู้เรียก ใช้คำสั่ง return ตามด้วยค่าที่ต้องการคืนเป็นผลลัพธ์ def score2grade( s ) : if s >= 80 : return "A" elif s >= 70 : return "B" elif s >= 60 : return "C" elif s >= 50 : return "D" else : return "F" print( score2grade( 75 ) )
ส่วนตัว : ถ้าต้องการคืนผลลัพธ์หลายค่า ก็คืนเป็น tuple หรือ list def quadratic_roots( a, b, c ) : t = (b**2 – 4*a*c) ** 0.5 r1 = (-b – t)/(2*a) r2 = (-b + t)/(2*a) return (r1,r2) a1 = float(input("a = ")) b1 = float(input("b = ")) c1 = float(input("c = ")) root1, root2 = quadratic_roots(a1, b1 ,c1) print("roots are ", root1, root2)
ส่วนตัว : ถ้าไม่ต้องการคืนผลลัพธ์ใด ๆ ก็ใช้คำสั้ง return เฉย ๆ หรือไม่ก็ เมื่อทำงานถึงคำสั่งล่างสุดของฟังก์ชัน ก็คือการคืนการทำงานโดยไม่มีผลลัพธ์ def body_mass_index() : h = float(input("h = ")) if h <= 0 : return w = float(input("w = ")) if w <= 0 : bmi = w / (h*h) print("bmi = ", bmi) body_mass_index()
ส่วนตัว : Local Variables พารามิเตอร์และตัวแปรในฟังก์ชันใด เป็นตัวแปรที่ใช้ได้เฉพาะในฟังก์ชันนั้น ชื่อซ้ำกันได้ ถ้าอยู่คนละฟังก์ชัน เรียกใช้ตัวแปร ที่อยู่ฟังก์ชันอื่นไม่ได้ def read_data(): n = int(input()) d = [int(input()) for i in range(n)] return d #---------------------------------------------------------- def get_mean(d): return sum(d)/len(d) def get_median(d): sorted_d = sorted(d) n = len(d) return (sorted_d[(n-1)//2]+sorted_d[n//2])/2
ไม่ตั้งชื่อซ้ำ def mean(d) : return sum(d) / len(d) mean = mean([3,3,4,6,4]) mean = mean([34,4,35,6]) mean นี้คือชื่อฟังก์ชัน mean นี้คือชื่อตัวแปร จึงใช้เรียกฟังก์ชัน mean ไม่ได้ def average(a,b) : return (a+b)/2 def average(a,b,c) : return (a+b+c)/2 print( average(1,2,3) ) # ถูก print( average(4,5) ) # ผิด เป็นการนิยาม average ใหม่แบบรับพารามิเตอร์ 3 ตัว
ฟังก์ชันหาวันของสัปดาห์ def day_of_week( d, m, y ): if m < 3 : m = m + 12 y = y - 1 c = y // 100, k = y % 100 w = (d + 26*(m+1)//10 + k + k//4 + c//4 + 5*c) % 7 dow = ("SAT", "SUN","MON","TUE","WED","THU","FRI") return dow[w];
ตัวแปรของผู้เรียกกับของฟังก์ชันเป็นคนละตัว def clip(a) : if a < 0 : return 0 if a > 255 : return 255 return a a –9 a = -9 b = clip(a) a b –9
ตัวแปรของผู้เรียกกับของฟังก์ชันเป็นคนละตัว ตัวแปรเก็บ int, float, bool ของฟังก์ชันเปลี่ยน แต่ของผู้เรียกไม่เปลี่ยน เพราะคนละตัว a = 99 b = 20 clear( a ) swap( a, b ) a b 99 20 def clear(a) : a = 0 a 99 def swap(a, b) : t = a a = b b = t a b t 99 20 99 20 99
กรณีเป็นตัวแปรแบบ list, tuple, set, dict ตัวแปรของผู้เรียกกับพารามิเตอร์ของฟังก์ชันเป็นคนละตัว แต่อ้างอิงที่เก็บข้อมูลเดียวกัน a = [1,2,3,4] double_contents(a) def double_contents(x) : for i in range(len(x)): x[i] *= 2 เปลี่ยน list ที่ x อ้างอิง list ที่ a อ้างอิง ก็เปลี่ยนด้วย เพราะเป็น list เดียวกัน
แต่แบบนี้ไม่เปลี่ยน a = [1,2,3,4] double_contents(a) def double_contents(x) : x = [2*e for e in x] เปลี่ยน ค่าของ x ที่เดิมอ้างอิง list ไปอ้างอิง list อันใหม่ a ก็ยังอ้างอิง list เดิม ไม่เกี่ยวข้องกัน
เมื่อใดควรเขียนเมท็อดใหม่ เมื่อเมท็อดที่เขียนอยู่ยาวเกินไปหรือเข้าใจได้ยาก
ตัวอย่างการแยกกลุ่มคำสั่งออกเป็นฟังก์ชัน n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) maxcount = 0 c = 0 for j in range(n): if d[i] == d[j]: c += 1 if c > maxcount: maxcount = c mode = d[i] print(mode) def read_data(): n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) return d def get_mode(d): maxcount = 0 n = len(d) c = 0 for j in range(n): if d[i] == d[j]: c += 1 if c > maxcount: maxcount = c mode = d[i] return mode d = read_data(): print( get_mode(d) )
ตัวอย่างการแยกกลุ่มคำสั่งออกเป็นฟังก์ชัน def get_mode(d): maxcount = 0 n = len(d) for i in range(n): c = 0 for j in range(n): if d[i] == d[j]: c += 1 if c > maxcount: maxcount = c mode = d[i] return mode def count(d,x): c = 0 for j in range(n): if x == d[j] : c += 1 return c def get_mode(d): maxcount = 0 n = len(d) for i in range(n): c = count(d,d[i]) if c > maxcount: maxcount = c mode = d[i] return mode
ตัวอย่างการแยกกลุ่มคำสั่งที่เข้าใจยากเป็นฟังก์ชัน y = int(input("y = ")) m = int(input("m = ")) d = 31 if m in (4,6,9,11) : d = 30 elif m == 2 : if y%400==0 or \ y%4==0 and y%100!=0 : d = 29 else : d = 28 def is_leap_year(y): return y%400==0 or \ y%4==0 and y%100!=0 def days_in_month(m, y): d = 31 if m in (4,6,9,11) : d = 30 elif m == 2 : d = 28 if is_leap_year(y) : d = 29 return d y = int(input("y = ")) m = int(input("m = ")) d = days_in_month(m,y)
เมื่อใดควรเขียนเมท็อดใหม่ เมื่อมีกลุ่มคำสั่งที่เขียนซ้ำกัน หรือทำงานเหมือนกัน แต่ทำกับข้อมูลต่างกัน
ตัวอย่างการแยกกลุ่มคำสั่งที่ซ้ำกันออกเป็นฟังก์ชัน def read_data(): n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) return d d = read_data() mx = d[0] for e in d[1:] : if e > mx : mx = e max1 = mx max2 = mx print( max1 + max2 ) def read_data(): n = int(input()) d = [0]*n for i in range(n): d[i] = int(input()) return d def max_of_input(): d = read_data() mx = d[0] for e in d[1:] : if e > mx : mx = e return mx max1 = max_of_input() max2 = max_of_input() print( max1 + max2 )
ต.ย.การแยกกลุ่มคำสั่งที่ทำงานเหมือนกันเป็นฟังก์ชัน def read_data(): n = int(input()) d = [int(input()) \ for i in range(n)] return d y1 = read_data() u1 = [y for y in y1 \ if y != 1990] y2 = read_data() u2 = [y for y in y2 \ if y != 2000] y3 = read_data() u3 = [y for y in y3 \ if y != 1985] ... def read_data(): n = int(input()) d = [int(input()) \ for i in range(n)] return d def filter_input(x): y = read_data() u = [e for e in y \ if e != x] return u u1 = filter_input(1990) u2 = filter_input(2000) u3 = filter_input(1985) ...
ข้อแนะนำในการเขียนเมท็อด ควรตั้งชื่อที่สื่อความหมาย ควรมีภาระที่ต้องทำหนึ่งอย่างตามชื่อ ควรสั้นกะทัดรัด อ่านเข้าใจง่าย ควรมีพารามิเตอร์จำนวนไม่มาก
ค่าเฉลี่ยเคลื่อนที่ (moving average) 10 12 13 15 out 11 11.67 12.33 13.33 12.33 12.5
ตัวอย่าง : ค่าเฉลี่ยเคลื่อนที่ def moving_avg( d ) : mavg = [ (d[i-1]+d[i]+d[i+1])/3 \ for i in range(1,len(d)-1) ] mavg.insert( 0,(d[0]+d[1])/2 ) mavg.append( (d[-2]+d[-1])/2 ) return mavg x = [float(e) for e in input().split()] print(moving_avg(x))
การเวียนเกิด Recursive
ความสัมพันธ์เวียนเกิด (Recurrences) การเขียนความสัมพันธ์ของจำนวนเต็มในลำดับ 0, 1, 2, 3, 4, ... an = an-1 + 1 เมื่อ n > 0, a0 = 0 3, 5, 7, 9, 11, ... an = an-1 + 2 เมื่อ n > 0, a0 = 3 0, 1, 3, 6, 10, 15, ... an = an-1 + n เมื่อ n > 0, a0 = 0 0, 1, 1, 2, 3, 5, 8,... fn = fn-1 + fn-2 เมื่อ n > 1, f0 = 0, f1 = 1
an = 0, 1, 3, 6, 10, 15, ... รู้ว่า an = (0+1+2+...+n) รู้ว่า an = an-1 + n เมื่อ n > 0, a0 = 0 def a(n): s = 0; for i in range(n+1): s += i return s แบบที่ 1 def a(n) { if n <= 0: return 0 else: return a(n-1) + n; แบบที่ 2
เมท็อดหาค่า n! n! = n(n – 1)(n – 2) ... 21 def fac(n): แบบที่ 1 for i in range(1,n+1): f = f * i return f แบบที่ 1 n! = n(n – 1)(n – 2) ... 21 def fac(n): if n == 0: return 1 f = fac(n-1) return n * f แบบที่ 2 กรณีเล็กสุด ตามนิยามของแฟกตอเรียล
ข้อดี-ข้อด้อย การเขียนแบบ recursive มีทั้งข้อดีและข้อด้อย ข้อดี สั้นกระทัดรัด ในบางกรณี มุมมองแบบ recursive จะทำให้เห็น วิธีแก้ปัญหาได้ง่ายขึ้น ถ้าจำนวนชั้นของ loop ไม่ "คงที่" การใช้ recursive จะง่ายกว่ามาก ข้อด้อย บางครั้งทำงานช้ากว่าแบบ loop ใช้หน่วยความจำมากกว่า
1 ak % m = (ak/2 % m)2 % m a(ak/2 % m)2 % m การคำนวณ ak mod m ตัวอย่างที่ 1 : 220 % 31 = ? คำนวณ 220 ได้ 1048576 จากนั้น % 31 ได้ 1 ตัวอย่างที่ 2 : 2101 % 31 = ? คำนวณ 2101 ได้ 2535301200456458802993406410752 จากนั้น % 31 ได้ 2 อีกแบบ : 2101 % 31 = 2(250 % 31)2 % 31 = 2(1)2 % 31 = 2 1 k = 0 ak % m = (ak/2 % m)2 % m k is even a(ak/2 % m)2 % m k is odd
260 mod 10 = 42 mod 10 = 6 230 mod 10 = 82 mod 10 = 4 215 mod 10 = 282 mod 10 = 8 27 mod 10 = 282 mod 10 = 8 23 mod 10 = 222 mod 10 = 8 21 mod 10 = 212 mod 10 = 2 20 mod 10 = 1
การคำนวณ ak mod m def power_mod(a, k, m): if k == 0 : # กรณีพื้นฐาน หรือ base case return 1 p = power_mod(a, k//2, m); p = p * p # p p2 if (k % 2) == 1 : p *= a # k is odd return p % m
ตัวอย่าง : power_mod ใช้วิธียกกำลัง ด้วย ** และด้วย power_mod เพื่อหาเลข 4 ตัวท้ายของ f (9999) โดยที่ 𝑓(𝑛)= 𝑘=1 𝑛 𝑘 𝑘 = 1 1 + 2 2 + 3 3 ++ 𝑛 𝑛 ลองเปรียบเทียบเวลาการทำงานของวิธีทั้งสอง ใครเร็วกว่า ?
(a**k)%m vs power_mod(a,k,m) import time def power_mod(a,k,m): if k==0: return 1 p = power_mod(a,k//2,m) p = p*p if k%2 == 1: p *= a return p%m def sumpower2(n,m): s = 0 for k in range(1,n+1): s += power_mod(k,k,m) return s%m def sumpower1(n,m): s = 0 for k in range(1,n+1): s += k**k return s%m n = 9876 m = 10**4 t1 = time.time() sp1 = sumpower1(n,m) t2 = time.time() print("** \t\t", sp1, \ t2-t1, "s.") sp2 = sumpower2(n,m) t3 = time.time() print("power_mod\t", sp2, \ t3-t2, "s.") 𝑓(𝑛)= 𝑘=1 𝑛 𝑘 𝑘 = 1 1 + 2 2 + 3 3 ++ 𝑛 𝑛
ตัวอย่าง : flatten_list เขียนฟังก์ชัน flatten_list ซึ่งรับ list of lists of lists … เช่น [[1,2,3],4,[5,[6,7,[8,9],10],[11,12]]]) เพื่อคืน list ที่มี สมาชิกทุกตัวของ list เดิม เช่น input: [[1,2,3],4,[5,[6,7,[8,9],10],[11,12]]] output: [1,2,3,4,5,6,7,8,9,10,11,12] Hint: ถ้าอยากทดสอบว่า x เป็น list หรือไม่ ใช้ type(x) is list หรือ isinstance(x,list)
flatten_list def flatten_list(d): flat = [] for e in d: if type(e) is list: flat += flatten_list(e) else: flat.append(e) return flat x = [1,[[[2,3],4]],[[5,6],7],8] print(flatten_list(x))
ตัวอย่าง : anagram anagram คือ string ที่ได้จากการสลับตัวอักษรใน string เช่น anagram ทั้งหมดของ 'man' คือ 'man', 'mna', 'amn', 'anm', 'nma', 'nam' จงเขียนฟังก์ชัน anagram(x) ที่รับ x เป็น string แล้วจะคืน set ของ anagram ทั้งหมดของ x แนวคิด: anagram ทั้งหมดของ x ได้จากการนำอักษรแต่ละตัวใน x มาต่อด้วย anagram ของอักษรที่เหลือ เช่น anagram ทั้งหมดของ 'man' คือ 'm'+anagram('an'), 'a' +anagram('mn'), 'n'+anagram('ma')
(คล้าย list comprehension) anagram def anagram(x): if len(x) <= 1 : return {x} result = set() for i in range(len(x)): for a in anagram(x[:i]+x[i+1:]): result.add(x[i]+a) return result เขียนอีกแบบ ใช้ set comprehension (คล้าย list comprehension) def anagram(x): if len(x) <= 1 : return {x} return { x[i]+a for i in range(len(x)) for a in anagram(x[:i]+x[i+1:]) }
ลองเขียนดู : ผลรวมของจำนวนในลิสต์ จงเขียนฟังก์ชัน total(x) ที่รับ x ซึ่งเป็นลิสต์ที่เก็บจำนวน แล้วคืนผลบวกของจำนวนทุกตัวใน x ให้เขียนแบบ recursive ไม่มีการใช้วงวน ข้อแนะนำ : ให้ t(n) = x[0] + ... + x[n-1] ย่อมหมายความว่า t(n) = t(n-1) + x[n-1] สำหรับ n > 1 def total(x): if len(x) <= 1 : ___________________________________ return ________________________________
ลองเขียนดู : is_palindrome จงเขียนฟังก์ชัน is_palindrome(x) ที่รับสตริง x แล้วคืน True ถ้า x เป็น palindrome False ถ้า x ไม่ใช้ palindrome ต.ย. "21022012", "ทายาท" เป็น palindrome def is_palindrome(x): if len(x) <= 1 : __________________________________________ return ____________ and ______________________
นอกเรื่อง: การเรียงลำดับอีกแบบ (ทำความเข้าใจเอง) def qsort(x): if len(x) <= 1: return x p = x[0] less_than_p = [e for e in x if e < p] equal_p = [e for e in x if e ==p] more_than_p = [e for e in x if e > p] less_than_p = qsort(less_than_p) more_than_p = qsort(more_than_p) return less_than_p + equal_p + more_than_p def bubble_sort(x): n = len(x) for k in range(n-1): for i in range(n-1): if x[i] > x[i+1]: x[i],x[i+1] = x[i+1],x[i] ลองทดสอบการเรียงข้อมูลสุ่มสัก 10000 ตัว แบบไหนเร็วกว่า ?