Skip to content

Week 3: arithmetic, f-strings

Last week every exercise was the same shape: read some input, convert it, calculate a result, print it. That shape does not change this week, but three things inside it do. The arithmetic gains three operators, ** and // and %. Strings stop being text you can only print and become something you can index into and repeat. And the printing moves from print() with commas to f-strings, which is how this course formats output from here to the end of the semester.

Save each program in the Week03 folder inside ~/CS149, and commit and push your work when you finish. The exercises are ordered from easiest to hardest, and each one has an assignment on Gradescope so you can check your own work as you go.

Style counts from this week on

You installed ruff in the style lab at the end of last week, which means every autograder on this page now runs ruff check on your program as one of its tests. A program that produces perfect output and has a style violation in it does not earn full marks any more. Run ruff check on each program yourself before you submit, and fix what it reports by hand rather than by running ruff format.

Two of the rules catch people out at this point in the semester. Every program needs a module docstring as its first statement, holding your name and the date. And no line may be longer than 100 characters, which is a real constraint once f-strings start carrying whole sentences.

Ex3.1 Say My Name

The * operator does what you expect to two numbers:

>>> 3 * 10
30

It does something else entirely to a string and a number:

>>> "3" * 10
'3333333333'

That is repetition: ten concatenated copies of the string "3". Multiplying a string by a string is not a thing anyone can do, and Python says so:

>>> "3" * "10"
TypeError: can't multiply sequence by non-int of type 'str'

Write a program named say_my_name.py that reads a name and a number, then prints that many copies of the name. Use exactly the prompts "Name: " and "Number: ".

Name: Madison
Number: 3
Your name is: MadisonMadisonMadison

Your program must use two print statements: one to print Your name is:, and one to print the repeated name. Run it a second time with Duke and 5.

You already know the setting this needs

The label and the repeated name are on the same line, produced by two separate print statements. One of the two settings you met in last week's lab is what keeps the first print from ending its line.

The number needs converting, for the same reason everything needed converting last week. Repetition wants a string and an integer, and input() never hands you one of those.

Ex3.2 Powers of Two

Last week's final exercise went from a number of values to the number of bits it takes to store them, using a logarithm and a ceiling. This one goes the other way, which turns out to be much easier: \(n\) bits can represent \(2^n\) different values.

Write a program named powers.py that reads a number of bits and reports how many values that many bits can represent. Use exactly the prompt "How many bits? ".

How many bits? 10
10 bits can represent 1,024 different values

The comma in 1,024 is required, and you do not put it there yourself. Run your program again with 3, which reports 8 different values with no comma in it at all, and then with 20, which reports 1,048,576.

One specifier does all three

Python has an operator for exponents, so this needs no import and no repeated multiplication.

The thousands separators come from a format specifier inside the f-string, and it is a single character. The same specifier produces 8 for eight and 1,048,576 for a million, which is exactly why you want it doing the job instead of you.

Ex3.3 Baking Cookies

Your family is baking cookies. Each member of the family gets the same number of cookies, as many as possible, and the extras go to Rocky, your pet raccoon. Bake 32 cookies for a family of 5, and each person gets 6 while Rocky gets the 2 left over.

Write a program named cookies.py that reads the total number of cookies and the number of family members, not counting Rocky, and reports how many each of them gets.

Enter the number of cookies: 32
Enter the number of family members: 5

Each family member gets 6 cookies, and Rocky gets 2 cookies.

Notice the blank line between the last prompt and the output. Run your program again with 100 cookies and 7 family members, which gives each person 14 and Rocky 2, and then with 12 cookies and 4 family members, where Rocky goes hungry.

Two answers from one division

32 / 5 is 6.4, and 6.4 answers neither question: nobody gets four tenths of a cookie and Rocky does not get four tenths of anything either. The two operators you need this week split that one division into the two answers you actually want, the whole part and what is left over. Work out which is which, and notice that both of them give you an int when you start from two integers.

Ex3.4 Monograms

A monogram is a graphic symbol made from two or more letters, usually somebody's initials, printed on stationery or embroidered on clothing.

The American Sweater Company of Iona, Idaho (ASCII) makes sweaters with monograms on them, and management would like an easier way to work out a monogram from a customer's name.

Write a program named monogram.py that reads a customer's first, middle, and last names and reports the monogram. Use exactly the prompts "First name? ", "Middle name? ", and "Last name? ".

First name? Alfred
Middle name? Edward
Last name? Neuman

The customer Alfred Edward Neuman ordered a sweater with the monogram AEN.

Your program must use exactly one print statement, which means the blank line has to come from inside it. Run it a second time with Madison, Reese, and Carter, which should report the monogram MRC.

Two things one print statement needs

Just as \t inside a string means a tab, \n means a newline, so one string can begin with a blank line.

Building the three initials directly inside the f-string will push the line past 100 characters, and ruff will tell you so. Build the monogram into a variable of its own first, using the other string operator this week introduces, and then interpolate that one variable. This is the ordinary reason to name an intermediate value: not because Python needs it, but because the line was getting too long to read.

Ex3.5 It Runs, and It Is Wrong

Last week's repair job announced itself. The program refused to start, and once it started it crashed, and the error messages told you roughly where to look.

This one is worse. Below is a program that computes the average of three test scores and how many points were lost out of 300. It has four problems in it, and not one of them stops the program from running. It starts, it finishes, it prints two confident-looking lines, and both of them are wrong.

Save it into your Week03 folder as grades.py, exactly as it appears here.

"""HW3.5 Grade Report.

Name: (your name here)
Date: (today's date)
"""

first = int(input("Enter the first score: "))
second = int(input("Enter the second score: "))
third = int(input("Enter the third score: "))

average = first + second + third / 3
points_lost=300 // (first + second + third)
print (f"Average: {average:.1f}")
print(f"Points lost: {points_lost}")

Two of the four problems are style violations, and ruff check grades.py will name both of them; they are two you already fixed once in payroll.py during the style lab. The other two are wrong arithmetic, and nothing will tell you about those, because Python and ruff are both perfectly happy with a program that computes the wrong number.

Before you change anything, run it with the scores 80, 90, and 100, and read the two lines it prints. Write down all four problems and decide which kind each one is. Then fix them.

Enter the first score: 80
Enter the second score: 90
Enter the third score: 100
Average: 90.0
Points lost: 30

Run it a second time with 70, 75, and 80, which should report an average of 75.0 and 75 points lost. When you are finished, ruff check grades.py should report nothing at all.

The average of 80, 90, and 100 is not 203.3

You do not need to find the bug to know it is there. Three scores between 0 and 100 cannot average out above 100, and that alone tells you the first line is wrong before you have looked at a single operator.

This is the habit worth taking from this exercise. A calculation you cannot check is a calculation you have to trust, so pick inputs whose answer you already know, and glance at the output to see whether it is even the right size. Both of this program's arithmetic problems are visible that way in under a second.

One wrong operator, two different failures

Once you have found the second wrong operator, try running the original broken version with the scores 0, 0, and 0. It crashes, and the error tells you which operator was wrong.

Then check that your repaired version handles 0, 0, and 0 without complaint. The wrong operator produced a wrong answer for most inputs and an outright error for one of them, which is a useful thing to have seen: the same mistake can show up as either, depending on what you feed it.

Ex3.6 Three Digits

Write a program named digits.py that reads a three-digit number and takes it apart.

Enter a three-digit number: 472
Hundreds: 4
Tens: 7
Ones: 2
Sum: 13
Reversed: 274

You may assume the user enters exactly three digits. Getting the hundreds digit and the ones digit is one operator each; the tens digit in the middle is the interesting one, and it takes two. Run your program again with 105, which reverses to 501.

Then run it with 900

Your program reports Reversed: 9, and that is the right answer. A leading zero is not part of a number's value any more than the trailing zero you went looking for in Week 2, so 009 is a way of writing a number rather than a number you can arrive at. Nothing needs fixing here; noticing it is the point.

An extra challenge. Everything above treats the input as a number and takes it apart with arithmetic. You could instead treat it as a string and take it apart by indexing, since a three-digit number typed at the keyboard is already three characters sitting next to each other.

Try it, in a second file named digits2.py, and produce the same five lines of output. There is nothing to submit for this one; the point is what you run into on the way. Two things are worth watching for:

  • The three digits come back from indexing as str values rather than numbers, so Sum: will need something doing to them first.
  • Build the reversed number by concatenating the three characters in the other order, and the two programs will agree on 472 and then disagree on 900.

Work out what each version prints for 900 and why, and then decide which one is correct. They both are. They are answering slightly different questions, and knowing which question you asked is most of the difficulty of the whole week.

Extra Challenges

The six exercises above are the ones with Gradescope assignments, and they are the ones that count. These two have nothing to submit. They are here because each one needs a format specifier that none of the six asks for, so they are the fastest way to find out whether you can reach for the right one without being told.

  • Tip calculator. Read a bill total and a tip percentage, then print the tip and the new total, both to the cent and lined up in a column so the decimal points sit under each other. Getting the two numbers right is the easy half. Getting the two lines to line up is what a minimum-width specifier is for.
  • Test score. Read how many questions were on a test and how many you got right, then print the score as a percentage with one decimal place. Use the percent specifier, and do not multiply by 100 anywhere in your program.

The f-string lab has the table of specifiers to work from.

When you're done

Commit and push your Week03 folder:

git add -A
git commit -m "Add week 3 practice programs"
git push

Then do one more pass over what you wrote, with Quiz 1 in mind. Take each program, cover the output, and work out on paper what it will print for an input you have not tried yet. Then break each one deliberately: delete an int(), index one character past the end of a name, divide by a value you know is zero. Name the error before you run it, then run it and see whether you were right. Being able to say which kind of thing went wrong without running the program is exactly what the quiz asks you to do, and these six programs are the closest thing you have to practice material for it.