Home Identifying Challenges within Solutions
Post
Cancel

Identifying Challenges within Solutions

Identifying Challenges within Solutions

To learn about debugging, let’s remind ourselves that the computational thinking process is not linear. Even when we are working from the original problem, we will sometimes redefine the problem or need to adjust the generalization due to a change in the population our algorithm is for or if we want to tweak our design of the algorithm. But sometimes, we come at problems after an algorithm has been designed and used. Depending on our roles, we’ll be evaluating algorithms for errors, changes needed, and so on. Understanding how to find and analyze errors can help us, regardless of whether we are absolute Python beginners or deep in our careers.

I. Identifying errors in algorithm design

Errors in algorithms are just a fact of life for any coder. It’s important to get comfortable with making mistakes. Waiting until you have finished hundreds or thousands of lines of code to test something is a recipe for disaster. And yes, I was once working on copying a game and did not test at all. Not until I had all 4,585 lines copied. I was young. Truth be told, I never found the error I made. I started over and started testing at every corner. The second time was successful, but I’d wasted weeks copying everything (it was from a book—GitHub wasn’t a thing yet) and then trying to figure out the errors. So please don’t be me. Please test your algorithms.

Now, before moving on to debugging and working with codes, let’s take a look at the errors we can encounter when solving problems.

In this section, we’ll focus on the following two broad categories of errors: syntax errors and logic errors.

1. Syntax errors

Syntax errors are sometimes called parsing errors. They’re errors we create when we forget to indent, add a colon, add quotation marks for strings, and so on. Let’s have a look at the different types of syntax errors in the following sections.

2. Errors in logic

  • Using the wrong variable in an equation or statement
  • Using the wrong operator to test conditions
  • Using wrong indentation when checking for conditions

Now we’ll look at other errors in logic that have a specific callout from Python and what each error represents.

Errors in logic are also called runtime errors. The following table shows some of the built-in errors in Python and what they represent:

image

image

II. Debugging algorithms

  • breakpoints

III. Comparing solutions

As we look at problems, I’ve mentioned that we have multiple ways of doing the same things in Python. Depending on what we are trying to accomplish, some commands may be better than others in our algorithms. Let’s start by taking a look at a couple of solutions for one problem.

Problem 1 - Printing even numbers

You’ve been asked to write an algorithm that prints even numbers based on a range that the user provides. That is, if the user enters the range 2 through 20, then the program would print 2, 4, 6, 8, 10, 12, 14, 16, 18, and 20. Let’s assume we want to include the endpoints if they are even.

Let’s take a look at the first of two possible solutions. Remember, one solution may not be better than the other. A lot will depend on what the goal for your full algorithm is. Is a list more appropriate? A dictionary? A function? Those questions are important when we design solutions.

1. Algorithm solution 1 - Printing even numbers

Recall that we will be taking user input to create a list of even numbers given a range. Take a look at the following code, which asks the user for the input then prints out the numbers:

1
2
3
4
5
6
7
8
9
10
print("This program will print the even numbers for any range of numbers provided.")

endpoint1 = int(input("What is the lower endpoint of your range? "))
endpoint2 = int(input("What is the upper endpoint of your range? "))

endpoint2 = endpoint2 + 1

for i in range(endpoint1, endpoint2):
    if i % 2 == 0:
        print(i)

Notice that endpoint2 was converted into endpoint2 + 1. That is because if we do not add 1, then the upper endpoint will not be included if it is an even number. The program also begins with a printed message for the user that states what the program does.

When I run this program with the endpoints 2 and 6, I get the following output:

1
2
3
4
5
6
This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 2
What is the upper endpoint of your range? 6
2
4
6

As you can see, both endpoints are even and included. If we run the program with the endpoints 3 and 9, we get the following output:

1
2
3
4
5
6
This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 3
What is the upper endpoint of your range? 9
4
6
8

Even though the endpoint is technically 10 now, the upper limit of the range is not included, so the largest even number below 10 is 8. Now, I can run this program for a much larger range, but the larger the range, the harder it is to scroll to get all the numbers. So, let’s take a look at a different way to get our even numbers.

2. Algorithm solution 2 - Printing even numbers

As we saw from the previous example, each even number is being printed to a different line. Let’s see whether we can change that and instead create a list. Lists in Python can be empty. We use any name for them, then equal them to items inside braces or just the empty braces.

For example, I can create an empty list called evenNumbers = []. Let’s see what that looks like in the following algorithm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
print("This program will print the even numbers for any range of numbers provided.")

endpoint1 = int(input("What is the lower endpoint of your range? "))
endpoint2 = int(input("What is the upper endpoint of your range? "))

endpoint2 = endpoint2 + 1

evenNumbers = []

for i in range(endpoint1, endpoint2):
    if i % 2 == 0:
        evenNumbers.append(i)
        
        
print(evenNumbers)

You can see that the first few lines of code are the same. The only difference in this particular code is how the numbers are printed. The list is created before the for loop. Then, each of the numbers is appended to the list using the evenNumbers.append(i) code. Finally, we print our list to get the following output:

1
2
3
4
This program will print the even numbers for any range of numbers provided.
What is the lower endpoint of your range? 2
What is the upper endpoint of your range? 10
[2, 4, 6, 8, 10]

As you can see, the even numbers are all included in one list, which is easier to read than if printed one at a time, one line at a time. </i> Imagine if you had to print even numbers in the range 300–1,000 </i> . A list would make that easier to read when we run the program. The output would look as follows for the second algorithm:

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
31
32
33
34
This program will print the even numbers for any range of
numbers provided.
What is the lower endpoint of your range? 300
What is the upper endpoint of your range? 1000
[300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322,
324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346,
348, 350, 352, 354, 356, 358, 360, 362, 364, 366, 368, 370,
372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 392, 394,
396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418,
420, 422, 424, 426, 428, 430, 432, 434, 436, 438, 440, 442,
444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466,
468, 470, 472, 474, 476, 478, 480, 482, 484, 486, 488, 490,
492, 494, 496, 498, 500, 502, 504, 506, 508, 510, 512, 514,
516, 518, 520, 522, 524, 526, 528, 530, 532, 534, 536, 538,
540, 542, 544, 546, 548, 550, 552, 554, 556, 558, 560, 562,
564, 566, 568, 570, 572, 574, 576, 578, 580, 582, 584, 586,
588, 590, 592, 594, 596, 598, 600, 602, 604, 606, 608, 610,
612, 614, 616, 618, 620, 622, 624, 626, 628, 630, 632, 634,
636, 638, 640, 642, 644, 646, 648, 650, 652, 654, 656, 658,
660, 662, 664, 666, 668, 670, 672, 674, 676, 678, 680, 682,
684, 686, 688, 690, 692, 694, 696, 698, 700, 702, 704, 706,
708, 710, 712, 714, 716, 718, 720, 722, 724, 726, 728, 730,
732, 734, 736, 738, 740, 742, 744, 746, 748, 750, 752, 754,
756, 758, 760, 762, 764, 766, 768, 770, 772, 774, 776, 778,
780, 782, 784, 786, 788, 790, 792, 794, 796, 798, 800, 802,
804, 806, 808, 810, 812, 814, 816, 818, 820, 822, 824, 826,
828, 830, 832, 834, 836, 838, 840, 842, 844, 846, 848, 850,
852, 854, 856, 858, 860, 862, 864, 866, 868, 870, 872, 874,
876, 878, 880, 882, 884, 886, 888, 890, 892, 894, 896, 898,
900, 902, 904, 906, 908, 910, 912, 914, 916, 918, 920, 922,
924, 926, 928, 930, 932, 934, 936, 938, 940, 942, 944, 946,
948, 950, 952, 954, 956, 958, 960, 962, 964, 966, 968, 970,
972, 974, 976, 978, 980, 982, 984, 986, 988, 990, 992, 994,
996, 998, 1000]

The reason I only printed this one versus the first algorithm is that the first algorithm would take pages, and we don’t want to waste printed pages in this book. You can see that one is just easier to use and more appropriate than the other due to the ease of reading the larger group of numbers.

This is why we need to look at all of our algorithms and determine whether they are the best possible way to express what we need. While some algorithms work, they may not be the best solution, and sometimes that’s okay. But other times, making some changes, sometimes as subtle as adding a couple of lines of code, as we did with algorithm 2, can change our output fairly dramatically and be much more helpful for us.

As we compared these two algorithms, we were also refining and redefining our solution, which we will do more of in the next section.

IV. Refining and redefining solutions

If we look at algorithms long enough, we can always find ways to refine them and redefine them. Think about how many updates we get for apps on our phones. Someone is always playing with the apps, making them more secure, adding levels to games, updating the art files, and so on. As programmers/coders, we are always trying to make our work better.

We are going to start this section with an algorithm. The following program prints out the names of three pets:

1
2
3
4
5
6
7
cat = "Whiskers"
dog = "King Kong"
bird = "Pirate"

print("The cat's name is " + cat + ", the dog's name is " + dog + \
      ", and the bird's name is " + bird + ".")

This simple code has everything within it, so there’s no user input this time. You can see the \ character used after dog + in the print() command. This backslash allows us to add the remaining code in the next line so we can more easily read it.

The output for the code looks as follows:

1
The cat's name is Whiskers, the dog's name is King Kong, and the bird's name is Pirate.

As you can see, it’s a simple sentence with the pet names.

Now, let’s say we have a cat, dog, and bird, but their names are not the same. We can instead use a function that takes three arguments.

1
2
3
4
5
def myPets(cat, dog, bird):
    print("The cat's name is " + cat + ", the dog's name is " + dog +\
          ", and the bird's name is " + bird + ".")

myPets(cat = "Whiskers", dog = "King Kong", bird = "Pirate")

The algorithm looks very similar to the previous one, except the definitions of the names are in the last line of the code. The function is called, using the information from that line to fill in the blanks from the definition in the algorithm lines above it. The output looks the same as the previous code:

1
The cat's name is Whiskers, the dog's name is King Kong, and the bird's name is Pirate.

Now, as you can see, this only printed one function because we only provided information for one, but we can call the function as many times as we want to with as many values as we want. Take a look at this algorithm:

1
2
3
4
5
6
7
8
def myPets(cat, dog, bird):
    print("The cat's name is " + cat + ", the dog's name is " + dog +\
          ", and the bird's name is " + bird + ".")

myPets(cat = "Whiskers", dog = "King Kong", bird = "Pirate")
myPets(cat = "Mimi", dog = "Jack", bird = "Peyo")
myPets(cat = "Softy", dog = "Leila", bird = "Oliver")

As you can see, the function will now be called three times. We only have one print() command, but the function definition means that print() command will be used any time the function is called. Take a look at what the output looks like:

1
2
3
The cat's name is Whiskers, the dog's name is King Kong, and the bird's name is Pirate.
The cat's name is Mimi, the dog's name is Jack, and the bird's name is Peyo.
The cat's name is Softy, the dog's name is Leila, and the bird's name is Oliver.

Notice that three different sentences were printed with the three sets of pet names provided when we called the function.

When we’re writing algorithms, it’s important to take into consideration what we need now and what we might need later. Using the first algorithm was fine for one instance, but if we wanted to run the algorithm for every person in a community or every student in a classroom, for example, the second algorithm is more helpful. Redefining what we need and refining our algorithms helps us to improve what we get out of our programs.

One of the things we’ll address is creating a function for an unknown number of arguments. For example, what if I only had a dog and a bird? We can address that with a few changes to the algorithm. We will look into that soon. For now, we know just a little more about why we need to sometimes compare algorithms and redefine and redesign them to better fit our needs.

This post is licensed under CC BY 4.0 by the author.