วิธีคิดแบบนักวิทยาการคอมพิวเตอร์/Conditionals and recursion
บทที่ 4 Conditionals and recursion
แก้ไข4.1 The modulus operator
แก้ไขเป็นสัญลักษณ์ซึ่งทำหน้าที่แทนการคำนวณ ซึ่งทำงานร่วมกับ integer (จำนวนเต็ม) และ integer expressions ใน Python สัญลักษณ์ตัวนี้คือ เครื่องหมาย % โครงสร้างของประโยคก็เหมือนกับ operator ตัวอื่นๆ เช่น
>>> quotient = 7 / 3
>>> print quotient
2
>>> remainder = 7 % 3
>>> print remainder
1
7 เมื่อหารด้วย 3 จะเท่ากับ 2 และเหลือเศษ 1
modulus operator ให้ผลลัพธ์ที่มีประโยชน์อย่างน่าประหลาดใจ ตัวอย่างเช่นคุณสามารถที่ตะตรวจสอบตัวเลขว่าตัวเลขหนึ่งๆหารลงตัวด้วยตัวเลขอื่นหรือไม่ - ถ้า x % y แล้วเท่ากับ 0 ดังนั้น x หารด้วย y ลงตัว
4.2 Boolean expressions
แก้ไขเป็น expression ที่ประกอบด้วย true หรือ false ในการเขียน expression นี้ ทำโดยการใช้ operator == สำหรับเปรีบยเทียบค่า2ค่า และสร้างค่าทางตรรกะ
>>> 5 == 5
True
>>> 5 == 6
False
ใน statement แรก ค่าทั้งสองมีค่าเท่ากัน ดังนั้นผลลัพธ์ที่ออกมาคือ True ใน statement ที่สอง 5 ไม่เท่ากับ 6 ดังนั้นเราจะได้ค่า False
เครื่องหมาย == เป็นหนึ่งในเครื่องหมายที่ใช้ในการเปรียบเทียบ และนอกจากนี้ได้แก่:
x != y # x ไม่เท่ากับ y
x > y # x มากกว่า y
x < y # x น้อยกว่า y
x >= y # x มากกว่าหรือเท่ากับ y
x <= y # x น้อยกว่าหรือเท่ากับ y
สัญลักษณ์ใน Python นั้นจะแตกต่างกัยสัญลักษณ์ทางคณิตศาสตร์ ข้อผิดพลาดที่เกิดขึ้นโดยทั่วไปคือการใช้ = แทน == ให้จำไว้ว่า = เป็นเครื่องหมายที่สำหรับใช้ในการกำหนดค่าให้กับตัวแปร และ == เป็นเครื่องหมายที่ใช้สำหรับการเปรียบเทียบค่า
4.3 Logical operators
แก้ไขมีด้วยกันอยู่ 3 ตัวคือ and or and not ความหมายของ operator เหล่านี้มีความหมายคล้ายคลึงกับความหมายของตัวมันเองในภาษาอังกฤษ ตัวอย่างเช่น x > 0 and x < 10 เป็นจริงในกรณีเดียวเมื่อ x มากกว่า 0 และน้อยกว่า 10
n%2 == 0 or n%3 == 0 เป็นจริงถ้า ตัวใดตัวหนึ่งของเงื่อนไขเป็นจริง นั่นก็คือ เมื่อตัวเลขใดๆหารด้วย 2 หรือ 3 ได้ลงตัว
สุดท้าย not ปฏิเสธ boolean expression ดังนั้น not(x > y) เป็น True ถ้า (x > y) เป็น False นั่นคือ ถ้า x น้อยกว่าหรือเท่ากับ y
พูดอย่างเคร่งครัด ค่าที่ใช้ร่วมกับตัว operator ของ logical operator ควรจะเป็น boolean expressions( expression ที่ประกอบด้วย True และ False ) แต่ใน Python ไม่ได้ยึดติดกับตรงนี้ ตัวเลขใดก็ได้ที่ไม่ใช่ 0 จะถูกแปลความให้เป็น "true"
>>> x = 5
>>> x and 1
1
>>> y = 0
>>> y and 1
0
โดยทั่วไป รูปแบบชนิดนี้ไม่ได้เป็นรูปแบบที่ดี ถ้าคุณต้องการที่จะเปรียบเทียบค่ากับ 0 คุณควรที่จะทำให้เห็นอย่างชัดเจน
4.4 Conditional execution
แก้ไขเพื่อทีจะเขียนโปรแกรมให้ดีนั้น เราต้องการความสามารถที่จะตรวจเช็คเงื่อนไขและเปลี่ยนแปลงการทำงานของโปรแกรมอย่างสอดคล้องอยู่เสมอ Conditional statements ให้ความสามารถนี้กับเรา รูปแบบอย่างง่ายที่สุดของ if statement:
if x > 0:
print "x is positive"
boolean expression หลังจาก if statement เรียกว่า condition(เงื่อนไข) ถ้าหากว่ามันเป็น True หลังจากนั้น statement ที่ย่อหน้าก็จะถูกดำเนินการ แต่ถ้าไม่ก็จะไม่มีอะไรเกิดขึ้น
อย่าง compound statement อื่นๆ if statement สร้างขึ้นด้วย header และ block ของstatement:
HEADER:
FIRST STATEMENT
LAST STATEMENT
header จะเริ่มบนบรรทัดใหม่และจบด้วยเครื่องหมาย (:) ย่อหน้าที่ตามมาจะเรียกว่า block และ statement ที่อยู่ใน compound statement จะเรียกว่า body ของstatement
statement ใน body ของ if statement นั้นสามารถที่จะมีเท่าไหร่ก็ได้ไม่จำกัด แต่ต้องมีอย่างน้อย 1 statement ในบางครั้ง body ที่ไม่มี statementนั้นก็มีประโยชน์ กล่าวคือ เสมือนกับการจองพื้นที่สำหรับโค้ดที่เรายังไม่ได้เขียนลงไป ในกรณีนี้ คุณสามารถที่จะใช้ pass statement ซึ่งจะไม่ทำอะไรเลย
4.5 Alternative execution
แก้ไขรูปแบบที่สองของ if statement คือ ทางเลือกของการดำเนินการ ที่ซึ่งมีความเป็นไปได้อยู่ 2 และตัดสินใจเลือกเงื่อนไขที่จะทำการดำเนินการ ไวยกรณ์มีลักษณะเช่นนี้:
if x%2 == 0:
print x, "is even"
else:
print x, "is odd"
ถ้าส่วนที่เหลืออย เมื่อ x ถูกหารด้วย 2 ลงตัว เราก็จะรู้ว่า x เป็นจำนวนคู่ แล้วโปรแกรมก็จะทำการแสดงข้อความของผลนั้น ถ้าเงื่อนไขไม่ถูกต้อง statement อีกชุดหนึ่งก็จะถูกดำเนินการแทนตั้งแต่ที่เงื่อนไขจะต้องเป็น True และ False แน่นอนว่าจะต้องมี 1ทางเลือกที่จะต้องถูกดำเนินการ ทางเลือกดังกล่าวนั้นถูกเรียกว่า branches เพราะว่าทางเลือกเหล่านั้นเป็นแขนงหรือทางแยกในการไหลของการดำเนินการ
เช่นด้านล่าง ถ้าคุณต้องการเช็ค parity ของตัวเลขบ่อยครั้ง คุณควรตัด code นี้ไปเป็น function:
def printParity(x):
if x%2 == 0:
print x, "is even"
else:
print x, "is odd"
สำหรับทุกๆค่าของ x printParityแสดงข้อความที่ได้เตรียมไว้ เมื่อคุณทำการเรียก คุณสามารถให้ตัวเลขใดๆก็ได้แทน argument
>>> printParity(17)
17 is odd
>>> printParity(y+1)
18 is even
4.6 Chained conditionals
แก้ไขบางครั้งมีความเป็นไปได้ที่มากกว่า 2 และเราก็ต้องการทางแยกที่มากกว่า 2 เช่นกัน ทางเดียวที่จะแสดงการคำนวณเช่นนั้นคือ chained conditional:
if x < y:
print x, "is less than", y
elif x > y:
print x, "is greater than", y
else:
print x, "and" , y, "are equal"
elif คือ การย่อ ของ "else if" Again แน่นอนว่าเพียงทางเลือกเดียวเท่านั้นที่จะถูกดำเนินการ สำหรับ elif statements นั้นสามารถจะมีได้เท่าไหรก็ได้ไม่จำกัด แต่ทางเลือกสุดท้ายจะต้องเป็น else statements:
if choice == 'A' :
functionA()
elif choice == 'B' :
functionB()
elif choice == 'C' :
functionC()
else:
print "Invalid choice"
แต่ละเงื่อนไขจะถูกตรวจสอบในคำสั่ง ถ้าเงื่อนไขแรกเป็น false เงื่อนไขต่อไปก็จะถูกตรวจสอบ เป็นต้น ถ้าหนึ่งในเงื่อนไขนั้นๆเป็น true ทางเลือกนั้นๆก็จะถูกดำเนินการ และก็จะจบ statement แม้ว่าจะมีมากกว่า 1 เงื่อนไขที่เป็น true เงื่อนไขแรกที่เป็น true เท่านั้นที่จะถูกดำเนินการ
4.7 Nested conditionals
แก้ไขเงื่อนไขหนึ่งๆสามารถที่จะใส่อยู่ในเงื่อนไขอื่นๆได้เช่นเดียวกัน เราสามารถที่จะเขียนตัวอย่างของการแบ่งแยกออกเป็นสามส่วนได้ตามตัวอย่างต่อไปนี้:
if x == y:
print x, "and", y, "are equal"
else:
if x < y:
print x, "is less than", y
else:
print x, "is greater than", y
เงื่อนไขที่อยู่ข้างนอกประกอบด้วยสองทางเลือก ทางเลือกแรกมี statement ซึ่งเป็นผลลัพธ์ง่ายๆ ทางเลือกที่สองประกอบด้วย if statement ซึ่งมีทางเลือกอีก 2 ที่เป็นของตัวมันเอง
ถึงแม้ว่าการย่อหน้าของ statement จะทำให้เกิดเป็นโครงร่างเกิดขึ้น nested conditionals ก็ยังคงยากต่อการอ่านโดยเร็ว โดยทั่วไปมันเป็นความคิดที่ดีที่จะหลีกเลี่ยงมันเมื่อคุณทำได้
Logical operators เป็นทางหนึ่งที่จะช่วยทำให้ nested conditional statements ดูง่ายขึ้น สำหรับตัวอย่าง เราจะทำการเขียนโค๊ดต่อไปนี้ใหม่โดยใช้เพียงเงื่อนไขเดียว
if 0 < x:
x < 10:
print "x is a positive single digit"
print statement จะถูกดำเนินการเพียงกรณีเดียวถ้าเราทำให้เงื่อนไขทั้งสองผ่าน ดังนั้นเราสามรถใช้ and operator เข้ามาช่วยได้
if 0 < x and x < 10:
print "x is a positive single digit"
เงื่อนไขเหล่านี้เป็นชนิดที่ธรรมดา ดังนั้น Python จึงมีการจัดเตรียมตัวเลือกไวยกรณ์ที่เหมือนกับเครื่องหมายการคำนวณทางคณิตศาสตร:
if 0 < x < 10:
print "x is a positive single digit"
เงื่อนไขนี้เป็นความหมายเดียวกับ compound boolean expression และ nested conditional
4.8 The return statement
แก้ไขเป็น statement ที่ยินยอมให้เรายุติการดำเนินการของ function ก่อนที่จะจบ เหตุผลหนึ่งที่ใช้มันก็คือถ้าคุณตรวจเจอเงื่อนไขที่ผิดพลาด
import math
def printLogarithm(x):
if x <= 0:
print "Positive numbers only, please"
return
result = mathlog(x)
print "The log of x is", result
function printLogarithm มีพารามิเตอร์หรือตัวแปรที่ชื่อ x อย่างแรกที่ทำก็คือ ตรวจสอบว่าไม่ว่า x จะน้อยกว่าหรือเท่ากับ 0 กรณีนี้มันจะแสดงข้อความ error และใช้ return เพื่อออกจาก function นั้น
ให้จำไว้ว่าถ้าจะมีการใช้ฟังก์ชั่น math จะต้องมีการ import มันก่อนเสมอ
4.9 Recursion
แก้ไขเราก็ได้กล่าวถึงกันไปแล้วสำหรับ function หนึ่ง เรียกไปยัง function อื่น และคุณก็ได้เห็นตัวอย่างเหล่านั้นไปพอสมควร เราจะไม่จะกล่าวถึงว่ามันถูกต้องสำหรับ function ที่เรียกตัวมันเอง มันอาจไม่แน่ชัดว่าทำไมมันถึงเป็นสิ่งที่ดี แต่มันกลับกลายมาเป็นสิ่งหนึ่งที่มหัศจรรย์และน่าสนใจที่โปรแกรมสามารถทำได้ ตัวอย่าง ดูจาก function ต่อไปนี้:
def countdown(n):
if n == 0:
print "Blastoff!"
else:
print n
countdown(n-1)
countdown คาดหวังกับตัวแปร n ว่าจะต้องเป็นจำนวนเต็มบวก ถ้า n เป็น 0 ผลลัพธ์ที่จะออกมาคือ "Blastoff!" มิฉะนั้น ก็จะแสดงค่า n ออกมาและทำการเรียก function ที่ชื่อ countdown ตัวมันเอง โดยมี (n-1)เป็นตัวแปรที่ใช้รับค่าเข้าฟังก์ชั่น
จะเกิดอะไรขึ้นหากเราเรียก function เช่นนี้:
>>> countdown(3)
การดำเนินการของ countdown จะเริ่มด้วย n=3 และเมื่อ n ไม่เท่ากับ 0 มันจะออกผลลัพธ์เป็นค่าเท่ากับ 3, และเรียกฟังก์ชั่นตัวมันเอง
การดำเนินการของ countdown เริ่มด้วย n=2 และเมื่อ n ไม่เท่ากับ 0 มันจะออกผลลัพธ์เป็นค่าเท่ากับ 2, และเรียกฟังก์ชั่นตัวมันเอง
การดำเนินการของ countdown เริ่มด้วย n=1 และเมื่อ n ไม่เท่ากับ 0 มันจะออกผลลัพธ์เป็นค่าเท่ากับ 1, และเรียกฟังก์ชั่นตัวมันเอง
การดำเนินการของ countdown เริ่มด้วย n=0 และเมื่อ n เป็น 0 มันจะออกผลลัพธ์เป็นคำว่า, "Blastoff!" จากนั้นก็จะ return
countdown ทำการคืนค่า n=1
countdown ทำการคืนค่า n=2
countdown ทำการคืนค่า n=3
ซึ่งผลทั้งหมดจะออกมาในลักษณะนี้:
3
2
1
Blastoff!
ตัวอย่างถัดมา ให้ดูเช่นเดิมที่ function newLine และ threeLines
def newLine():
def threeLines():
newLine():
newLine():
newLine():
แม้ว่ามันจะสามารถใช้งานได้มันก็ไม่ได้ช่วยอะไรได้มากถ้าเราต้องการผลลัพธ์เป็น 2บรรทัดใหม่ หรือจะ 106 ทางเลือกที่ดีกว่าน่าจะเป็นเช่นนี้: def nLines(n)
if n > 0:
nLines(n-1)
โปรแกรมนี้เหมือนกับ countdown; ตราบเท่าที่ n มากกว่า 0 มันจะออกผลลัพธ์เป็นบรรทัดใหม่แล้วก็ทำการเรียกตัวเองซ้ำโดยผ่านตัวแปร(n-1) เพิ่มบรรทัดใหม่
กระบวนการที่ function เรียกตัวเองนั้นคือ recursion
4.10 Stack diagrams for recursive function
แก้ไขในหมวดที่ 3.11 เราคุ้นเคยกับ stack diagram ซึ่งอธิบายถึงสภาพของโปรแกรม ใรระหว่างที่มีการเรียกฟังก์ชั่น (function call: statement ที่ทำการดำเนินการฟังก์ชั่น ประกอบด้วยชื่อของฟังก์ชั่นและตามด้วย argument หรือตัวแปรที่ใช้รับค่าเข้าฟังก์ชั่นที่อยู่ภายในวงเล็บ)
ซึ่ง diagram ชนิดเดียวกันนี้สามารถที่จะช่วยอธิบาย recursive function ทุกครั้งที่มีการเรียกฟังก์ชั่น Python จะทำการสร้าง frame (โครงสร้างฟังก์ชั่นขึ้นมา), ซึ่งประกอบด้วย ตัวแปรของฟังก์ชั่นนั้นและ parameters (ชื่อซึ่งใช้ในฟังก์ชั่นเพื่ออ้างถึงค่าที่ผ่านด้วย argument หรือตัวแปรที่ใช้รับค่าเข้าฟังก์ชั่น)
สำหรับ recursive function จะมี frame มากกว่า 1 บน stack ในเวลาเดียวกัน นี่เป็นรูปภาพของ stack diagram ของฟังก์ชั่น countdown ซึ่งถูกเรียกด้วยมีค่า n=3
โดยปรกติ บนสุดของ stack จะเป็น frame สำหรับ ฟังก์ชั่น main มันจะว่างเปล่าเนื่องจากไม่ได้มีการสร้างตัวแปรใดๆใน main หรือผ่านค่า parameters ใดๆไปที่มัน
frame ของ ฟังก์ชั่น countdown มีค่า parameter n ที่ต่างกัน ด้านล่างสุดของ stack ที่ซึ่ง n=0 เรียกว่า base case (ทางแยกของเงื่อนไขใน recursive function ซึ่งจะไม่เกิดผลในการเรียกซ้ำ) มันจะไม่ทำการเรียกซ้ำ และจะไม่มี frame เพิ่มขึ้นอีก
4.11 Infinite recursion
แก้ไขถ้าหากการเรียกซ้ำไม่เคยไปถึง base case มันก็จะทำการเรียกซ้ำต่ออย่างต่อเนื่อง และโปรแกรมจะไม่มีการยุติ นี่เป็นที่รู้จักกันคือ infinite recursion และโดยทั่วไปมันไม่ได้ถูกพิจารณาว่าเป็นความคิดที่ดี
นี่เป็นตัวอย่างโปรแกรมอย่างเล็กที่สุดที่เป็น infinite recursion:
def recurse():
recure():
ในทั่วไปกระบวนการของการเขียนและทดสอบโปรแกรม โปรแกรมที่เป็น infinite recursion จะไม่สามารถรันอย่างไม่สิ้นสุดได้ Python จะรายงานข้อความ error เมื่อมีการเรียกซ้ำจนถึงที่สุด:
File "<stdin>", line 2, in recurse
(98 repetitions omitted)
File "<stdin>", line 2, in recurse
RuntimeError: Maximum recursion depth exceeded
traceback (list ของฟังก์ชั่นที่กำลังทำการดำเนินการ จะถูกปริ้นเมื่อมี runtime error เกิดขึ้น) นี้มีขนาดใหญ่กว่าเล็กน้อยกว่าตัวอย่างที่เราได้เห็นกันในบทที่แล้ว
เมื่อ error เกิดขึ้น ก็มีถึง 100 recurse frame บน stack!
4.12 Keyboard input
แก้ไขโปรแกรมที่เราได้เขียนจนเดี๋ยวนี้นั้นมีความหยาบอยู่เล็กน้อยคือมันไม่ได้มีการรับค่ามาจากผู้ใช้งานเลย โปรแกรมเหล่านั้นแค่ทำงานเหมือนๆเดิมอยู่ตลอดเวลา
Python จึงได้มีการจัด built-in functions ที่สามารถทำาการรับค่าจากคีย์บอร์ด อย่างง่ายที่สุดเรียกว่า raw_input เมื่อฟังก์ชั่นนี้ถูกเรียกใช้งาน โปรแกรมก็จะหยุดและรอให้ผู้ใช้งาน พิมพ์บางอย่าง
เมื่อผู้ใช้งาน กด Return หรือ ปุ่ม Enter โปรแกรมก็จะดำเนินต่อไปและ raw_input จะทำการคืนค่าที่ผู้ใช้งานป้อนเข้ามาเป็น string:
>>> input = raw_input ()
What are you waiting for?
>>> print input
What are you waiting for?
ก่อนที่จะทำการเรียก raw_input เป็นความคิดที่ดีที่จะแสดงข้อความเพื่อบอกให้ผู้ใช้งานรู้ว่าต้องป้อนข้อมูลหรือพิมอะไร ข้อความนี้เรียกว่า Prompt
เราสามารถที่จะให้ prompt นั้นทำหน้าที่แทน argument:
>>> name = raw_input ("Whatis your name? ")
Whatis your name? Arthur, King of the Brintons!
>>> print name
Arthur, King of the Brintons!
หากเราคาดหวังที่จะต้องการให้เป็น integer เราสามารถที่จะใช้ input function:
prompt = "Whatis the airspeed velocity of an unladen swallow?\n"
speed = input(prompt)
ถ้าผู้ใช้พิมพ์อักษรที่เป็นตัวเลขเข้ามา มันจะถูกแปลงให้เป็น integer และทำการกำหนดค่าให้กับ speed โชคร้าย ถ้าหากผู้ใช้งานพิมพ์ตัวอักษรที่ไม่ได้เป็นตัวเลขเข้ามา โปรแกรมก็จะล้ม:
>>> speed = input (prompt)
Whatis the airspeed velocity of an unladen swallow?
What do you mean, an African or a European swallow?
SyntaxError: invalid syntax
เพื่อที่จะหลีกเลี่ยง error ชนิดนี้ เป็นความคิดที่ดีที่จะใช้ raw_input เพื่อรับค่าที่เป็น string หลังจากนั้นก็ใช้ฟังก์ชั่นสำหรับการแปลง เพื่อแปลงค่าที่รับเป็นชนิดอื่น
4.13 Glossary
แก้ไขmodulus operator: An operator, denoted with a percent sign (%), that works on integers and yields the remainder when one number is divided by another.
boolean expression: An expression that is either true or false.
comparison operator: One of the operators that compares two values: ==, !=, >, <, >=, and <=.
logical operator: One of the operators that combines boolean expressions: and, or, and not.
conditional statement: A statement that controls the flow of execution depending on some condition.
condition: The boolean expression in a conditional statement that determines which branch is executed.
compound statement: A statement that consists of a header and a body. The header ends with a colon (:). The body is indented relative to the header.
block: A group of consecutive statements with the same indentation.
body: The block in a compound statement that follows the header.
nesting: One program structure within another, such as a conditional statement inside a branch of another conditional statement.
recursion: The process of calling the function that is currently executing. base case: A branch of the conditional statement in a recursive function that does not result in a recursive call.
infinite recursion: A function that calls itself recursively without ever reaching the base case. Eventually, an infinite recursion causes a runtime error.
prompt: A visual cue that tells the user to input data.