G3 Computing Textbook Chapter 06
G3 Computing Textbook Chapter 06
235
236
236
6.1 Bugs and Debugging
When a program does not behave as intended by the programmer, the program is said to have a bug. The
process of fixing the program so that it behaves as intended is called debugging.
U
DID YO
N O W ?
K
Figure 6.1 shows a journal entry dated
9 September 1947 by users of an
early electronic computer called the
“Mark II”. They had discovered a moth
trapped in the computer, causing it to
malfunction. This dead moth is often
considered the first actual computer
“bug” to be found.
ER MS
KEY T
Bug
A defect in a program that causes it to behave in an unintended manner
Debugging
The process of identifying and removing defects from a program
LEARNING OUTCOMES
2.4.8 Understand and describe types of program errors: syntax, logic and run-time; and explain
why they occur.
237
There are three main types of program errors that cause bugs:
Note that the three types of program errors are not mutually exclusive. For instance, some run-time errors
may also be considered logic errors.
Syntax errors are usually the easiest to detect as they are “caught” at the initial stage before the code can
even run. It is usually not necessary to design test cases to find syntax errors.
Let us look at a program to find the average value in a list of numbers. In the program in Figure 6.3, we
introduced a syntax error on line 5 by using the assignment operator (=) instead of the equality operator
(==).
238
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2.0
Figure 6.4 The syntax error on line 5 is fixed by using the correct operator (highlighted)
239
6.2.2 Logic Errors
While syntax errors are usually obvious when they occur, logic errors may be “hidden” and may not show
any obvious sign that something is amiss. In such cases, the only way to detect the logic error is by running
a test case (see section 6.3) and observing that the actual output of the program is different from the
expected output.
For example, in Figure 6.5, we have introduced a logic error on line 12 by using an incorrect formula that
multiplies (instead of dividing) sum_values by the number of items in values to calculate the average.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
32
While the program appears to finish running without errors, the output value of 32 is different from the
expected output of 2.0 based on the input data of 2, 0, 2 and 4. Such unexpected or incorrect output is the
most obvious sign that a program contains “hidden” logic errors.
240
6.2.3 Run-Time Errors
For example, for the program in Figure 6.4, when the input is “four” instead of the digit “4”, a run-time error
occurs. This run-time error is caused by not applying a format check on the input data to ensure that it is a
valid integer (i.e., numeric digits only with a possible minus sign in front).
In Figure 6.7, we have removed any checks for an empty list before calculating the average. When we
run this program and immediately enter a blank, the program crashes with a division by zero error. This is
another example of a run-time error.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
241
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Input In [1], in <cell line: 11>()
9 # Process
10 sum_values = sum(values)
---> 11 average = sum_values / len(values)
13 # Output
14 print(average)
Run-time errors may be caused by logic errors. However, the run-time error may not occur immediately on
the line of code containing the logic error. Such cases are tricky because the programmer would usually
need to “work backwards” from the location of the crash to find out where the logic error is actually located
(see section 6.4.3). This is because the crash would usually be caused by a variable with an invalid value
assigned to it by a previous line of code. This value could in turn be derived from another variable with an
incorrect value assigned by a previous line of code, and so on.
For instance, in the program below, a run-time error occurs on line 11 because the contents of values are
invalid for summing. However, this is actually caused by a logic error introduced on line 7, where strs are
being inserted into values instead of ints.
On the other hand, a syntax error prevents code from being run at all. Hence, a syntax error is generally not
considered to be a run-time error.
1
2
3
4
5
6
7 Run-time error on line 11 is actually
8 caused by missing type conversion
on line 7.
9
10
11
12
13
14
15
16
17
242
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [1], in <cell line: 10>()
9 # Process
10 if len(values) > 0:
---> 11 sum_values = sum(values)
12 average = sum_values / len(values)
13 else:
QUICK
C K 6
3 . 32
CHE
1. The following program that prints the multiplication table for an integer between 1 to 10 (inclusive) contains
several syntax errors.
1
2
3
4
5
6
7
8
9
10
11
12
243
6.3 Designing Test Cases
LEARNING OUTCOMES
2.4.9 Design appropriate test cases to cover normal, error and boundary conditions and specify
which type(s) of conditions is/are being tested for each test case.
Designing good test cases is important because it is usually impossible to test every possible input
combination. To ensure that we do not miss out on any situations where errors are likely to occur, test cases
should cover three types of conditions:
To study the three types of conditions, let us consider the problem of converting a phrase into an
acronym. An acronym is an abbreviation formed from the first letter of each word. Acronyms must usually
be pronounceable, but for our problem, we simply want to combine the first letter of each word into a
capitalised abbreviation regardless of whether the result is pronounceable.
The input and output requirements for this problem are specified in Table 6.1.
Input Output
244
6.3.2 Normal Conditions
First, let us design test cases to make sure that the program to be tested works as intended under normal
conditions – situations where the input data is what we expect to be provided with during normal use of
the program. In general, under normal conditions, at least one test case should be designed for each type
of expected input. In this case, only one type of input is expected (a str), so we will design a test case
accordingly:
However, we also want to be sure that the test cases for normal conditions accurately represent the variety
of inputs that we are expecting. To test this, we shall design an additional test case so that words that start
with both upper-case and lower-case letters are represented.
Test cases for normal conditions are the most straightforward and will usually be the first test cases to be
written.
Next, let us design test cases to make sure that the program works as intended under boundary conditions
– situations where the input data is at the limit of what the program is designed for, or where special
handling of the input data is required.
• The limits of a program are usually in terms of quantity (for int and oat inputs) and/ or length (for
list and str inputs).
• On the other hand, special handling is usually needed when the problem definition specifies that
certain values are to be treated as exceptions (such as using −1 to represent missing data in a
survey).
Note that boundary conditions may overlap with normal conditions and error conditions as input data
under boundary conditions is still classified as either valid (i.e., normal) or invalid (i.e., erroneous). For
situations with minimum or maximum limits for an input, test cases should be designed for both sides
of the boundary to ensure that the limits are enforced correctly. For instance, if an integer input must be
greater than 0, test cases should be written for both the valid boundary value of 1 and the invalid boundary
value of 0.
245
Boundary conditions
... -2 -1 0 1 2 3 4
Figure 6.9 Normal, error and boundary conditions for integer input
that must be greater than 0
For Test Case 4, the expected output for " Computer Science " is exactly the same as the expected
output for "Computer Science" (with no additional spaces).
Determining the boundary conditions for a problem and the expected output for each case can be tedious
as well as difficult. However, this process is necessary to ensure that the program behaves as intended
regardless of the input supplied.
Finally, let us design test cases to make sure that the program behaves as expected under error conditions
– situations where the input data would normally be rejected by the program via input validation (see
Chapter 5).
For the acronym generation problem, it is clearly stated that the input phrase should contain only letters
and spaces. Based on this definition, any input that contains punctuation symbols or digits should generate
an error. The following two test cases cover these situations:
246
→ Input Expected Output →
Test cases for error conditions are necessary because programs that fail to reject invalid input may end
up performing unintended or even harmful actions. For instance, a large number of security flaws in
programs today are caused by the improper handling of error conditions. A typical example in some
programming languages would be forgetting to check whether the input data supplied by a user can
fit into the memory space allocated to store the data. A program that tries to store this invalid data
will end up overwriting subsequent areas of memory and allow potential attackers to insert their own
instructions into the program.
QUICK 3
C K 6
3 .
CHE
1. A student wants to solve the following problem:
Input Output
• n: number of “*” characters needed for • a text-based square made out of “*”
each side of the square, should be an characters with n “*” characters on each
integer between 1 and 20 inclusive side, square should be filled in with “*”
characters with no other characters inside
Table 6.8 Input and output requirements for the problem of drawing a square
The following are two test cases under normal conditions for this problem. (Note that although the expected
outputs may appear rectangular, they can be considered as squares as they have the same number of characters
on each side.)
247
QUICK 3
C K 6
3 .
CHE
→ Input Expected Output →
• n: 10 **********
**********
**********
**********
**********
**********
**********
**********
**********
**********
a) Design another test case that covers normal conditions for this problem.
b) Design two test cases that cover the boundary conditions for this problem. For each test case, explain why it
covers a boundary condition.
c) Design two test cases that cover the error conditions for this problem. For each test case, explain why it
covers an error condition. (You may choose your own expected error message.)
Questions 2 and 3 involve programs meant to solve the following diamond drawing problem:
Input Output
• n: height of diamond in number • a text-based symmetrical diamond
of lines (and also the number of “*” made out of “*” and “ ” (space) characters
characters needed for the widest part of with a height of n lines and width of n
the diamond), should be an odd integer “*” characters for the widest part of the
between 1 and 19 inclusive diamond, diamond should be filled in with
“*” characters with no other characters
inside
Table 6.11 Input and output requirements for the problem of drawing a diamond
The following is a test case under normal conditions for this problem:
i. State whether the input falls under normal conditions, boundary conditions and/or error conditions
for this problem.
ii. Design a corresponding test case by specifying the expected output. (If needed, you may choose your own
expected error message.)
a) Input: n = 1
b) Input: n = 6
c) Input: n = 7
248
QUICK 3
C K 6
3 .
CHE
3. The program below for the diamond drawing problem has three syntax errors.
a) Identify where the syntax errors are.
b) Rewrite the program to fix these syntax errors.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Figure 6.10 Program for diamond drawing problem with syntax errors
249
6.4 Common Debugging Techniques
LEARNING OUTCOMES
2.4.1 Produce a trace table by performing a manual dry run and inspecting the value of variables
at each step of a program.
2.4.2 Inspect the value of variables at selected steps of a program by inserting print statements.
2.4.3 Locate logic errors by backtracking from a point where unexpected behaviour is observed.
2.4.4 Test programs incrementally as small additions and changes are made during development.
2.4.5 Test small parts of a program by commenting out other parts of the program that are not
needed for testing.
To demonstrate how debugging is performed, let us work on a draft program to solve the acronym
generation problem. Since words usually have a space before them, we can try to loop through input
text and extract all the characters that come after a space:
1
2
3
4
5
6
7
8
9
10
11
However, when we try to run the program on Test Case 1, we find that its output is different from what
is expected.
This is a sign that there is a bug. Let us look at some common debugging techniques to fix it.
250
6.4.1 Using Print Statements
One simple yet effective debugging technique is to keep track of how variables change as the program runs
using the print() function. By adding a print statement that outputs the value of important variables
inside a loop, we can get valuable information on how the variables change over time. For instance, the
program below has been modified with the addition of line 9, which prints out the values of index and
acronym each time the loop repeats.
1
2
3
4
5
6
7
8
9
10
11
12
Figure 6.12 Draft program with addition of print statement on line 9 (highlighted)
Running this new program on Test Case 1 reveals how these two variables change over time as characters
are extracted from input_text to form the acronym.
From the debugging output, we can see that the first letter of “Science” is correctly extracted into the
acronym when index reaches 9. However, the first letter of “Computer” is not.
251
6.4.2 Using a Trace Table
For instance, Table 6.15 shows the completed trace table from performing a dry run on the program in
Figure 6.11 with the input from Test Case 1:
252
We see that a trace table can provide the same information as using print statements but the process is
manual. On the other hand, trace tables do not require you to modify the program and can be used with
algorithms outside of programming.
U
DID YO
N O W ? An alternative method of filling in a truth table is to add a new row every time
K output is produced, or a value is assigned to a variable, instead of only when
a new row is needed. However, this method can require much more space. In
general, alternative methods are acceptable as long as the trace table allows
you to analyse the program chronologically when read from top to bottom.
6.4.3 Backtracking
253
6.4.4 Testing Incrementally
When writing or editing programs, we may be tempted to write or change a large amount of code at once.
However, if the resulting program fails to work, it becomes harder to narrow down where the error may be
located. Implementing too many features at once also makes it more likely that some features do not get
thoroughly tested.
Instead, a good practice is to test programs incrementally as small additions and changes are made during
development. This allows us to test new features or changes more thoroughly and verify that the small
amount of code added or modified works as intended before moving on. If a bug is detected, debugging
will also be easier given the smaller amount of code that likely caused the error.
For the acronym generation program, we have discovered the cause of why our program fails Test Case
1 and may have some ideas on how to fix it. Let us demonstrate how we might implement these fixes by
testing incrementally while making small changes.
We know Test Case 1 fails because there is no “previous character” for the first character of a string and
subtracting 1 when index is 0 will result in a negative index. Let us try to fix this bug by checking for this
special case on line 7.
1
2
3
4
5
6
7
8
9
10
11
12
13
Figure 6.14 Draft program with attempt at fixing first character bug
254
After making this small change, let us incrementally run Test Case 1 again:
It turns out our change does not affect the program’s output at all!
Backtracking from the output at line 10 when index is 0, we see that on line 7 the if condition evaluates
to False as intended, preventing us from incorrectly using a negative index on line 8. However, this also
means we skip line 9 and the first letter of “Computer” is not appended to acronym.
Let us make another small change to address this issue. This time, we will make a copy of line 9 that always
runs when index is 0:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Figure 6.15 Draft program with another attempt at fixing first character bug
255
Running this amended program on Test Case 1 now produces the expected output (if we ignore the lines of
debugging output that start with “DEBUG”).
We can refine the program further by removing the print statement used for debugging, removing repeated
lines of code and consolidating the checks for each character into a function:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
256
Once again, we test incrementally and find that the program still passes Test Case 1.
However, when we run the program on Test Case 2, we receive an unexpected output.
Fortunately, the cause of this bug is quite clear: We did not convert the output to upper case. We make
another small change by replacing the print statement used for debugging and adding a line to convert the
output to upper case when the program has exited the loop.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Figure 6.17 Draft program with letter case bug fixed by adding the highlighted code on line 14
257
The program still passes Test Case 1 and running the amended program on Test Case 2 now generates the
expected output.
This process demonstrates how running and testing programs incrementally as small additions and
changes are made can make it easier to locate and fix errors, compared to making a large number of
additions or changes before testing.
Let us continue running our program on the test cases we designed. The program behaves as intended for
Test Case 3 under the boundary conditions of having an empty str as input.
258
From the output, it looks like the acronym has some extra spaces that should not be there. To investigate
the cause of this bug, we shall break down the program into smaller parts and test the parts one at a time.
This allows us to narrow down the possible locations of any bugs and reduce the amount of code that we
need to study at any one time.
To test only one part of a program at a time, we can temporarily disable lines of code by inserting a
hash symbol (#) at the start of these lines. This process is called commenting out code since it works by
temporarily turning the code into comments.
We start by disabling code that is already working and not likely to be where the error is located. From the
output, we know the letters “C” and “S” that are part of the expected output were extracted correctly, so
this implies that the following processes are all working:
1
Getting input from the
2
keyboard
3
4
5
Iterating through the
6
input characters
7
8
9
10
11
12
Adding characters to
13
the acronym
14
15
16
17
Displaying the acronym
on the screen
18
19
20
21
22
Figure 6.18 Testing a function with different input arguments to confirm location of bug
U
DID YO
Hence, we can deduce that the error is located in should_include()
as part of the code that tests if the current character should be part
KNOW?
of the acronym (line 12). We can also eliminate the possibility that
the error is located at the code converting the acronym to upper case
(lines 15) as it is not relevant in this case.
To test functions like should_include(), we can run mini test cases. A quick way to comment
We comment out all other code except for the function definition. At and uncomment code in
the end of the program, we then call should_include() repeatedly Jupyter Desktop is to select
with different combinations of input arguments to determine if the it and use the keyboard
function is working as expected. This is shown in Figure 6.18. shortcut Ctrl-/.
259
In this case, the expected outputs for the three calls to should_include() are False, False and
True. This is because the first three characters of s are " C". When index is either 0 or 1, we expect
should_include() to return False since both s[0] and s[1] are spaces and spaces should not appear in
acronyms. On the other hand, when index is 2 we expect should_include() to return True since s[2] is
precisely the first letter of the word “Computer”.
However, when we run the program, the actual outputs of True, True and True do not match the expected
outputs:
To fix this bug, we can add an additional condition to should_include() – In this instance, the character
being tested must not be a space.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Figure 6.19 Draft program with unwanted spaces bug fixed by adding the highlighted code
260
This modified program now passes Test Case 4.
However, our program still does not pass Test Case 5 and Test Case 6 because it does not perform any data
validation. To fix this, a format check is added to the program to ensure that the input phrase contains
letters and spaces only, as shown in Figure 6.20.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
261
This final version of the program passes Test Case 5 and Test Case 6.
Since the program passes all the test cases we have designed, we can be confident that it works as intended
in solving the acronym generation problem.
262
QUICK 4
C K 6.
CHE
1. Produce a trace table for the following program given an input of 5:
1
2
3
4
5
6
7
8
9
10
11
2. A new program has been written to solve the acronym generation problem. State whether it passes or fails each
of the six test cases we have designed for the acronym generation problem.
1
2
3
4
5
6
7
8
9
10
11
12
13
Input Output
• n: height of diamond in number of • a text-based symmetrical diamond
lines (and also the number of “*” made out of “*” and “ ” (space)
characters needed for the widest part characters with a height of n lines and
of the diamond), should be an odd width of n “*” characters for the widest
integer between 1 and 19 inclusive part of the diamond, diamond should
be filled in with “*” characters only
The following is a test case under normal conditions for this problem:
263
QUICK 4
C K 6.
CHE
The following program for the diamond drawing problem crashes with a run-time error on line 25 when it runs
on the given test case:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
n: 5 *
***
*****
***
*
Enter n: 5 Failed
*
***
***
*
-----------------------------------------------------
----------------------
IndexError Traceback
(most recent call last)
Input In [1], in <cell line: 24>()
23 print(stars[index])
24 for index in range(n // 2 + 1):
---> 25 print(stars[-index - 1])
264
QUICK
EC K 6.4
CH
The program uses a list named stars to store the lines of text that make up the diamond before printing them
in a specific order. stars is initialised in lines 16 to 20.
a) By commenting out other parts of the program or otherwise, modify the program to print out the contents
of stars after it is initialised for n = 5. The program should not produce any other output. (Take note of the
number of items in stars.)
b) Undo any changes from part (a). By using print statements and backtracking from the error on line 25 or
otherwise, explain why the program crashes for n = 5.
c) Fix the program by changing only one line and state whether line 25 should be the line of code to change.
W
REVIE
ES TI ON
QU
1. A program is needed to solve the following word reversal problem:
Input Output
• input_text: phrase to reverse, • Given phrase with the order of
should contain only letters and the letters of each word reversed
spaces but with the words themselves
still in order, separated by one
space between each word
Table 6.30 Input and output requirements for the word reversal problem
# Program: word_reversal_draft.ipynb
1 # Input
2 input_text = input("Enter input text: ")
3
4 # Process
5 word_list = input_text.split()
6
7 for i in range(len(word_list)):
8 current_word = ""
9 for letter_index in range(len(word_list[i])):
10 current_word += word_list[i][-letter_index]
11 word_list[i] = current_word
12
13 result = ""
14 for word in word_list:
15 result += word + " "
16 result = result[:-1]
17
18 # Output
19 print(result)
265
W
REVIE
E ST IO N
QU
a) The main algorithm for the program consists of three parts. Read through the code and state what each of
the following parts of the program are intended to do:
i. Line 5
ii. Lines 7 to 11
iii. Lines 13 to 16
b) When this program is run on the following test case, the actual output does not match the expected
output:
i. State the kind of condition (normal, boundary or error) covered by this test case.
ii. State whether the program has a syntax error or a logic error and explain why.
iii. Find out where the error is located by disabling lines of code and testing the program in chunks or parts.
State the line on which the error is located and explain why the line is incorrect.
iv. Rewrite the program to fix the error so that it passes the test case.
2. The program in Figure 6.25 accepts a string of alphabetical letters only and outputs the string with its
upper-case letters converted to lower-case and vice versa. For example, if the input were “comPUting”, then
the output would be “COMpuTING”.
# opposite_case_unfinished.ipynb
1 # Returns True if s has alphabetical letters only
2 def is_letters_only(s):
3 # TODO: See part (b)
4
5 # Converts single letter to opposite case
6 def convert_letter(l):
7 if l.islower():
8 return l.upper()
9 return l.lower()
10
11 # Converts string of letters to opposite case
12 def convert_string(s):
13 result = ''
14 for c in s:
15 result = convert_letter(c)
16 return result
17
18 while True:
19 s = input('Enter string: ')
20 if is_letters_only(s):
21 break
22 print('String must have alphabetical letters only')
23
24 print(convert_string(s))
a) The is_letters_only() function is supposed to return True if its parameter is made up of alphabetical
letters only. Write down the expected return value for each of the following test cases:
i. is_letters_only('hello')
ii. is_letters_only('Hello World')
iii. is_letters_only('HelloWorld')
iv. is_letters_only('Hello, World!')
266
W
REVIE
E ST IO N
QU
b) Using an empty string as input is a boundary condition for the is_letters_only() function. Boundary
conditions can be difficult to handle as there may be multiple options for expected behavior.
i. Suggest one possible reason why the return value for is_letters_only('') should be True.
ii. Suggest one possible reason why the return value for is_letters_only('') should be False.
iii. Suppose an empty string should be treated as valid input for this program. Choose an appropriate return
value for is_letters_only('') and fill in the function body for is_letters_only() accordingly. (To test
your code, temporarily comment out the main program and run test cases.)
c) Even with is_letters_only() correctly filled in, there is a bug in the program that causes its output to be
different from what is expected. For example, if the input were “comPUting”, the program will output “G”
instead of the expected “COMpuTING”.
ii. Find the bug in convert_string() and suggest how it can be fixed.
ANSWER
Pg. 243-Quick Check 6.2
1.
# Program: multiplication_table_fixed.ipynb
1 # Input
2 while True:
3 number = input('Enter integer (1-10): ')
4 if number.isdigit():
5 number = int(number)
6 if number >= 1 and number <= 10:
7 break
8 print('Data validation failed!')
9
10 # Process and Output
11 for i in range(1, 11):
12 print(f'{number} * {i} = {number * i}')
2. a) True
b) False. It is a syntax error.
c) False. A program with a logic error may not display an error message.
d) True. Run-time errors can occur due to conditions outside of the program’s control (e.g., running out of
memory).
267
ANSWER
Pg. 247-Quick Check 6.3
1. a) Test case that covers normal conditions (accept any possible answer):
b) Test cases for the boundary condition when n is at its lower limit:
These are boundary conditions as the inputs of 0 and 1 cover both sides of the boundary where n is at its
lower limit.
Test cases for the boundary condition when n is at its upper limit:
These are boundary conditions as the inputs of 20 and 21 cover both sides of the boundary where n is at its
upper limit.
c) As n is defined as an integer between 1 and 20 inclusive, an input of -1 is not within the valid range of values
of n and should be considered as an error condition:
268
ANSWER
2. a)
i. Boundary / Normal condition
ii.
→ Input Expected Output →
• n: 1 *
b)
i. Error condition
ii.
→ Input Expected Output →
• n: 6 Data validation failed!
n should be an odd integer
between 1 and 19 inclusive
c)
i. Normal condition
ii.
→ Input Expected Output →
• n: 7 *
***
*****
*******
*****
***
*
# Program: diamond_with_syntax_errors_fixed.ipynb
1 # Input and input validation
2 while True:
3 input_text = input("Enter n: ")
4 if input_text.isdigit():
5 n = int(input_text)
6 if n >= 1 and n <= 19 and n % 2 == 1:
7 break
8 else:
9 print("Data validation failed!")
10 print("n should be odd and between 1 and 19 inclusive")
11 else:
12 print("Data validation failed!')
13 print("n should be a positive integer")
14
15 # Process and output
16 for index in range(n // 2):
17 print(' ' * (n // 2 - index) + '*' * (2 * index + 1))
18 for index in range(n // 2 + 1):
19 print(' ' * index + '*' * (n - index * 2))
269
ANSWER
# Program: diamond_debugging.ipynb
1 # # Input and input validation
2 # while True:
3 # n_str = input("Enter n: ")
4 # if not n_str.isdigit():
5 # print("Data validation failed!")
6 # print("n should be a positive integer")
7 # continue
8 # n = int(n_str)
9 # if n < 1 or n > 19 or n % 2 == 0:
10 # print("Data validation failed!")
11 # print("n should be odd and between 1 and 19 inclusive")
12 # continue
13 # break
14
15 # Process and output
16 n = 5
17 stars = []
18 index = 1
19 while index < n:
20 stars += [" " * ((n - index) // 2) + "*" * index]
21 index += 2
22 print(stars)
23
24 # for index in range(n // 2):
25 # print(stars[index])
26 # for index in range(n // 2 + 1):
27 # print(stars[-index - 1]
b) We can add a print statement to track the value of index up to when the program crashes:
# Program: diamond_debugging_2.ipynb
1 Input and input validation
2 while True:
3 n_str = input("Enter n: ")
4 if not n_str.isdigit():
5 print("Data validation failed!")
6 print("n should be a positive integer")
7 continue
8 n = int(n_str)
9 if n < 1 or n > 19 or n % 2 == 0:
10 print("Data validation failed!")
11 print("n should be odd and between 1 and 19 inclusive")
12 continue
13 break
14
270
ANSWER
# Program: diamond_debugging_2.ipynb
15 # Process and output
16 stars = []
17 index = 1
18 while index < n:
19 stars += [" " * ((n - index) // 2) + "*" * index]
20 index += 2
21
22 for index in range(n // 2):
23 print(stars[index])
24 for index in range(n // 2 + 1):
25 print(f"DEBUG: index={index}")
26 print(stars[-index - 1])
Enter n: 5
*
***
DEBUG: index=0
***
DEBUG: index=1
*
DEBUG: index=2
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Input In [1], in <cell line: 24>()
24 for index in range(n // 2 + 1):
25 print(f"DEBUG: index={index}")
---> 26 print(stars[-index - 1])
The debug output shows that index is 2 when the program crashes. This indicates that -index - 1 on the last
line evaluates to -3. Using our results from part (a), we know that stars has only 2 str items, so -3 is indeed out
of range. Working backwards, we see that the program crashes because stars is missing the widest line of the
diamond (i.e., "*****") due to a logic error.
c) The most straightforward way to fix this error is to change the condition on line 18 from “index < n” to
“index <= n”.
# Program: diamond_fixed.ipynb
1 Input and input validation
2 while True:
3 n_str = input("Enter n: ")
4 if not n_str.isdigit():
5 print("Data validation failed!")
6 print("n should be a positive integer")
7 continue
8 n = int(n_str)
9 if n < 1 or n > 19 or n % 2 == 0:
10 print("Data validation failed!")
11 print("n should be odd and between 1 and 19 inclusive")
12 continue
13 break
14
15 # Process and output
16 stars = []
17 index = 1
18 while index <= n:
19 stars += [" " * ((n - index) // 2) + "*" * index]
20 index += 2
21
22 for index in range(n // 2):
23 print(stars[index])
24 for index in range(n // 2 + 1):
25 print(stars[-index - 1])
271
ANSWER
# Program:word_reversal.ipynb
1 # Input
2 input_text = input("Enter input text: ")
3
4 # Process
5 word_list = input_text.split()
6
7 for i in range(len(word_list)):
8 current_word = ""
9 for letter_index in range(len(word_list[i])):
10 current_word += word_list[index][-letter_index - 1]
11 word_list[i] = current_word
12
13 result = ""
14 for word in word_list:
15 result += word + " "
16 result = result[:-1]
17
18 # Output
19 print(result)
2.
a) i. True
ii. False
iii. True
iv. False
b) i. The return value should be True since an empty string, having no characters, trivially satisfies the
requirement of not containing any non-letter characters.
ii. The return value should be False since an empty string, having no characters, is not a sensible input for
the task of checking whether every character is a letter.
iii. Since an empty string is considered valid input, we choose to have is_letters_only('') return True so
an empty string will pass data validation. A possible answer is as follows:
c) i. This is because convert_string() calls and uses the return value of convert_letter() in its function
body to determine its own return value. An incorrect return value from convert_letter() is likely to
result in an incorrect return value for convert_string().
ii. The bug is in convert_string(). A possible answer is as follows:
272
ANSWER
# Program: opposite_case_finished.ipynb
1 # Returns True if s has alphabetical letters only
2 def is_letters_only(s):
3 for c in s:
4 if not c.isalpha():
5 return False
6 return True
7
8 # Converts single letter to opposite case
9 def convert_letter(l):
10 if l.islower():
11 return l.upper()
12 return l.lower()
13
14 # Converts string of letters to opposite case
15 def convert_string(s):
16 result = ''
17 for c in s:
18 result = result + convert_letter(c)
19 return result
20
21 while True:
22 s = input('Enter string: ')
23 if is_letters_only(s):
24 break
25 print('String must have alphabetical letters only')
26
27 print(convert_string(s))
273