3.15 Random Values: Homework Notebook

Lesson: CSP 3.15 Random Values (SFI Backend QA Test Garage) Contents: Popcorn Hacks 1-3 (with predictions + output), MCQ result, Homework Hack (SFI Backend QA Simulator), College Board pseudocode check, submission notes.

Popcorn Hack 1: Random SFI Record Number

Prediction: random.randint(1, 4) includes both endpoints, so every run prints 1, 2, 3, or 4. The same number can appear twice in a row.

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

# One random record number from 1 through record_count (inclusive)
record_number = random.randint(1, record_count)
print("SFI record selected for QA:", record_number)

# Run it many times to see the pattern of outputs
picks = [random.randint(1, record_count) for _ in range(12)]
print("12 more picks:", picks)

# Every generated value must be a valid record number
assert all(1 <= p <= record_count for p in picks)
print("All picks valid (1 to", record_count, "):", all(1 <= p <= record_count for p in picks))

# Repeated values are normal because each pick is independent
repeats = any(picks[i] == picks[i + 1] for i in range(len(picks) - 1))
print("Back-to-back repeat in these picks:", repeats)
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Explanation: The complete set of possible outputs is {1, 2, 3, 4}. randint is inclusive, so both 1 and 4 can be returned. Repeats are allowed because each call is an independent random selection, not a rotation through the list. Seeing only a few runs doesnโ€™t prove other values are impossible.

Popcorn Hack 2: Random Structured SFI Car Part

Prediction: One of the three records is printed whole: its product name, category, and spec number always belong together (for example, spec 1.2 always goes with Multiple Disc Clutch Assemblies and 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"
    }
]

# random.choice picks one COMPLETE dictionary, so its fields stay together
selected = random.choice(parts)
print("Selected record for testing:")
print("  Product:  ", selected["product_name"])
print("  Category: ", selected["category"])
print("  Spec no.: ", selected["spec_number"])

# Change one input and compare: run several picks and confirm each record stays intact
print()
for i in range(3):
    p = random.choice(parts)
    assert p in parts          # the chosen object is exactly one of the original records
    print(f"Pick {i + 1}: {p['spec_number']} -> {p['product_name']} ({p['category']})")
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Explanation: Choosing a whole dictionary keeps the record intact. Choosing separate random names, categories, and spec numbers could mix fields from different parts and create an invalid record.

Popcorn Hack 3: Random SFI API Test

Prediction: Each run picks one part and one action. GET/search should report a lookup, POST/create should report that the part is validated before creating a record, and PUT/update should report an update for that part. There are 3 parts x 3 actions = 9 possible part/action 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"
]

def run_test(part, action):
    if action == "GET/search":
        return f"Searching for spec {part['spec_number']} -> found '{part['product_name']}'"
    elif action == "POST/create":
        return f"Validating '{part['product_name']}' (spec {part['spec_number']}) before creating a new record"
    else:  # PUT/update
        return f"Updating the stored record for spec {part['spec_number']} ({part['product_name']})"

# One random test
part = random.choice(parts)
action = random.choice(actions)
print(f"[{action}] {run_test(part, action)}")

# Change the action to see different branches: run 6 more random tests
print()
for _ in range(6):
    part = random.choice(parts)
    action = random.choice(actions)
    print(f"[{action:<11}] {run_test(part, action)}")
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Explanation: The random action flows through an if / elif / else, so a different random value takes a different branch of the program. Testing it only once would show just one of the 9 possible outcomes.

In-class MCQ Check

# Question My answer Why
1 What can random.randint(2, 5) return? C (2, 3, 4, 5) randint includes both endpoints.
2 Record A appears twice in a row. What does it mean? B Repeats are allowed in independent random selection.
3 Python match for RANDOM(1,4)? B (random.randint(1,4)) Integer from 1 through 4 inclusive.
4 Why is testing a random program once insufficient? B One run shows only one of many possible outcomes.

Code Runner Challenge

Homework Hack - SFI Backend QA Simulator

View IPYNB Source
MCQ 3.15: 4/4 | answers: C,B,B,B
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Homework Hack: SFI Backend QA Simulator

A reusable randomized QA simulator. Each run randomly selects a complete part record and a backend action, then returns a backend-style response. It tracks the stored spec numbers, so creating a duplicate is rejected, and updating or removing a record that isnโ€™t stored returns โ€œnot foundโ€.

# 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 simulate_action(part, action, stored):
    """
    Simulate one backend call.
    Parameters:
        part:   one complete SFI record (dictionary with product_name, category, spec_number)
        action: one of "GET/search", "POST/create", "PUT/update", "DELETE/remove"
        stored: list of spec numbers currently in the backend (changed by create/remove)
    Returns:
        a backend-style response string
    """
    spec = part["spec_number"]
    name = part["product_name"]
    in_db = spec in stored

    if action == "GET/search":
        return f"200 OK: found {name} (spec {spec})" if in_db else f"404 NOT FOUND: spec {spec} is not stored"
    elif action == "POST/create":
        if in_db:
            return f"409 CONFLICT: spec {spec} already exists, create rejected"
        stored.append(spec)
        return f"201 CREATED: {name} saved with new spec {spec}"
    elif action == "PUT/update":
        return f"200 OK: updated {name} (spec {spec})" if in_db else f"404 NOT FOUND: cannot update spec {spec}"
    else:  # DELETE/remove
        if in_db:
            stored.remove(spec)
            return f"200 OK: removed spec {spec} ({name})"
        return f"404 NOT FOUND: cannot remove spec {spec}"


def run_qa(count, stored_start):
    stored = list(stored_start)            # work on a copy so each simulation starts clean
    for run in range(1, count + 1):
        part = random.choice(parts)          # random complete record
        action = random.choice(actions)      # random API action
        result = simulate_action(part, action, stored)
        print(f"Run {run}: {action:<13} on spec {part['spec_number']} -> {result}")
    print("Stored spec numbers after run:", sorted(stored))


print("=== QA simulation: 5 random tests ===")
run_qa(test_count, existing_spec_numbers)

print()
print("=== Second simulation (different random results) ===")
run_qa(test_count, existing_spec_numbers)

# Larger run to show that every part/action combination is reachable
print()
print("=== 300 random picks: combinations reached ===")
seen = set()
for _ in range(300):
    seen.add((random.choice(parts)["spec_number"], random.choice(actions)))
print(f"Distinct (record, action) pairs seen: {len(seen)} of {len(parts) * len(actions)} possible")

Possible outputs and duplicate handling

  • random.choice(parts) has 3 possible outcomes and random.choice(actions) has 4, so there are 3 x 4 = 12 possible record/action pairs. A pair can repeat on consecutive runs. Because the stored list changes during a run (create adds, remove deletes), the same pair can legitimately give different responses later.
  • Duplicate-spec handling: POST/create checks spec in stored first. Spec 1.1 or 2.1 is already stored, so create returns 409 CONFLICT; spec 1.2 is new, so it returns 201 CREATED the first time and 409 if it is created again.
  • Records stay intact because I choose a whole dictionary rather than picking fields independently.

College Board Pseudocode Check

Choose recordIndex = 2 and actionNumber = 3. Tracing by hand: actionNumber = 1 is false, actionNumber = 2 is false, actionNumber = 3 is true, so the program displays PUT/update for parts[2] (Multiple Disc Clutch Assemblies, spec 1.2). In Python, random.choice(actions) can produce "PUT/update" too, which is the same decision reached by a different route. RANDOM(1, LENGTH(parts)) and random.choice(parts) both select one of the 3 records, and since pseudocode lists start at index 1 while Python lists start at 0, randint(1, 3) would need to be adjusted to parts[index - 1].

Submission Notes (for the Notes field)

Lesson: CSP 3.15 Random Values
Notebook: <published URL>
MCQ 3.15: 4/4 | answers: C,B,B,B
Popcorn Hacks 1-3 completed during class and copied to notebook: yes
Homework tested: ran the simulator several times (5 tests each) plus a 300-pick run; all 12 record/action pairs reached, duplicate create returned 409, new create returned 201