# COMP1010 MASTER EXAM NOTES - SEARCHABLE CTRL+F VERSION

Use this file in VS Code with Ctrl+F. Search the big headings, e.g.:
`FIND BY ID`, `FILTER LIST`, `PYHTML LOOP`, `FORM POST`, `SESSION`, `JSON`, `KEYERROR`, `USABILITY`, `DATA STRUCTURE`, `PLANTS`, `CHESS`, `TIMELINE`, `TIPS`, `CROCHET`.

---

# 0. EXAM MINDSET / QUICK PROCESS

## EXAM PROCESS - WHAT TO DO WHEN STUCK

1. Read the question slowly.
2. Find the function/route/file the question mentions.
3. Identify the input type and output type.
4. Write comments first.
5. Handle the easiest case first.
6. Use `print()` to debug.
7. Test one small example.
8. Write partial code even if not perfect.

```python
# GENERAL FUNCTION PLAN
def some_function(input_data):
    # 1. create result variable if needed
    # 2. loop through input data if needed
    # 3. check conditions with if
    # 4. append/count/sum/find
    # 5. return correct type
    return result
```

## EXAM WEB APP DATA FLOW

User opens route -> Flask returns page/form -> user submits form -> request.form/request.args gets input -> helper function processes data -> PyHTML displays result.

---

# 1. PYTHON BASICS

## PRINT / VARIABLE / TYPES

Use variables to store values. Use `print()` to check what code is doing.

```python
name = "xxxxxxx"       # str
age = 55           # int
price = 4.50       # float
is_valid = True    # bool
items = []         # list
person = {}        # dict
nothing = None     # NoneType

print(name)
print("DEBUG:", items)
print(type(age))
```

## CASTING / CONVERTING TYPES

Form input usually arrives as a string. Convert it before maths.

```python
age = int("55")
price = float("4.50")
text = str(123)

# from form:
quantity = int(request.form["quantity"])
cost = float(request.form["cost"])
```

## F STRINGS

Use f-strings to insert variables into text.

```python
name = "Alice"
score = 10
message = f"{name} scored {score}"
money = f"${price:.2f}"
percent = f"{rate:.1f}%"
```

---

# 2. IF / ELIF / ELSE / BOOLEAN LOGIC

## IF ELIF ELSE

Use when choosing between branches.

```python
if score >= 85:
    grade = "HD"
elif score >= 75:
    grade = "D"
elif score >= 65:
    grade = "C"
else:
    grade = "P/F"
```

## AND / OR / NOT

```python
if age >= 18 and has_id:
    print("Allowed")

if colour == "red" or colour == "blue":
    print("Valid colour")

if not finished:
    print("Keep going")
```

## IN OPERATOR

Use `in` for strings, lists, and dictionary keys.

```python
if "a" in "cat":
    print("yes")

if "apple" in fruits:
    print("yes")

if "name" in person:
    print(person["name"])
```

## CHECK NONE

Use when a function might return `None`.

```python
player = get_by_id(players, "bad-id")

if player is None:
    return "Not found"

return player["name"]
```

---

# 3. LOOPS

## FOR LOOP

Use when looping over a known sequence/list/range/string/dictionary.

```python
for item in items:
    print(item)

for i in range(5):
    print(i)
```

## WHILE LOOP

Use when repeating until a condition changes.

```python
answer = ""

while answer != "yes":
    answer = input("Type yes: ")
```

## BREAK / CONTINUE / PASS

```python
for item in items:
    if item["id"] == target_id:
        found = item
        break

for item in items:
    if item["skip"]:
        continue
    print(item)

if condition:
    pass  # placeholder to avoid error
```

---

# 4. FUNCTIONS

## FUNCTION BASICS

Functions group reusable logic. They only run when called.

```python
def add(a, b):
    return a + b

result = add(2, 3)
```

## FUNCTION DOCSTRING METHOD

When given a function stub and docstring:

1. What are the parameters?
2. What type is each input?
3. What should be returned?
4. What are the examples saying?
5. What should happen if invalid/not found?
6. Write comments first.

```python
def get_by_id(items, target_id):
    # loop through all items
    # if id matches, return item
    # after loop, return None
    for item in items:
        if item["id"] == target_id:
            return item
    return None
```

## RETURN VALUES

```python
return None     # not found / invalid input
return []       # no matching results
return True     # validation passed
return False    # validation failed
return total    # calculated number
return results  # list of matching items
```

---

# 5. STRINGS

## STRING METHODS

```python
s = " Hello World "

len(s)
s.count("l")
s.replace("World", "Cam")
s.startswith("Hello")
s.lower()
s.upper()
s.strip()
```

## SPLIT / JOIN

```python
parts = "a,b,c".split(",")       # ["a", "b", "c"]
text = ", ".join(parts)          # "a, b, c"
```

---

# 6. LISTS

## LIST BASICS

Lists store multiple values in order.

```python
items = ["a", "b", "c"]
items.append("d")
first = items[0]
length = len(items)
```

## FILTER LIST

Use when question says: find all matching items, recommendations, courses, valid records.

```python
def filter_by_category(items, category):
    results = []

    for item in items:
        if item["category"] == category:
            results.append(item)

    return results
```

## COUNT ITEMS

Use when question says: count wins, count matches, count valid things.

```python
def count_active(items):
    count = 0

    for item in items:
        if item["active"]:
            count += 1

    return count
```

## SUM VALUES

Use when question says: calculate total, total cost, total score.

```python
def sum_prices(items):
    total = 0

    for item in items:
        total += item["price"]

    return total
```

## FIND MAXIMUM

Use when question says: highest score, winner, best item, max Elo.

```python
def find_best(items):
    if len(items) == 0:
        return None

    best = items[0]

    for item in items:
        if item["score"] > best["score"]:
            best = item

    return best
```

## CHECK ALL ITEMS

Use when all items must pass validation.

```python
def all_valid(items):
    for item in items:
        if not item["valid"]:
            return False
    return True
```

## CHECK ANY ITEM

Use when one match is enough.

```python
def any_match(items, target):
    for item in items:
        if item["name"] == target:
            return True
    return False
```

---

# 7. DICTIONARIES

## DICTIONARY BASICS

Dictionaries store labelled data using key-value pairs.

```python
person = {
    "name": "Cam",
    "age": 21,
    "courses": ["COMP1010", "ECON2206"]
}
```

## ACCESS VALUES

```python
name = person["name"]
age = person.get("age")
score = person.get("score", 0)
```

## AVOID KEYERROR

```python
if "score" in person:
    print(person["score"])

score = person.get("score", 0)
```

## ADD / UPDATE VALUES

```python
person["age"] = 22
person["score"] = 100
```

## LOOP DICTIONARY

```python
for key in person:
    print(key, person[key])

for key, value in person.items():
    print(key, value)
```

---

# 8. NESTED DATA STRUCTURES

## LIST OF DICTIONARIES

Use when there are many items and each item has properties.

```python
players = [
    {"id": "p1", "name": "Alice", "score": 10},
    {"id": "p2", "name": "Bob", "score": 20},
]
```

## FIND BY ID / SEARCH LIST OF DICTIONARIES

Use when question says: find player by ID, find plant, find record, find course.

```python
def get_by_id(items, target_id):
    for item in items:
        if item["id"] == target_id:
            return item
    return None
```

Common mistake: do not put `return None` inside the loop too early.

## LIST INSIDE DICTIONARY

```python
player = {
    "name": "Alice",
    "games": [
        {"opponent": "Bob", "result": "win"},
        {"opponent": "Chris", "result": "draw"},
    ]
}
```

Access:

```python
games = player["games"]
first_game = player["games"][0]
result = player["games"][0]["result"]
```

Loop:

```python
for game in player["games"]:
    print(game["result"])
```

## COUNT NESTED VALUES

```python
def count_wins(player):
    wins = 0

    for game in player["games"]:
        if game["result"] == "win":
            wins += 1

    return wins
```

## DICTIONARY OF DICTIONARIES

Useful when IDs are unique and fast lookup matters.

```python
players = {
    "p1": {"name": "Alice", "score": 10},
    "p2": {"name": "Bob", "score": 20},
}

player = players["p1"]
```

## DICTIONARY OF LISTS

```python
courses = {
    "COMP1010": ["Python", "Flask"],
    "ECON2206": ["OLS", "Stata"]
}

for topic in courses["COMP1010"]:
    print(topic)
```

---

# 9. JSON AND FILES

## JSON READ FILE

Use when data is stored in a `.json` file.

```python
import json

with open("data.json") as f:
    data = json.load(f)
```

## JSON WRITE FILE

```python
with open("data.json", "w") as f:
    json.dump(data, f)
```

## JSON READ MODIFY SAVE

```python
with open("data.json") as f:
    data = json.load(f)

data.append(new_item)

with open("data.json", "w") as f:
    json.dump(data, f)
```

## SAFE GET DATA / SAVE DATA FUNCTIONS

Useful in `data.py`.

```python
import json

def get_data():
    try:
        with open("data.json", "r") as file:
            return json.load(file)
    except:
        return []

def save_data(data):
    with open("data.json", "w") as file:
        json.dump(data, file, indent=4)
```

## TEXT FILE READ

```python
with open("file.txt") as f:
    text = f.read()

with open("file.txt") as f:
    lines = f.readlines()

for line in lines:
    line = line.strip()
```

## JSON STRING VS JSON FILE

```python
json.load(file)       # file -> Python
json.loads(text)      # string -> Python
json.dump(data, file) # Python -> file
json.dumps(data)      # Python -> string
```

Use `json.loads()` when a user pastes JSON into a textarea.

---

# 10. FLASK BASICS

## FLASK IMPORTS / APP SETUP

```python
from flask import Flask, request, redirect, session
import pyhtml as p
import json

app = Flask(__name__)
app.secret_key = "secret"

if __name__ == "__main__":
    app.run(debug=True)
```

## BASIC ROUTE

```python
@app.get("/")
def home():
    return "Hello"
```

## ROUTE METHODS

```python
@app.get("/form")
def form_page():
    return "show form"

@app.post("/form")
def form_submit():
    return "handle form"
```

## 404 AND 405 ERRORS

404 = route/URL not found. Check `href`, `action`, and `@app.get(...)`.

405 = method wrong. Example: form uses POST but route is `@app.get`, or form uses GET but route is `@app.post`.

---

# 11. PYHTML

## PYHTML PAGE SKELETON

```python
@app.get("/")
def home():
    return str(
        p.html(
            p.head(
                p.title("Home"),
                p.link(rel="stylesheet", href="/static/style.css")
            ),
            p.body(
                p.h1("Heading"),
                p.p("Text"),
                p.a(href="/form")("Go")
            )
        )
    )
```

## COMMON PYHTML TAGS

```python
p.h1("Title")
p.h2("Subtitle")
p.p("Text")
p.br()
p.a(href="/")("Home")
p.div(class_="card")(p.p("content"))
p.ul(p.li("A"), p.li("B"))
```

Use `class_`, not `class`.

## REUSABLE HEAD

Use to avoid repeating title/CSS code.

```python
def head_html(title):
    return p.head(
        p.title(title),
        p.link(rel="stylesheet", href="/static/style.css"),
    )
```

## REUSABLE NAVBAR

```python
def navbar_html():
    return p.nav(
        p.a(href="/")("Home"),
        " | ",
        p.a(href="/form")("Form"),
        " | ",
        p.a(href="/history")("History"),
    )
```

## PYHTML LOOP - LIST ITEMS

```python
p.ul(
    *[
        p.li(item["name"])
        for item in items
    ]
)
```

## PYHTML LOOP - CARDS

```python
p.div(
    *[
        p.div(class_="card")(
            p.h2(item["name"]),
            p.p(item["description"]),
            p.a(href=f"/item/{item['id']}")("View")
        )
        for item in items
    ]
)
```

## PYHTML TABLE

```python
p.table(
    p.tr(p.th("Name"), p.th("Score")),
    *[
        p.tr(
            p.td(item["name"]),
            p.td(str(item["score"]))
        )
        for item in items
    ]
)
```

## CONDITIONAL PYHTML CONTENT

```python
if len(results) == 0:
    content = p.p("No results found")
else:
    content = p.ul(*[p.li(item["name"]) for item in results])

return str(p.html(p.body(content)))
```

---

# 12. FLASK FORMS

## FORM PAGE GET ROUTE

Use GET route to display a form.

```python
@app.get("/form")
def form():
    return str(
        p.html(
            p.body(
                p.h1("Form"),
                p.form(action="/result", method="post")(
                    p.label("Name"),
                    p.input(type="text", name="name"),
                    p.input(type="submit", value="Submit")
                )
            )
        )
    )
```

## RECEIVE FORM POST ROUTE

Use POST route to process submitted data.

```python
@app.post("/result")
def result():
    name = request.form["name"]

    return str(
        p.html(
            p.body(
                p.h1("Result"),
                p.p(f"Hello {name}")
            )
        )
    )
```

## REQUEST.FORM

Use for POST forms.

```python
name = request.form["name"]             # required
name = request.form.get("name", "")     # safer optional
pets = request.form.getlist("pets")     # multiple checkboxes
```

## REQUEST.ARGS

Use for GET query string forms.

```python
@app.get("/search")
def search():
    query = request.args.get("query", "")
    return str(p.html(p.body(p.p(query))))
```

## GET VS POST

GET = data in URL. Good for search/filter pages.
POST = data in request body. Good for add/save/delete/change actions.

## SELECT INPUT

```python
p.select(name="lighting")(
    p.option(value="sun")("Sun"),
    p.option(value="part shade")("Part shade"),
    p.option(value="full shade")("Full shade")
)
```

## CHECKBOX INPUT

```python
p.input(type="checkbox", name="pets", value="cats")
p.label("Cats")
p.input(type="checkbox", name="pets", value="dogs")
p.label("Dogs")
```

Read checkboxes:

```python
pets = request.form.getlist("pets")
```

## RADIO BUTTON

```python
p.input(type="radio", name="size", value="small")
p.label("Small")
p.input(type="radio", name="size", value="large")
p.label("Large")
```

## NUMBER INPUT

```python
p.input(type="number", name="age")
p.input(type="number", name="price", step="0.01")
```

## TEXTAREA

```python
p.textarea(name="message")("")
```

Read:

```python
message = request.form.get("message", "")
```

## HIDDEN INPUT

Use to carry an ID through a form.

```python
p.input(type="hidden", name="item_id", value=item["id"])
```

---

# 13. DYNAMIC ROUTES

## DYNAMIC ROUTE WITH ID

Use when URL contains a specific item ID.

```python
@app.get("/player/<player_id>")
def player_page(player_id):
    player = get_by_id(players, player_id)

    if player is None:
        return "Not found"

    return str(
        p.html(
            p.body(
                p.h1(player["name"]),
                p.p(f"Score: {player['score']}")
            )
        )
    )
```

Link to dynamic route:

```python
p.a(href=f"/player/{player['id']}")("View player")
```

---

# 14. REDIRECT

## REDIRECT AFTER POST

Redirect after saving/changing data so refreshing does not resubmit form.

```python
@app.post("/submit")
def submit():
    # process data
    return redirect("/")
```

---

# 15. SESSIONS / COOKIES

## SESSION SETUP

Use sessions for user-specific temporary data. Do not use global variables for changing app state.

```python
from flask import session

app.secret_key = "secret"
```

## SESSION SAVE / READ

```python
session["name"] = "Cam"
name = session.get("name", "Guest")
```

## SESSION LIST / HISTORY

```python
history = session.get("history", [])
history.append(new_value)
session["history"] = history
session.modified = True
```

## SESSION CLEAR

```python
@app.get("/clear")
def clear():
    session.clear()
    return redirect("/")
```

## WHEN TO USE SESSION VS FILES

Session/cookies = temporary user-specific information like search history or preferences.
Files/JSON = persistent data that should remain after browser closes or server restarts.

---

# 16. CSS

## CSS BASIC STARTER

```css
body {
    font-family: Arial, sans-serif;
    max-width: 900px;
    margin: 0 auto;
    padding: 20px;
}

.card {
    border: 1px solid #ccc;
    border-radius: 8px;
    padding: 12px;
    margin: 10px 0;
}

input, select, textarea, button {
    padding: 6px;
    margin: 4px 0;
}

a {
    display: inline-block;
    margin-top: 8px;
}
```

## CSS LINK IN PYHTML

```python
p.link(rel="stylesheet", href="/static/style.css")
```

If CSS is not working, check file is in `static/style.css` and href starts with `/static/...`.

---

# 17. COMMON ERRORS / DEBUGGING

## DEBUG PRINTS

```python
print("DEBUG:", variable)
print("DEBUG type:", type(variable))
print("DEBUG len:", len(items))
print("DEBUG keys:", item.keys())
```

## KEYERROR

A dictionary key is missing.

```python
# bad if score might not exist
score = person["score"]

# safer
score = person.get("score", 0)

if "score" in person:
    score = person["score"]
```

## TYPEERROR

Wrong type, common with string + int or maths on form strings.

```python
# bad
text = "Age: " + 21

# good
text = "Age: " + str(21)

# form numbers
age = int(request.form["age"])
price = float(request.form["price"])
```

## NONETYPE ERROR

A function returned `None`, then code used it like a dict/list.

```python
item = get_by_id(items, target_id)

if item is None:
    return "Not found"

return item["name"]
```

## INDEXERROR

List index does not exist.

```python
if len(items) > 0:
    first = items[0]
```

## NAMEERROR

Variable/function not defined. Check spelling and imports.

## INDENTATIONERROR

Code inside `if`, `for`, `def`, etc. must be indented consistently.

---

# 18. WRITTEN ANSWER TEMPLATES

## IF VS LOOP

An if/else statement chooses between different branches of code based on a condition. A loop repeats code multiple times, usually over a sequence or while a condition remains true.

## FOR VS WHILE

A for loop is best when iterating over a known sequence, such as a list, string, dictionary or range. A while loop is best when the number of repetitions is not known beforehand and depends on a condition changing.

## HTML CSS PYTHON

HTML defines the structure and content of a webpage. CSS controls the styling and visual presentation. Python handles server-side logic, such as processing form input, reading data and generating dynamic pages.

## GET VS POST

GET sends data in the URL and is useful for search/filter pages. POST sends data in the request body and is better for actions that change data, such as adding, saving or deleting.

## ROUTE REQUEST RESPONSE

A route maps a URL to a Python function. The request contains information sent by the browser, such as form data. The response is what Flask sends back, usually HTML.

## CLIENT VS SERVER

The client is the browser/user side. The server is the Flask/Python side. The browser sends a request, Flask processes it, then returns a response.

## FUNCTIONS

Functions reduce repetition, improve readability, and make code easier to test and debug. They group related logic into named reusable blocks.

## WHY VALIDATE INPUT

Validation prevents invalid data from breaking the program or being saved. It improves error prevention and gives users clearer feedback.

## WHY USE IDS

IDs uniquely identify items even when names are repeated. They are useful in dynamic routes, hidden form inputs, and finding specific records.

---

# 19. USABILITY

## USABILITY HEURISTICS / IDEAS

Visibility of system status:
- Show results, confirmations, and error messages.

Match between system and real world:
- Use words users understand.

User control and freedom:
- Back, cancel, clear, undo buttons.

Consistency and standards:
- Same labels, layout, navigation, colours.

Error prevention:
- Dropdowns, checkboxes, validation, required fields.

Recognition rather than recall:
- Show options instead of making users remember exact inputs.

Aesthetic and minimalist design:
- Avoid clutter; keep layout readable.

Help users recover from errors:
- Clear message explaining what went wrong and how to fix it.

## USABILITY SAMPLE ANSWER

The form improves error prevention by using dropdowns/checkboxes so users choose valid options rather than typing free text. Clear labels improve recognition rather than recall. A confirmation or results page improves visibility of system status. Back or clear buttons improve user control and freedom.

---

# 20. DATA STRUCTURE DISCUSSION

## LIST OF DICTIONARIES DISCUSSION

A list of dictionaries is suitable because each item can store multiple named fields, such as ID, name and category. It is easy to loop through, display all items, and filter items based on conditions. However, finding one item by ID requires searching the whole list, so a dictionary indexed by ID would be faster for frequent lookups.

## DICTIONARY OF DICTIONARIES DISCUSSION

A dictionary of dictionaries is useful when each item has a unique ID. It allows fast lookup by ID because the ID can be used directly as the key. However, it can be slightly less natural for displaying all items in order compared with a list.

## LIST VS DICT QUICK CHOICE

Use list when:
- many items
- order matters
- need to loop/filter/display all

Use dictionary when:
- one item has many labelled properties
- need lookup by key/ID
- labels make data clearer

---

# 21. PRACTICE EXAM: PLANTS

Search terms: PLANTS, RECOMMENDATIONS, FILTER LIGHTING, PETS, TOXIC.

## PLANTS - LIKELY DATA STRUCTURE

Plants are likely dictionaries with name, lighting options, toxicity, image/description.

```python
plant = {
    "name": "Cattleya Orchid",
    "lighting": ["part shade"],
    "toxic_to": [],
    "image": "cattleya-orchid.jpeg"
}
```

## PLANTS - FILTER BY LIGHTING AND PETS

Question style: recommend plants based on lighting and pets.

```python
def get_plant_recommendations(lighting, pets):
    if lighting not in VALID_LIGHTING:
        return None

    results = []

    for plant in plants:
        good_lighting = lighting in plant["lighting"]

        safe_for_pets = True
        for pet in pets:
            if pet in plant["toxic_to"]:
                safe_for_pets = False

        if good_lighting and safe_for_pets:
            results.append(plant)

    return results
```

## PLANTS - CHECKBOX PETS FORM

```python
p.form(action="/recommendations", method="post")(
    p.select(name="lighting")(
        p.option(value="sun")("Sun"),
        p.option(value="part shade")("Part shade"),
        p.option(value="full shade")("Full shade"),
    ),
    p.input(type="checkbox", name="pets", value="cats"),
    p.label("Cats"),
    p.input(type="checkbox", name="pets", value="dogs"),
    p.label("Dogs"),
    p.input(type="submit", value="Find plants")
)
```

Read:

```python
lighting = request.form["lighting"]
pets = request.form.getlist("pets")
```

## PLANTS - DISPLAY RECOMMENDATION CARDS

```python
p.div(
    *[
        p.div(class_="card")(
            p.h2(plant["name"]),
            p.p(plant["description"]),
            p.img(src=f"/static/{plant['image']}", alt=plant["name"])
        )
        for plant in recommendations
    ]
)
```

---

# 22. PRACTICE EXAM: CHESS

Search terms: CHESS, PLAYER, ELO, TOURNAMENT, WINS DRAWS LOSSES.

## CHESS - ADD PLAYER

Use when adding a dictionary to a list.

```python
def add_player(data, id, name, faculty):
    new_player = {
        "id": id,
        "name": name,
        "faculty": faculty,
        "elo": 1200,
        "past_games": []
    }

    data.append(new_player)
    return data
```

## CHESS - GET PLAYER BY ID

```python
def get_player_by_id(data, id):
    for player in data:
        if player["id"] == id:
            return player
    return None
```

## CHESS - FIND TOURNAMENT WINNER

Usually winner = highest Elo / score depending on spec.

```python
def find_tournament_winner(data):
    if len(data) == 0:
        return None

    winner = data[0]

    for player in data:
        if player["elo"] > winner["elo"]:
            winner = player

    return winner
```

## CHESS - WINS DRAWS LOSSES TEXT

```python
def get_wins_draws_losses_text(player):
    wins = 0
    draws = 0
    losses = 0

    for game in player["past_games"]:
        if game["result"] == "win":
            wins += 1
        elif game["result"] == "draw":
            draws += 1
        elif game["result"] == "loss":
            losses += 1

    return f"{wins} wins, {draws} draws, {losses} losses"
```

## CHESS - PLAYER PAGE DYNAMIC ROUTE

```python
@app.get("/player/<player_id>")
def player_page(player_id):
    data = get_data()
    player = get_player_by_id(data, player_id)

    if player is None:
        return "Player not found"

    return str(
        p.html(
            head_html(player["name"]),
            p.body(
                navbar_html(),
                p.h1(player["name"]),
                p.p(f"Elo: {player['elo']}")
            )
        )
    )
```

## CHESS - ADD PLAYER FORM SUBMIT

```python
@app.post("/add_player")
def add_player_submit():
    data = get_data()

    player_id = request.form.get("id")
    name = request.form.get("name")
    faculty = request.form.get("faculty")

    add_player(data, player_id, name, faculty)
    save_data(data)

    return redirect("/")
```

---

# 23. PRACTICE EXAM: TIMELINE

Search terms: TIMELINE, JUMP, LANDING, MACHINE, VALIDATE, JSON TEXTAREA.

## TIMELINE - MACHINE VALID

```python
def is_machine_valid(machine_id, machines):
    return machine_id in machines
```

Or expanded:

```python
def is_machine_valid(machine_id, machines):
    for machine in machines:
        if machine == machine_id:
            return True
    return False
```

## TIMELINE - FIND MATCHING LANDING

Use when matching two lists by machine and time.

```python
def find_matching_landing(jump, landings):
    for landing in landings:
        same_machine = landing["machine_id"] == jump["machine_id"]
        same_time = landing["time"] == jump["target_time"]

        if same_machine and same_time:
            return landing

    return None
```

## TIMELINE - VALIDATE TIMELINE

Basic pattern: loop through jumps, check machine valid, check matching landing exists.

```python
def validate_timeline(jumps, landings, machines):
    alerts = []

    for jump in jumps:
        if not is_machine_valid(jump["machine_id"], machines):
            alerts.append({
                "type": "invalid_machine",
                "jump": jump
            })

        matching_landing = find_matching_landing(jump, landings)
        if matching_landing is None:
            alerts.append({
                "type": "missing_landing",
                "jump": jump
            })

    return alerts
```

Always check exact expected return type in spec. It may want `True/False`, list of alerts, or dictionary.

## TIMELINE - TEXTAREA JSON FORM

```python
p.form(action="/upload", method="post")(
    p.label("Timeline JSON"),
    p.textarea(name="timeline_json")(""),
    p.input(type="submit", value="Upload")
)
```

Read JSON string:

```python
timeline_json = request.form.get("timeline_json", "")

try:
    timeline = json.loads(timeline_json)
except:
    return "Invalid JSON"
```

## TIMELINE - SESSION STORE TIMELINE

```python
session["timeline"] = timeline
session.modified = True
```

Read:

```python
timeline = session.get("timeline")
if timeline is None:
    return "No timeline uploaded"
```

---

# 24. PRACTICE EXAM: TIPS / COURSES

Search terms: TIPS, PREREQS, COURSES, TIP CALCULATOR.

## TIPS - CALCULATE TIP AND TOTAL

```python
def calculate_tip(amount, tip_percent):
    tip = amount * tip_percent / 100
    total = amount + tip
    return round(tip, 2), round(total, 2)
```

If returning dictionary:

```python
return {
    "tip": round(tip, 2),
    "total": round(total, 2)
}
```

## TIPS - FORM NUMBERS

```python
amount = float(request.form["amount"])
tip_percent = float(request.form["tip_percent"])
```

## TIPS - FIND PREREQS

If courses are a dictionary:

```python
courses = {
    "COMP2521": ["COMP1511"],
    "COMP3121": ["COMP2521"]
}
```

Find prereqs:

```python
def find_prereqs(code):
    if code in courses:
        return courses[code]
    return []
```

If courses are a list of dictionaries:

```python
def find_prereqs(courses, code):
    for course in courses:
        if course["code"] == code:
            return course["prereqs"]
    return []
```

## TIPS - DISPLAY PREREQS

```python
prereqs = find_prereqs(code)

if len(prereqs) == 0:
    content = p.p("No prerequisites")
else:
    content = p.ul(*[p.li(prereq) for prereq in prereqs])
```

---

# 25. PRACTICE EXAM: CROCHET

Search terms: CROCHET, SCARF, COST, SEGMENTS, COLOURS, LENGTH.

## CROCHET - LIKELY DATA STRUCTURE

```python
scarf_info = {
    "segments": [
        {"colour": "red", "length": 10},
        {"colour": "blue", "length": 5}
    ],
    "costs": {
        "red": 0.50,
        "blue": 0.75
    }
}
```

## CROCHET - CALCULATE SCARF COST

Use when looping through segments and multiplying length by colour cost.

```python
def calculate_scarf_cost(scarf_info):
    total = 0

    for segment in scarf_info["segments"]:
        colour = segment["colour"]
        length = segment["length"]
        cost_per_unit = scarf_info["costs"][colour]

        total += length * cost_per_unit

    return round(total, 2)
```

## CROCHET - IF COLOUR MIGHT BE MISSING

```python
if colour not in scarf_info["costs"]:
    return None
```

## CROCHET - DISPLAY MONEY

```python
p.p(f"Total cost: ${cost:.2f}")
```

---

# 26. ROMAN NUMERALS / BASIC ALGORITHM PATTERN

Search terms: ROMAN, ALGORITHM, STRING LOOP.

Use dictionary mapping symbols to numbers.

```python
values = {
    "I": 1,
    "V": 5,
    "X": 10,
    "L": 50,
    "C": 100,
    "D": 500,
    "M": 1000
}
```

Basic left-to-right idea:

```python
def roman_to_int(roman):
    total = 0

    for i in range(len(roman)):
        current = values[roman[i]]

        if i + 1 < len(roman):
            next_value = values[roman[i + 1]]
            if current < next_value:
                total -= current
            else:
                total += current
        else:
            total += current

    return total
```

---

# 27. BOXING / MATCH COUNT / BASIC MATH PATTERN

Search terms: BOXING, MATCH COUNT, COMBINATIONS.

If every person/team plays every other once:

```python
def calculate_match_count(n):
    return n * (n - 1) // 2
```

Expanded loop version:

```python
def calculate_match_count(n):
    count = 0

    for i in range(n):
        for j in range(i + 1, n):
            count += 1

    return count
```

---

# 28. BASIC FULL APP TEMPLATE

Search terms: FULL APP, STARTER, HOME FORM RESULT.

```python
from flask import Flask, request, redirect, session
import pyhtml as p
import json

app = Flask(__name__)
app.secret_key = "secret"

def head_html(title):
    return p.head(
        p.title(title),
        p.link(rel="stylesheet", href="/static/style.css")
    )

def navbar_html():
    return p.nav(
        p.a(href="/")("Home"),
        " | ",
        p.a(href="/form")("Form")
    )

@app.get("/")
def home():
    return str(
        p.html(
            head_html("Home"),
            p.body(
                navbar_html(),
                p.h1("Home"),
                p.a(href="/form")("Go to form")
            )
        )
    )

@app.get("/form")
def form():
    return str(
        p.html(
            head_html("Form"),
            p.body(
                navbar_html(),
                p.h1("Form"),
                p.form(action="/result", method="post")(
                    p.label("Name"),
                    p.input(type="text", name="name"),
                    p.input(type="submit", value="Submit")
                )
            )
        )
    )

@app.post("/result")
def result():
    name = request.form.get("name", "")

    return str(
        p.html(
            head_html("Result"),
            p.body(
                navbar_html(),
                p.h1("Result"),
                p.p(f"Hello {name}")
            )
        )
    )

if __name__ == "__main__":
    app.run(debug=True)
```

---

# 29. COMMON EXAM TRIGGERS / WHAT PATTERN TO USE

If question says "find the..." -> use FIND BY ID / FIND MAX / loop with if.

If question says "all matching..." -> use FILTER LIST.

If question says "how many..." -> use COUNT.

If question says "total/cost/sum..." -> use SUM.

If question says "valid/invalid..." -> use CHECK ALL / VALIDATION.

If question says "show results on webpage..." -> use PYHTML LOOP / CARDS.

If question says "form..." -> use GET form route + POST submit route.

If question says "remember previous..." -> use SESSION.

If question says "save permanently..." -> use JSON file read/write.

If question says "upload/paste JSON..." -> use textarea + json.loads().

If question says "style/readability..." -> use CSS.

If question says "discuss usability..." -> use Nielsen ideas.

If question says "discuss data structure..." -> list of dictionaries vs dictionary of dictionaries.

---

# 30. LAST MINUTE CHECKLIST

- `return None` goes after loop when not found.
- `request.form` for POST.
- `request.args` for GET.
- `request.form.getlist()` for checkboxes.
- Convert form numbers with `int()` or `float()`.
- Use `class_` in PyHTML.
- Use `*[]` to unpack list of PyHTML elements.
- Use `session.modified = True` after changing nested session data.
- Check `None` before doing `item["key"]`.
- `json.load()` for file, `json.loads()` for string.
- 404 = route mismatch.
- 405 = GET/POST mismatch.
- Save files often.
