Bonus Material

CS 106

Project Notes

  • Start simple.
    • Get a blank screen first.
    • Don’t build out all of the details yet.
  • Work smart, not hard. If you’re spending an hour just typing stuff into Thonny, you’re doing it wrong.
  • Make connections with your major, life story, friends, …
  • Make it your own. “What if?”, “but when my family plays the game, we ___”

You don’t need to use any particular framework or library from this class. But do it if it saves time.

Comprehensions

A compact replacement for some accumulator patterns.

List Comprehensions

nums = list(range(0, 10))
nums
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Simple list comprehensions

squares_acc = []
for num in nums:
    squares_acc.append(
        num * num
    )
squares_comp = [
    num * num
    for num in nums
]
squares_comp
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Including only certain items

squares_acc = []
for num in nums:
    if num % 2 == 1:
        squares_acc.append(
            num * num
        )
squares_comp = [
    num * num
    for num in nums
    if num % 2 == 1
]
squares_comp
[1, 9, 25, 49, 81]

Dict and Set comprehensions

{word: len(word) for word in ['hello', 'world']}
{'hello': 5, 'world': 5}
{char.lower() for char in 'Hello, world!' if char != ' '}
{'!', ',', 'd', 'e', 'h', 'l', 'o', 'r', 'w'}

Comprehensions Summary

  • “declarative” style: say what you want, not how to make it
    • But no magic; it’s just shorthand for the for loop.
  • A tool in the toolbox
    • especially useful if you want to construct a list to pass on to something else
    • but don’t overuse it.

Comprehensions Exercises

  1. Write the non-comprehension version of the following comprehension:
squares_comp = [num * num for num in nums if num % 2 == 1]
squares_comp
[1, 9, 25, 49, 81]
  1. Write a dict comprehension that gives the indices of each word in a list of words. For example, if words = ['hello', 'world'], the output should be {'hello': 0, 'world': 1}.

  2. Do these:

{word: len(word) for word in ['hello', 'world']}
{char.lower() for char in 'Hello, world!' if char != ' '}
{'!', ',', 'd', 'e', 'h', 'l', 'o', 'r', 'w'}

Some algorithms

Searching algorithms

Suppose we have a sorted list:

breakpoints = [60, 63, 67, 70, 73, 77, 80, 83, 87, 90, 93]
grades =      'F|D-|D|D+|C-|C|C+|B-|B|B+|A-|A'.split('|')
assert len(breakpoints) == len(grades) - 1

We want to find the letter grade for, say, 89. Can we do this faster than searching the whole list?

Exercise: Write the code to search the whole list.

def grade_naive(score):
    grade = grades[-1]
    for i in range(len(breakpoints)):
        if score < breakpoints[i]:
            grade = grades[i]
            break
    return grade
[grade_naive(score) for score in [33, 99, 77, 70, 89, 90, 100]]
['F', 'A', 'C+', 'C-', 'B+', 'A-', 'A']

Binary Search (Bisection) Algorithm

Trick: check the item in the middle element to see whether to look in the left or right half.

from bisect import bisect
def grade_bisect(score):
    """Return the grade corresponding to a score."""
    i = bisect(breakpoints, score)
    return grades[i]

[grade_bisect(score) for score in [33, 99, 77, 70, 89, 90, 100]]
['F', 'A', 'C+', 'C-', 'B+', 'A-', 'A']

Timing comparison

%timeit [grade_naive(score) for score in [33, 99, 77, 70, 89, 90, 100]]
1.52 μs ± 5.23 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%timeit [grade_bisect(score) for score in [33, 99, 77, 70, 89, 90, 100]]
488 ns ± 2.75 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Sorting

import random
words = random.__doc__.split()
'|'.join(words[:20])
'Random|variable|generators.|bytes|-----|uniform|bytes|(values|between|0|and|255)|integers|--------|uniform|within|range|sequences|---------|pick'
'|'.join(sorted(words, key=len)[-10:])
'2**19937-1.|extensively|implemented|threadsafe.|distributions|distributions|distributions|----------------------|------------------------------|---------------------------------------------'
def last_letter(word):
    return word[-1]
'|'.join(sorted(words, key=last_letter)[-10:])
'notes|is|is|generators|is|executes|element|It|most|extensively'

Simulation Stuff

Some material useful for simulations

Serialization (save and load data)

CSV files

  • Pro: tabular data to share with others
  • Cons: can’t store nested data; reader has to guess data types (e.g., Bar Harbor, Maine has ZIP code 4609?)
  • sometimes better to use Excel (XLSX)

JSON (JavaScript Object Notation) files

  • Pros: Used a lot for APIs because…
    • Can store nested objects (e.g., board game states)
    • Looks like normal Python (actually, JavaScript) code
    • Fast; lots of programming languages can work with it
  • Con:
    • few applications can work with them

Pickle

Making random decisions

Suppose you want to add thunderstorms in your population simulation. For simpliciticy, they happen each day with probability p. Remember that random.random() generates a random number between 0 and 1. What fills in the blank?

if ____:
    print("Thunderstorm!")
if random.random() < p:
    print("Thunderstorm!")

What if you have 3 options and you want to choose equally between them?

r = random()
if ___:
    print("A")
elif __:
    print("B")
else:
    print("C")
r = random()
if r < 1/3:
    print("A")
elif r < 2/3:
    print("B")
else:
    print("C")

Cool tools

Course Evals

Do them now. Come get the prof out in the hall when everyone’s done.

Some standard library features

Code you don’t have to write, or even install!

Counting stuff

from collections import Counter
Counter('How many times does each letter appear?')
Counter({' ': 6,
         'e': 6,
         'a': 4,
         't': 3,
         'o': 2,
         'm': 2,
         's': 2,
         'r': 2,
         'p': 2,
         'H': 1,
         'w': 1,
         'n': 1,
         'y': 1,
         'i': 1,
         'd': 1,
         'c': 1,
         'h': 1,
         'l': 1,
         '?': 1})

Regular expressions

import re
ssn_regex = re.compile(r"""
      ^  # match the beginning of the string
      (\d{3}) # match exactly 3 digits
      -       # match one dash
      (\d{2}) # match exactly 2 digits
      -
      (\d{4}) # match exactly 4 digits
      $       # match end of string
    """, re.VERBOSE)

def is_valid_ssn(ssn):
    match = ssn_regex.match(ssn)
    if match:
        print(match.groups())
        return True
    return False
is_valid_ssn("123-45-6789")
('123', '45', '6789')
True
is_valid_ssn("51-345-5212")
False

enumerate

letters = list("ABCDEFG")
for i in range(len(letters)):
    print(i, letters[i])
0 A
1 B
2 C
3 D
4 E
5 F
6 G
letters = list("ABCDEFG")
for i, letter in enumerate(letters):
    print(i, letter)
0 A
1 B
2 C
3 D
4 E
5 F
6 G

Code Structure

Data vs Code

Example: Spelling Alphabet

def spell(letter):
    if letter == "A":
        return "Alfa"
    elif letter == "B":
        return "Bravo"
    elif letter == "C":
        return "Charlie"
    elif letter == "D":
        return "Delta"
    elif letter == "S":
        return "Sierra"
    else:
        return "?"
[spell(letter) for letter in "CS"]
['Charlie', 'Sierra']

Is there a better way?

code_word = {
    "A": "Alfa",
    "B": "Bravo",
    "C": "Charlie",
    "D": "Delta",
    "S": "Sierra",
}
def spell(letter):
    if letter in code_word.keys():
        return code_word[letter]
    return "?"
[spell(letter) for letter in "CS"]
['Charlie', 'Sierra']
[code_word.get(letter, "?") for letter in "CS"]
['Charlie', 'Sierra']

Aside: Multiple Cursors

Live demo. CodeMirror

LLMs

Using large language models

import llm
model = llm.get_model("gemini/gemini-2.5-flash")
response = model.prompt("Replace some words to make the following sentence more absurd: 'The quick brown fox jumps over the lazy dog.'")
print(response.text())
Here are a few options to make it more absurd, playing with different elements:

1.  **The enthusiastic lint roller tap-dances over the napping cumulonimbus cloud.**
2.  **The wobbly purple teapot serenades the perpetually confused jellyfish.**
3.  **The existential quantum foam meditates beneath the bewildered lamppost.**
4.  **The vibrant spoon yodels into the mind of the forgotten comma.**
5.  **The grumpy garden gnome procrastinates adjacent to the sarcastic dandelion.**

To set this up:

  1. Install the llm package (Manage Packages in Thonny, or pip install llm).
  2. Install llm-gemini in the same way.
  3. Get a Gemini API key from https://aistudio.google.com/app/apikey
  4. Save the API key: go in Thonny Settings, General tab, and put in the Environment Variables:
LLM_GEMINI_KEY=

and paste the key in the right-hand side (without quotes). Then restart Thonny.

(alternatively, run llm keys set gemini on a system Terminal.)

Conversation Mode

This way the model can remember the context of the conversation.

import llm
model = llm.get_model("gemini/gemini-2.5-flash")
conversation = model.conversation()
response = conversation.prompt("Five fun facts about the moon.")
print(response.text())
Here are five fun facts about the Moon:

1.  **Footprints Last Forever (Almost):** Because the Moon has no atmosphere, there's no wind or water to erode anything. This means the footprints left by the Apollo astronauts (and their rover tracks!) will likely remain perfectly preserved for millions of years, unless disturbed by a meteorite impact.

2.  **It's Our Solar System's Fifth-Largest Moon:** For a natural satellite, our Moon is surprisingly large. It's the fifth-largest moon in our solar system, even bigger than the dwarf planet Pluto!

3.  **The Moon is "Tidally Locked":** We always see the same side of the Moon because it rotates on its axis at the exact same rate it orbits Earth. This phenomenon is called "tidal locking."

4.  **It's Moving Away From Us:** The Moon is slowly drifting away from Earth at a rate of about 3.8 centimeters (1.5 inches) per year. This means that billions of years ago, it appeared much larger in the sky!

5.  **The Moon Has "Moonquakes":** Just like Earth has earthquakes, the Moon experiences "moonquakes." These are much weaker than Earth's quakes and are thought to be caused by Earth's tidal pull, as well as thermal expansion and contraction, and meteorite impacts.
response = conversation.prompt("Now the sun.")
print(response.text())
Here are five fun facts about the Sun:

1.  **It's a Million-Earth Powerhouse:** You could fit approximately **1.3 million Earths** inside the Sun! It's so massive that it accounts for about 99.8% of the entire mass of our solar system.

2.  **Not "Burning," But Fusing:** The Sun isn't "on fire" in the way wood burns. Instead, it's a giant ball of plasma (superheated, ionized gas) where **nuclear fusion** takes place in its core. Hydrogen atoms are constantly smashing together to form helium, releasing an enormous amount of energy in the process.

3.  **Light Takes Its Time:** While sunlight reaches Earth in about 8 minutes and 20 seconds, the energy generated in the Sun's core takes a much, much longer journey. It can take **hundreds of thousands of years** for a photon of light to battle its way from the core to the Sun's surface before it finally zips towards Earth!

4.  **It Has Its Own Weather System:** The Sun experiences incredible "space weather," including **solar flares** (intense bursts of radiation), **coronal mass ejections** (giant clouds of plasma), and **sunspots** (cooler, darker areas on its surface). These phenomena can sometimes affect technology on Earth.

5.  **The Sun is Middle-Aged:** Our Sun is about **4.6 billion years old**, which makes it a "main-sequence star" in its prime. It has roughly another **5 billion years** of life left before it runs out of hydrogen fuel in its core and begins to swell into a red giant, eventually shedding its outer layers to become a white dwarf.