1 แบบฝึกหัด

“Sail away from the safe harbor. Catch the trade winds in your sails. Explore. Dream. Discover.”

—Mark Twain

“ออกเรือไปจากอ่าวที่ปลอดภัย กางใบไปกับลมสำเภา ออกสำรวจ ออกฝัน ออกค้นพบ. ”

—มาร์ค ทเวน

เพื่อเป็นการทบทวนทักษะการเขียนโปรแกรม แบบฝึกหัดทบทวนการเขียนโปรแกรมทั่วไป. แม้ ตำรา จะอภิปรายเนื้อหา ศาสตร์การรู้จำรู้แบบและการเรียนรู้ของเครื่อง โดยทั่วไป แต่เพื่อให้ผู้อ่านเข้าใจอย่างชัดเจน ตัวอย่างโปรแกรมที่ใช้จะแสดงด้วยภาษาไพธอน (เวอร์ชั่นสาม). แบบฝึกหัดเขียนโปรแกรมนี้ออกแบบมา เพื่อทบทวนทักษะการเขียนโปรแกรมด้วยภาษาไพธอน

1.0.0.0.1 แบบฝึกหัด

จงเขียนโปรแกรมเพื่อพิมพ์ข้อความต่อไปนี้ ออกมาที่หน้าจอ โดยให้มีการขึ้นบรรทัดตามที่แสดง

Bruce Lee:
Knowing is not enough, we must apply.
Willing is not enough, we must do.

คำใบ้ ลองคำสั่ง print

1.0.0.0.2 แบบฝึกหัด

จงเขียนโปรแกรมเพื่อรับตัวเลขจำนวนเต็มจากผู้ใช้ และพิมพ์ตัวเลขนั้น พร้อมค่ากำลังสองออกมา ดังตัวอย่าง

Enter a number: 4
4 is squared to 16

เมื่อ 4 ในท้ายบรรทัดแรกเป็นอินพุตจากผู้ใช้

คำใบ้ (1) ลองคำสั่ง input, (2) เปรียบเทียบผลลัพธ์ของ "3"+"5" กับของ int("3") + 5 และ (3) ลองคำสั่ง 5**2

1.0.0.0.3 แบบฝึกหัด

จากภาพยนต์เรื่องคนหลุดโลก (Cast Away ค.ศ. 2000) ชัค โนแลนด์ รอดชีวิตจากเครื่องบินตก และติดอยู่ที่เกาะร้าง เขาลองคำนวณหาโอกาส ที่ทีมค้นหาจะพบเขาที่เกาะร้าง ดังนี้ (1) เครื่องบินด้วยความเร็ว \(v\) ไมล์ต่อชั่วโมง. (2) เครื่องบินติดต่อกับหอควบคุมการบินไม่ได้ เป็นเวลา \(T\) ชั่งโมงก่อนจะตก. ชัคต้องการคำนวณหาพื้นที่ที่ทีมกู้ภัยต้องค้นหา.

จงเขียนโปรแกรม เพื่อคำนวณพื้นที่ค้นหา โดยรับความเร็วเครื่องบิน \(v\) ไมล์ต่อชั่วโมง และเวลา \(T\) ชั่วโมง จากที่ขาดการติดต่อจนถึงเครื่องตก. โปรแกรมรายงานออกมาเป็นพื้นที่ตารางไมล์ และเปรียบเทียบกับพื้นที่ของประเทศไทย โดยพื้นที่ประเทศไทย มีขนาดประมาณ 513120 ตารางกิโลเมตร หรือ 198120 ตารางไมล์.

ตัวอย่างโปรแกรม

Plane speed (mph): 475
Time from the last contact to crash (h): 1
Search area = 708821.84 sq.mi.
That is 3.58 times the size of Thailand.

เมื่อ 475 ในบรรทัดแรก และ 1 ในบรรทัดที่สอง เป็นอินพุตจากผู้ใช้ และ 708821.84 กับ 3.58 เป็นผลการคำนวณ

คำใบ้ (1) พื้นที่ค้นหา \(a\) ตารางไมล์ คำนวณได้จาก \(a = \pi r^2\) เมื่อ \(r = v \cdot T\). (2) คำสั่ง round สามารถใช้ช่วยปัดเศษได้ เช่น round(21.842,2) จะให้ผลลัพธ์เป็น \(21.84\) (ปัดเป็นเลขทศนิยมสองตำแหน่ง). (3) มอดูล math มีฟังก์ชันและค่าคงที่ทางคณิตศาสตร์ต่าง ๆ ที่มีประโยชน์. มอดูล math จะถูกนำเข้ามาใช้งานได้ โดยคำสั่ง import math และค่า \(\pi\) สามารถเรียกได้จาก math.pi

1.0.0.0.4 แบบฝึกหัด

จงเขียนฟังก์ชันคำนวณเวลาที่ลูกเทนนิสวิ่งจากหน้าไม้ของผู้เซิร์ฟไปถึงท้ายสนามเทนนิสฝั่งผู้รับ และคำนวณพลังงานที่ใช้ในการเซิร์ฟ ในหน่วยจูล (Joules ตัวย่อ J) และในหน่วยแคลอรี่ (calories ตัวย่อ cal) โดยฟังก์ชันรับ ค่าน้ำหนักของลูกบอล \(m\) กรัม ค่าความเร็วสูงสุดของลูกบอล \(v\) ในหน่วยกิโลเมตรต่อชั่วโมง และความยาวของสนามเทนนิส \(d\) เมตร. สมมติว่าไม่มีแรงต้านทางอากาศ ไม่มีผลจากแรงดึงดูดของโลก ไม่มีผลจากการกระเด้งที่ผิวสนาม และคิดประมาณระยะทางเฉพาะในแนวราบทิศทางความยาวสนาม.

ตัวอย่างการเรียกใช้ฟังก์ชัน

time, energy, cal = serve(200, 23.8, 58)
print(time, 's')
print(energy, 'J')
print(cal, 'cal')

เมื่อ 200 คือความเร็วสูงสุดของลูกบอล ในหน่วยกิโลเมตรต่อชั่วโมง 23.8 คือความยาวสนาม ในหน่วยเมตร 58 คือน้ำหนักลูกบอล ในหน่วยกรัม และ serve คือฟังก์ชันที่ใช้คำนวณ. ผลลัพธ์คือ

0.8568 s
89.51 J
21.39 cal

คำใบ้ (1) ระยะที่ลูกบอลเดินทางประมาณจากความยาวสนาม. (2) เวลาที่ลูกบอลวิ่ง \(t\) คำนวณจาก \(d = v_0 + \frac{1}{2} \cdot a \cdot t^2\) เมื่อ \(v_0\) คือความเร็วต้น (ประมาณเป็นศูนย์ ขณะลูกกระทบหน้าไม้) และ \(a\) เป็นความเร่งเฉลี่ยของลูกบอล ในหน่วย เมตรต่อวินาทีกำลังสอง. (3) ความเร่งเฉลี่ยของลูกบอล \(a\) ประมาณได้จาก \(a = v/t\). (4) แรงเฉลี่ยที่ใช้ \(f\) ในหน่วยนิวตัน คำนวณได้จาก \(f = m \cdot a\). (5) พลังงานที่ใช้ \(e\) ในหน่วยจูล ประมาณได้จาก \(e = f \cdot d\). (6) หนึ่งแคลอรี่เท่ากับ 4.184 จูล. (7) ไพธอนกำหนดฟังก์ชันด้วยไวยากรณ์

def func_name(arg1, arg2, arg3):
    # function body
    ...
    return output1, output2

(8) แนวทางปฏิบัติที่ดีในการเขียนโปรแกรมไพธอน คือ ส่วนของโปรแกรมหลักจะเขียนอยู่ในรูปแบบ

if __name__ == '__main__':
    # main program
    ...
1.0.0.0.5 แบบฝึกหัด

บริษัทขนส่งแห่งหนึ่ง คิดค่าบริการซึ่งประกอบด้วย ค่าบริการส่ง (คิดตามพื้นที่) และค่าส่งของ (คิดตามน้ำหนัก) โดย ค่าบริการส่ง คิด 50 บาท ถ้าส่งในเขตจังหวัดขอนแก่น และคิด 100 บาท ถ้าส่งนอกเขตจังหวัดขอนแก่น. ค่าส่งของ คิดดังนี้ (1) คิด 8 บาทต่อกิโลกรัม สำหรับของน้ำหนักไม่เกิน 10 กิโลกรัม (2) คิด 12 บาทต่อกิโลกรัม สำหรับของน้ำหนักเกิน 10 กิโลกรัม แต่ไม่เกิน 20 กิโลกรัม และ (3) คิด 15 บาทต่อกิโลกรัม สำหรับของน้ำหนัก 20 กิโลกรัมขึ้นไป.

จงเขียนฟังก์ชันรับที่อยู่ และน้ำหนักของ แล้วคำนวณค่าส่งของบริษัทแห่งนี้.

ตัวอย่างการเรียกใช้ฟังก์ชัน

cost = delivery_kk("Khon Kaen", 14)
print(cost)

เมื่อ "Khon Kaen" คือพื้นที่ส่ง (อยู่ในเขตจังหวัดขอนแก่น) 14 คือน้ำหนักของที่ต้องการส่ง และฟังก์ชัน delivery_kk ทำหน้าที่คำนวณค่าจัดส่ง. ผลลัพธ์คือ 218 ซึ่งคือค่าจัดส่ง \(50 + 14 \cdot 12 = 218\) บาท.

คำใบ้ ไพธอนใช้ไวยากรณ์เงื่อนไข ดังนี้

if cond:
    # if body
    ...
# statement after condition

หากเป็นเงื่อนไขทางเลือก ใช้ไวยากรณ์ดังนี้

if cond:
    # if body
    ...
else:
    # else body
    ...
# statement after condition

หากเป็นเงื่อนไขทางเลือกหลายทาง ใช้ไวยากรณ์ดังนี้

if cond:
    # if body
    ...
elif cond:
    # elif body
    ...
else:
    # else body
    ...
# statement after condition
1.0.0.0.6 แบบฝึกหัด

จงเขียนโปรแกรมเพื่อคำนวณค่ารากกำลังสองเฉลี่ย (root mean square คำย่อ RMS) โดยรับจำนวนของค่าที่ต้องการนำมาคำนวณ และรับค่าเหล่านั้นทีละค่าจนครบ และคำนวณค่ารากกำลังสองเฉลี่ย เมื่อได้รับค่าต่าง ๆ ครบตามจำนวนแล้ว.

ตัวอย่างโปรแกรม

Number of values: 4
value 1: 10
value 2: 2
value 3: 0.4
value 4: 3.8
RMS = 5.445181356024793

เมื่อ 4 ในบรรทัดแรก เป็นอินพุตที่ผู้ใช้ระบุจำนวนค่า และค่า 10 ค่า 2 ค่า 0.4 และ 3.8 เป็นอินพุตที่ผู้ใช้ป้อน ส่วน value 1 ไปจนถึง value 4 เป็นสิ่งที่โปรแกรมพิมพ์ออกไปหน้าจอ และ 5.445181356024793 เป็นผลลัพธ์การคำนวณ \(\sqrt{\frac{10 + 2 + 0.4 + 3.8}{4}} = 5.445181356024793\).

คำใบ้ (1) ค่ารากกำลังสองเฉลี่ย \(\mathrm{rms}\) คำนวณจาก \(rms = \sqrt{\frac{1}{N} \sum_{i=1}^N x_i^2}\) เมื่อ \(N\) เป็นจำนวนค่า และ \(x_i\) เป็นค่าต่าง ๆ ที่ต้องการนำมาคำนวณ. (2) มอดูล math มีฟังก์ชัน math.sqrt เพื่อใช้คำนวณค่าราก. (3) ตัวอย่างรูปแบบไวยากรณ์ไพธอนสำหรับการวนซ้ำ คือ

for i in range(num):
    # statement to be repeated
    ...
# statement after for loop

เมื่อ num เป็นจำนวนครั้งที่ต้องการวนซ้ำ และตัวแปร i เป็นดัชนีของการวนซ้ำ. (4) ไพธอนมีวิธีจัดรูปแบบข้อมูลสายอักขระ (string) ได้หลายแบบ (4.1) ใช้ตัวดำเนินการ % เช่น "value %d"%8 ซึ่งจะแสดงผลเป็น value 8 หรือ (4.2) ใช้เมท็อด format ของข้อมูลสายอักขระ เช่น "value {}".format(8) ซึ่งจะแสดงผลเป็น value 8 เช่นกัน. (5) ตัวดำเนินการ = เป็นตัวดำเนินการกำหนดค่า (assignment operator) ซึ่งทำงาน โดย ประเมินค่าจากนิพจน์ที่อยู่ทางซ้ายมือ และนำค่าไปเก็บไว้ในตัวแปรที่อยู่ทางขวา เช่น x = 3 + 4 คือ การกำหนดค่าให้ตัวแปร x เป็นค่า 7 ซึ่งได้จากการประเมินนิพจน์ 3+4. ทำนองเดียวกัน x = x + 1 คือ การกำหนดค่าให้ตัวแปร x เป็น ค่าจากการประเมินนิพจน์ x + 1 ดังนั้นหากรันคำสั่ง x = x + 1 นี้แล้ว ตัวแปร x จะมีค่าเพิ่มจากเดิมขึ้นหนึ่ง.

1.0.0.0.7 แบบฝึกหัด

จงเขียนฟังก์ชัน เพื่อประมาณค่าความน่าจะเป็นของเหตุการณ์ต่าง ๆ จากจำนวนครั้งที่พบ.

ตัวอย่างการเรียกใช้ฟังก์ชัน

count = [0, 8, 20, 4, 12, 1, 5]
p = est_prob(count)
print(p)

เมื่อ count คือ ตัวแปรของข้อมูลชนิดลิสต์ (list) ที่เก็บจำนวนครั้งของเหตุกาณ์ 7 เหตุการณ์ โดย ตัวเลขในแต่ละตำแหน่ง แทนจำนวนครั้งที่พบเหตุการณ์นั้น เช่น เหตุการณ์ที่ 1 ไม่พบเลย เหตุการณ์ที่ 2 พบ 8 ครั้ง. ส่วน est_prob คือฟังก์ชันที่ประมาณความน่าจะเป็น. ผลลัพธ์คือ

[0.0, 0.16, 0.4, 0.08, 0.24, 0.02, 0.1]

ซึ่งหมายถึง ความน่าจะเป็นที่คำนวณได้ สำหรับเหตุการณ์ต่าง ๆ ตามลำดับ เช่น เหตุการณ์ที่ 1 มีความน่าจะเป็น เป็น 0 เหตุการณ์ที่ 2 มีความน่าจะเป็น เป็น 0.16.

คำใบ้ (1) ความน่าจะเป็น \(p_i\) ประมาณได้จาก \(p_i = \frac{c_i}{\sum_{j=1}^N c_j}\) เมื่อ \(c_i\) คือจำนวนครั้งที่พบเหตุการณ์ \(i\) และ \(N\) คือจำนวนเหตุการณ์ทั้งหมด. (2) แต่ละค่าของลิสต์สามารถนำออกมาได้ โดยการใช้ดัชนี เช่น count[2] จะได้ค่า 20 ออกมา (ดัชนีแรก เริ่มที่ 0). (3) ฟังก์ชัน len สามารถช่วยนับจำนวนรายการทั้งหมดในลิสต์ได้. (4) คำสั่ง for สามารถทำงานกับลิสต์ได้โดยตรง เช่น

for c in count: 
    print(c)

(5) ลิสต์ว่าง สามารถสร้างได้ เช่น prob = [] กำหนดตัวแปร prob ให้มีค่าเป็นลิสต์ว่าง. (6) ลิสต์ สามารถเพิ่มรายการเข้าไปได้ เช่น prob.append(0.1) เป็นการเพิ่มรายการ 0.1 เข้าไปในลิสต์ของตัวแปร prob.

1.0.0.0.8 แบบฝึกหัด

จงเขียนฟังก์ชันที่รับข้อความ และนับความถี่ของคำต่าง ๆ ในข้อความ แล้วส่งผลการนับความถี่ออกมา.

ตัวอย่างการเรียกใช้ฟังก์ชัน

txt = "Evil is done by oneself; " + \
"by oneself is one defiled. "+ \
"Evil is left undone by oneself; " + \
"by oneself is one cleansed. "

wf = word_freq(txt)
print(wf)

เมื่อ txt คือ ตัวแปรที่เก็บข้อความ และ word_freq คือฟังก์ชันที่นับความถี่ของคำ ในข้อความของ txt. ผลลัพธ์คือ

{'is': 4, 'left': 1, 'done': 1, 'Evil': 2, 'one': 2, 
'cleansed': 1, 'oneself': 4, 'undone': 1, 'defiled': 1, 
'by': 4}

ซึ่งอยู่ในรูปของไพธอนดิกชั่นนารี (dictionary).

คำใบ้ (1) ใช้ฟังก์ชันข้างล่าง เพื่อจัดการคำต่าง ๆ ให้เรียบร้อย

def clean_txt(msg):
    msg = msg.replace('.', ' ')
    msg = msg.replace(';', ' ')
    msg = msg.replace('\n', ' ')
    msg = msg.replace('  ', ' ')

    return msg

(2) เมท็อด split ของข้อมูลสายอักขระ สามารถช่วยแตกคำต่าง ๆ ออกมาจากข้อความได้สะดวก เช่น "Evil is left".split() จะให้ลิสต์ ['Evil', 'is', 'left'] ออกมา.
(3) เมท็อด strip ช่วยตัดช่องว่างรอบคำออกได้สะดวก. (4) ดิกชันนารีว่าง สามารถสร้างได้ เช่น w = {} จะสร้างดิกชันนารีว่าง ให้กับตัวแปร w. (5) การอ้างอิงรายการของดิกชันนารี จะใช้กุญแจดัชนี ซึ่งเป็นเช่นเดียวกับดัชนีของลิสต์ เพียงแต่ กุญแจดัชนีของดิกชันนารีสามารถใช้เป็นสายอักขระได้ เช่น w['Evil'] = 1 เป็นการกำหนดค่า \(1\) ให้กับรายการที่มีกุญแจดัชนีเป็น 'Evil' ซึ่งหากยังไม่มีรายการของกุญแจนี้อยู่ ไพธอนจะสร้างขึ้นมาใหม่ แต่หากมีอยู่แล้วค่า 1 ก็จะไปแทนที่ค่าเดิมของรายการนี้. กลไกนี้ทำให้ดิกชันนารีสะดวกมากกับการใช้นับความถี่คำในลักษณะเช่นนี้. (6) เช่นเดียวกับตัวแปรเดี่ยว รายการของดัชนีสามารถใช้ในลักษณะการเปลี่ยนแปลงค่าได้ เช่น w['Evil'] += 1 จะเป็นการเพิ่มค่าของรายการของกุญแจดัชนี 'Evil' จากเดิม ขึ้นไปหนึ่ง.

1.0.0.0.9 แบบฝึกหัด

ถอดรหัสดีเอ็นเอ. ดีเอ็นเอประกอบด้วย ฐานนิวคลีโอไทด์ (nucleotide bases) สี่ชนิด ได้แก่ อะดีนีน (adenin ตัวย่อ A) ไซโตซีน (cytosine ตัวย่อ C) กัวอานีน (guanine ตัวย่อ G) และ ไธมีน (thymine ตัวย่อ T). ลำดับของฐานนิวคลีโอไทด์ต่าง ๆ จะเป็นข้อมูลที่เซลล์นำไปใช้ ในกระบวนการสร้างโปรตีน. นั่นคือ ลำดับของฐานนิวคลีโอไทด์สามตัว จะบอกชนิดของกรดอะมิโน (amino acid) ที่จะเซลล์จะสร้างเพื่อไปประกอบเป็นโปรตีน (หรืออาจจะเป็นรหัส เพื่อบอกการจบของลำดับสายกรดอะมิโน). ชุดของฐานนิวคลีโอไทด์สามตัว จะเรียกว่า โคดอน (codon). โคดอน จะถูกอ่านตามลำดับ และจะไม่มีอ่านดีเอ็นเอซ้อนกัน เช่น ‘AAGGGC’ จะอ่านเป็นโคดอนสองชุด คือ ‘AAG’ และ ‘GGC’.

จงเขียนฟังก์ชัน เพื่อแปลงจากลำดับดีเอ็นเอ ไปเป็นโปรตีน ซึ่งคือ สายของกรดอะมิโน โดย ฟังก์ชันรับไฟล์ตารางโคดอน ที่เป็นตารางการแปลงโคดอนไปเป็นกรดอะมิโน และรับไฟล์ลำดับดีเอ็นเอ แล้วถอดลำดับดีเอ็นเอ ทีละสามฐาน และส่งออกผลที่แปลงออกมาได้.

ตัวอย่างไฟล์ตารางโคดอนและตัวอย่างไฟล์ดีเอ็นเอ สามารถดาวน์โหลดได้จาก http://degas.en.kku.ac.th/coewiki/doku.php?id=pr:advbook (ภายใต้หัวข้อ ข้อมูลประกอบแบบฝึกหัด). ตัวอย่างการเรียกใช้ฟังก์ชัน

protein = codon('codons.txt', 'homo_sapiens_mitochondrion.txt')
print(protein)

เมื่อ codons.txt คือ ชื่อไฟล์ตารางแปลงโคดอน homo_sapiens_mitochondrion.txt คือชื่อไฟล์ลำดับของดีเอ็นเอ ที่ต้องการแปลง และ codon คือฟังก์ชันที่แปลงโคดอนเป็นโปรตีน. ผลลัพธ์คือ

['Lysine', 'Glycine', 'Leucine', 'Alanine', 'stop', 'Leucine', 
'Lysine', 'Tryptophan', 'Leucine', 'Isoleucine', 'Cysteine', 
'Valine', 'Glutamine', 'Leucine', 'Methionine', 'Glutamine', 
'Serine', 'Glycine', 'Valine', 'Leucine', 'Glutamine', 
'Serine', 'Leucine']

ซึ่งอยู่ในรูปของลิสต์.

คำใบ้ (1) เปิดดูเนื้อหาในไฟล์ก่อน เพื่อเข้าในรูปแบบของข้อมูลที่เก็บ. (2) ไพธอนใช้ไวยากรณ์ดังนี้ในการเปิดอ่านไฟล์

with open('filename', 'r') as f:
   file_content = f.read()
   # ... process file_content

โดย 'filename' แทนชื่อไฟล์ที่ต้องการเปิดอ่าน (ระบุด้วย 'r') และใช้ตัวแปร f เป็นตัวจัดการไฟล์ (file handle). เมท็อด read ใช้อ่านเนื้อหาทั้งหมดของไฟล์ออกมา. (3) การอ่านดีเอ็นเอมาทีละชุด ชุดละสาม สามารถทำได้หลายวิธี หนึ่งในเทคนิคที่สะดวกคือ (3.1) ใช้ range(0, len(dna), 3) เพื่อหาตำแหน่งเริ่มต้นของแต่ละชุดโคดอน เมื่อ dna เป็นข้อมูลสายอักขระที่เก็บลำดับของดีเอ็นเอ (3.2) ใช้เทคนิคการตัด (slicing) เช่น dna[i:(i+3)] เพื่อดึงโคดอนออกมา เมื่อ i เป็นตำแหน่งเริ่มของโคดอน. (4) ถ้าอ่านตารางโคดอนและจัดทำเป็นดิกชันนารีไว้ก่อน จะทำให้การแปลงสะดวกมาก.

1.0.0.0.10 แบบฝึกหัด

โน้ตดนตรีในระดับเสียงเต็มรูป (diatonic notes) คือ โน้ตดนตรี \(7\) ตัวโน๊ตในระดับเสียง (scale). ตัวโน๊ตทั้งเจ็ดนี้ จะนิยามต่างกันไปสำหรับแต่ละกุญแจเสียง. ตัวอย่าง เช่น ระดับเสียงหลัก (major scale) ของกุญแจเสียง C (key of C) จะมีโน๊ต C, D, E, F, G, A และ B. ระดับเสียงหลักของกุญแจเสียง G (key of G) จะมีโน๊ต G, A, B, C, D, E และ F#. ระดับเสียงหลัก นิยามระดับเสียงเต็มรูป ตามเกณฑ์ดังนี้

โน้ตดนตรีในระดับเสียงเต็มรูป กุญแจเสียง C กุญแจเสียง G
ตัวแรก เป็นโน๊ตของกุญแจเสียง C G
ตัวที่สอง เสียงสูงขึ้น \(2\) ครึ่งขั้น จากตัวแรก D A
ตัวที่สาม เสียงสูงขึ้น \(2\) ครึ่งขั้น จากตัวที่สอง E B
ตัวที่สี่ เสียงสูงขึ้น \(1\) ครึ่งขั้น จากตัวที่สาม F C
ตัวที่ห้า เสียงสูงขึ้น \(2\) ครึ่งขั้น จากตัวที่สี่ G D
ตัวที่หก เสียงสูงขึ้น \(2\) ครึ่งขั้น จากตัวที่ห้า A E
ตัวที่เจ็ด เสียงสูงขึ้น \(2\) ครึ่งขั้น จากตัวที่หก B F# (\(2\) ครึ่งขั้น นั่นคือ E \(\rightarrow\) F \(\rightarrow\) F#)


หมายเหตุ ครึ่งขั้น (half-step) กล่าวโดยง่าย คือ ห่างกัน \(1\) ก้านดีดเปียโน (รวมก้านดีดทั้งสีขาวและดำ ดูรูป 1 ประกอบ).

จงเขียนฟังก์ชัน diatonic ที่รับอาร์กิวเมนต์ scale_key สำหรับกุญแจเสียง แล้วให้ค่าโน๊ตดนตรีในระดับเสียงเต็มรูปออกมา โดยใช้เลขจำนวนเต็มแทนโน๊ตดนตรีต่าง ๆ ดังนี้ เลข \(1\) แทนโน๊ต C, เลข \(2\) แทนโน๊ต C#, เลข \(3\) แทนโน๊ต D, เลข \(4\) แทนโน๊ต D# เป็นต้น.

คำใบ้ มอดุโล (modulo) หรือการหารเอาเศษ ซึ่งใช้ตัวดำเนินการ % อาจช่วยให้ทุกอย่างง่ายขึ้น

ตัวอย่างผลการทำงาน

>>> diatonic(1)
(1, 3, 5, 6, 8, 10, 12)
>>> diatonic(5)
(5, 7, 9, 10, 12, 2, 4)
>>> diatonic(10)
(10, 12, 2, 3, 5, 7, 9)
 ภาพแถวบนสุดซ้าย แสดงก้านดีดเปียโน พร้อมโน๊ตดนตรี. ภาพซ้ายแถวสอง แถวสาม และแถวสี่ แสดงลูกศรระบุระดับเสียงครึ่งขั้น. สังเกต E \rightarrow F และ B \rightarrow C เพิ่มระดับเสียงแค่ครึ่งขั้น (ไม่มีก้านดีดดำอยู่ตรงกลาง). ภาพบนทางขวา แสดงก้านดีดเปียโน พร้อมโน๊ตดนตรี และลูกศรแสดงการเพิ่มระดับเสียงทีละครึ่งขั้น จากกุญแจเสียง C เปรียบเทียบกับภาพล่างทางขวา ที่แสดงการหาโน๊ตในระดับเสียงเต็มรูป เมื่อใช้กุญแจเสียง G.