Week 8: Comprehensions & Custom Sorting
4411 words
22 min read
2026-09-12T00:00:00.000Z
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
List comprehensions, dict comprehensions, nested data structures, tuple unpacking, and sorting with key functions. # Programming in Python — Week 8: Comprehensions & Custom Sorting The official IITM syllabus lists **Week 8: Basic Collections in Python (Continued)**.

Visual strip
Relevant recall pieces for this note
Programming in Python — Week 8: Comprehensions & Custom Sorting
The official IITM syllabus lists Week 8: Basic Collections in Python (Continued). It does not specify finer subtopics, so the material below is a standard continuation of collection-processing concepts, not a claim about exact lecture ordering.
TL;DR
Week 6–7 introduced:
pythonlist tuple string dictionary set
Week 8 is where these structures start being combined into actual data-processing algorithms.
The central patterns are:
textcollection ↓ traverse ↓ filter ↓ transform ↓ aggregate ↓ build another collection
The most useful ideas to master are:
- nested collections
- list/dict/set comprehensions
zip()enumerate()- sorting collections
- sorting with keys
- copying and aliasing
- nested mutation
- grouping
- frequency analysis
- collection conversions
- structural transformations
- combined list/dictionary/set problems
1. Collections start becoming composable
Earlier:
pythonnumbers = [10, 20, 30]
was just a list.
Now we may have:
pythonstudents = { "A": [80, 90, 85], "B": [70, 60, 75], "C": [95, 88, 91] }
This combines:
- dictionary
- string keys
- list values
- numerical aggregation
Real programs frequently use such nested structures.
2. List of tuples
Example:
pythonrecords = [ ("A", 80), ("B", 90), ("C", 75) ]
Each element is one tuple:
text(name, mark)
So:
pythonrecords[1]
gives:
python("B", 90)
and:
pythonrecords[1][0]
gives:
text"B"
3. Tuple unpacking while iterating
Instead of:
pythonfor record in records: name = record[0] mark = record[1]
write:
pythonfor name, mark in records: print(name, mark)
Python assigns the tuple components directly.
4. Structured iteration
For:
pythonrecords = [ ("A", 80), ("B", 90) ]
this:
pythonfor name, mark in records:
iterates conceptually as:
textname = "A", mark = 80 name = "B", mark = 90
This is an important pattern.
5. enumerate()
Sometimes you need both:
- element
- position
Instead of:
pythonfor i in range(len(values)): print(i, values[i])
Python provides:
pythonfor i, value in enumerate(values): print(i, value)
6. enumerate() example
pythonnames = ["A", "B", "C"] for i, name in enumerate(names): print(i, name)
Output:
text0 A 1 B 2 C
7. Starting enumeration from 1
pythonfor i, name in enumerate(names, start=1): print(i, name)
Output:
text1 A 2 B 3 C
Useful when human numbering starts from 1.
8. zip()
zip() combines multiple sequences position-by-position.pythonnames = ["A", "B", "C"] marks = [80, 90, 70] for name, mark in zip(names, marks): print(name, mark)
Conceptually creates pairs:
text("A", 80) ("B", 90) ("C", 70)
9. Why zip() is useful
Without it:
pythonfor i in range(len(names)): print(names[i], marks[i])
With it:
pythonfor name, mark in zip(names, marks): print(name, mark)
The second form directly expresses the relationship.
10. Different lengths with zip()
Suppose:
pythona = [1, 2, 3] b = [10, 20]
Then ordinary:
pythonzip(a, b)
stops when the shorter iterable is exhausted.
Conceptually:
text(1,10) (2,20)
The third element of
a has no partner.11. Create dictionary using zip()
pythonnames = ["A", "B", "C"] marks = [80, 90, 70] d = dict(zip(names, marks))
Result:
python{ "A": 80, "B": 90, "C": 70 }
This converts parallel sequences into a mapping.
12. Collection conversion
Python can convert between compatible collection forms.
Examples:
pythonlist("abc")
gives:
python["a", "b", "c"]
pythontuple([1, 2, 3])
gives:
python(1, 2, 3)
pythonset([1, 2, 2, 3])
gives a set of distinct values.
13. Dictionary conversion
A sequence of pairs can become a dictionary:
pythonpairs = [ ("a", 1), ("b", 2) ] d = dict(pairs)
Result:
python{"a": 1, "b": 2}
14. List comprehension
Expanded form:
pythonsquares = [] for x in range(5): squares.append(x ** 2)
Comprehension:
pythonsquares = [x ** 2 for x in range(5)]
Result:
text[0, 1, 4, 9, 16]
15. Comprehension mental model
Read:
python[f(x) for x in data]
as:
for everyxindata, computef(x)and collect the results.
16. Filtering comprehension
pythonevens = [x for x in range(10) if x % 2 == 0]
Result:
text[0, 2, 4, 6, 8]
Expanded:
pythonevens = [] for x in range(10): if x % 2 == 0: evens.append(x)
17. Transform + filter
pythonresult = [x ** 2 for x in values if x > 0]
means:
- traverse values
- keep only positive values
- square them
- collect results
Order:
textfilter → transform
18. Conditional expression inside comprehension
pythonlabels = ["even" if x % 2 == 0 else "odd" for x in values]
This transforms every value.
Compare with:
python[x for x in values if x % 2 == 0]
which filters elements.
These are different roles for
if.19. Dict comprehension
pythonsquares = {x: x ** 2 for x in range(5)}
Result:
python{ 0: 0, 1: 1, 2: 4, 3: 9, 4: 16 }
General pattern:
python{key_expression: value_expression for item in collection}
20. Filter dict comprehension
pythonhigh = { name: mark for name, mark in marks.items() if mark >= 80 }
This produces only entries satisfying the condition.
21. Set comprehension
pythonremainders = {x % 3 for x in range(10)}
Possible result:
text{0, 1, 2}
Duplicates collapse because the target collection is a set.
22. Why set comprehension can shrink output
Input may contain many items:
python[x % 3 for x in range(10)]
produces repeated values.
But:
python{x % 3 for x in range(10)}
keeps only distinct outcomes.
23. Nested list comprehension
A nested loop:
pythonpairs = [] for i in range(3): for j in range(2): pairs.append((i, j))
can be written:
pythonpairs = [(i, j) for i in range(3) for j in range(2)]
24. Order in nested comprehension
Read:
python[(i, j) for i in range(3) for j in range(2)]
in the same order as:
pythonfor i in range(3): for j in range(2): ...
Result:
text(0,0) (0,1) (1,0) (1,1) (2,0) (2,1)
25. Prefer readability over compression
This:
python[x*y for x in a for y in b if complicated_condition]
may be valid but hard to understand.
Expanded loops are often better when:
- logic is complex
- multiple state variables exist
- debugging matters
Compact code is not automatically better code.
26. Sorting a list
Mutation:
pythonvalues.sort()
Example:
pythonvalues = [5, 2, 9, 1] values.sort()
Result:
text[1, 2, 5, 9]
27. Descending order
pythonvalues.sort(reverse=True)
Result:
text[9, 5, 2, 1]
28. sorted()
pythonnew_values = sorted(values)
returns a new sorted list.
Original collection need not be modified.
This is often useful if you still need original ordering.
29. Sorting strings
pythonnames = ["Zara", "Asha", "Ravi"] print(sorted(names))
uses Python's string ordering.
Output conceptually:
text["Asha", "Ravi", "Zara"]
30. Sorting tuples
Tuples can be sorted lexicographically.
pythonpairs = [(2, 5), (1, 9), (2, 3)] print(sorted(pairs))
Comparison first uses first element.
For equal first element, next element breaks the tie.
Result:
text[(1, 9), (2, 3), (2, 5)]
31. Sorting records by chosen field
Suppose:
pythonstudents = [ ("A", 80), ("B", 95), ("C", 70) ]
Want sort by mark.
Python:
pythonsorted_students = sorted(students, key=lambda x: x[1])
Result:
text[("C", 70), ("A", 80), ("B", 95)]
32. key= intuition
The
key function tells Python:when comparing elements, use this derived value.
For:
pythonkey=lambda x: x[1]
each tuple:
python(name, mark)
is compared using:
textmark
33. Descending by key
pythonsorted_students = sorted( students, key=lambda x: x[1], reverse=True )
Now highest mark comes first.
34. lambda supporting intuition
A lambda is a small anonymous function.
pythonlambda x: x[1]
conceptually means:
pythondef f(x): return x[1]
For Week 8 purposes, focus on its use as a simple sorting key rather than advanced functional-programming theory.
35. Sort dictionary items
Suppose:
pythonmarks = { "A": 80, "B": 95, "C": 70 }
Sort by value:
pythonsorted_items = sorted( marks.items(), key=lambda pair: pair[1] )
Result:
text[("C",70), ("A",80), ("B",95)]
36. Dictionary retains mapping, sorted result is a list
Important:
pythonsorted(marks.items())
returns a list of tuples, not automatically a dictionary.
Always track the datatype produced by an operation.
37. reversed()
Python provides:
pythonreversed(values)
which gives an iterator over values in reverse order.
Often:
pythonlist(reversed(values))
is used if an actual list is needed.
38. Mutation vs returned result recap
Mutates
pythonx.append(...) x.extend(...) x.sort() x.reverse()
Returns a new useful value
pythonsorted(x) list(reversed(x)) x + y x[:]
This distinction is central to collection reasoning.
39. Shallow copies and nested lists
Consider:
pythona = [1, 2], [3, 4](/courses/python/notes/1%2C%202%5D%2C%20%5B3%2C%204) b = a.copy()
a and b are different outer lists.But their inner lists are still shared.
40. Shallow-copy example
pythona = [1, 2], [3, 4](/courses/python/notes/1%2C%202%5D%2C%20%5B3%2C%204) b = a.copy() b[0][0] = 99
Now both may show:
text[99, 2], [3, 4](/courses/python/notes/99%2C%202%5D%2C%20%5B3%2C%204)
because the inner list object was shared.
41. Why this happens
Conceptually:
texta ──> outer list A ──┐ ├─> inner list [1,2] b ──> outer list B ──┘
Only the outer list was copied.
Nested mutable objects were not recursively duplicated.
42. Replacing inner list behaves differently
pythonb[0] = [9, 9]
changes which inner list
b points to at index 0.That does not necessarily alter:
pythona[0]
because the outer lists themselves are different.
43. Nested aliasing is a common exam trap
Distinguish:
pythonb[0] = ...
from:
pythonb[0][0] = ...
The first replaces an inner-object reference in
b.The second mutates the shared inner object.
44. Creating a matrix correctly
Potentially dangerous:
pythonmatrix = [[0] * 3] * 3
This can create repeated references to the same inner list.
45. Example of shared-row issue
pythonmatrix = [[0] * 3] * 3 matrix[0][0] = 1
You may unexpectedly get:
text[ [1,0,0], [1,0,0], [1,0,0] ]
because all rows reference the same inner list.
46. Safer matrix construction
Use:
pythonmatrix = [[0 for _ in range(3)] for _ in range(3)]
Now each row is a distinct list.
This is a major nested-mutable-object lesson.
47. Matrix traversal
pythonfor row in matrix: for value in row: print(value)
This is direct value traversal.
48. Matrix traversal with indices
pythonfor i in range(len(matrix)): for j in range(len(matrix[i])): print(i, j, matrix[i][j])
Use this when positions matter.
49. Row sums
pythonrow_sums = [] for row in matrix: row_sums.append(sum(row))
Or:
pythonrow_sums = [sum(row) for row in matrix]
50. Column sums
For a rectangular matrix:
pythonrows = len(matrix) cols = len(matrix[0]) column_sums = [] for j in range(cols): total = 0 for i in range(rows): total += matrix[i][j] column_sums.append(total)
51. Transpose intuition
Transpose swaps rows and columns.
If:
is an entry, transpose uses:
Python nested-list version:
pythontranspose = [] for j in range(cols): row = [] for i in range(rows): row.append(matrix[i][j]) transpose.append(row)
52. Nested comprehension transpose
pythontranspose = [ [matrix[i][j] for i in range(rows)] for j in range(cols) ]
Expand it mentally if tracing becomes difficult.
53. List of dictionaries
Very common structured-data form:
pythonstudents = [ {"name": "A", "mark": 80}, {"name": "B", "mark": 92}, {"name": "C", "mark": 70} ]
Each list element is one record.
54. Traverse records
pythonfor student in students: print(student["name"], student["mark"])
This resembles a table:
| name | mark |
|---|---|
| A | 80 |
| B | 92 |
| C | 70 |
55. Filter records
pythonhigh = [] for student in students: if student["mark"] >= 80: high.append(student)
Result contains whole records satisfying the condition.
56. Extract one field
pythonnames = [student["name"] for student in students]
Result:
text["A", "B", "C"]
57. Aggregate records
Average mark:
pythontotal = 0 for student in students: total += student["mark"] average = total / len(students)
This is data-table-style processing.
58. Find record with maximum field
pythonbest = students[0] for student in students[1:]: if student["mark"] > best["mark"]: best = student
At end,
best contains the entire record, not only the maximum number.59. Why keeping whole record matters
If you only store:
pythonmaximum = 92
you lose associated information such as:
textwho scored 92?
Keeping the record preserves context.
60. Dictionary of records
Another organization:
pythonstudents = { "A": {"mark": 80, "city": "X"}, "B": {"mark": 92, "city": "Y"} }
Now name is the dictionary key.
Lookup:
pythonstudents["B"]["mark"]
gives:
text92
61. Choosing list-of-dicts vs dict-of-dicts
List of dictionaries
Useful when primarily processing all records sequentially.
Dictionary keyed by ID/name
Useful when frequent direct lookup by unique identifier matters.
Data structure should reflect the access pattern.
62. Grouping records
Suppose:
pythonrecords = [ ("A", "Maths"), ("B", "Python"), ("A", "Python") ]
Want:
python{ "A": ["Maths", "Python"], "B": ["Python"] }
Algorithm:
pythongroups = {} for student, course in records: if student not in groups: groups[student] = [] groups[student].append(course)
63. Group into sets instead
If duplicate courses should not repeat:
pythongroups = {} for student, course in records: if student not in groups: groups[student] = set() groups[student].add(course)
Now each student's courses are unique.
64. Frequency + maximum
Suppose:
pythonvalues = ["a", "b", "a", "c", "a", "b"]
First build frequency:
pythonfreq = {} for x in values: freq[x] = freq.get(x, 0) + 1
Then find most frequent:
pythonbest = None for x in freq: if best is None or freq[x] > freq[best]: best = x
Result:
text"a"
65. Mode
The most frequent value is often called the mode.
Dictionary frequency counting is a natural algorithm for finding it.
Be careful with ties: a dataset can have multiple values sharing maximum frequency.
66. Handling ties
pythonmax_freq = max(freq.values()) modes = [] for value, count in freq.items(): if count == max_freq: modes.append(value)
Now every value tied for highest frequency is retained.
67. Inverting a dictionary safely
Suppose:
pythond = { "A": 1, "B": 2, "C": 1 }
Naive inversion loses data.
Better:
pythonreverse = {} for key, value in d.items(): if value not in reverse: reverse[value] = [] reverse[value].append(key)
Result:
python{ 1: ["A", "C"], 2: ["B"] }
68. This is another grouping problem
Original:
textkey → value
Reverse safely:
textvalue → list of keys
This generalizes many data-transformation tasks.
69. Set operations in data comparison
Suppose:
pythonpython_students = {"A", "B", "C"} maths_students = {"B", "C", "D"}
Both courses:
pythonpython_students & maths_students
gives:
text{"B", "C"}
70. Either course
pythonpython_students | maths_students
gives all students in at least one course.
71. Python only
pythonpython_students - maths_students
gives students in Python but not Maths.
72. Exactly one course
pythonpython_students ^ maths_students
gives students enrolled in exactly one of the two.
This is Boolean-style reasoning applied to sets.
73. Any / all intuition
Python has:
pythonany(...) all(...)
for Boolean collections/iterables.
Example:
pythonvalues = [5, 8, 2] any(x > 7 for x in values)
asks:
Does at least one value exceed 7?
Result:
textTrue
74. all()
pythonall(x > 0 for x in values)
asks:
Are all values positive?
For:
python[5, 8, 2]
result:
textTrue
75. Existential and universal patterns
Earlier manual pattern:
pythonfound = False for x in values: if condition: found = True break
corresponds conceptually to:
pythonany(condition for x in values)
Similarly, universal checking maps naturally to
all().76. all() on an empty collection
Mathematically/Pythonically:
pythonall([])
is:
textTrue
while:
pythonany([])
is:
textFalse
This follows the logical identities:
- no counterexample exists in an empty set → universal condition is vacuously true
- no witness exists → existential condition is false
77. min() and max() with key
Suppose:
pythonstudents = [ {"name": "A", "mark": 80}, {"name": "B", "mark": 92} ]
You can write:
pythonbest = max(students, key=lambda s: s["mark"])
Result is the whole dictionary record with greatest mark.
78. Why key= is powerful
Without
key, Python compares objects according to their normal comparison rules.With:
pythonkey=...
you explicitly define the feature relevant to the ordering.
This is similar to:
sort/classify records by a chosen attribute.
79. Multiple sort criteria
Tuples can encode tie-breaking.
Suppose rank by:
- higher marks
- alphabetically smaller name
Conceptually, sorting logic may use a composite key.
The exact key expression can vary, but the algorithmic lesson is:
80. Stable sorting intuition
Python sorting is stable: items that compare equal under the sorting key retain their relative order.
This allows multi-stage sorting strategies.
Treat this as useful Python behavior rather than the central Week 8 syllabus point.
81. String splitting
Collections and strings often interact through:
pythontext.split()
Example:
pythontext = "one two three" words = text.split()
Result:
python["one", "two", "three"]
Now string data becomes a list.
82. Split by delimiter
python"a,b,c".split(",")
gives:
python["a", "b", "c"]
Useful for simple structured text.
83. Joining strings
The inverse-style operation:
python",".join(["a", "b", "c"])
gives:
text"a,b,c"
Pattern:
textlist of strings → join → one string
84. Word frequency
pythontext = "cat dog cat bird dog cat" words = text.split() freq = {} for word in words: freq[word] = freq.get(word, 0) + 1
Final:
python{ "cat": 3, "dog": 2, "bird": 1 }
This combines strings, lists and dictionaries.
85. Normalize before counting
Suppose text contains:
textCat cat CAT
Without normalization, they are distinct strings.
A common step:
pythonword = word.lower()
before frequency counting.
Data cleaning often precedes aggregation.
86. Nested structure trace
pythond = { "A": [1, 2], "B": [3] } d["A"].append(4) d["B"][0] = 9
Final:
python{ "A": [1, 2, 4], "B": [9] }
Trace from outer lookup to inner mutation.
87. Shared nested values
Consider:
pythonx = [1, 2] d = { "A": x, "B": x }
Both dictionary keys reference the same list.
Then:
pythond["A"].append(3)
also affects the list observed through:
pythond["B"]
Aliasing applies inside dictionaries too.
88. Equality vs identity revisited
Two lists can have equal contents:
pythona = [1, 2] b = [1, 2]
So:
pythona == b
is true.
But they may be different objects.
Collection algorithms usually care about equality of contents, while mutation/aliasing questions care about identity/reference relationships.
Common mistake: nested-list multiplication aliasing
Avoid:
pythonrows = [[0] * 4] * 3
when you need independent mutable rows.
Prefer:
pythonrows = [[0] * 4 for _ in range(3)]
Common mistake: assuming comprehension changes original
pythony = [x * 2 for x in values]
creates a new list.
It does not normally mutate
values.Common mistake: expecting set order
Do not index or rely on a set's traversal order for semantic correctness.
If sorted output is required:
pythonsorted(s)
returns a list in sorted order.
Common mistake: sorting dictionary directly by values
pythonsorted(d)
sorts dictionary keys, because iterating over a dictionary yields keys.
To sort by values, operate on:
pythond.items()
with a suitable
key.Common mistake:
zip() fills missing valuesOrdinary
zip() does not.It stops at the shortest iterable.
So mismatched lengths may silently omit extra elements.
Common mistake:
enumerate() gives value firstStandard unpacking:
pythonfor index, value in enumerate(values):
not:
pythonfor value, index in enumerate(values):
unless you intentionally want variables misnamed.
Common mistake: losing associated data
Suppose you need:
student with highest mark
If you calculate only:
pythonmax(marks)
you may lose which student produced that maximum.
Keep the relationship via tuples/dictionaries/records.
Common mistake: overwrite a group
Wrong:
pythongroups[key] = value
when multiple values may belong to the same key.
Better:
pythonif key not in groups: groups[key] = [] groups[key].append(value)
Common mistake: mutate shared nested object unexpectedly
Before modifying:
pythoncollection[a][b]
ask:
Could this inner object also be referenced somewhere else?
This is the main nested-aliasing question.
98. Data-pipeline mental model
By Week 8, a large fraction of basic Python problems fit:
textRAW DATA ↓ parse / convert ↓ collection ↓ filter ↓ transform ↓ group ↓ aggregate ↓ sort ↓ output
That same pipeline becomes central later in data science.
99. Example full pipeline
Input:
pythonrecords = [ ("A", 80), ("B", 45), ("C", 90), ("D", 75) ]
Goal:
names of students scoring at least 60, highest first.
pythonpassed = [] for name, mark in records: if mark >= 60: passed.append((name, mark)) passed = sorted( passed, key=lambda x: x[1], reverse=True ) names = [name for name, mark in passed]
Result:
text["C", "A", "D"]
100. Combined frequency pipeline
Input:
pythonwords = ["a", "b", "a", "c", "b", "a"]
Frequency:
pythonfreq = {} for word in words: freq[word] = freq.get(word, 0) + 1
Sort by descending count:
pythonranking = sorted( freq.items(), key=lambda item: item[1], reverse=True )
Result conceptually:
text[("a",3), ("b",2), ("c",1)]
101. Week 8 synthesis
You should now see collections less as isolated syntax:
textlist dict set tuple
and more as tools answering different questions:
textNeed ordered sequence? → list Need fixed structured record? → tuple Need key-based lookup/grouping? → dictionary Need uniqueness/membership? → set
102. Compact Week 8 cheat sheet
python## index + value for i, x in enumerate(values): ... ## parallel iteration for a, b in zip(xs, ys): ... ## list comprehension [x * 2 for x in values] ## filtered comprehension [x for x in values if x > 0] ## dict comprehension {x: x ** 2 for x in values} ## set comprehension {x % 3 for x in values} ## sorting sorted(values) sorted(values, reverse=True) ## sort records by field sorted(records, key=lambda x: x[1]) ## split string words = text.split() ## join strings text = " ".join(words) ## existential / universal any(condition for x in values) all(condition for x in values)
103. What you should actually master
By the end of Python Week 8, you should be able to:
- Work with nested collections.
- Iterate through tuples using unpacking.
- Use
enumerate()when both index and value matter. - Use
zip()for parallel sequences. - Convert between common collection types.
- Write and mentally expand list comprehensions.
- Use filters in comprehensions.
- Understand dict and set comprehensions.
- Sort lists and records.
- Use
key=for sorting by a chosen attribute. - Distinguish mutation from returned new collections.
- Understand shallow copies.
- Trace nested aliasing.
- Construct independent nested lists correctly.
- Traverse matrices and nested records.
- Group data into lists/sets using dictionaries.
- Build and analyze frequency maps.
- Find modes and handle ties.
- Compare groups using set operations.
- Use
any()andall()conceptually. - Split strings into lists and join lists into strings.
- Build multi-stage collection-processing pipelines.
- Choose the correct collection based on access pattern and required semantics.
If these are comfortable, Programming in Python Week 8 is under control.
Quick Recall & Flashcards
Test your active recall before proceeding to practice problems. Format:
Question :: Answer.- Week 8 Diagnostic: What does the list comprehension
[x * 2 for x in range(5) if x % 2 == 0]evaluate to? :: Answer: A — Even numbers in range(5): 0, 2, 4. Multiplied by 2: 0, 4, 8. - Week 8 Diagnostic: What does
sorted(['banana', 'pie', 'apple'], key=len)sort the list by? :: Answer: B — Passingkey=lenorders items by the result oflen(item): ['pie', 'apple', 'banana'].
Practice & Self-Check
Attempt these diagnostic questions to verify your operational understanding.
1
Question 1
What does the list comprehension
[x * 2 for x in range(5) if x % 2 == 0] evaluate to?MCQ
3 Unit Assessment
A. [0, 4, 8]
B. [0, 2, 4]
C. [2, 4, 6]
D. [0, 2, 4, 6, 8]
2
Question 2
What does
sorted(['banana', 'pie', 'apple'], key=len) sort the list by?MCQ
3 Unit Assessment
A. Alphabetical order
B. Length of the string (ascending)
C. Number of vowels
D. Reverse alphabetical order