Skip to content

Commit 4a75240

Browse files
committed
fixes and answers
1 parent 18e3678 commit 4a75240

8 files changed

Lines changed: 388 additions & 10 deletions

‎materials/03_python_data_types_and_operators.md‎

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,33 @@ What are the different primitive data types in expenses_calculator.py
4646
Why were they chosen?
4747
Are there any other data types you recognise?
4848

49+
<!--
50+
::: {.callout-answer}
51+
1.
52+
Strings (str)
53+
54+
Examples: "food", "expenses.csv", "done".
55+
56+
Why they were chosen: Strings are used for any textual data. In your script, they handle category names, filenames, and user input. Since Python treats strings as sequences of characters, they are perfect for labels and messages.
57+
58+
Floats (float)
59+
60+
Examples: 100.0, amount = float(parts[1]).
61+
62+
Why they were chosen: Floats represent real numbers with decimal points. In a financial calculator, you need precision for cents (e.g., 10.50). If you used integers, you wouldn't be able to track partial currency units.
63+
64+
Booleans (bool)
65+
66+
Examples: True (in while True:), and the result of category == "done".
67+
68+
Why they were chosen: Booleans represent truth values. They are the backbone of your script's logic, controlling the loop and the if/else validation checks.
69+
70+
2.
71+
72+
Dictionaries (dict) and Lists (list)
73+
:::
74+
-->
75+
4976
:::
5077

5178
## Python operators
@@ -237,6 +264,35 @@ Why are these data types used?
237264

238265
Can you try using a set to identify expense type - what is the advantage/disadvantage?
239266

267+
<!--
268+
::: {.callout-answer}
269+
1, 2.
270+
Dictionaries (dict)
271+
272+
Example: thresholds = {"food": 100.0, ...}
273+
274+
Why it's used: Dictionaries allow for key-value mapping. In your script, the category name is the "key" and the budget limit is the "value." This is chosen because it allows for quick lookup. Instead of searching through a list to find the limit for "food," Python jumps straight to it.
275+
276+
Lists (list)
277+
278+
Example: expenses = [] and [category, amount]
279+
280+
Why it's used: Lists are ordered and mutable. You use a list of lists (a nested structure) to keep a running history of transactions. Since you don't know how many expenses a user will enter, the list’s ability to grow dynamically is essential.
281+
282+
2. Using a set to Identify Expense Types
283+
A set is an unordered collection of unique elements. The script could define valid categories using a set like this:
284+
285+
valid_categories = {"food", "transport", "entertainment", "other"}
286+
287+
Yes, you could use a set to identify the expense type.
288+
This is fast - faster than a list. You cannot store duplicate values in a set. This is not an issue and instead beneficial for category types.
289+
290+
Unlike the dictionary used for thresholds, a set cannot hold an associated value. It can store the word "food," but it cannot link it to the numerical value 100.0.
291+
The Verdict:
292+
While the script could incorporate a set to validate category names, the Dictionary (thresholds) remains the most effective choice. It functions similarly to a set by allowing for fast membership checks of category names (the keys), but it provides the added necessity of storing the specific budget limits (the values) required for the script's calculations.
293+
:::
294+
-->
295+
240296
:::
241297

242298
::: {.callout-exercise}
@@ -269,7 +325,7 @@ State the best data type to use in Python:
269325

270326
12. You are working with text that may need to be split, joined, or manipulated.
271327

272-
328+
<!--
273329
::: {.callout-answer}
274330
1. list — Ordered and mutable collection of names.
275331
2. set — For fast membership tests with unique elements, order is irrelevant.
@@ -287,6 +343,7 @@ State the best data type to use in Python:
287343
Which data type you use effects memory useage and speed of computations which may be important in larger programs.
288344
For further reading you can investigate why sets are generally faster to find unordered elements (membership tests) than lists or tuples
289345
:::
346+
-->
290347
:::
291348

292349

‎materials/04_python_functions.md‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,89 @@ Functions can have default parameters, accept multiple arguments, and return mul
197197
Can you remove some of the code in the expense_calculator script into new functions that are used later in the script?
198198
Are there any other functions you might want to write and use in the script?
199199

200+
<!--
201+
::: {.callout-answer}
202+
```
203+
import os
204+
205+
# Configuration
206+
THRESHOLDS = {
207+
"food": 100.0,
208+
"transport": 50.0,
209+
"entertainment": 80.0,
210+
"other": 70.0
211+
}
212+
FILENAME = "expenses.csv"
213+
214+
def load_expenses(filename):
215+
"""Loads expenses from a CSV file into a list."""
216+
expenses = []
217+
if not os.path.exists(filename):
218+
return expenses
219+
220+
with open(filename, "r") as file:
221+
for line in file:
222+
parts = line.strip().split(",")
223+
if len(parts) == 2:
224+
expenses.append([parts[0].lower(), float(parts[1])])
225+
return expenses
226+
227+
def save_expenses(filename, expenses):
228+
"""Saves the current list of expenses to a CSV file."""
229+
with open(filename, "w") as file:
230+
for cat, amt in expenses:
231+
file.write(f"{cat},{amt}\n")
232+
233+
def get_category_total(expenses, category):
234+
"""Calculates the sum of expenses for a specific category."""
235+
return sum(amt for cat, amt in expenses if cat == category)
236+
237+
def check_budget(category, total_spent):
238+
"""Prints status messages based on the spending threshold."""
239+
limit = THRESHOLDS.get(category, 0)
240+
if total_spent > limit:
241+
print(f"Warning: You have exceeded the {category} threshold!")
242+
print(f"Total spent: {total_spent}")
243+
else:
244+
remaining = limit - total_spent
245+
print(f"Total spent in {category}: {total_spent}")
246+
print(f"Amount left for {category}: {remaining}")
247+
248+
def main():
249+
expenses = load_expenses(FILENAME)
250+
251+
print("--- Expense Tracker ---")
252+
print("Type 'done' as the category to finish.\n")
253+
254+
while True:
255+
category = input("Category (food, transport, entertainment, other): ").lower()
256+
if category == "done":
257+
break
258+
259+
if category not in THRESHOLDS:
260+
print("Invalid category. Please try again.")
261+
continue
262+
263+
try:
264+
amount = float(input("Amount: "))
265+
expenses.append([category, amount])
266+
267+
current_total = get_category_total(expenses, category)
268+
check_budget(category, current_total)
269+
print()
270+
271+
except ValueError:
272+
print("Invalid amount. Please enter a number.\n")
273+
274+
save_expenses(FILENAME, expenses)
275+
print("All expenses saved successfully.")
276+
277+
if __name__ == "__main__":
278+
main()
279+
```
280+
:::
281+
-->
282+
200283
:::
201284

202285
::: {.callout-exercise}

‎materials/05_python_conditionals.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,47 @@ What is the different indentation doing?
145145
Can you add an extra condition that you would like to your expense calculator?
146146

147147
What are the limitations of conditional statements?
148+
149+
<!--
150+
::: {.callout-answer}
151+
1. What do the conditional statements do?
152+
153+
In the script, the if, elif (implied by the logic flow), and else blocks act as filters and triggers:
154+
155+
Flow Control: The if category == "done": break statement monitors user input to decide exactly when to stop the loop and move to the saving phase.
156+
157+
Validation: The line if category in thresholds and amount_str... ensures the program doesn't crash if someone types "pizza" as a category or "a lot" as an amount. It acts as a gatekeeper for clean data.
158+
159+
Threshold Monitoring: The if current_total > thresholds[category]: block compares actual spending against set goals to trigger a specific warning message.
160+
161+
2. What is the different indentation doing?
162+
163+
In Python, indentation isn't just for neatness; it defines scope. It tells the computer which lines of code "belong" to a specific condition or loop.
164+
165+
Level 1 (No indent): Code that runs globally (like setting the initial thresholds).
166+
167+
Level 2 (One indent): Code that lives inside the while loop or the try block.
168+
169+
Level 3 (Two indents): Code that only runs if a specific condition is met (like the code that processes an expense only if the input was validated).
170+
171+
The "Nesting" Logic: You can only reach the "Total spent" calculation if you have already successfully passed through the "Is the input valid?" door. If that condition isn't met, the indented code is skipped entirely.
172+
173+
3. Adding a "Savings Milestone" Condition
174+
175+
A great addition would be an Overall Budget check. This warns the user if total spending across all categories exceeds a certain limit, even if individual categories are still under their specific thresholds.
176+
177+
Add:
178+
179+
```
180+
# Add this near the bottom of the while loop logic
181+
total_all_categories = sum(e[1] for e in expenses)
182+
overall_limit = 300.0
183+
184+
if total_all_categories > overall_limit:
185+
print(" CAUTION: You are over your budget!!!")
186+
```
187+
:::
188+
-->
148189
:::
149190

150191

‎materials/06_python_for_loops.md‎

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,111 @@ Animal 3: elephant
117117
{{< level 2 >}}
118118

119119
Using conditional statements, for loops and the functions we have seen,
120-
Modify the expense calculator to also allow the input of a dictionary rather than a file.
120+
Modify the expense calculator to allow the input of a dictionary specified at the top of the script rather than a file.
121+
The dictionary should look like the following:
122+
```
123+
pre_existing_expenses = {
124+
"food": [20.50, 15.00],
125+
"transport": [10.00],
126+
"entertainment": [90.00]
127+
}
128+
```
121129
Write code such that the calculator correctly loops through the dictionary to correctly load in pre-existing expenses and test your script.
122130

123131
Look at eachothers code - how might it be improved?
124132

133+
<!--
134+
::: {.callout-answer}
135+
```
136+
# --- Inputs ---
137+
# Thresholds
138+
thresholds = {
139+
"food": 100.0,
140+
"transport": 50.0,
141+
"entertainment": 80.0,
142+
"other": 70.0
143+
}
144+
145+
# Example input dictionary simulating pre-existing data
146+
pre_existing_expenses = {
147+
"food": [20.50, 15.00],
148+
"transport": [10.00],
149+
"entertainment": [90.00]
150+
}
151+
152+
expenses = []
153+
154+
# --- Functions ---
155+
156+
def load_from_dict(data_dict):
157+
"""Loops through a dictionary to load pre-existing expenses."""
158+
keys = list(data_dict.keys())
159+
160+
# Using range and len to iterate through the dictionary keys
161+
for i in range(len(keys)):
162+
category = keys[i]
163+
amounts = data_dict[category]
164+
165+
# Using enumerate to process individual amounts
166+
for index, amt in enumerate(amounts):
167+
if category in thresholds:
168+
expenses.append([category, float(amt)])
169+
else:
170+
print(f"Skipping unknown category: {category}")
171+
172+
def get_total(category):
173+
"""Calculates total spent for a specific category."""
174+
total = 0.0
175+
for item in expenses:
176+
if item[0] == category:
177+
total += item[1]
178+
return total
179+
180+
def process_expense(category, amount):
181+
"""Handles the logic for adding an expense and checking thresholds."""
182+
expenses.append([category, amount])
183+
current_total = get_total(category)
184+
limit = thresholds[category]
185+
186+
print(f"\n--- Update for {category.upper()} ---")
187+
if current_total > limit:
188+
over_by = current_total - limit
189+
print(f"ALERT: Threshold exceeded by {over_by}!")
190+
else:
191+
print(f"Added {amount}. Total: {current_total}")
192+
print(f"Remaining budget: {limit - current_total}")
193+
194+
# --- Execution part ---
195+
196+
# 1. Load the initial data
197+
load_from_dict(pre_existing_expenses)
198+
print(f"Loaded {len(expenses)} existing records.\n")
199+
200+
# 2. User Input Loop
201+
while True:
202+
cat_input = input("Enter category (food, transport, entertainment, other) or 'done': ").lower()
203+
204+
if cat_input == "done":
205+
break
206+
207+
if cat_input in thresholds:
208+
amt_input = input(f"Enter amount for {cat_input}: ")
209+
210+
# Simple validation
211+
if amt_input.replace('.', '', 1).isdigit():
212+
process_expense(cat_input, float(amt_input))
213+
else:
214+
print("Invalid numeric amount.")
215+
else:
216+
print("Invalid category. Please choose from the list.")
217+
218+
print("\nFinal Expense List:", expenses)
219+
220+
```
221+
:::
222+
223+
-->
224+
125225
:::
126226

127227
::: {.callout-exercise}

‎materials/07_python_while_conditional_loops.md‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ Improve the code, by adressing these limitations
187187

188188
::: {.callout-answer}
189189

190-
Add code introducing a maximum number of iterations or a global valuse cap to force exit and stop an infinite loop
190+
Add code introducing a maximum number of iterations or a global value cap to force exit and stop an infinite loop
191191
You can also add an extra break statement to stop if a user has exceeded a limit
192192
:::
193193

@@ -206,9 +206,9 @@ If it is not recognised from a set of your choice, then respond accordingly
206206
If they input "stop" or order more than 10 items, complete the order.
207207
Tell the customer the total cost.
208208

209-
:::
210-
211209
<!--
210+
::: {.callout-answer}
211+
```
212212
count = 0
213213
214214
while True:
@@ -227,8 +227,14 @@ while True:
227227
continue
228228
229229
print("Total drinks ordered:", count)
230+
```
231+
:::
230232
-->
231233

234+
:::
235+
236+
237+
232238
## Summary
233239

234240
::: {.callout-tip}

0 commit comments

Comments
 (0)