S3Q1 · Student Score Filter¶
⚡ Quick Reference
Function: filter_students(data: dict, criteria: str) -> set
Four criteria, one dispatcher:
def filter_students(data: dict, criteria: str) -> set:
if criteria == "excellent":
return {name for name, scores in data.items()
if sum(scores) / len(scores) >= 85}
elif criteria == "good":
return {name for name, scores in data.items()
if 50 <= sum(scores) / len(scores) < 85}
elif criteria == "all_pass":
return {name for name, scores in data.items()
if all(s >= 50 for s in scores)}
elif criteria == "balanced":
return {name for name, scores in data.items()
if max(scores) - min(scores) <= 10}
Key rules:
- Returns a set of student names
- excellent: average >= 85
- good: 50 <= average < 85
- all_pass: every score >= 50 - use all()
- balanced: max - min <= 10
Problem Statement¶
Problem
Write a function filter_students(data: dict, criteria: str) -> set that filters student names from a dictionary based on the given criteria.
| Criteria | Condition |
|---|---|
"excellent" |
Average score >= 85 |
"good" |
Average score >= 50 and < 85 |
"all_pass" |
All individual scores >= 50 |
"balanced" |
max score - min score <= 10 |
Example data:
data = {
"Alice": [90, 80, 85],
"Bob": [40, 50, 60],
"Charlie": [30, 40, 20],
"Ram": [78, 92, 85, 79, 81],
"Babu": [67, 70, 75],
"Kumar": [100, 100, 100, 100, 100, 100, 40]
}
Expected outputs:
"excellent"
{'Alice', 'Kumar'}
"good"
{'Babu', 'Bob', 'Ram'}
"all_pass"
{'Alice', 'Babu', 'Ram'}
"balanced"
{'Alice', 'Babu'}
Breaking down each criteria¶
excellent - average >= 85¶
| Student | Scores | Average | >= 85? |
|---|---|---|---|
| Alice | [90, 80, 85] | 85.0 | ✅ |
| Bob | [40, 50, 60] | 50.0 | ❌ |
| Charlie | [30, 40, 20] | 30.0 | ❌ |
| Ram | [78, 92, 85, 79, 81] | 83.0 | ❌ |
| Babu | [67, 70, 75] | 70.67 | ❌ |
| Kumar | [100×6, 40] | 91.43 | ✅ |
Result: {'Alice', 'Kumar'}
good - 50 <= average < 85¶
Same average calculation, different bounds. Note: good and excellent are mutually exclusive - a student can't be in both.
| Student | Average | 50 <= avg < 85? |
|---|---|---|
| Bob | 50.0 | ✅ |
| Ram | 83.0 | ✅ |
| Babu | 70.67 | ✅ |
Result: {'Bob', 'Ram', 'Babu'}
all_pass - every score >= 50¶
One failing score disqualifies. Use all():
| Student | Scores | Any score < 50? | all_pass? |
|---|---|---|---|
| Alice | [90, 80, 85] | No | ✅ |
| Bob | [40, 50, 60] | Yes (40) | ❌ |
| Charlie | [30, 40, 20] | Yes | ❌ |
| Ram | [78, 92, 85, 79, 81] | No | ✅ |
| Babu | [67, 70, 75] | No | ✅ |
| Kumar | [100×6, 40] | Yes (40) | ❌ |
Result: {'Alice', 'Ram', 'Babu'}
all() short-circuits
all(condition for x in iterable) returns False the moment it finds one element that fails the condition - it doesn't check the rest. This makes it efficient for large score lists.
balanced - max - min <= 10¶
| Student | Max | Min | Difference | <= 10? |
|---|---|---|---|---|
| Alice | 90 | 80 | 10 | ✅ |
| Bob | 60 | 40 | 20 | ❌ |
| Charlie | 40 | 20 | 20 | ❌ |
| Ram | 92 | 78 | 14 | ❌ |
| Babu | 75 | 67 | 8 | ✅ |
| Kumar | 100 | 40 | 60 | ❌ |
Result: {'Alice', 'Babu'}
Solution approaches¶
def filter_students(data: dict, criteria: str) -> set:
if criteria == "excellent":
return {name for name, scores in data.items()
if sum(scores) / len(scores) >= 85}
elif criteria == "good":
return {name for name, scores in data.items()
if 50 <= sum(scores) / len(scores) < 85}
elif criteria == "all_pass":
return {name for name, scores in data.items()
if all(s >= 50 for s in scores)}
elif criteria == "balanced":
return {name for name, scores in data.items()
if max(scores) - min(scores) <= 10}
A set comprehension per criteria - clean, readable, and directly returns the required set type.
def filter_students(data: dict, criteria: str) -> set:
result = set()
for name, scores in data.items():
avg = sum(scores) / len(scores)
if criteria == "excellent":
if avg >= 85:
result.add(name)
elif criteria == "good":
if 50 <= avg < 85:
result.add(name)
elif criteria == "all_pass":
if all(s >= 50 for s in scores):
result.add(name)
elif criteria == "balanced":
if max(scores) - min(scores) <= 10:
result.add(name)
return result
One loop through all students, check the criteria inside. The average is computed once per student regardless of criteria.
def is_excellent(scores):
return sum(scores) / len(scores) >= 85
def is_good(scores):
return 50 <= sum(scores) / len(scores) < 85
def is_all_pass(scores):
return all(s >= 50 for s in scores)
def is_balanced(scores):
return max(scores) - min(scores) <= 10
def filter_students(data: dict, criteria: str) -> set:
checks = {
"excellent": is_excellent,
"good": is_good,
"all_pass": is_all_pass,
"balanced": is_balanced,
}
check = checks[criteria]
return {name for name, scores in data.items() if check(scores)}
Each condition is a named helper function. The dispatch dictionary maps criteria strings to the right function. The most extensible design - adding a new criteria is just adding one helper and one dict entry.
def filter_students(data: dict, criteria: str) -> set:
conditions = {
"excellent": lambda scores: sum(scores) / len(scores) >= 85,
"good": lambda scores: 50 <= sum(scores) / len(scores) < 85,
"all_pass": lambda scores: all(s >= 50 for s in scores),
"balanced": lambda scores: max(scores) - min(scores) <= 10,
}
check = conditions[criteria]
return set(
name for name, scores in data.items() if check(scores)
)
Each criteria condition is expressed as a lambda stored directly in the dispatch dictionary - no need for separately defined helper functions. filter selects matching names, and set() wraps the result. Compact and all logic lives in one place.
Key takeaways¶
all() for "every element must pass"
all(cond for x in lst) returns True only if every element satisfies the condition. Short-circuits on the first failure - efficient for large lists.
Chained comparison for ranges
50 <= avg < 85 is cleaner than avg >= 50 and avg < 85. Python supports chained comparisons natively.
Set comprehension {x for x in ...}
Like a list comprehension but returns a set. Use it when the problem explicitly asks for a set, or when the result must have no duplicates.