S3Q1 · Filter Customers by Spending Pattern¶
⚡ Quick Reference
Function: filter_customers(data: dict, command: str) -> set
Core idea: For each customer, compute avg / min / max, then test the condition for the requested command.
def avg(L): return sum(L) / len(L)
def filter_customers(data, command):
result = set()
for name, spending in data.items():
a, mn, mx = avg(spending), min(spending), max(spending)
if command == 'high_spender' and a >= 50000: result.add(name)
elif command == 'moderate_spender' and 20000 <= a < 50000: result.add(name)
elif command == 'consistent_spender' and mn >= 10000: result.add(name)
elif command == 'stable_spending' and mx - mn <= 5000: result.add(name)
return result
Key rules:
- high_spender: avg >= 50000
- moderate_spender: 20000 <= avg < 50000
- consistent_spender: every month >= 10000, so min >= 10000
- stable_spending: max - min <= 5000
Problem Statement¶
Problem
Given a dictionary data mapping customer names to lists of monthly spending amounts, return a set of customer names matching the given command:
'high_spender'-- average >= 50000'moderate_spender'-- average in [20000, 50000)'consistent_spender'-- every month >= 10000'stable_spending'-- max - min <= 5000
Similar problems
This follows the same four-category filter pattern as S3Q1 of 2026 May Set 1 (Workout Data Analytics) and S3Q1 of 2026 May Set 2 (Filter Houses). The structure is identical -- only the domain and thresholds differ.
Example:
data = {
"Akhil": [45000, 55000, 50000],
"Bina": [12000, 18000, 15000],
"Charan": [70000, 65000, 72000],
"Divya": [25000, 30000, 28000],
"Esha": [15000, 16000, 15500]
}
filter_customers(data, 'high_spender')
{'Akhil', 'Charan'}
filter_customers(data, 'stable_spending')
{'Divya', 'Esha'}
Understanding the problem¶
| Statistic | Used by |
|---|---|
avg = sum / len |
high_spender, moderate_spender |
min(spending) |
consistent_spender |
max - min |
stable_spending |
Full trace:
| Customer | Avg | Min | Spread | HS | MS | CS | SS |
|---|---|---|---|---|---|---|---|
| Akhil | 50000 | 45000 | 10000 | ✅ | ✅ | ||
| Bina | 15000 | 12000 | 6000 | ✅ | |||
| Charan | 69000 | 65000 | 7000 | ✅ | ✅ | ||
| Divya | 27667 | 25000 | 5000 | ✅ | ✅ | ✅ | |
| Esha | 15500 | 15000 | 1000 | ✅ | ✅ |
Akhil is high_spender, not moderate_spender
Akhil's average is exactly 50000, which meets avg >= 50000 (high) but fails avg < 50000 (moderate). The boundary belongs to high_spender. Read the inequality direction carefully in each problem.
Divya's spread is exactly 5000 -- qualifies for stable_spending
max - min = 30000 - 25000 = 5000 <= 5000 ✅. The condition is <=, so the boundary value qualifies.
Solution approaches¶
def avg(L): return sum(L) / len(L)
def filter_customers(data: dict, command: str) -> set:
result = set()
for name, spending in data.items():
a = avg(spending)
minimum = min(spending)
maximum = max(spending)
spread = maximum - minimum
if command == 'high_spender':
if a >= 50000:
result.add(name)
elif command == 'moderate_spender':
if 20000 <= a < 50000:
result.add(name)
elif command == 'consistent_spender':
if minimum >= 10000: # min >= 10000 means all months >= 10000
result.add(name)
elif command == 'stable_spending':
if spread <= 5000:
result.add(name)
return result
def avg(L): return sum(L) / len(L)
def filter_customers(data: dict, command: str) -> set:
conditions = {
'high_spender': lambda s: avg(s) >= 50000,
'moderate_spender': lambda s: 20000 <= avg(s) < 50000,
'consistent_spender': lambda s: min(s) >= 10000,
'stable_spending': lambda s: max(s) - min(s) <= 5000,
}
test = conditions[command]
return {name for name, spending in data.items() if test(spending)}
def filter_customers(data: dict, command: str) -> set:
avg = lambda s: sum(s) / len(s)
test = {
'high_spender': lambda s: avg(s) >= 50000,
'moderate_spender': lambda s: 20000 <= avg(s) < 50000,
'consistent_spender': lambda s: min(s) >= 10000,
'stable_spending': lambda s: max(s) - min(s) <= 5000,
}[command]
return {n for n, s in data.items() if test(s)}
Key takeaways¶
Helper function avg() keeps code DRY
The template provides avg(L) as a helper. Using it avoids repeating sum(L)/len(L) in every branch. When the OPPE template includes a helper, use it -- it signals the intended approach and keeps the solution readable.
min() for "all elements above threshold"
min(spending) >= 10000 is equivalent to all(x >= 10000 for x in spending). If the smallest value passes the threshold, all values do. It's O(n) and more concise than an explicit loop.
Dict of lambdas scales to new categories
Adding a fifth category is one new line in the dict -- no new elif branch to insert. This pattern separates the rule definitions from the iteration logic, making the code easier to extend and test independently.