3.15 Random Values

Source lesson: python/random

Learning Objective: Generate random values in Python, determine every possible result, and use randomness safely in backend-style testing.

Submission notes

  • Lesson: CSP 3.15 Random Values
  • Notebook: https://jupiterian.opencodingsociety.com/python/random-hw
  • MCQ 3.15: 4/4 answers: C,B,B,B
  • Popcorn Hacks 1–3 completed during class and copied to notebook: yes
  • Homework tested: 5 random runs, 12 random runs, plus a fixed script that forces every action and the duplicate case

Popcorn Hack 1: Random SFI Record Number

Prediction: each draw is 1, 2, 3 or 4. I can’t predict which, but it will never be 0 or 5. The 20 draws below should include repeats, and probably all four values.

Code Runner Challenge

Popcorn Hack 1 - Random SFI Record Number

View IPYNB Source
# CODE_RUNNER: Popcorn Hack 1 - Random SFI Record Number
import random

record_count = 4

# randint includes BOTH endpoints, so 1..record_count covers every record and nothing else
record_number = random.randint(1, record_count)
print("SFI record selected for QA:", record_number)

# Run it 20 times to see the spread and the repeats
draws = []
for i in range(20):
    draws.append(random.randint(1, record_count))
print("20 draws:", draws)

all_valid = True
for d in draws:
    if d < 1 or d > record_count:
        all_valid = False
print("Every draw valid (1-4):", all_valid)

# Changed input: 6 records
record_count = 6
print("With 6 records:", random.randint(1, record_count))
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Possible outputs: exactly {1, 2, 3, 4}, because randint(1, 4) includes both 1 and 4. randint(0, 4) would allow an invalid record 0, and randint(1, 3) would make record 4 unreachable.

Why repeats are allowed: each call is independent. The generator doesn’t remember earlier picks, so getting record 2 twice in a row is normal. It isn’t cycling through the values. Seeing only some numbers in a short run doesn’t mean the others are impossible.

Popcorn Hack 2: Random Structured SFI Car Part

Prediction: one of the three records prints, and its name, category and spec number always belong together (for example spec 1.2 always shows with Multiple Disc Clutch / Drag Racing).

Code Runner Challenge

Popcorn Hack 2 - Random SFI Part Record

View IPYNB Source
# CODE_RUNNER: Popcorn Hack 2 - Random SFI Part Record
import random

parts = [
    {"product_name": "Replacement Flywheels and Clutch Assemblies", "category": "Auto Racing", "spec_number": "1.1"},
    {"product_name": "Multiple Disc Clutch Assemblies", "category": "Drag Racing", "spec_number": "1.2"},
    {"product_name": "Racing Flywheel Record", "category": "Auto Racing", "spec_number": "2.1"}
]

# choose ONE whole record so its fields stay together
selected = random.choice(parts)
print("Product:", selected["product_name"])
print("Category:", selected["category"])
print("Spec number:", selected["spec_number"])

# Changed input: add a fourth record and draw 3 more times
parts.append({"product_name": "Harmonic Balancer", "category": "Drag Racing", "spec_number": "18.1"})
for i in range(3):
    r = random.choice(parts)
    print("Draw", i + 1, "->", r["spec_number"], "|", r["product_name"], "|", r["category"])
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Choosing the whole dictionary keeps the record intact. Picking a random name, a random category and a random spec separately could produce a record that doesn’t exist (e.g. “Racing Flywheel Record / Drag Racing / 1.2”). After appending a fourth record, random.choice automatically includes it without any range to update.

Popcorn Hack 3: Random SFI API Test

Prediction (two possible outcomes): if it picks GET/search with spec 1.2, it prints a search for spec 1.2. If it picks POST/create with spec 1.1, it prints a create-validation message for that part. There are 3 parts × 3 actions = 9 possible pairs.

Code Runner Challenge

Popcorn Hack 3 - Random SFI API Test

View IPYNB Source
# CODE_RUNNER: Popcorn Hack 3 - Random SFI API Test
import random

parts = [
    {"product_name": "Replacement Flywheels", "spec_number": "1.1"},
    {"product_name": "Multiple Disc Clutch Assemblies", "spec_number": "1.2"},
    {"product_name": "Racing Flywheel Record", "spec_number": "2.1"}
]
actions = ["GET/search", "POST/create", "PUT/update"]

for run in range(1, 5):
    part = random.choice(parts)
    action = random.choice(actions)
    print("Run", run, "|", action, "|", part["product_name"], "(spec " + part["spec_number"] + ")")
    if action == "GET/search":
        print("   200 OK: searching records for spec", part["spec_number"])
    elif action == "POST/create":
        print("   Validating", part["product_name"], "before creating it")
    else:
        print("   Updating fields on spec", part["spec_number"])
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Each run picks the part and the action independently, and the if / elif / else sends each action to its own response, so every action has a path. Re-running changes which pairs show up, and repeats are normal.

MCQ

**MCQ 3.15: 4/4 answers: C,B,B,B**
  1. What can random.randint(2, 5) return? A 2,3,4 only · B 3,4,5 only · C 2,3,4,5 · D any decimal from 2 to 5. C, both endpoints are included.
  2. Record A appears twice in a row. What does that mean? A the code is broken · B repeats are allowed in random selection · C A is always most likely · D all three must appear next. B
  3. Which Python matches RANDOM(1,4)? A random.choice([1,2,3]) · B random.randint(1,4) · C random.randint(0,4) · D random.random(1,4). B
  4. Why is testing a random program once insufficient? A random code never runs twice · B one run may show only one of many possible outcomes · C Python changes the source · D random values can’t be printed. B

Homework Hack: SFI Backend QA Simulator

The simulator keeps a small backend state (existing_spec_numbers), so actions affect each other the way a real API would:

Action Spec already stored Spec not stored
GET/search 200 found 404 not found
POST/create 409 duplicate, rejected 201 created, spec added
PUT/update 200 updated 404 can’t update a missing record
DELETE/remove 200 removed, spec deleted 404 nothing to remove

run_qa(test_count) is reusable: it picks a random whole record and a random action each run, then prints the record, the action and the result. It also checks that the backend never ends up holding a spec number twice.

Possible pairs per run: 3 records × 4 actions = 12.

Code Runner Challenge

Homework Hack - SFI Backend QA Simulator

View IPYNB Source
# CODE_RUNNER: Homework Hack - SFI Backend QA Simulator
import random

parts = [
    {"product_name": "Replacement Flywheels", "category": "Auto Racing", "spec_number": "1.1"},
    {"product_name": "Multiple Disc Clutch Assemblies", "category": "Drag Racing", "spec_number": "1.2"},
    {"product_name": "Racing Flywheel Record", "category": "Auto Racing", "spec_number": "2.1"}
]
actions = ["GET/search", "POST/create", "PUT/update", "DELETE/remove"]
existing_spec_numbers = ["1.1", "2.1"]
test_count = 5

def handle_request(action, part, stored_specs):
    """Apply one backend action to stored_specs and return a status message."""
    spec = part["spec_number"]
    exists = spec in stored_specs
    if action == "GET/search":
        if exists:
            return "200 OK - found spec " + spec
        return "404 Not Found - no record with spec " + spec
    elif action == "POST/create":
        if exists:
            return "409 Conflict - duplicate spec " + spec + ", create rejected"
        stored_specs.append(spec)
        return "201 Created - spec " + spec + " added"
    elif action == "PUT/update":
        if exists:
            return "200 OK - spec " + spec + " updated"
        return "404 Not Found - cannot update missing spec " + spec
    else:  # DELETE/remove
        if exists:
            stored_specs.remove(spec)
            return "200 OK - spec " + spec + " removed"
        return "404 Not Found - nothing to remove for spec " + spec

def run_qa(test_count, stored_specs):
    """Run test_count random record/action tests against a copy of stored_specs."""
    state = stored_specs.copy()          # don't change the caller's list
    actions_seen = []
    print("Starting specs:", state)
    for run in range(1, test_count + 1):
        part = random.choice(parts)       # whole record, fields stay together
        action = random.choice(actions)
        result = handle_request(action, part, state)
        if action not in actions_seen:
            actions_seen.append(action)
        print(f"Run {run}: {action:<14} | {part['product_name']} ({part['category']}, spec {part['spec_number']}) -> {result}")

    # Invariant: the backend never stores the same spec twice
    no_duplicates = len(state) == len(set(state))
    print("Ending specs:", state, "| no duplicate specs:", no_duplicates)
    print("Actions exercised:", len(actions_seen), "of", len(actions))
    return state

print("=== Random QA: 5 runs ===")
run_qa(test_count, existing_spec_numbers)
print()
print("=== Random QA: 12 runs ===")
run_qa(12, existing_spec_numbers)
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Random runs can miss a path (a 5-run test might never pick DELETE), so this fixed script forces every action and both sides of the duplicate check. It reuses handle_request from the cell above.

Prediction: POST 1.1 → 409 duplicate. POST 1.2 → 201 created. POST 1.2 again → 409 (now a duplicate). GET 1.2 → 200. DELETE 2.1 → 200. GET 2.1 → 404. PUT 2.1 → 404. DELETE 2.1 → 404. Final specs: [‘1.1’, ‘1.2’].

Code Runner Challenge

Homework Hack - forced test of every action and the duplicate rule

View IPYNB Source
# CODE_RUNNER: Homework Hack - forced test of every action and the duplicate rule
state = ["1.1", "2.1"]
flywheel, clutch, racing = parts[0], parts[1], parts[2]

script = [
    ("POST/create", flywheel),    # already stored -> duplicate
    ("POST/create", clutch),      # new -> created
    ("POST/create", clutch),      # now stored -> duplicate
    ("GET/search", clutch),       # found
    ("DELETE/remove", racing),    # removed
    ("GET/search", racing),       # not found anymore
    ("PUT/update", racing),       # can't update missing
    ("DELETE/remove", racing),    # nothing to remove
]
for action, part in script:
    print(f"{action:<14} spec {part['spec_number']} -> {handle_request(action, part, state)}")
print("Final specs:", state)
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Test evidence: the forced script matches every prediction, including the same POST being accepted once and then rejected as a duplicate. The random runs differ each time I run the notebook, as they should. In every run each line shows a whole record, a valid action and a status that matches the backend state at that moment, and “no duplicate specs” is always True.

Pseudocode check: with recordIndex ← 2 and actionNumber ← 2, the lesson’s pseudocode selects parts[2] (Multiple Disc Clutch, 1-indexed) and displays POST/create. In Python that’s parts[1] with "POST/create", which handle_request turns into “201 Created” the first time and “409 Conflict” after that.