3.15 Random Values HW
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))
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"])
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"])
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** |
- 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. - 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
- Which Python matches
RANDOM(1,4)? Arandom.choice([1,2,3])· Brandom.randint(1,4)· Crandom.randint(0,4)· Drandom.random(1,4). B - 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)
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)
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.