ข้ามไปเนื้อหา

วิธีคิดแบบนักวิทยาการคอมพิวเตอร์/Conditionals and recursion

จาก วิกิตำรา

บทที่ 4 Conditionals and recursion

[แก้ไข | แก้ไขต้นฉบับ]

เป็นสัญลักษณ์ซึ่งทำหน้าที่แทนการคำนวณ ซึ่งทำงานร่วมกับ 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 ลงตัว

เป็น 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 นั้นจะแตกต่างกัยสัญลักษณ์ทางคณิตศาสตร์ ข้อผิดพลาดที่เกิดขึ้นโดยทั่วไปคือการใช้ = แทน == ให้จำไว้ว่า = เป็นเครื่องหมายที่สำหรับใช้ในการกำหนดค่าให้กับตัวแปร และ == เป็นเครื่องหมายที่ใช้สำหรับการเปรียบเทียบค่า

มีด้วยกันอยู่ 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 คุณควรที่จะทำให้เห็นอย่างชัดเจน

เพื่อทีจะเขียนโปรแกรมให้ดีนั้น เราต้องการความสามารถที่จะตรวจเช็คเงื่อนไขและเปลี่ยนแปลงการทำงานของโปรแกรมอย่างสอดคล้องอยู่เสมอ 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 ซึ่งจะไม่ทำอะไรเลย

รูปแบบที่สองของ 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

บางครั้งมีความเป็นไปได้ที่มากกว่า 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 เท่านั้นที่จะถูกดำเนินการ

เงื่อนไขหนึ่งๆสามารถที่จะใส่อยู่ในเงื่อนไขอื่นๆได้เช่นเดียวกัน เราสามารถที่จะเขียนตัวอย่างของการแบ่งแยกออกเป็นสามส่วนได้ตามตัวอย่างต่อไปนี้:

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

เป็น 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 มันก่อนเสมอ

เราก็ได้กล่าวถึงกันไปแล้วสำหรับ 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():

print

def threeLines():

newLine():

newLine():

newLine():

แม้ว่ามันจะสามารถใช้งานได้มันก็ไม่ได้ช่วยอะไรได้มากถ้าเราต้องการผลลัพธ์เป็น 2บรรทัดใหม่ หรือจะ 106 ทางเลือกที่ดีกว่าน่าจะเป็นเช่นนี้: def nLines(n)

if n > 0:

print

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 เพิ่มขึ้นอีก

ถ้าหากการเรียกซ้ำไม่เคยไปถึง 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!

โปรแกรมที่เราได้เขียนจนเดี๋ยวนี้นั้นมีความหยาบอยู่เล็กน้อยคือมันไม่ได้มีการรับค่ามาจากผู้ใช้งานเลย โปรแกรมเหล่านั้นแค่ทำงานเหมือนๆเดิมอยู่ตลอดเวลา

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 หลังจากนั้นก็ใช้ฟังก์ชั่นสำหรับการแปลง เพื่อแปลงค่าที่รับเป็นชนิดอื่น

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.