Neural Sync Active
Week 8: Comprehensions & Custom Sorting
Registry Synced
Week 8: Comprehensions & Custom Sorting
4411 words
22 min read
2026-09-12
Reading compass
Now · TL;DR
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](/viewer?path=1, 2], [3, 4) 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](/viewer?path=1, 2], [3, 4) b = a.copy() b[0][0] = 99
Now both may show:
text[99, 2], [3, 4](/viewer?path=99, 2], [3, 4)
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