🔄 Lesson 5.3: Iterating Over Collections
You know how to create and modify collections. Now it's time to master the art of processing every element — transforming data, filtering results, accumulating totals, and combining collections. These patterns are the backbone of real-world programming.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Choose the right loop type for each situation
- Use enumerate to get both index and value
- Apply map, filter, and reduce to transform collections
- Use any/all checks, zip, and other common patterns
- Chain operations for elegant data pipelines
- Know when to use loops vs. functional methods
Estimated Time: 45 minutes
Project: Build a grade report processor that filters, transforms, and summarizes student data
📑 In This Lesson
Basic Collection Loops
The simplest way to process every element: a for loop. You've seen this in Module 3 — now let's focus on the patterns specific to collections.
# for...in — the standard Python loop
colors = ["red", "green", "blue"]
for color in colors:
print(color)
# red, green, blue
# When you need the index too, DON'T do this:
# for i in range(len(colors)): ← works but not Pythonic
# print(i, colors[i])
# DO use enumerate() instead (next section!)
# Looping over a dictionary
scores = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Keys only (default)
for name in scores:
print(name)
# Key-value pairs
for name, score in scores.items():
print(f"{name}: {score}")
let colors = ["red", "green", "blue"];
// for...of — iterates over VALUES (use this for arrays!)
for (let color of colors) {
console.log(color);
}
// red, green, blue
// .forEach() — callback style
colors.forEach(color => console.log(color));
// Classic for loop — when you need the index
for (let i = 0; i < colors.length; i++) {
console.log(i, colors[i]);
}
// ⚠️ for...in — iterates over KEYS (use for objects, NOT arrays)
let scores = { Alice: 95, Bob: 87, Charlie: 92 };
for (let name in scores) {
console.log(`${name}: ${scores[name]}`);
}
// for...of with Object.entries() (cleaner for objects)
for (let [name, score] of Object.entries(scores)) {
console.log(`${name}: ${score}`);
}
List<string> colors = new List<string> { "red", "green", "blue" };
// foreach — the standard C# collection loop
foreach (string color in colors)
{
Console.WriteLine(color);
}
// Classic for loop — when you need the index
for (int i = 0; i < colors.Count; i++)
{
Console.WriteLine($"{i}: {colors[i]}");
}
// Looping over a dictionary
Dictionary<string, int> scores = new Dictionary<string, int>
{
{ "Alice", 95 }, { "Bob", 87 }, { "Charlie", 92 }
};
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
⚠️ JavaScript: for...of vs. for...in
for...of gives you values — use it for arrays. for...in gives you keys (indices for arrays, property names for objects) — use it for objects. Using for...in on arrays can produce unexpected results because it also iterates over inherited properties.
🎓 Instructor Note: Delivery Guidance
The for...of vs. for...in distinction is a perennial JavaScript confusion point. A simple mnemonic: "of = values of an array" and "in = keys in an object." Write both on the board side by side with examples. Python and C# don't have this ambiguity — their for...in / foreach always gives values for lists and key-value pairs for dicts.
Enumerate: Index + Value
Often you need both the position and the value during iteration. Each language has a clean way to do this.
fruits = ["apple", "banana", "cherry", "date"]
# enumerate() gives you (index, value) pairs
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# 0: apple
# 1: banana
# 2: cherry
# 3: date
# Start counting from a different number
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
# 1. apple
# 2. banana
# 3. cherry
# 4. date
# Useful for finding items
scores = [85, 92, 78, 95, 88]
for i, score in enumerate(scores):
if score >= 90:
print(f"High score at position {i}: {score}")
# High score at position 1: 92
# High score at position 3: 95
let fruits = ["apple", "banana", "cherry", "date"];
// .forEach() provides (value, index)
fruits.forEach((fruit, i) => {
console.log(`${i}: ${fruit}`);
});
// for...of with .entries() — gives [index, value] pairs
for (let [i, fruit] of fruits.entries()) {
console.log(`${i}: ${fruit}`);
}
// Classic for loop also works
for (let i = 0; i < fruits.length; i++) {
console.log(`${i + 1}. ${fruits[i]}`);
}
// Finding items with index
let scores = [85, 92, 78, 95, 88];
scores.forEach((score, i) => {
if (score >= 90) {
console.log(`High score at position ${i}: ${score}`);
}
});
List<string> fruits = new List<string>
{ "apple", "banana", "cherry", "date" };
// Classic for loop (most common in C# for indexed access)
for (int i = 0; i < fruits.Count; i++)
{
Console.WriteLine($"{i}: {fruits[i]}");
}
// LINQ Select with index
foreach (var (fruit, i) in fruits.Select((f, i) => (f, i)))
{
Console.WriteLine($"{i}: {fruit}");
}
// Finding items with index
List<int> scores = new List<int> { 85, 92, 78, 95, 88 };
for (int i = 0; i < scores.Count; i++)
{
if (scores[i] >= 90)
Console.WriteLine($"High score at position {i}: {scores[i]}");
}
Enumerate Quick Reference
| Pattern | 🐍 Python | ⚡ JavaScript | 🔷 C# |
|---|---|---|---|
| Index + value | enumerate(list) |
.forEach((val, i))or .entries() |
for (int i = 0; ...) |
| Custom start | enumerate(list, 1) |
Manual i + 1 |
Manual i + 1 |
Map and Filter
Two of the most important collection operations: map transforms every element, and filter keeps only elements that pass a test. We previewed these in Lesson 13 — now let's go deeper.
prices = [10.99, 24.50, 7.25, 15.00, 32.99]
# List comprehension (preferred in Python)
with_tax = [price * 1.08 for price in prices]
print(with_tax) # [11.87, 26.46, 7.83, 16.20, 35.63]
# Round them
with_tax = [round(price * 1.08, 2) for price in prices]
# map() function (alternative)
with_tax2 = list(map(lambda p: round(p * 1.08, 2), prices))
# Transform strings
names = ["alice smith", "bob jones", "charlie brown"]
titled = [name.title() for name in names]
print(titled) # ['Alice Smith', 'Bob Jones', 'Charlie Brown']
# Extract a field from dicts
students = [
{"name": "Alice", "grade": 95},
{"name": "Bob", "grade": 87},
{"name": "Charlie", "grade": 92}
]
names_only = [s["name"] for s in students]
print(names_only) # ['Alice', 'Bob', 'Charlie']
let prices = [10.99, 24.50, 7.25, 15.00, 32.99];
// .map() — returns a new array with transformed values
let withTax = prices.map(price => Math.round(price * 1.08 * 100) / 100);
console.log(withTax); // [11.87, 26.46, 7.83, 16.20, 35.63]
// Transform strings
let names = ["alice smith", "bob jones", "charlie brown"];
let titled = names.map(name =>
name.split(" ").map(w => w[0].toUpperCase() + w.slice(1)).join(" ")
);
console.log(titled); // ['Alice Smith', 'Bob Jones', 'Charlie Brown']
// Extract a field from objects
let students = [
{ name: "Alice", grade: 95 },
{ name: "Bob", grade: 87 },
{ name: "Charlie", grade: 92 }
];
let namesOnly = students.map(s => s.name);
console.log(namesOnly); // ['Alice', 'Bob', 'Charlie']
List<double> prices = new List<double>
{ 10.99, 24.50, 7.25, 15.00, 32.99 };
// .Select() — LINQ's map
List<double> withTax = prices
.Select(p => Math.Round(p * 1.08, 2))
.ToList();
Console.WriteLine(string.Join(", ", withTax));
// Transform strings
List<string> names = new List<string>
{ "alice smith", "bob jones", "charlie brown" };
var titled = names
.Select(n => System.Globalization.CultureInfo.CurrentCulture
.TextInfo.ToTitleCase(n))
.ToList();
// Extract a field (with anonymous types or tuples)
var students = new List<(string Name, int Grade)>
{
("Alice", 95), ("Bob", 87), ("Charlie", 92)
};
List<string> namesOnly = students.Select(s => s.Name).ToList();
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# List comprehension with condition
evens = [n for n in numbers if n % 2 == 0]
print(evens) # [2, 4, 6, 8, 10]
big = [n for n in numbers if n > 5]
print(big) # [6, 7, 8, 9, 10]
# filter() function
evens2 = list(filter(lambda n: n % 2 == 0, numbers))
# Filter objects
students = [
{"name": "Alice", "grade": 95},
{"name": "Bob", "grade": 67},
{"name": "Charlie", "grade": 92},
{"name": "Diana", "grade": 55}
]
passing = [s for s in students if s["grade"] >= 70]
print([s["name"] for s in passing]) # ['Alice', 'Charlie']
# Filter + transform in one comprehension
honor_names = [s["name"] for s in students if s["grade"] >= 90]
print(honor_names) # ['Alice', 'Charlie']
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// .filter() — returns elements that pass the test
let evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
let big = numbers.filter(n => n > 5);
console.log(big); // [6, 7, 8, 9, 10]
// Filter objects
let students = [
{ name: "Alice", grade: 95 },
{ name: "Bob", grade: 67 },
{ name: "Charlie", grade: 92 },
{ name: "Diana", grade: 55 }
];
let passing = students.filter(s => s.grade >= 70);
console.log(passing.map(s => s.name)); // ['Alice', 'Charlie']
// Filter + transform (chain them)
let honorNames = students
.filter(s => s.grade >= 90)
.map(s => s.name);
console.log(honorNames); // ['Alice', 'Charlie']
List<int> numbers = new List<int>
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// .Where() — LINQ's filter
List<int> evens = numbers.Where(n => n % 2 == 0).ToList();
Console.WriteLine(string.Join(", ", evens)); // 2, 4, 6, 8, 10
// Filter objects
var students = new List<(string Name, int Grade)>
{
("Alice", 95), ("Bob", 67), ("Charlie", 92), ("Diana", 55)
};
var passing = students.Where(s => s.Grade >= 70).ToList();
// Filter + transform (chain)
var honorNames = students
.Where(s => s.Grade >= 90)
.Select(s => s.Name)
.ToList();
Console.WriteLine(string.Join(", ", honorNames)); // Alice, Charlie
Reduce: Accumulating a Result
Reduce (also called fold or aggregate) processes every element and combines them into a single result — a total, a string, a dictionary, or anything else.
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# Sum (built-in is simpler, but reduce shows the concept)
total = reduce(lambda acc, n: acc + n, numbers, 0)
print(total) # 15
# But Python prefers built-in functions:
print(sum(numbers)) # 15
print(max(numbers)) # 5
print(min(numbers)) # 1
# Join strings
words = ["Hello", "beautiful", "world"]
sentence = " ".join(words)
print(sentence) # "Hello beautiful world"
# Build a frequency map (reduce-style thinking)
letters = ["a", "b", "a", "c", "b", "a"]
freq = {}
for letter in letters:
freq[letter] = freq.get(letter, 0) + 1
print(freq) # {'a': 3, 'b': 2, 'c': 1}
# Group items
students = [
{"name": "Alice", "grade": "A"},
{"name": "Bob", "grade": "B"},
{"name": "Charlie", "grade": "A"},
{"name": "Diana", "grade": "B"}
]
by_grade = {}
for s in students:
by_grade.setdefault(s["grade"], []).append(s["name"])
print(by_grade) # {'A': ['Alice', 'Charlie'], 'B': ['Bob', 'Diana']}
let numbers = [1, 2, 3, 4, 5];
// .reduce(callback, initialValue)
let total = numbers.reduce((acc, n) => acc + n, 0);
console.log(total); // 15
// Find max
let max = numbers.reduce((a, b) => a > b ? a : b);
console.log(max); // 5
// Join strings
let words = ["Hello", "beautiful", "world"];
let sentence = words.join(" ");
console.log(sentence); // "Hello beautiful world"
// Build a frequency map
let letters = ["a", "b", "a", "c", "b", "a"];
let freq = letters.reduce((acc, letter) => {
acc[letter] = (acc[letter] ?? 0) + 1;
return acc;
}, {});
console.log(freq); // {a: 3, b: 2, c: 1}
// Group items
let students = [
{ name: "Alice", grade: "A" },
{ name: "Bob", grade: "B" },
{ name: "Charlie", grade: "A" },
{ name: "Diana", grade: "B" }
];
let byGrade = students.reduce((acc, s) => {
(acc[s.grade] ??= []).push(s.name);
return acc;
}, {});
console.log(byGrade); // {A: ['Alice','Charlie'], B: ['Bob','Diana']}
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// .Aggregate() — LINQ's reduce
int total = numbers.Aggregate(0, (acc, n) => acc + n);
Console.WriteLine(total); // 15
// Built-in LINQ methods (usually preferred)
Console.WriteLine(numbers.Sum()); // 15
Console.WriteLine(numbers.Max()); // 5
Console.WriteLine(numbers.Min()); // 1
// Join strings
List<string> words = new List<string> { "Hello", "beautiful", "world" };
string sentence = string.Join(" ", words);
Console.WriteLine(sentence);
// Group items
var students = new List<(string Name, string Grade)>
{
("Alice", "A"), ("Bob", "B"), ("Charlie", "A"), ("Diana", "B")
};
var byGrade = students
.GroupBy(s => s.Grade)
.ToDictionary(g => g.Key, g => g.Select(s => s.Name).ToList());
foreach (var (grade, names) in byGrade)
Console.WriteLine($"{grade}: {string.Join(", ", names)}");
// A: Alice, Charlie
// B: Bob, Diana
💡 When to Use Reduce
Reduce is the most powerful iteration method — any operation you can do with a loop, you can express as a reduce. But power comes with complexity. Use built-in functions (sum, max, join) when they exist; reach for reduce when you need custom accumulation logic like building frequency maps or grouping data.
Common Patterns
Beyond map, filter, and reduce, here are patterns you'll use regularly.
Any / All — Check Conditions
scores = [85, 92, 78, 95, 88]
# any() — is at least one True?
has_perfect = any(s == 100 for s in scores)
has_failing = any(s < 60 for s in scores)
print(has_perfect) # False
print(has_failing) # False
# all() — are ALL True?
all_passing = all(s >= 70 for s in scores)
print(all_passing) # True
all_honor = all(s >= 90 for s in scores)
print(all_honor) # False
let scores = [85, 92, 78, 95, 88];
// .some() — at least one passes?
let hasPerfect = scores.some(s => s === 100);
let hasFailing = scores.some(s => s < 60);
console.log(hasPerfect); // false
console.log(hasFailing); // false
// .every() — ALL pass?
let allPassing = scores.every(s => s >= 70);
console.log(allPassing); // true
let allHonor = scores.every(s => s >= 90);
console.log(allHonor); // false
List<int> scores = new List<int> { 85, 92, 78, 95, 88 };
// .Any() — at least one?
bool hasPerfect = scores.Any(s => s == 100); // false
bool hasFailing = scores.Any(s => s < 60); // false
// .All() — every one?
bool allPassing = scores.All(s => s >= 70); // true
bool allHonor = scores.All(s => s >= 90); // false
Find — Get the First Match
users = [
{"name": "Alice", "role": "admin"},
{"name": "Bob", "role": "user"},
{"name": "Charlie", "role": "admin"}
]
# next() with generator expression
first_admin = next((u for u in users if u["role"] == "admin"), None)
print(first_admin) # {'name': 'Alice', 'role': 'admin'}
# With a default if not found
first_mod = next((u for u in users if u["role"] == "mod"), None)
print(first_mod) # None
let users = [
{ name: "Alice", role: "admin" },
{ name: "Bob", role: "user" },
{ name: "Charlie", role: "admin" }
];
// .find() — first element that passes
let firstAdmin = users.find(u => u.role === "admin");
console.log(firstAdmin); // {name: 'Alice', role: 'admin'}
// .findIndex() — index of first match
let idx = users.findIndex(u => u.role === "admin");
console.log(idx); // 0
// Not found = undefined / -1
let firstMod = users.find(u => u.role === "mod");
console.log(firstMod); // undefined
var users = new List<(string Name, string Role)>
{
("Alice", "admin"), ("Bob", "user"), ("Charlie", "admin")
};
// .First() — throws if none found
var firstAdmin = users.First(u => u.Role == "admin");
Console.WriteLine(firstAdmin.Name); // Alice
// .FirstOrDefault() — returns default if none found
var firstMod = users.FirstOrDefault(u => u.Role == "mod");
Console.WriteLine(firstMod.Name ?? "None"); // None
// .FindIndex()
int idx = users.FindIndex(u => u.Role == "admin");
Console.WriteLine(idx); // 0
Zip — Combine Two Collections
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
# zip() pairs elements by position
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Alice: 95, Bob: 87, Charlie: 92
# Create a dictionary from two lists
grade_book = dict(zip(names, scores))
print(grade_book) # {'Alice': 95, 'Bob': 87, 'Charlie': 92}
let names = ["Alice", "Bob", "Charlie"];
let scores = [95, 87, 92];
// No built-in zip — use .map() with index
let paired = names.map((name, i) => [name, scores[i]]);
console.log(paired); // [['Alice',95], ['Bob',87], ['Charlie',92]]
// Create an object from two arrays
let gradeBook = Object.fromEntries(
names.map((name, i) => [name, scores[i]])
);
console.log(gradeBook); // {Alice: 95, Bob: 87, Charlie: 92}
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
List<int> scores = new List<int> { 95, 87, 92 };
// .Zip() pairs elements
foreach (var (name, score) in names.Zip(scores))
{
Console.WriteLine($"{name}: {score}");
}
// Create a dictionary from two lists
var gradeBook = names.Zip(scores)
.ToDictionary(pair => pair.First, pair => pair.Second);
Chaining Operations
The real power of map/filter/reduce is chaining them together to build data processing pipelines.
# Scenario: Process student records
students = [
{"name": "Alice", "grade": 95, "subject": "Math"},
{"name": "Bob", "grade": 67, "subject": "Math"},
{"name": "Charlie", "grade": 92, "subject": "Science"},
{"name": "Diana", "grade": 88, "subject": "Math"},
{"name": "Eve", "grade": 55, "subject": "Science"},
{"name": "Frank", "grade": 91, "subject": "Math"}
]
# Pipeline: Math students → passing → names → sorted
math_honor = sorted([
s["name"]
for s in students
if s["subject"] == "Math" and s["grade"] >= 70
], reverse=True)
print(math_honor) # ['Frank', 'Diana', 'Alice']
# Pipeline: Average grade per subject
from collections import defaultdict
by_subject = defaultdict(list)
for s in students:
by_subject[s["subject"]].append(s["grade"])
averages = {
subject: round(sum(grades) / len(grades), 1)
for subject, grades in by_subject.items()
}
print(averages) # {'Math': 85.3, 'Science': 73.5}
let students = [
{ name: "Alice", grade: 95, subject: "Math" },
{ name: "Bob", grade: 67, subject: "Math" },
{ name: "Charlie", grade: 92, subject: "Science" },
{ name: "Diana", grade: 88, subject: "Math" },
{ name: "Eve", grade: 55, subject: "Science" },
{ name: "Frank", grade: 91, subject: "Math" }
];
// Pipeline: Math students → passing → names → sorted
let mathHonor = students
.filter(s => s.subject === "Math")
.filter(s => s.grade >= 70)
.map(s => s.name)
.sort()
.reverse();
console.log(mathHonor); // ['Frank', 'Diana', 'Alice']
// Pipeline: Average grade per subject
let bySubject = students.reduce((acc, s) => {
(acc[s.subject] ??= []).push(s.grade);
return acc;
}, {});
let averages = Object.fromEntries(
Object.entries(bySubject).map(([subject, grades]) => [
subject,
Math.round(grades.reduce((a, b) => a + b, 0) / grades.length * 10) / 10
])
);
console.log(averages); // {Math: 85.3, Science: 73.5}
var students = new List<(string Name, int Grade, string Subject)>
{
("Alice", 95, "Math"), ("Bob", 67, "Math"),
("Charlie", 92, "Science"), ("Diana", 88, "Math"),
("Eve", 55, "Science"), ("Frank", 91, "Math")
};
// Pipeline: Math students → passing → names → sorted
var mathHonor = students
.Where(s => s.Subject == "Math")
.Where(s => s.Grade >= 70)
.Select(s => s.Name)
.OrderByDescending(n => n)
.ToList();
Console.WriteLine(string.Join(", ", mathHonor)); // Frank, Diana, Alice
// Pipeline: Average grade per subject
var averages = students
.GroupBy(s => s.Subject)
.ToDictionary(g => g.Key, g => Math.Round(g.Average(s => s.Grade), 1));
foreach (var (subject, avg) in averages)
Console.WriteLine($"{subject}: {avg}");
✅ Read Chained Operations Top to Bottom
When you see a chain like .filter().filter().map().sort(), read it as a step-by-step process: "Start with all students, keep only Math students, keep only passing, extract names, sort alphabetically." Each step feeds its result into the next. This is called a data pipeline.
Loops vs. Functional Methods
You can accomplish the same thing with either approach. Here's guidance on when to use which.
| Use Case | Recommendation | Why |
|---|---|---|
| Transform every element | map / comprehension | Clear intent: "make a new collection from this one" |
| Keep some elements | filter / comprehension | Clear intent: "keep items matching this condition" |
| Accumulate a single result | reduce or loop | Use built-ins (sum, max) when possible; loops for complex logic |
| Side effects (print, modify external state) | for loop | Loops make side effects visible; map/filter should be pure |
| Break early on condition | for loop | map/filter process everything; loops can break |
| Complex multi-step processing | chained methods | Pipeline reads like a recipe; nested loops get confusing |
💡 The Pragmatic Rule
Use whichever is easier to read. If a chain of map/filter makes the intent clear in one line, use it. If you're struggling to cram logic into a lambda, use a for loop. Readability beats cleverness every time.
Exercises
🏋️ Exercise 1: Data Transformer
Objective: Given a list of temperatures in Fahrenheit, produce:
- A list of temperatures converted to Celsius (formula:
(F - 32) × 5/9) - Only temperatures above freezing (Celsius > 0)
- The average of all Celsius temperatures
Input: [32, 50, 68, 14, 86, 23, 104]
✅ Solution
fahrenheit = [32, 50, 68, 14, 86, 23, 104]
celsius = [round((f - 32) * 5/9, 1) for f in fahrenheit]
print(celsius) # [0.0, 10.0, 20.0, -10.0, 30.0, -5.0, 40.0]
above_freezing = [c for c in celsius if c > 0]
print(above_freezing) # [10.0, 20.0, 30.0, 40.0]
average = round(sum(celsius) / len(celsius), 1)
print(f"Average: {average}°C") # 12.1°C
let fahrenheit = [32, 50, 68, 14, 86, 23, 104];
let celsius = fahrenheit.map(f => Math.round((f - 32) * 5/9 * 10) / 10);
console.log(celsius); // [0, 10, 20, -10, 30, -5, 40]
let aboveFreezing = celsius.filter(c => c > 0);
console.log(aboveFreezing); // [10, 20, 30, 40]
let average = Math.round(celsius.reduce((a, b) => a + b, 0) / celsius.length * 10) / 10;
console.log(`Average: ${average}°C`); // 12.1°C
List<int> fahrenheit = new List<int> { 32, 50, 68, 14, 86, 23, 104 };
var celsius = fahrenheit.Select(f => Math.Round((f - 32) * 5.0 / 9, 1)).ToList();
var aboveFreezing = celsius.Where(c => c > 0).ToList();
double average = Math.Round(celsius.Average(), 1);
Console.WriteLine($"Average: {average}°C"); // 12.1°C
🏋️ Exercise 2: Grade Report Processor
Objective: Given student records, write functions to:
get_passing(students)— return students with grade >= 70get_honor_roll(students)— return names of students with grade >= 90, sorted alphabeticallyaverage_by_subject(students)— return a dict of subject → average gradehighest_per_subject(students)— return the top student per subject
✅ Solution
students = [
{"name": "Alice", "grade": 95, "subject": "Math"},
{"name": "Bob", "grade": 67, "subject": "Math"},
{"name": "Charlie", "grade": 92, "subject": "Science"},
{"name": "Diana", "grade": 88, "subject": "Science"},
{"name": "Eve", "grade": 91, "subject": "Math"}
]
def get_passing(students):
return [s for s in students if s["grade"] >= 70]
def get_honor_roll(students):
return sorted([s["name"] for s in students if s["grade"] >= 90])
def average_by_subject(students):
from collections import defaultdict
by_subj = defaultdict(list)
for s in students:
by_subj[s["subject"]].append(s["grade"])
return {subj: round(sum(g)/len(g), 1) for subj, g in by_subj.items()}
def highest_per_subject(students):
best = {}
for s in students:
subj = s["subject"]
if subj not in best or s["grade"] > best[subj]["grade"]:
best[subj] = s
return best
print("Passing:", [s["name"] for s in get_passing(students)])
print("Honor Roll:", get_honor_roll(students))
print("Averages:", average_by_subject(students))
print("Top per subject:", {k: v["name"] for k, v in highest_per_subject(students).items()})
let students = [
{ name: "Alice", grade: 95, subject: "Math" },
{ name: "Bob", grade: 67, subject: "Math" },
{ name: "Charlie", grade: 92, subject: "Science" },
{ name: "Diana", grade: 88, subject: "Science" },
{ name: "Eve", grade: 91, subject: "Math" }
];
const getPassing = (students) =>
students.filter(s => s.grade >= 70);
const getHonorRoll = (students) =>
students.filter(s => s.grade >= 90).map(s => s.name).sort();
const averageBySubject = (students) => {
let groups = students.reduce((acc, s) => {
(acc[s.subject] ??= []).push(s.grade);
return acc;
}, {});
return Object.fromEntries(
Object.entries(groups).map(([subj, grades]) =>
[subj, Math.round(grades.reduce((a,b) => a+b,0) / grades.length * 10) / 10])
);
};
const highestPerSubject = (students) =>
students.reduce((acc, s) => {
if (!acc[s.subject] || s.grade > acc[s.subject].grade)
acc[s.subject] = s;
return acc;
}, {});
console.log("Passing:", getPassing(students).map(s => s.name));
console.log("Honor Roll:", getHonorRoll(students));
console.log("Averages:", averageBySubject(students));
console.log("Top:", highestPerSubject(students));
var students = new List<(string Name, int Grade, string Subject)>
{
("Alice", 95, "Math"), ("Bob", 67, "Math"),
("Charlie", 92, "Science"), ("Diana", 88, "Science"),
("Eve", 91, "Math")
};
var passing = students.Where(s => s.Grade >= 70).ToList();
var honorRoll = students.Where(s => s.Grade >= 90)
.Select(s => s.Name).OrderBy(n => n).ToList();
var avgBySubj = students.GroupBy(s => s.Subject)
.ToDictionary(g => g.Key, g => Math.Round(g.Average(s => s.Grade), 1));
var topPerSubj = students.GroupBy(s => s.Subject)
.ToDictionary(g => g.Key, g => g.OrderByDescending(s => s.Grade).First());
Console.WriteLine($"Passing: {string.Join(", ", passing.Select(s => s.Name))}");
Console.WriteLine($"Honor Roll: {string.Join(", ", honorRoll)}");
foreach (var (subj, avg) in avgBySubj)
Console.WriteLine($"{subj} avg: {avg}");
foreach (var (subj, top) in topPerSubj)
Console.WriteLine($"{subj} top: {top.Name} ({top.Grade})");
🎓 Instructor Note: Delivery Guidance
Exercise 1 is a gentle warm-up — convert, filter, average. Exercise 2 is the full capstone that ties together everything from Module 5: lists, dicts, filter, map, reduce, grouping, and chaining. Walk through the JavaScript .reduce() grouping pattern carefully — it's the hardest concept here. Note how C# LINQ shines for this kind of data processing with GroupBy and Average. Challenge fast students: add a generate_report(students) that produces a formatted multi-line string summary.
Summary
🎉 Key Takeaways
- Basic loops: Python
for...in, JSfor...of(values) /for...in(keys), C#foreach - Enumerate: Python
enumerate(), JS.forEach((val, i))or.entries(), C# classicfor - Map transforms every element: Python comprehension, JS
.map(), C#.Select() - Filter keeps elements that pass: Python comprehension with
if, JS.filter(), C#.Where() - Reduce accumulates a single result: Python
reduce(), JS.reduce(), C#.Aggregate() - Any/All: Python
any()/all(), JS.some()/.every(), C#.Any()/.All() - Zip pairs two collections: Python
zip(), JS.map()with index, C#.Zip() - Chaining builds readable data pipelines: filter → transform → sort → extract
🚀 What's Next?
That wraps up Module 5: Collections! You've learned to store, access, transform, and process data with lists and dictionaries. In Module 6, we step into Object-Oriented Programming — modeling real-world concepts with classes, objects, and methods.
🎯 Quick Check
Question 1: In JavaScript, which array method transforms every element into a new value?
Question 2: What does Python's any([False, False, True, False]) return?
Question 3: Which LINQ method is C#'s equivalent of JavaScript's .filter()?