How to Tame Your Input
(This text assumes that you have read about looping patterns and are at least somewhat confident using them. If that’s not the case, I recommend that you start there and get more practice first!)
If you have used for
loops to work on lists for a while, you might be beginning to think that they are the answer to every problem. As it turns out, that intuition isn’t that bad, because most problems you have seen so far (and a fair share of the ones you will see in the future) are based on lists as input for your code. Unless you have a task where you really only care about a specific part of the list (e.g., “Print the first 2 elements of the list”), loops are the easiest way to break down something complex and ominous like a list into a number of much smaller sub-problems.
Unfortunately, lists won’t be the only input you will ever encounter when programming. While we can’t come up with patterns for every single kind of data and every possible problem, I want to use this text to show you a general approach that will work most of the time to get from input (i.e., everything given to your code) to output (i.e., the thing that you, the auto-grader, or one day your boss might care about).
The 3 Step Approach
I will explain the following steps in more detail below.
- Ask yourself: Do I need to do different things based on how my input looks like?
- If yes, add some if/else logic and go back to the start, repeating the steps for each case!
- Ask yourself: Is my input a structure or a single thing?
- If yes, it’s a structure, break it down and go back to the start!
- Ask yourself: What do I need to do with the data to solve the task at hand?
- Then do that thing!
Step 1: Check if there are multiple cases
This step is new, and often one that you can skip, but it is always a good idea to at least briefly consider if there are several cases to take care of. For example, you might be tasked to do something with a list, but that task makes no sense if the list is empty. Similarly, you might be tasked to do some calculation where you might accidentally divide by 0. In all these scenarios, the best thing to do is to simply cover all the options by adding some logic and then handle each case separately. You could for example write if len(my_input_list) > 0:
and then deal with that case, and handle the else:
case separately.
Of course there are also scenarios where the cases are more explicitly laid out in the task: If you are supposed to calculate the absolute value of a number, you will have to check if my_input_number < 0:
, and if that’s not the case, you’ll do something else:
.
Step 2: Break down structures
This is the part that you have already learned, at least for one specific structure: lists! For lists and most other structures, the best way to break them down is to use a for
loop and one of the patterns (action or accumulator) you’ve already learned about. However, in some cases you might want to do something else to break down the structure, like access a specific key in a dictionary.
Warning: This step can require a bit of common sense for inputs like strings. If you have the task to do something specific to every character in a string (e.g., capitalize it if it is vowel), you will have to think of the string as a “list” of individual characters. However, most of the time, a string is more like a number where it is just a single thing that shouldn’t be broken down further.
Step 3: Do the thing
This is normally the easiest part. If you have just a single input, and you know there’s only one case left to consider, you just do the thing you need to do – add the numbers, or capitalize the letter, or change the value of the accumulator if you used the accumulator pattern in Step 2.
Examples
The Trivial Example
Let’s start with the most basic example: You are supposed to print the square of an input number x
. It is not hard to see that the answer to Steps 1 and 2 is a big “No!”. Therefore, our code is just as simple:
print(x**2)
The List Example
Let’s add one more step to the example: Now, you are supposed to print the squares of a list of input numbers xx
. Now, the answer to Step 1 is still “No!”, but Step 2 becomes a “Yes!”. We know how to break down lists: a for
loop! Don’t forget, for good measure, to go back to Step 1 and ask yourself, now for each list element x in xx
, if it is another structure or if we need multiple cases. Since the answer is “No!” to both, we are done and can finish with Step 3:
for x in xx:
print(x**2)
The Double List Example
Before we add the last step to our example, let’s actually stress why it is important to always go back to Step 1:
Let’s say you are given a list of lists and you are supposed to print it. What I mean with that is that you get an input xx
like this one: [[1, 2, 3], [4, 5], [6]]
. This might seem a bit contrived, but once you work with more complex data like matrices, this becomes not at all uncommon.
There’s a lot to unpack here, so let’s follow the steps from before, and for easier readability I will start writing them out line by line:
- Step 1: “No!”, we always have a list (we don’t care about the content yet!), and have no special cases to consider.
- Step 2: “Yes!” We write
for x in xx:
to unpack the list of lists - Step 1: “No!”, we are now looking at the individual lists inside our big list of lists, but we still don’t need to worry about any special cases.
- Step 2: “Yes!” Since we are still looking at lists, we need to unpack further, writing
for n in x:
(we maybe should have thought of a better naming scheme!) - Step 1: “No!”, we once again skip this step because even though we are looking at individual numbers now, we still want to do the same thing with all of them.
- Step 2: “No!”, we are done with the unpacking and can finally move on to the last step!
- Step 3: We print the number by writing
print(n)
.
Let’s write this out in one block:
for x in xx:
for n in x:
print(n)
The Full Example
Now we have ramped to do the full sequence: This time, you are supposed to print the squares of a list of input numbers xx
, but you should instead print Empty!
if it is empty!. Now our sequence of steps is:
- Step 1: “Yes!”, so add
if len(xx) > 0:
- Step 1: “No!” I bet you would have skipped over that one!
- Step 2: “Yes!” We write
for x in xx:
- Step 1: “No!” Yes, we go all the way back to the start!
- Step 2: “No!”
- Step 3: We
print(x**2)
- (and then we have the
else:
case)- Step 1: “No!”
- Step 2: “No!” We skip this because we already know the list is empty!
- Step 3: We
print("Empty!")
Let’s write that out as a full program:
if len(xx) > 0:
for x in xx:
print(x**2)
else:
print("Empty!")
The Full Example, Reversed
There is another case that’s pretty common (and that makes it important to actually go back to Step 1 all the time you do Steps 1 and 2), which is that you only encounter multiple cases after breaking down a structure.
Suppose you want to print the absolute value of a list of input numbers xx
, without using the built-in function abs
. Let’s outline the steps once again:
-
Step 1: “No!”, because this time we handle all lists the same way!
-
Step 2: “Yes!” We write
for x in xx:
-
Step 1: “Yes!” Now that we have broken down the list and are looking at individual numbers
x
, we need to handle them differently based on whether they are positive or negative!We write
if x < 0:
- Step 1: “No!”, because now we already know that x is negative.
- Step 2: “No!”
- Step 3: We
print(-x)
.
-
(and then we have the
else:
case)- Step 1: “No!”, because now we already know that x is positive.
- Step 2: “No!”
- Step 3: We
print(x)
for x in xx:
if x < 0:
print(-x)
else:
print(x)
The Full Example, Reversed, and With Accumulator
So far, we only used the action pattern (i.e., a basic for
loop) for Step 2, but breaking down a list can also involve an accumulator. As a rule of thumb, you will always need an accumulator if you want to transform your list into something else (e.g., a number, a different kind of list, …).
Let’s actually go back to an example from the Looping Patterns text: summing only even elements of a list
We solved this example already, but let’s look at how the steps from above still work for our input list xx
:
-
Step 1: “No!”, because we handle all lists the same way!
-
Step 2: “Yes!” We write
for x in xx:
. We also create an accumulatorsum = 0
before the loop to store our result of filtering. -
Step 1: “Yes!” Now that we have broken down the list and are looking at individual numbers
x
, we need to handle them differently based on whether they are even or odd!We write
if x % 2 == 0:
- Step 1: “No!”, because now we already know that x is even.
- Step 2: “No!”
- Step 3, which is
sum = sum + x
-
(and then we have the
else:
case)- Step 1: “No!”, because now we already know that x is odd.
- Step 2: “No!”
- Step 3: We do nothing, since we really don’t care about odd numbers! We can actually go back and delete the entire else case - sorry for wasting your time!
The final result is the same as in the previous text:
sum = 0
for x in xx:
if x % 2 == 0:
sum = sum + x
The Dictionary Example, With Accumulator
Let’s solve an exercise similar to those from Lecture 7 together:
We are given a dictionary like this, and want to add up all the salaries:
d = { 'Frodo': { 'title': 'Ring Bearer', 'salary': 120000, 'years': 2},
'Gandalf': { 'title': "Wizard", 'salary': 420000, 'years': 3000 },
'Gollum': { 'title': "Pest", 'salary': 0, 'years': 120}}
One quick reminder: When we use a for
loop on dictionaries, we can use d.keys()
or d.items()
to access the data inside.
-
Step 1: “No!”, because we handle all dictionaries the same way!
-
Step 2: “Yes!” This time, we break down a dictionary, but we can still use a
for
loop!We write
for key, value in d.items():
. Note that we could also writefor key in d.keys()
, but then we would still have to write something likevalue = d[key]
to get the value for our key, so in this cased.items()
is more convenient.We also create an accumulator because we want to transform our dictionary into a single number. Fortunately, that works exactly the same way for dictionaries as it does for lists: we can write
salaries = 0
before the loop to store our result. -
Step 1: “No!” We now have to work with the
key
andvalue
of the current dictionary entry. We still handle all those the same. -
Step 2: “Yes!” Since our
value
s are still dictionaries (that contain the'title'
,'salary'
and'year'
), we need to keep going deeper!We write
salary = value['salary']
to access the actual salary in thevalue
dictionary. -
Step 1: “No!” Now we are at the actual salary level, but we handle them all the same way.
-
Step 2: “No!” No more structures to break down! (are we there yet?!)
-
Step 3: We write
salaries = salaries + salary
to add the current salary to the sum.
The final result is:
salaries = 0
for name, value in d.items():
salary = value['salary']
salaries = salaries + salary
The Full Dictionary Example, With Two Accumulators
You know what, as a little bonus for reading so far, let’s actually solve one of the exercises for Lecture 7 together:
We are given the same dictionary as before, but this time we want to determine the name of the person with the highest salary. Okay, let’s do this!
-
Step 1: “No!”, because we handle all dictionaries the same way!
-
Step 2: “Yes!” We again write
for key, value in d.items():
. We also create an accumulatorlargestSalary = 0
before the loop to store our result.Also, because this question is a bit trickier, we need a second accumulator
largestSalaryName = ''
to store the name of the person with the largest salary. Yes, unfortunately that happens sometimes if you need to keep track of multiple results at once! -
Step 1: “No!” We still handle all
key
andvalue
entries the same. -
Step 2: “Yes!” We once again write
salary = value['salary']
to enter the Salary Zone of Dictionary Land. -
Step 1: “Yes!” Now that we are at the actual salary level, we can check if the person’s salary is larger than our current
largestSalary
.We write
if salary > largestSalary:
- Step 1: “No!” No more cases to consider!
- Step 2: “No!” No more structures to break down!
- Step 3: We write
largestSalary = salary
, to make note of the new largest salary, as well aslargestSalaryName = name
to keep track of the name.
-
(and then we have the
else:
case, which fortunately turns out to be unnecessary because ifsalary
is not greater thanlargestSalary
, we can ignore it!
The final result is:
largestSalary = 0
largestSalaryName = ''
for name, value in d.items():
salary = value['salary']
if salary > largestSalary:
largestSalary = salary
largestSalaryName = name
To solve the actual assignment question completely, we need to wrap this code into a function and fix one more mistake that has to do with our initial accumulator values.
(Hint: what happens if Gollum has to pay the others to be part of the Fellowship of the Ring?)
I will leave this final part as an exercise for you.