S2Q1 · Sales Analysis by Product¶
⚡ Quick Reference
Function: analyze_sales(data: list, threshold: int) -> dict
Core idea: for each product, compute aggregations, include only if total >= threshold.
def analyze_sales(data: list, threshold: int) -> dict:
result = {}
for record in data:
product = record["product"]
sales = record["sales"]
if not sales:
continue
total = sum(sales)
if total >= threshold:
result[product] = {
"total": total,
"count": len(sales),
"average": total / len(sales),
"max": max(sales),
}
return result
Key rules:
- Skip products with no sales (avoid division by zero)
- Include only if total >= threshold
- average = total / count as a float
- max = largest single sale value
Template Code¶
def analyze_sales(data: list, threshold: int) -> dict:
'''
Analyze sales records by product and return stats for products
meeting the total sales threshold.
Args:
data (list): List of dicts with keys "product" and "sales"
threshold (int): Minimum total sales to include a product
Returns:
dict: {product: {"total", "count", "average", "max"}}
Only products with total >= threshold are included.
Example:
>>> analyze_sales([
... {"product": "P1", "sales": [100, 150]},
... {"product": "P2", "sales": [40]}
... ], 200)
{"P1": {"total": 250, "count": 2, "average": 125.0, "max": 150}}
'''
...
Problem Statement¶
Problem
Write a function analyze_sales(data, threshold) that computes total, count, average, and max sales per product, returning only those with total ≥ threshold.
Example:
data=[{"product":"P1","sales":[100,150]},
{"product":"P2","sales":[40]}],
threshold=200
{"P1": {"total": 250, "count": 2, "average": 125.0, "max": 150}}
P1 total = 250 ≥ 200 → included. P2 total = 40 < 200 → excluded.
Tracing the example¶
| Product | Sales | Total | Count | Average | Max | Total ≥ 200? |
|---|---|---|---|---|---|---|
| P1 | [100, 150] | 250 | 2 | 125.0 | 150 | ✅ included |
| P2 | [40] | 40 | 1 | 40.0 | 40 | ❌ excluded |
Solution approaches¶
def analyze_sales(data: list, threshold: int) -> dict:
result = {}
for record in data:
sales = record["sales"]
if not sales:
continue
total = sum(sales)
if total >= threshold:
result[record["product"]] = {
"total": total,
"count": len(sales),
"average": total / len(sales),
"max": max(sales),
}
return result
def analyze_sales(data: list, threshold: int) -> dict:
result = {}
for record in data:
product = record["product"]
sales = record["sales"]
if len(sales) == 0:
continue
total = 0
maximum = sales[0]
for s in sales:
total += s
if s > maximum:
maximum = s
if total >= threshold:
result[product] = {
"total": total,
"count": len(sales),
"average": total / len(sales),
"max": maximum,
}
return result
def analyze_sales(data: list, threshold: int) -> dict:
def make_stats(sales):
if not sales:
return None
total = sum(sales)
if total < threshold:
return None
return {"total": total, "count": len(sales),
"average": total / len(sales), "max": max(sales)}
return {
r["product"]: stats
for r in data
if (stats := make_stats(r["sales"])) is not None
}
def analyze_sales(data: list, threshold: int) -> dict:
get_sales = lambda r: r["sales"]
valid = filter(lambda r: get_sales(r) and sum(get_sales(r)) >= threshold, data)
def make_agg(r):
sales = r["sales"]
total = sum(sales)
return (r["product"], {
"total": total,
"count": len(sales),
"average": total / len(sales),
"max": max(sales),
})
return dict(map(make_agg, valid))
Key takeaways¶
Compute sales once, reuse everywhere
Extract sales = record["sales"] and compute total = sum(sales) once. Then use total, len(sales), and max(sales) - no repeated iteration over the same list.
Guard against empty sales lists
if not sales: continue skips records with no transactions before any computation. Without this, sum([])/len([]) raises a ZeroDivisionError.
This is the same pattern as process_orders (S3Q1 Set 3)
Both problems: extract values, compute aggregations, filter by threshold. The structure is identical - only the field names differ. Recognising the pattern saves time in an exam setting.