Exploring Problem Analysis
I. Understanding the problem definitions
Computational thinking uses four elements in order to solve problems:
- Problem decomposition : This is the process of breaking down data.
- Pattern recognition : This is the process of finding similarities or patterns.
- Abstraction : This element deals with generalizing the pattern.
- Algorithm design : This is where we define the set of instructions for the solution to the problem.
In this section, in order to learn more about how to analyze problems, we’re going to analyze a larger problem and work through the steps needed to create the algorithm. To be able to create algorithms, it is imperative that we analyze the problems and clearly identify what we are trying to solve. That is, what is our algorithm for? Why do we need to build it? Looking at the decomposition of problems and then defining what we need will provide us with a better algorithm at the end.
1. Problem 5A – Building an online store
Let’s take a look at the following problem. You are starting an online store. It’s in its infancy, but you’ll have three different types of item available. They are keychains, water bottles, and t-shirts. For this particular problem, we will go through a three-step process:
- Making assumptions
- Things to consider
- Building a dictionary
We will look the preceding steps in the upcoming sections.
a. Making assumptions
Let me state some assumptions about this store that we are going to use:
- This is a company that provides items for clients to share with their customers.
- Each item can have a logo and/or personalized information, such as name, email, and phone number.
We will now move on to the next section, which is about things to consider.
b. Things to consider
Now let’s take a look at some of the things that you’ll need to think about before we even start working on an algorithm:
- Are the items personalized?
- Will personalization be charged by character, line, or item?
- Will the price be fixed or will it change when customers make bulk purchases?
- Will there be discounts if a client orders more than one type of item?
- How much is the base price-point for each item?
The preceding points are not the only questions that we could go over. But they’re the questions that we’ll start taking a look at when we decompose the problem.
c. Building a dictionary
Before we take a look at the complexities presented by this problem and decomposing that information, we can build our own dictionary. We can make it so that the price for each item in the dictionary is the base price (the price that does not contain any customizations or discounts), as follows:
- Cost per keychain: $0.75
- Cost per t-shirt: $8.50
- Cost per water bottle: $10.00
Now let’s build the dictionary. Remember that you can do this without the dictionary, but creating a dictionary allows you to update the pricing, if necessary, at a later date. You can also create functions to solve this problem. We are using logic and the dictionary for this problem. The following code shows you how to build a dictionary:
1
2
3
4
5
6
online_store = {
'keychain': 0.75,
'tshirt': 8.50,
'bottle': 10.00
}
print(online_store)
From the preceding code snippet, keep in mind that the print() function is not needed here, but I use it often in order to ensure that the code is working properly while I continue to build the algorithms. Also notice that the names of the variables—keychain, tshirt, and bottle—are simplified.
1
{'keychain': 0.75, 'tshirt': 8.5, 'bottle': 10.0}
What the output shows me is that the prices are saved correctly for each of the variables. I’m using that print function to test my dictionary and ensure that it runs correctly before I start working on what I need from that dictionary.
This helps us when we are writing the code and reuse the variables in multiple areas of that code. Having these simple and easy-to-identify variables will allow us to change and add to the algorithm without adding errors.
II. Learning to decompose problems
When we decompose problems, we’re identifying what we need the algorithm to provide us with. The end user will need to see something seamless. Look at the flowchart below; this is a basic decision-making flowchart to help us design our algorithm.
Let’s make another assumption first, that is, if the user enters more than 10, the price will be lower. We’re only going to do less than 10 or more than or equal to 10 in this case. However, if you needed to subdivide this further, you can add more cases, such as the following:
- Less than or equal to 10
- More than 10 and less than or equal to 50
- More than or equal to 50
You can have as many cases as you need. For the purposes of this algorithm, we’re going to keep it to two cases, since we also have to include personalization costs and we don’t want to create an overly complicated algorithm.
The following diagram shows you the flowchart for the algorithm:
As you can see in the preceding diagram, this isn’t a completed flowchart. After we make the decisions about the t-shirts, we need to move on to the bottles. How we write the algorithm will depend on what we’d like to output. Right now, we’re providing the user the information they’d get when they check out of the online store that you created.
Converting the flowchart into an algorithm
The diagram in above allows us to look at the decision-making process for the algorithm we’re writing. We’ll want to look at the following key points when writing the algorithm:
- Dictionary and input: Input can be within the algorithm or user-entered; dictionaries are built within the algorithm. This means, to use a dictionary, we have to define it in our algorithm before we are able to use it.
- Cost: This is the base cost for each item.
- Personalization costs: This is added to the base cost.
a. Building a dictionary and giving inputs
Before we add any of the complications, let’s look at how to grab the price of each item and use it at the base price. We’ll need a count for the number of each item. The following code shows you this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
online_store = {
"keychain": 0.75,
"tshirt": 8.50,
"bottle": 10.00
}
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
choicekey = input("How many keychains will you be purchasing? If not purchasing keychains, enter 0. ")
choicetshirt = input("How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0. ")
choicebottle = input("How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0. ")
print("You are purchasing " + str(choicekey) + " keychains, " + str(choicetshirt) + " t-shirts, and " + str(choicebottle) + " water bottles.")
From the preceding code snippet, notice that we added the variables under the dictionary. This will be useful later. These variables are named choicekey, choicetshirt, and choicebottle. Naming the variables allows us to return to them and change code, as needed. In this case, each variable asks for input from the person running the program in order to get the number of keychains, t-shirts, and bottles they are ordering. Again, there are multiple ways to tackle this problem, but we’re using what we’ve learned so far to create an algorithmic solution.
When we run the previous code for 3 keychains, 0 t-shirts, and 10 water bottles, this is our output:
1
2
3
4
How many keychains will you be purchasing? If not purchasing keychains, enter 0. 3
How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0. 0
How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0. 10
You are purchasing 3 keychains, 0 t-shirts, and 10 water bottles.
As you can see, we have a program that takes user input, then confirms to the user the choices they have made for each of the items.
b. Making changes to the cost
Now let’s add the changes in cost. Let’s say that if a customer is purchasing over 10 items, then the updated costs are as follows:
- Keychains: $ 0.65
- T-shirts: $ 8.00
- Water bottles: $ 8.75
To make the preceding changes, we can make the program update the difference in cost, which is shown in the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
online_store = {
"keychain": 0.75,
"tshirt": 8.50,
"bottle": 10.00
}
choicekey = int(input("How many keychains will you be purchasing? If not purchasing keychains, enter 0. "))
choicetshirt = int(input("How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0. "))
choicebottle = int(input("How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0. "))
print("You are purchasing " + str(choicekey) + " keychains, " + str(choicetshirt) + " t-shirts, and " + str(choicebottle) + " water bottles.")
if choicekey > 9:
online_store['keychain'] = 0.65
if choicetshirt > 9:
online_store['tshirt'] = 8.00
if choicebottle > 9:
online_store['bottle'] = 8.75
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
print(online_store)
Now that we have updated the code, I’d like to print out my progress to make sure that the code is working properly and changes take place. In this case, I wanted to make sure that the costs would update if I had totals greater than 10. (That is, when a customer orders more than 10 of an item, it updates the cost for each item to the lower cost.) The output of the preceding code is as follows:
1
2
3
4
5
How many keychains will you be purchasing? If not purchasing keychains, enter 0. 10
How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0. 14
How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0. 10
You are purchasing 10 keychains, 14 t-shirts, and 10 water bottles.
{'keychain': 0.65, 'tshirt': 8.0, 'bottle': 8.75}
You can now see from the preceding output that the dictionary has updated the values based on the totals the user provided.
Now we need to go ahead and provide the cost. We can provide the total item cost or the total cost of the full purchase, or both (let’s do both). Take a look at the following code snippet:
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
online_store = {
"keychain": 0.75,
"tshirt": 8.50,
"bottle": 10.00
}
choicekey = int(input("How many keychains will you be purchasing? If not purchasing keychains, enter 0. "))
choicetshirt = int(input("How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0. "))
choicebottle = int(input("How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0. "))
if choicekey > 9:
online_store['keychain'] = 0.65
if choicetshirt > 9:
online_store['tshirt'] = 8.00
if choicebottle > 9:
online_store['bottle'] = 8.75
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
print("You are purchasing " + str(choicekey) + " keychains, " + str(choicetshirt) + " t-shirts, and " + str(choicebottle) + " water bottles.")
totalkey = choicekey * keychain
totaltshirt = choicetshirt * tshirt
totalbottle = choicebottle * bottle
grandtotal = totalkey + totaltshirt + totalbottle
print("Keychain total: $" + str(totalkey))
print("T-shirt total: $" + str(totaltshirt))
print("Water bottle total: $" + str(totalbottle))
print("Your order total: $" + str(grandtotal))
The result of the preceding code is as follows:
1
2
3
4
5
6
7
8
How many keychains will you be purchasing? If not purchasing keychains, enter 0. 10
How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0. 7
How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0. 14
You are purchasing 10 keychains, 7 t-shirts, and 14 water bottles.
Keychain total: $6.5
T-shirt total: $59.5
Water bottle total: $122.5
Your order total: $188.5
Now that we have the totals of the items without personalization, we need to be able to take into account the costs of that personalization, if ordered.
c. Adding personalization
For now, let’s limit the personalization of keychains, t-shirts, and water bottles to binary questions, that is, either the user wants personalization or not. We are not looking at tiered costs of personalization, which you may have seen. If you wanted to add tiers, you’d need to make more decisions, such as cost of choosing fonts, length of the personalization, and so on. We’ll forgo those for now, but feel free to add to this code in order to address those kinds of customizations. Let’s add another assumption for the personalization:
- $1.00 for the keychains
- $5.00 for the t-shirts
- $7.50 for the water bottles
We’ll need to create the preceding conditions and then implement them into our variables. Let’s look at the code in parts. The following file contains each of the parts we’ll break down.
Recall that our algorithm first asked for input for the number of items they were purchasing. The following code snippet takes user input in order to take personalization into account:
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
35
36
37
38
39
40
41
42
43
44
online_store = {
"keychain": 0.75,
"tshirt": 8.50,
"bottle": 10.00
}
choicekey = int(input("How many keychains will you be purchasing? If not purchasing keychains, enter 0. "))
choicetshirt = int(input("How many t-shirts will you be purchasing? If not purchasing t-shirts, enter 0. "))
choicebottle = int(input("How many t-shirts will you be purchasing? If not purchasing water bottles, enter 0. "))
if choicekey > 9:
online_store['keychain'] = 0.65
if choicetshirt > 9:
online_store['tshirt'] = 8.00
if choicebottle > 9:
online_store['bottle'] = 8.75
print("You are purchasing " + str(choicekey) + " keychains, " + str(choicetshirt) + " t-shirts, and " + str(choicebottle) + " water bottles.")
perskey = input("Will you personalize the keychains for an additional $1.00 each? Type yes or no. ")
perstshirt = input("Will you personalize the t-shirts for an additional $5.00 each? Type yes or no. ")
persbottle = input("Will you personalize the water bottles for an additional $7.50 each? Type yes or no. ")
if perskey == ("yes" or "Yes"):
online_store['keychain'] = online_store['keychain'] + 1.00
if perstshirt == ("yes" or "Yes"):
online_store['tshirt'] = online_store['tshirt'] + 5.00
if persbottle == ("yes" or "Yes"):
online_store['bottle'] = online_store['bottle'] + 7.50
keychain = online_store['keychain']
tshirt = online_store['tshirt']
bottle = online_store['bottle']
totalkey = choicekey * keychain
totaltshirt = choicetshirt * tshirt
totalbottle = choicebottle * bottle
grandtotal = totalkey + totaltshirt + totalbottle
print("Keychain total: $" + str(totalkey))
print("T-shirt total: $" + str(totaltshirt))
print("Water bottle total: $" + str(totalbottle))
print("Your order total: $" + str(grandtotal))
The preceding code snippet asks the user the binary questions on personalization. After grabbing the input, the code then makes some decisions based on the user input and defines the keychain, tshirt, and bottle variables and the totals for the choices. The following snippet then uses the totals to print out the information for each item purchased as well as the final total:
1
2
3
4
print('Keychain total: $' + str(totalkey))
print('T-shirt total: $' + str(totaltshirt))
print('Water bottle total: $' + str(totalbottle))
print('Your order total: $' + str(grandtotal))
From the preceding code, notice that the keychain, tshirt, and bottle variables are defined after all our customizations based on total numbers and personalization. Remember that in algorithm design, the order matters. If we locate those variables earlier in the program, the conditions, such as personalization, that follow will not affect those variables.
So, to be able to get everything we need for our variables, we need to define them after defining some of the conditions that affect them, such as customization. Take a look at the preceding code to note where the variables are. Feel free to play with the code by changing where you define the variables to see whether your end results change.
Here’s a visual flowchart with the keychain decision-making process:
As you can see from the preceding diagram, this is only for Keychains . We need to repeat the process for the other two variables. In the diagram, you can see the decision-making process for the item. First, the user indicates the number of items bought, then whether they will personalize them or not.
Depending on each answer, the total is calculated by the program. For example, if there is no personalization, the total is calculated sooner in the decision-making tree. We can rewrite this program using functions (as I mentioned before) to simplify some of the processes. For now, we are focusing on learning how to break down problems, analyze conditions, and how to design algorithms that take into account multiple decisions. Remember to complete the diagram for the other items so that the decision-making process is easier to code when designing the algorithm.
III. Analyzing problems
When analyzing problems, there are some steps that we can keep in mind to help us ensure that we are creating the best possible algorithm:
- Clearly read and understand the problem.
- Identify the main purpose of the solution.
- Identify the constraints of the problem.
- Identify the decision-making flow.
- Establish the possible algorithms that could solve the problem.
- Identify the best possible algorithm tools for the problem.
- Test the algorithm pieces frequently.
- Verify that the algorithm provides the solution for the identified problem.
If we go back to our problem, we went through this process throughout the part:
- We had an online store with three items.
- Item cost was dependent on quantity purchased.
- Item price was also dependent on personalization customizations.
- We created flowcharts to help us identify the decision process and how to code it.
- We verified our code through code lines that allowed us to check whether the algorithm was producing the correct response multiple times.
- We revisited and reordered pieces of code, as needed.
- We verified that the algorithm’s output was in line with the problem we had identified.
The preceding process bears repeating, that is, this is not a linear process. Sometimes we’ll write an algorithm and then revisit the decision flowchart, make adjustments, then tackle the algorithm again.
The need for analyzing our problems at multiple stopping points becomes even clearer when we are looking at larger problems. Should we write hundreds of lines of code before testing? No! Imagine having 300 lines of code, only to find an error on line 20 that is carried forward throughout the rest of the algorithm.
Testing at every possible progress point will allow us to catch the small mistakes that can cost us in the long run. Remember, it’s almost impossible to write a perfect algorithm on the first try. We all make mistakes, small and large, so it is important that we continue to test and analyze our progress.
Problem 5B – Analyzing a simple game problem
You want to design a number guessing game. The user has to guess a random number.
Let’s start by defining our problem, which in this case is a game. Let’s identify the known information:
- The computer will need to randomly select a number.
- The user will need to input a number.
- The computer will have to check whether the input from the user matches the randomly generated number.
Now, that’s not enough! If I don’t match on the first try, do I lose? How many chances do I get? Is the random number a number between 1 and 10 or between 1 and 500? We’re going to have to make some decisions before we start coding this. Let’s add some parameters:
- The number is between 1 and 100.
- The user will get 5 chances to guess.
- The computer will tell the user if the answer is too high or too low.
Now that we have those parameters, we can create a decision flowchart:
From the preceding diagram you can see that the chart is not complete. That’s because we will use some logic to make the process repeat 5 times. We’ll get into that in a moment. For now, notice the decisions. First, a number is generated by the program (but is not revealed). The user then inputs a guess, which is either correct or incorrect. If it’s correct, then the user wins the game. If it’s incorrect, then the program lets the user know if the answer was too low or too high and asks for a new guess. The process will then repeat itself, as needed. Now, let’s write the algorithm.
First, let’s generate the random number and get the user to guess it. Add a print() function for both the randomly generated number and the input from the user so that you can see that the information is working properly. Remember, we’ll take those out later, but it’s important to keep checking and rechecking our code as part of our problem analysis process. The following code will do the same:
1
2
3
4
5
6
import random as rand
compnumber = rand.randint(1, 100)
print(compnumber)
usernumber = int(input("Choose a number between 1 and 100. You'll get 5 guesses or you lose! "))
print(usernumber)
You’ll notice from the preceding code, the imported random module. We also imported it as rand. That’s just to save time and space. When you import a module in Python, you can rename it. The random module gives us a way to generate the number in the range that we had selected.
The rand.randint(1, 100) code line includes 1 and 100. These are the endpoints, or limits for the random number generator. The rand function refers to the module, as mentioned, while randint(a, b) refers to a random integer between a and b (including a and b).
Run the code a few times to see how the number generated by the computer changes each time. The following points show three test cases:
• The following is test case 1 of the preceding code:
1
2
3
27
Choose a number between 1 and 100. You'll get 5 guesses or you lose! 10
10
As you can see from the preceding output, 27 is the computer-generated random number and 10 is what the user entered.
• The following is test case 2 results of the previous code:
1
2
3
68
Choose a number between 1 and 100. You'll get 5 guesses or you lose! 65
65
As you can see from the preceding output of the code, 68 is the value of the compnumber variable, while the user (me) entered the number 65. So close, yet so far!
• The following is test case 3 output:
1
2
3
4
50
Choose a number between 1 and 100. You'll get 5 guesses
or you lose! 23
23
As you can see from the preceding output, the computer chose the number 50, while the user entered 23.
For our final version of this game, we won’t print out the computer number. That would be cheating! Right now, we’re just testing.
Let’s go ahead and add one condition—whether or not the first guess is correct. To do so, we’ll have to verify compnumber == usernumber. We’re going to test this again before going into the additional repetitions and logic, so we’ll just say if it’s true, then you win; if it’s false, then you lose:
1
2
3
4
5
6
7
8
import random as rand
compnumber = rand.randint(1, 100)
usernumber = int(input("Choose a number between 1 and 100. You'll get 5 guesses or you lose! "))
if compnumber == usernumber:
print("You win!")
else:
print("You lose!")
Let’s just say I lost on the first try when I ran it. I’m not going to run this until I do win, however, because that could take, well, 100 tries or more. Here’s what that looks like when you run the program:
1
2
Choose a number between 1 and 100. You'll get 5 guesses or you lose! 35
You lose!
Now let’s talk about repeating a line of code. We’re giving the user 5 guesses. How can we do that ?
1
2
3
4
5
6
7
8
9
10
11
12
import random as rand
compnumber = rand.randint(1, 100)
i = 5
for number in range(5):
usernumber = int(input("Choose a number between 1 and 100. You have " + str(i) + " guesses left. "))
if compnumber == usernumber:
print("You win!")
else:
i = i - 1
print("You're out of guesses! You lose! ")
1
2
3
4
5
6
Choose a number between 1 and 100. You have 5 guesses left. 14
Choose a number between 1 and 100. You have 4 guesses left. 98
Choose a number between 1 and 100. You have 3 guesses left. 48
Choose a number between 1 and 100. You have 2 guesses left. 12
Choose a number between 1 and 100. You have 1 guesses left. 54
You're out of guesses! You lose!
Now, we’re not really being fair. As mentioned earlier, we want to give the user a hint each time they attempt a guess. Now that we have the condition checking whether they are equal, we can add an elif condition to check whether it’s larger or smaller. The following code shows this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import random as rand
compnumber = rand.randint(1, 100)
i = 5
for number in range(5):
usernumber = int(input("Choose a number between 1 and 100. You have " + str(i) + " guesses left. "))
if compnumber == usernumber:
print("You win!")
exit()
elif compnumber > usernumber:
print("Your number is too small!")
i = i - 1
elif compnumber < usernumber:
print("Your number is too large!")
i = i - 1
print("You're out of guesses! You lose! ")
The preceding code now provides some feedback to the user. If the number was greater than the computer-generated number, the user receives the feedback ‘Your number is too large!’, and if the user number is less than the computer-generated number, then they receive the feedback ‘Your number is too small!’. We also used an exit() code if the user wins. That’s because we want the game to stop when we win.
This gives us a fighting chance to win this game, take a look at what the output looks like now:
1
2
3
4
5
6
7
8
9
10
Choose a number between 1 and 100. You have 5 guesses left. 50
Your number is too small!
Choose a number between 1 and 100. You have 4 guesses left. 75
Your number is too large!
Choose a number between 1 and 100. You have 3 guesses left. 65
Your number is too small!
Choose a number between 1 and 100. You have 2 guesses left. 70
Your number is too large!
Choose a number between 1 and 100. You have 1 guesses left. 68
You win!
Now look at what happens when we lose the game:
1
2
3
4
5
6
7
8
9
10
11
Choose a number between 1 and 100. You have 5 guesses left. 10
Your number is too small!
Choose a number between 1 and 100. You have 4 guesses left. 40
Your number is too large!
Choose a number between 1 and 100. You have 3 guesses left. 20
Your number is too small!
Choose a number between 1 and 100. You have 2 guesses left. 30
Your number is too small!
Choose a number between 1 and 100. You have 1 guesses left. 35
Your number is too large!
You're out of guesses! You lose!
As you can see, you get a different final message. I confess it took me quite a few tries to win a game so I could get the output that follows, but you can see the game where the second guess was correct:
1
2
3
4
Choose a number between 1 and 100. You have 5 guesses left. 10
Your number is too small!
Choose a number between 1 and 100. You have 4 guesses left. 90
You win!
We are going to stop this game with that last algorithm. We could actually make this game better if we wanted to, but it does the job that we needed it to do. Some of the changes that you could consider making to your game are as follows:
- Adding an option that alerts the user of a number already guessed.
- Adding an option that alerts the user that they ignored a previous hint (so if the user gave a number that was too small and gave one that was even smaller, the computer would alert them).
I’m sure there are more customizations that you could try. But for now, we went through that problem and followed the points that we should consider when analyzing problems:
- We read and understood the problem.
- We identified the purpose—creating a computer player versus user player guessing game.
- We identified the constraints of the problem—the range of numbers, the number of guesses, and providing hints.
- We created a decision flowchart.
- We wrote and established an algorithm for the problem.
- We looked at how to create a simple algorithm that would iterate rather than having to write each condition individually.
- We tested the algorithm at multiple points.
- We verified that the algorithm ran accurately for both wins and losses.
What you don’t get to see here is the number of errors I went through before I got to the algorithms shown. While writing, I had to use the preceding steps to help me identify errors, check the best algorithms, and iterate through the programs. This is a process that we’ll continue to use.