Data Science and Advanced Programming · Session 3

Session 3: the Wooclap questions

The reading questions from class, to redo at home.

Answer each question on paper first, without running anything. Then open the answer. If you got it wrong, run the code in your workspace and find the line where your prediction and Python part ways.

Warm-up

Warm-up 1

A price arrives from a file as text. What does the program print?

price = "190.5"      # read from a file
shares = 3
value = shares * price
print(value)
  1. 190.5190.5190.5
  2. TypeError on line 3
  3. 190.5
  4. 571.5
Show the answer

190.5190.5190.5

int times str is allowed: it repeats the text. No error, wrong number: runs and lies.

Warm-up 2

One line of a ledger file is cut at the commas. What does the program print?

line = "AAPL,10,190.5"
parts = line.split(",")
print(parts[1] * 2, len(parts))
  1. 1010 13
  2. 20 3
  3. 1010 3
  4. 20 13
Show the answer

1010 3

.split() gives a list of TEXT pieces. '10' * 2 repeats the text.

Warm-up 3

We add up two prices read from a file, in a loop. What happens?

total = 0
for p in ["190.5", "200.0"]:
    total = total + p
print(total)
  1. 390.5
  2. TypeError on line 3
  3. 190.5200.0
  4. 0190.5200.0
Show the answer

TypeError on line 3

int + str: Python refuses. Convert inside the loop: total = total + float(p).

Warm-up 4

Prices read from a file are still text. What do max() and sorted() give?

prices = ["190.5", "20.0", "3.75"]   # read from a file
print(max(prices), sorted(prices)[0])
  1. 190.5 3.75
  2. 190.5 20.0
  3. 20.0 3.75
  4. 3.75 190.5
Show the answer

3.75 190.5

Text compares letter by letter, like a dictionary: '3' > '2' > '1'. Convert to float before max or sorted.

Warm-up 5

The same * on a list, a string, an int and a numpy array. What does the program print?

import numpy as np
a = [1, 2]
s = "12"
arr = np.array([1, 2])
print(a * 2, s * 2, 12 * 2, arr * 2)
  1. [2, 4] 24 24 [2 4]
  2. [1, 2, 1, 2] 1212 24 [1 2 1 2]
  3. [1, 2, 1, 2] 1212 24 [2 4]
  4. [2, 4] 1212 24 [2 4]
Show the answer

[1, 2, 1, 2] 1212 24 [2 4]

Same *, four types, four operations: list and str repeat, int multiplies, a numpy array multiplies every element.

Warm-up 6

A dict with the days 1 and 2 as keys. We look up day 1 twice. What happens?

by_day = {1: 190.5, 2: 191.0}
print(by_day[1], by_day["1"])
  1. 190.5 None
  2. KeyError on line 2
  3. 190.5 KeyError
  4. 190.5 190.5
Show the answer

KeyError on line 2

The key 1 (int) and the key '1' (text) are different keys. Nothing prints: the whole print fails before it writes anything.

Warm-up 7

Each price gets a label with if / elif / else. What is in the list at the end?

labels = []
for p in [95, 105, 150]:
    if p > 100:
        labels.append("high")
    elif p > 140:
        labels.append("very high")
    else:
        labels.append("low")
print(labels)
  1. ['low', 'high', 'high']
  2. ['high', 'high', 'high']
  3. ['low', 'high', 'very high']
  4. ['low', 'very high', 'very high']
Show the answer

['low', 'high', 'high']

The first true condition wins. 150 > 100 is already true, so the elif is never checked. Put the stricter test first.

Warm-up 8

We sort the closes and keep what .sort() gives back. What does the program print?

closes = [191.4, 185.1, 198.6]
result = closes.sort()
print(closes, result)
  1. [191.4, 185.1, 198.6] [185.1, 191.4, 198.6]
  2. [185.1, 191.4, 198.6] None
  3. [191.4, 185.1, 198.6] None
  4. [185.1, 191.4, 198.6] [185.1, 191.4, 198.6]
Show the answer

[185.1, 191.4, 198.6] None

.sort() changes the list itself and returns None. sorted(closes) would return a new list and leave closes alone.

Warm-up 9

We loop over the holdings and keep the tickers with at least 4 shares. What does the program print?

holdings = {"AAPL": 10, "NESN": 4, "VOO": 2}
big = []
for ticker, n in holdings.items():
    if n >= 4:
        big.append(ticker)
print(big, len(holdings))
  1. ['AAPL', 'NESN'] 3
  2. ['AAPL', 'NESN'] 16
  3. ['AAPL'] 3
  4. [10, 4] 3
Show the answer

['AAPL', 'NESN'] 3

.items() gives (key, value) pairs. >= 4 includes 4. len() of a dict counts the keys.

Warm-up 10

We look up a ticker with .get() and test two things with in. What does the program print?

holdings = {"AAPL": 10, "NESN": 5}
print(holdings.get("aapl"), "NESN" in holdings, 5 in holdings)
  1. None True False
  2. None True True
  3. KeyError on line 2
  4. 10 True True
Show the answer

None True False

Keys are case-sensitive; .get() gives None for a missing key. 'in' looks at the keys, never at the values.

Warm-up 11

1000 CHF grows by 5 % a year until it is at least 1100. What does the program print?

cash = 1000
years = 0
while cash < 1100:
    cash = cash * 1.05
    years += 1
print(years, round(cash, 2))
  1. 2 1102.5
  2. 3 1157.63
  3. 2 1100.0
  4. 1 1050.0
Show the answer

2 1102.5

After one year 1050 < 1100, so the loop runs again. It stops at the first value that is NOT below 1100.

Warm-up 12

We cut a ledger line at the commas, give the pieces names, and multiply. What happens?

line = "AAPL,10,190.5"
ticker, shares, price = line.split(",")
value = int(shares) * int(price)
print(ticker, value)
  1. ValueError on line 3
  2. TypeError on line 2
  3. AAPL 1900
  4. AAPL 1905.0
Show the answer

ValueError on line 3

int() of a text accepts whole numbers only: int('190.5') fails. Use float(price).

Reading tasks

Every task starts from this ledger:

tickers = ["AAPL", "NESN", "VOO"]
shares = [10, 4, 2]
price = {"AAPL": 190.5, "NESN": 102.3, "VOO": 512.0}
closes = [190.5, 187.2, 193.8, 185.1, 191.4, 198.6, 194.0]   # AAPL, day 0 to day 6

Task 1 · slices

On paper, predict what each line prints. Then run and compare. Remember: positions start at 0, the stop is not included, and a slice never crashes.

print(closes[1:4])
print(closes[::3])
print(closes[::-1][:2])
print(closes[5:100])
print(tickers[3:])

What is closes[::-1][:2]?

  1. [190.5, 187.2]
  2. [187.2, 190.5]
  3. [198.6, 194.0]
  4. [194.0, 198.6]
Show the answer

[194.0, 198.6]

[1:4] is positions 1, 2, 3. [::3] takes every third item from the start. [::-1] reverses, then [:2] keeps the first two of the reversed list: the last two days, newest first. [5:100] stops at the end instead of crashing. tickers[3:] is empty, while tickers[3] would be an IndexError.

Task 2 · a copy, or a second name?

Predict both lines. Then say in one sentence what backup = shares creates, and what shares.copy() creates.

backup = shares
backup.append(5)
print(shares)

shares = [10, 4, 2]
backup = shares.copy()
backup[1] = 10
print(shares, backup)

What does Task 2a print?

  1. [10, 4, 2]
  2. [5]
  3. an error
  4. [10, 4, 2, 5]
Show the answer

[10, 4, 2, 5]

backup = shares puts a second name on the SAME list, so append through one name shows through the other. shares.copy() builds a NEW list; changing the copy leaves the original alone. Numbers and text never surprise you like this, because they cannot change; lists and dicts can.

Task 3 · the zip that lies

The total of the ledger is 3338.2. Someone wanted the positions "in order" and put sorted(shares) inside the zip. It runs, and prints a total that looks like money. First fill in an iteration table ON PAPER (ticker, shares, value, running total), then explain what went wrong and fix it.

total = 0.0
for t, n in zip(tickers, sorted(shares)):
    total += n * price[t]
print(total)

What total does the buggy loop print?

  1. 3338.2
  2. 1024.0
  3. 5910.2
  4. 8048.0
Show the answer

5910.2

The buggy version prints 5910.2. sorted(shares) is [2, 4, 10], and zip pairs by position, so AAPL gets 2 shares and VOO gets 10. Two lists that describe the same rows must stay in the same order. Rough check: VOO alone (5120.0) cannot be worth more than the whole ledger.

Task 4 · the boundary

The broker's rule: "positions of 1000 CHF or more pay a 0.1 % fee". A position of 8 shares at 125.00 is exactly 1000.00 CHF. The code says it pays nothing. Who is right? Fix the code.

value = 8 * 125.0
if value > 1000:
    fee = value * 0.001
else:
    fee = 0.0
print(value, fee)

What should the fee be for exactly 1000.00 CHF?

  1. 0.0
  2. 0.1
  3. 10.0
  4. 1.0
Show the answer

1.0

"Or more" means >=. With > the boundary case pays nothing: no error, a wrong number, and only one value in the whole world triggers it. The code does not decide which is right; the rule written down does.

Task 5 · a price that is not there

MSFT is not in the price dict. Predict what happens if you remove the # in front of the print line and run. Then put the # back, so the rest of the file still runs.

# print(price["MSFT"])
print("Task 5: done")

What does price["MSFT"] do?

  1. It gives None
  2. It gives 0.0
  3. It stops with KeyError: 'MSFT'
  4. It stops with NameError: MSFT
Show the answer

It stops with KeyError: 'MSFT'

price["MSFT"] stops the program with KeyError: 'MSFT'. A dict only answers for keys it has. in asks first; .get() answers None (or a default you give) instead of crashing. Task 9 uses this.

Self-check (home questionnaire)

Check 1

What does this print?

p = [190.5, 187.2, 193.8, 185.1]; print(p[1:3], p[-1])
  1. [187.2, 193.8, 185.1] 190.5
  2. [187.2, 193.8] 185.1
  3. [190.5, 187.2, 193.8] 185.1
  4. [187.2] 185.1
Show the answer

[187.2, 193.8] 185.1

Check 2

What does this print?

print(list(range(2, 11, 3)))
  1. [2, 5, 8]
  2. [2, 5, 8, 11]
  3. [2, 3, 4, 5, 6, 7, 8, 9, 10]
  4. [3, 6, 9]
Show the answer

[2, 5, 8]

Check 3

What does this print?

a = [3, 1, 2]; b = a; b.append(0); print(len(a))
  1. 0
  2. 1
  3. 3
  4. 4
Show the answer

4

Check 4

What does this print?

a = [3, 1, 2]; print(sorted(a), a)
  1. [1, 2, 3] [3, 1, 2]
  2. [1, 2, 3] [1, 2, 3]
  3. None [1, 2, 3]
  4. [3, 2, 1] [3, 1, 2]
Show the answer

[1, 2, 3] [3, 1, 2]

Check 5

What does this print?

total = 0
for x in [4, 5, 6]:
    total = x
print(total)
  1. 6
  2. 4
  3. 15
  4. 0
Show the answer

6

Check 6

What does this print?

value = 500
if value >= 1500:
    print("large")
elif value >= 500:
    print("medium")
else:
    print("small")
  1. medium
  2. small
  3. medium and small
  4. large
Show the answer

medium

Check 7

What does this print?

d = {'AAPL': 10}; print(d.get('MSFT', 0), 'MSFT' in d)
  1. 0 False
  2. None False
  3. KeyError
  4. 0 True
Show the answer

0 False

Check 8

What does this print?

print("NESN.SW".split(".")[1][::-1])
  1. SW
  2. ['WS']
  3. WS
  4. NSEN
Show the answer

WS

Check 9

What does this print?

n = 1000
years = 0
while n < 2000:
    n = n * 1.1
    years += 1
print(years)
  1. 7
  2. 8
  3. 9
  4. 10
Show the answer

8

Check 10

What does this print?

print([1, 2] + [3], [1, 2] * 2)
  1. [1, 2, 3] [2, 4]
  2. [4, 5] [2, 4]
  3. [1, 2, 3] [1, 2, 1, 2]
  4. TypeError
Show the answer

[1, 2, 3] [1, 2, 1, 2]