Problem Solving
Logistics
Reminder re: Final Project Presentations: While I hope to see many of you in person for your final project demos, we will be set up to do final project presentations in hybrid or remote mode. Thus, if you need to leave campus before demos, it’s OK. Don’t feel you need to spend more for airfare or miss family time over winter break because of 0112.
Hours Change Forthcoming: Morgan’s hours are changing. This week she will hold them remotely from 5:30pm to 7:30pm.
Hours Reminder: Alison’s hours are 12pm–2pm Tuesdays.
Homework 2: Homework 2 will be due at 9pm on the 17th.
Lab Feedback: Any feedback on the lab? (Was livecoding the solution effective?)
Problem Solving
Let’s say we have a problem we need to solve, and we want to solve it by writing a program. How should we start?
In an earlier lecture, I recommended writing down what data you have (and what form it’s in), and what you want to produce. From there, you can break down the problem into smaller pieces. If you can solve each subproblem, and then combine them together, you can solve the whole problem.
Some subproblems can be solved by writing helper functions, and we’ve seen a number of examples of this. Other times, subproblems correspond to calls to built-in functions, or particular variables we keep track of. Breaking down problems is a skill you develop over time, and it’s a skill we’ll be practicing a lot in this class.
The most important thing to remember is: even if you aren’t sure how to do something in Python, you can still make progress. Here’s an example.
Example 1: Cast of Characters
Given a string that contains the text of a book, suppose we want to find all the words that begin with a capital letter, then make each word all caps, and then return them all in a set. We can call our function cast_of_chars
, short for “cast of characters”. This isn’t quite the right name for what we’ll get; we’ll see lots of words at the beginning of sentences, too.
Now that we know the type of our function’s input and output, let’s go ahead and write some examples, in test form:
# in lecture06.py:
def cast_of_chars(txt: str) -> set:
pass
# in test_lecture06.py:
def test_cast_of_chars():
assert cast_of_chars("") == set()
assert cast_of_chars("Tim") == {"TIM"}
assert cast_of_chars("'hello,' said TIM") == {"TIM"}
assert cast_of_chars("hello Tim hello Tim hello Annabelle") == {"TIM", "ANNABELLE"}
# This example points out something we might need to _decide on_. Why?
assert cast_of_chars("hello Tim hello TiM hello Annabelle") == {"TIM", "ANNABELLE"}
Ok, now we have a (very) rough understanding of what the function should do. How do we get started? Well, let’s keep following the recipe.
- We know that we’re taking in a single string containing the text we want to process.
- We know we want to return a set of strings, each containing fully capitalized words.
Now, let’s break down the task into a bunch of little pieces. What might we need to do?
Think, then click!
In class, some thoughts included:
- splitting the input into a list of separate words;
- removing punctuation from the words (not something I started with, but someone asked!);
- filtering out uncapitalized words; and
- fully capitalizing each word remaining.
Note that these suggest a bit of structure: one naturally flows into another, and so on. This won’t always be the case, but it certainly helps here! We can write a skeleton for the function. Maybe we don’t yet know how to actually do all the subtasks, but we can give the result of each a name and record which uses which.
Think, then click!
# in wordcount_test.py:
def cast_of_chars(s: str):
words = [] # ??? should use s
caps_words = set() # ??? should use words
cleaned = set() # ??? should use caps_words
all_caps = set() # ??? should use cleaned
return all_caps
Do we know how to split a string up by blank space? Yes: use split()
! Also, do we have an idea how we might pass over a list, looking for capitalized words? Yes: use a loop! Even if we don’t know what to do in that loop, or how exactly to detect capitalization, we can fill it in. And so on. (Remember to look for the # ???
comments I’ve left to remind myself to fill in a hole later.)
Let’s run another exercise where you help me figure out where different potential Python lines go. We’ll proceed in a couple stages, however. Starting from the last partial program, let’s add some of that structure. These lines aren’t the only lines we’ll need, so just focus on putting them into the stencil roughly where they need to go, and add some # ???
comments around where you think there are still holes in the program. This will give you practice structuring a function even if you don’t know how to write some of it.
caps_words.add(word)
cleaned.add(word)
for word in cleaned:
words = s.split()
for word in caps_words:
for word in words:
all_caps.add(word)
We might end up somewhere like this, where I’ve completely omitted all the filtering and capitalization:
Think, then click!
# in wordcount_test.py:
def cast_of_chars(s: str):
words = s.split() # split by blank space
caps_words = set()
for word in words:
caps_words.add(word) # ??? add only if capitalized
cleaned = set()
for word in caps_words:
cleaned.add(word) # ??? add version without punctuation
all_caps = set()
for word in cleaned:
all_caps.add(word) # ??? add capitalized version
return all_caps
Notice that the shape of the function is beginning to appear, even if we aren’t sure how to do the smaller pieces yet. Instead of trying to write the entire function at once, I’ve got incomplete functionality, like just adding a word instead of cleaning it or capitalizing it first. Gradually, we can fill these in, too.
Think, then click!
# in wordcount_test.py:
def cast_of_chars(s: str):
words = s.split() # split by blank space
caps_words = set()
for word in words:
if word[0].isupper(): # check first letter
caps_words.add(word)
cleaned = set()
for word in caps_words:
cleaned.add(word.replace(",", "")) # remove commas
all_caps = set()
for word in cleaned:
all_caps.add(word.upper()) # fully uppercase
return all_caps
Breaking problems down like this is useful, even if you have decades of programming experience. If you watch an long-time expert programmer working, it might appear like they aren’t following this process, but they’ve likely internalized it so well that it’s hard to see. If you’re just starting to learn to play piano, would you judge your progress against someone with decades of full-time experience? I hope not!
Introducing Comprehensions
I want to very quickly cover a new kind of Python syntax. Think of it as a convenient and concise alternative to for
loops. You aren’t required to use these, but you’ll see them used some in my lecture materials, and you’ll definitely see them if you ever work in Python professionally.
Suppose we’d written this function:
def cast_of_chars(txt: str) -> set:
words = txt.split()
s = set()
for word in words:
if word[0].isupper():
s.add(word.upper())
return s
Python gives us another way to write the same thing. Comprehensions let us build sets, dictionaries, or lists. Using a comprehension, we could have written:
def cast_of_chars(txt: string) -> set:
words = txt.split()
s = {word.upper() for word in words if word[0].isupper()}
return s
List Comprehensions
I’ll introduce comprehensions with the list version, because the syntax is largely the same for sets and dictionaries.
The most basic comprehension looks like this:
[x for x in my_list]
This loops over my_list
and creates a list of every element. It makes a list because we used square brackets; using squiggly braces would make a set or dictionary.
We could insert something more complicated than just x
into the resulting list by writing that expression instead of x
:
[x + 1 for x in my_list]
And we can add a conditional that filters which values will actually get added to the new list:
[x + 1 for x in my_list if x > 4]
Note that my_list
can be anything iterable: a list, a set, a dictionary, and so on. So I could write something like this:
[k for k in {'a':1, 'b':2}]
which produces:
['a', 'b']
This means that comprehensions are great ways to convert between lists, sets, etc. when you don’t want to just copy all the contents of the original verbatim.
Set Comprehensions
In the same way we can build lists with a comprehension, we can build a set—just by using squiggle braces instead of square brackets:
{x + 1 for x in [3,5,7] if x > 4}
produces:
{8, 6}
Dictionary Comprehensions
We can also build dictionaries with comprehensions:
{x: x + 1 for x in [3,5,7] if x > 4}
which gives us:
{5: 6, 7: 8}
Again, you don’t have to use comprehensions in your own codt, but you can if you want. I’ll be using them sometimes, so I wanted to introduce them now.