Example 8 — Make a Code Name (debugging) ตัวอย่างที่ 8 — สร้างชื่อรหัสจากชื่อกลับหลัง (debugging)
A debugging problem: the program below should turn a name into a code name written from the last letter to the first, but it hides one bug. โจทย์ debugging: โปรแกรมด้านล่างควรเปลี่ยนชื่อเป็นชื่อรหัสที่เขียนจากตัวสุดท้ายไปตัวแรก แต่มี bug ซ่อนอยู่ 1 จุด
How to work on this page: read the problem and the flowcharts, then check the code against the flowcharts box by box. Run the test list, find the wrong line, and fix the program in the code box yourself. Run the test list again until every test gives its expected output, and only then open the solution. To review the kinds of error, the kinds of test case and the bug-hunting tools, see 11 Testing and Debugging.
วิธีทำโจทย์หน้านี้: อ่านโจทย์และ flowchart แล้วตรวจโค้ดเทียบกับ flowchart ทีละกล่อง จากนั้นรันตามรายการทดสอบ หาบรรทัดที่ผิด และแก้โปรแกรมในกล่องโค้ดเอง รันรายการทดสอบซ้ำจนทุกข้อได้ผลตามที่ควรได้ แล้วจึงเปิดดูเฉลย ทบทวนชนิดของ error, ชนิดของ test case และเครื่องมือหา bug ได้ที่บท 11 การทดสอบและการแก้ไขโปรแกรม
Read a name made of 1 to 10 English letters. If the name is empty, longer than 10 letters, or has anything else mixed in (spaces, digits, punctuation), warn the user and ask again. Once the name is valid, make a code name: write the name from its last letter to its first, with the first letter of the code name as a capital letter and every other letter small, then show Code name: <code name>.
รับชื่อที่เป็นตัวอักษรภาษาอังกฤษ 1 ถึง 10 ตัว ถ้าชื่อว่าง ยาวเกิน 10 ตัว หรือมีอย่างอื่นปน (ช่องว่าง ตัวเลข เครื่องหมาย) ให้แจ้งเตือนแล้วถามใหม่ เมื่อได้ชื่อที่ถูกต้องแล้ว ให้สร้างชื่อรหัส โดยเขียนชื่อจากตัวสุดท้ายไปตัวแรก ให้ตัวแรกของชื่อรหัสเป็นตัวพิมพ์ใหญ่และตัวที่เหลือเป็นตัวพิมพ์เล็กทั้งหมด แล้วแสดง Code name: <ชื่อรหัส>
- Example: Robert → Code name: Trebor
- is_name(text) — returns True only when text has 1 to 10 characters and every one of them is a letter
- read_backward(text) — returns text written from the last letter to the first
- name_case(text) — returns text with a capital first letter and small letters after it
- ตัวอย่าง: Robert → Code name: Trebor
- is_name(text) — คืนค่า True เฉพาะเมื่อ text มี 1 ถึง 10 ตัว และทุกตัวเป็นตัวอักษร
- read_backward(text) — คืนค่า text ที่เขียนจากตัวสุดท้ายไปตัวแรก
- name_case(text) — คืนค่า text ที่ตัวแรกเป็นตัวพิมพ์ใหญ่และตัวที่เหลือเป็นตัวพิมพ์เล็ก
- isalpha() is True when the character is a letter.
- len(text) == 0 or len(text) > 10 is True when at least one of the two checks is true: the text is empty or it is longer than 10.
- text[i] is the character at position i counted from the left, starting at 0. A negative index counts from the end: text[-1] is the last letter and text[-2] the one before it.
- upper() / lower() give the capital / small form of a letter.
- while True repeats forever until break leaves the loop.
- isalpha() ให้ค่า True เมื่อตัวนั้นเป็นตัวอักษร
- len(text) == 0 or len(text) > 10 เป็น True เมื่อเงื่อนไขข้อใดข้อหนึ่งเป็นจริง คือข้อความว่างหรือยาวเกิน 10 ตัว
- text[i] คือตัวอักษรตำแหน่งที่ i นับจากซ้าย เริ่มที่ 0 ส่วนดัชนีติดลบนับจากท้าย text[-1] คือตัวสุดท้าย และ text[-2] คือตัวรองสุดท้าย
- upper() / lower() แปลงตัวอักษรเป็นตัวพิมพ์ใหญ่ / ตัวพิมพ์เล็ก
- while True วนซ้ำไปเรื่อยๆ จนกว่าจะเจอ break ซึ่งสั่งให้ออกจาก loop
The program starts a while True loop and reads a name into name. It checks whether the name is valid with the sub-function is_name: if it is, break leaves the loop; if not, it shows a warning with print and goes back to ask again. After leaving the loop it calls read_backward(name), passes the result to name_case, shows the code name with print, and the program ends.
โปรแกรมนี้เริ่มต้นด้วยวน loop while True แล้วรับชื่อเก็บใน name ตรวจสอบเงื่อนไขว่าชื่อถูกต้องหรือไม่ด้วย function ย่อย is_name ถ้าใช่ให้ break ออกจาก loop ถ้าไม่ใช่ให้แสดงข้อความเตือนด้วย print แล้วกลับไปรับค่าใหม่ หลังออกจาก loop จึงเรียก read_backward(name) แล้วส่งผลที่ได้ต่อให้ name_case แสดงชื่อรหัสด้วย print และจบการทำงานของโปรแกรม
Your name: Mary Ann Use 1 to 10 letters (A-Z, a-z), please try again Your name: Robert Code name: Rtrebo
Try it yourself — the first value you submit clears the example and starts a new run. Submit one value at a time: a name that is not valid shows the warning and waits for the next value; a valid name finishes the run. Submitting after that starts a new run. Press Submit with an empty box to test an empty name.
ลองป้อนค่าเอง — ค่าแรกที่กดส่งจะล้างตัวอย่างเดิมแล้วเริ่มรันใหม่ ส่งทีละค่า: ถ้าชื่อไม่ถูกต้องจะขึ้นข้อความเตือนแล้วรอค่าถัดไป ถ้าชื่อถูกต้องโปรแกรมจะจบ และการส่งครั้งถัดไปจะเริ่มรันใหม่ ถ้ากดส่งโดยไม่พิมพ์อะไรจะเป็นการทดสอบชื่อว่าง
is_name starts by checking whether len(text) == 0 or len(text) > 10; if it is, it sends back False with return straight away. If not, it sets i = 0 as the character position and checks whether i < len(text); if it is, it checks whether text[i].isalpha(). If that character is not a letter, it sends back False with return straight away. If it is a letter, it increases i by 1 and checks again. This repeats until i < len(text) is false, so every character was a letter and it sends back True with return.
is_name เริ่มต้นด้วยตรวจสอบเงื่อนไขว่า len(text) == 0 or len(text) > 10 หรือไม่ ถ้าใช่ให้ส่งค่า False กลับด้วย return ทันที ถ้าไม่ใช่ให้กำหนดค่า i = 0 เป็นตัวชี้ตำแหน่งตัวอักษร แล้วตรวจสอบเงื่อนไขว่า i < len(text) หรือไม่ ถ้าใช่ให้ตรวจสอบว่า text[i].isalpha() หรือไม่ ถ้าตัวนั้นไม่ใช่ตัวอักษรให้ส่งค่า False กลับด้วย return ทันที ถ้าเป็นตัวอักษรให้เพิ่ม i ขึ้น 1 แล้วกลับไปตรวจสอบอีกครั้ง ทำแบบนี้จน i < len(text) เป็นเท็จ แสดงว่าทุกตัวเป็นตัวอักษร จึงส่งค่า True กลับด้วย return
Test the function on its own before using it in the main program. Decide which kind of test case each value is.
ทดสอบฟังก์ชันนี้แยกเดี่ยวๆ ก่อนนำไปใช้ในโปรแกรมหลัก แล้วจัดว่าแต่ละค่าเป็น test case ชนิดไหน
True False False True False False
Show the kinds of test case ดูชนิดของ test case
- 'Robert' — Normal
- 'Maximilian' (10 letters, the longest name allowed) — Boundary
- 'Christopher' (11 letters, one past the edge), '' (empty text) — Boundary
- 'Mary Ann' (has a space), 'R2D2' (has digits) — Error
- 'Robert' — Normal
- 'Maximilian' (10 ตัว ยาวที่สุดที่รับได้) — Boundary
- 'Christopher' (11 ตัว เลยขอบไปหนึ่งค่า), '' (ข้อความว่าง) — Boundary
- 'Mary Ann' (มีช่องว่าง), 'R2D2' (มีตัวเลข) — Error
read_backward starts by setting backward = '' to hold the result and i = 0 as the letter position. It checks whether i < len(text); if it is, it adds the letter text[-i] to the end of backward, increases i by 1 and checks again. This repeats until the condition is false, so it sends backward back with return.
read_backward เริ่มต้นด้วยกำหนดค่า backward = '' สำหรับเก็บผลลัพธ์ และ i = 0 เป็นตัวชี้ตำแหน่งตัวอักษร แล้วตรวจสอบเงื่อนไขว่า i < len(text) หรือไม่ ถ้าใช่ให้ต่อตัวอักษร text[-i] เข้าท้าย backward เพิ่ม i ขึ้น 1 แล้วกลับไปตรวจสอบอีกครั้ง ทำแบบนี้จนเงื่อนไขเป็นเท็จ จึงส่งค่า backward กลับด้วย return
name_case starts by setting result = '' to hold the result and i = 0 as the letter position. It checks whether i < len(text); if it is, it checks whether i == 0. If it is the first letter, it adds text[i].upper() to the end of result; if not, it adds text[i].lower(). Either way it increases i by 1 and checks i < len(text) again. This repeats until the condition is false, so it sends result back with return.
name_case เริ่มต้นด้วยกำหนดค่า result = '' สำหรับเก็บผลลัพธ์ และ i = 0 เป็นตัวชี้ตำแหน่งตัวอักษร แล้วตรวจสอบเงื่อนไขว่า i < len(text) หรือไม่ ถ้าใช่ให้ตรวจสอบว่า i == 0 หรือไม่ ถ้าเป็นตัวแรกให้ต่อ text[i].upper() เข้าท้าย result ถ้าไม่ใช่ให้ต่อ text[i].lower() ไม่ว่าทางใดทางหนึ่ง หลังจากนั้นเพิ่ม i ขึ้น 1 แล้วกลับไปตรวจสอบ i < len(text) อีกครั้ง ทำแบบนี้จนเงื่อนไขเป็นเท็จ จึงส่งค่า result กลับด้วย return
Submit the values in order with the form in card 2, or put them one per line in the [input] box, then compare with the expected output column. Every run ends with a line Code name: …; the table shows only the part after it. The last column shows what the code with the bug gives.
ส่งค่าตามลำดับด้วยช่องในการ์ดที่ 2 หรือใส่ไว้บรรทัดละค่าในกล่อง [input] แล้วเทียบกับคอลัมน์ผลที่ควรได้ ทุกครั้งที่รันจบจะได้บรรทัด Code name: … ตารางแสดงเฉพาะส่วนหลัง Code name: ส่วนคอลัมน์สุดท้ายคือผลจากโค้ดที่ยังมี bug
| Input (in order)ข้อมูลที่ป้อน (ตามลำดับ) | Kindชนิด | Expected outputผลที่ควรได้ | Output of the buggy codeผลจากโค้ดที่มี bug |
|---|---|---|---|
| Robert | Normal | Trebor | Rtrebo ✗ |
| sIMON | Normal (mixed case)Normal (ตัวพิมพ์ปนกัน) | Nomis | Snomi ✗ |
| j | Boundary (one letter)Boundary (ตัวอักษรเดียว) | J | J ✓ |
| Maximilian | Boundary (exactly 10 letters)Boundary (10 ตัวพอดี) | Nailimixam | Mnailimixa ✗ |
| Christopher, Leo | Boundary (11 letters)Boundary (11 ตัว) | 1 warning, thenเตือน 1 ครั้ง แล้ว Oel | 1 warning, thenเตือน 1 ครั้ง แล้ว Loe ✗ |
| (empty), then(ว่าง) แล้ว Tom | Boundary (empty text)Boundary (ข้อความว่าง) | 1 warning, thenเตือน 1 ครั้ง แล้ว Mot | 1 warning, thenเตือน 1 ครั้ง แล้ว Tmo ✗ |
| Mary Ann, R2D2, Kim | Error | 2 warnings, thenเตือน 2 ครั้ง แล้ว Mik | 2 warnings, thenเตือน 2 ครั้ง แล้ว Kmi ✗ |
- Check the code of all three functions and the main program against their flowcharts, and note anything that looks suspicious.
- Run every input in the test list and note whether the actual output matches the expected output.
- The name check (warn, then ask again) works, but the code name is wrong. Compare Rtrebo with Trebor: which letter is in the wrong place? And why does j give the right answer even though the program has a bug?
- Which kind of error is this bug, and why?
- Find the wrong line:
- change the last line to print(read_backward(name)) for a moment, to see whether the letters are already in the wrong order before name_case runs, or
- add print(i, -i, text[-i], backward) inside the loop of read_backward,
- Fix the code in the box and run the test list again. Every input must give its expected output.
- Think further: why is testing with j alone not enough?
- ตรวจโค้ดทั้ง 3 ฟังก์ชันและส่วนหลักเทียบกับ flowchart แล้วจดจุดที่สงสัย
- รันทุกข้อในรายการทดสอบ แล้วจดผลที่ได้จริงว่าตรงกับผลที่ควรได้หรือไม่
- ส่วนตรวจชื่อ (เตือนแล้วถามใหม่) ทำงานถูก แต่ชื่อรหัสผิด ลองเทียบ Rtrebo กับ Trebor ตัวอักษรตัวไหนอยู่ผิดที่ และทำไม j ได้ผลถูกทั้งที่โปรแกรมมี bug
- bug นี้เป็น error ชนิดไหน เพราะอะไร
- หาบรรทัดที่ผิด
- เปลี่ยนบรรทัดสุดท้ายเป็น print(read_backward(name)) ชั่วคราว เพื่อดูว่าตัวอักษรเรียงผิดตั้งแต่ก่อนเข้า name_case หรือไม่ หรือ
- ใส่ print(i, -i, text[-i], backward) ใน loop ของ read_backward
- แก้โค้ดในกล่อง แล้วรันรายการทดสอบซ้ำ ทุกข้อต้องได้ผลตามที่ควรได้
- ชวนคิดต่อ: ทำไมทดสอบด้วย j อย่างเดียวจึงไม่พอ
| i | -i | text[-i] | backward |
|---|---|---|---|
| 0 | |||
| 1 | |||
| 2 |
Show the fixed code ดูโค้ดที่แก้แล้ว
The bug is on the line backward += text[-i] in read_backward. Because i starts at 0, -i is -0, which is the same as 0, so the first round takes the first letter instead of the last one. After that, -1, -2, … read from the end, so the result is the first letter followed by the other letters backward: R + trebo. Change the line to backward += text[-(i + 1)]. It is a logic error: the program runs to the end with no error message.
bug อยู่ที่บรรทัด backward += text[-i] ใน read_backward เพราะ i เริ่มที่ 0 ทำให้ -i คือ -0 ซึ่งเท่ากับ 0 รอบแรกจึงหยิบตัวแรกแทนตัวสุดท้าย หลังจากนั้น -1, -2, … อ่านจากท้ายตามปกติ ผลที่ได้จึงเป็นตัวแรกตามด้วยตัวที่เหลือแบบกลับหลัง คือ R + trebo แก้เป็น backward += text[-(i + 1)] เป็น logic error เพราะโปรแกรมรันจบโดยไม่มีข้อความ error
is_name and name_case are already correct. The symptom shows in the final code name, but the letters are already in the wrong order when read_backward returns; name_case only changes their case. A one-letter name such as j hides the bug, because its first letter is also its last one.
is_name และ name_case ถูกต้องอยู่แล้ว อาการแสดงที่ชื่อรหัสตอนท้าย แต่ตัวอักษรเรียงผิดตั้งแต่ read_backward ส่งค่ากลับมา ส่วน name_case แค่เปลี่ยนตัวพิมพ์ ชื่อที่มีตัวอักษรตัวเดียวอย่าง j ซ่อน bug ไว้ เพราะตัวแรกกับตัวสุดท้ายเป็นตัวเดียวกัน
| i | -i | text[-i] | backward |
|---|---|---|---|
| 0 | 0 | 'L' | 'L' |
| 1 | -1 | 'o' | 'Lo' |
| 2 | -2 | 'e' | 'Loe' |
After the fix, -(i + 1) runs through -1, -2, -3, so backward grows as 'o', 'oe', 'oeL', and name_case turns it into Oel.
หลังแก้แล้ว -(i + 1) มีค่า -1, -2, -3 ตามลำดับ backward จึงเพิ่มทีละตัวเป็น 'o', 'oe', 'oeL' แล้ว name_case เปลี่ยนเป็น Oel
Your name: Mary Ann Use 1 to 10 letters (A-Z, a-z), please try again Your name: Robert Code name: Trebor
This program needs two loops after the name check: one in read_backward and one in name_case. Example 9 does the same job with one loop.
โปรแกรมนี้ใช้ 2 loop หลังตรวจชื่อ คือใน read_backward และใน name_case ตัวอย่างที่ 9 ทำงานเดียวกันได้ด้วย loop เดียว
The Run buttons on this page simulate Python with JavaScript, the same as the other lessons. To run the real Python, paste the code into Google Colab.
ปุ่ม Run ในหน้านี้จำลองโค้ด Python ด้วย JavaScript เหมือนบทเรียนอื่นๆ หากต้องการรัน Python จริง ให้คัดลอกโค้ดไปวางที่ Google Colab
References References / แหล่งอ้างอิง
-
Python docs — String Methods
(
isalpha,upper,lower) (isalpha,upper,lower) - Python docs — Common Sequence Operations (indexing a string, including negative indexes counted from the end) (การใช้ดัชนีกับสตริง รวมถึงดัชนีติดลบที่นับจากท้าย)
-
Python docs — The while statement
(
while Trueandbreak) (while Trueและbreak) - Kinds of error, kinds of test case and bug-hunting tools: 11 Testing and Debugging; flowchart symbols: 01 Basic → Flowchart Basic. ชนิดของ error, ชนิดของ test case และเครื่องมือหา bug: 11 การทดสอบและการแก้ไขโปรแกรม ; สัญลักษณ์ของ flowchart: 01 Basic → Flowchart Basic