Advanced Data Structures
Level: 4 | Version: v1.0 | Author: Meptrasoft
This module builds on Level 3's strings, lists, tuples, sets, and dictionaries. It uses a College Result Management System as the running example — storing and processing student names, marks, and full student records.
Packing & Unpacking
What Gets Packed and Unpacked
Recall from Level 3 that packing combines multiple comma-separated values
into a tuple (parentheses optional), and unpacking splits a tuple back
into separate names, one value per name — with the name count
matching the value count, or Python raises ValueError.
student = ("Arun", 78, "Pass")
name, marks, status = student
print(name, marks, status)
Output:
Arun 78 Pass
Unpacking Any Iterable
Unpacking works with any iterable — lists, strings, and the results of
functions like zip() — not only tuples. (zip() pairs items from two
iterables by position.)
subjects = ["Maths", "Science", "English"]
first, second, third = subjects
print(first, second, third)
Output:
Maths Science English
letters = "abc"
a, b, c = letters
print(a, b, c)
Output:
a b c
Multiple Assignment
Python allows assigning several names in a single line, using packing and unpacking behind the scenes.
Same Value to Multiple Names
a = b = c = 0
print(a, b, c)
Output:
0 0 0
All three names refer to the same value here — useful for initializing counters or totals together.
Different Values in One Line
name, marks, status = "Arun", 78, "Pass"
print(name, marks, status)
Output:
Arun 78 Pass
Swapping Values Using Unpacking
A quick recap from Level 3 — swapping two values still uses pack-then- unpack, with no temporary variable needed:
first_marks, second_marks = 78, 92
first_marks, second_marks = second_marks, first_marks
print(first_marks, second_marks)
Output:
92 78
Extended Unpacking with *
When the number of values is unknown or larger than the number of names,
* collects the "rest" into a list.
marks = [78, 92, 85, 60, 74]
first, *remaining = marks
print(first)
print(remaining)
Output:
78
[92, 85, 60, 74]
* can appear at the start, middle, or end — it always collects whatever is
left over into a list:
marks = [78, 92, 85, 60, 74]
*rest, last = marks
print(rest, last)
first, *middle, last = marks
print(first, middle, last)
Output:
[78, 92, 85, 60] 74
78 [92, 85, 60] 74
*name → COLLECTS REMAINING VALUES → LIST
Use only one starred name in one unpacking assignment.
Unpacking in a for Loop
Unpacking is very common while looping over a list of tuples — for example, student records.
students = [("Arun", 78), ("Divya", 92), ("Karthik", 85)]
for name, marks in students:
print(name, "scored", marks)
Output:
Arun scored 78
Divya scored 92
Karthik scored 85
Each tuple in the list is unpacked into name and marks automatically on
every loop iteration.
Ignoring Values with _
When a value from unpacking is not needed, _ is a common convention to mark
it as intentionally ignored.
name, _, status = ("Arun", 78, "Pass")
print(name, status)
Output:
Arun Pass
_ → CONVENTION FOR "VALUE NOT NEEDED"
Comprehensions
A comprehension builds a new list, set, or dictionary from an existing iterable, in a single readable line.
List Comprehensions
Without a comprehension, building a new list needs a loop and append():
marks = [78, 92, 85, 60, 74]
updated_marks = []
for mark in marks:
updated_marks.append(mark + 5)
print(updated_marks)
Output:
[83, 97, 90, 65, 79]
The same result with a list comprehension:
marks = [78, 92, 85, 60, 74]
updated_marks = [mark + 5 for mark in marks]
print(updated_marks)
Output:
[83, 97, 90, 65, 79]
[ EXPRESSION for ITEM in ITERABLE ]
Comprehension with a Condition (Filter)
Adding if at the end keeps only the items that satisfy a condition.
marks = [78, 92, 85, 60, 45]
passed = [mark for mark in marks if mark >= 50]
print(passed)
Output:
[78, 92, 85, 60]
[ EXPRESSION for ITEM in ITERABLE if CONDITION ]
Comprehension with if...else (Transform Every Item)
An if...else placed before the for transforms every item instead of
filtering it out.
marks = [78, 92, 45, 60, 30]
result = ["Pass" if mark >= 50 else "Fail" for mark in marks]
print(result)
Output:
['Pass', 'Pass', 'Fail', 'Pass', 'Fail']
Filtering form (if at the end) |
Transforming form (if...else before for) |
|---|---|
| Removes items | Keeps every item, changes its value |
[x for x in data if cond] |
[a if cond else b for x in data] |
| Result can be shorter than the input | Result is always the same length as the input |
List Comprehension vs Loop
Loop with append() |
List Comprehension |
|---|---|
| Multiple lines | Single line |
| Needs an empty list created first | Builds the list directly |
| More explicit for complex logic | More readable for simple transforms |
SIMPLE TRANSFORM / FILTER → COMPREHENSION
COMPLEX MULTI-STEP LOGIC → LOOP
Nested List Comprehensions
A comprehension can contain another for, which is useful for working with a
list of lists (for example, marks per subject, per student).
subject_marks = [[78, 92, 85], [60, 74, 88]]
flat_marks = [mark for row in subject_marks for mark in row]
print(flat_marks)
Output:
[78, 92, 85, 60, 74, 88]
subject_marks:
[ [78, 92, 85], [60, 74, 88] ]
↓ ↓ ↓ ↓ ↓ ↓
flattened into one list
Reading order matters: the for clauses run left to right, exactly like
nested for loops written normally:
subject_marks = [[78, 92, 85], [60, 74, 88]]
flat_marks = []
for row in subject_marks:
for mark in row:
flat_marks.append(mark)
Building a Nested List with a Comprehension
A comprehension can also create nested structure, such as a small multiplication-style grid of pass marks:
students = ["Arun", "Divya"]
subjects = ["Maths", "Science"]
grid = [[f"{s}-{sub}" for sub in subjects] for s in students]
print(grid)
Output:
[['Arun-Maths', 'Arun-Science'], ['Divya-Maths', 'Divya-Science']]
Readability Caution
Nested comprehensions save lines but can quickly become hard to read. Beyond one level of nesting, a normal loop is usually clearer for interview and placement code:
1 LEVEL OF NESTING → COMPREHENSION IS FINE
2+ LEVELS / COMPLEX LOGIC → PREFER A NORMAL LOOP
Set Comprehensions
A set comprehension builds a set — automatically removing duplicate
values — using {} instead of [].
grades = ["A", "B", "A", "C", "B", "A"]
unique_grades = {grade for grade in grades}
print(unique_grades)
Output:
{'B', 'A', 'C'}
The displayed order may vary because sets are unordered.
{ EXPRESSION for ITEM in ITERABLE } → {} means SET
Set comprehensions are useful for collecting unique subjects, unique roll numbers, or unique grades from repeated records:
records = [("Arun", "Maths"), ("Divya", "Science"), ("Karthik", "Maths")]
subjects_offered = {subject for name, subject in records}
print(subjects_offered)
Output:
{'Science', 'Maths'}
| List Comprehension | Set Comprehension |
|---|---|
Uses [] |
Uses {} |
| Result is a list | Result is a set |
| Duplicates are kept | Duplicates are removed automatically |
| Order is preserved | Order is not guaranteed |
Dictionary Comprehensions
A dictionary comprehension builds a dictionary using {key: value for ...}.
zip() pairs the two lists by position, as introduced earlier in this
module.
students = ["Arun", "Divya", "Karthik"]
marks = [78, 92, 85]
result = {name: mark for name, mark in zip(students, marks)}
print(result)
Output:
{'Arun': 78, 'Divya': 92, 'Karthik': 85}
{ KEY : VALUE for ITEM in ITERABLE }
Filtering a Dictionary Comprehension
marks = {"Arun": 78, "Divya": 45, "Karthik": 85, "Priya": 40}
passed = {name: mark for name, mark in marks.items() if mark >= 50}
print(passed)
Output:
{'Arun': 78, 'Karthik': 85}
Transforming Keys or Values
marks = {"Arun": 78, "Divya": 92, "Karthik": 85}
result_status = {name: ("Pass" if mark >= 50 else "Fail") for name, mark in marks.items()}
print(result_status)
Output:
{'Arun': 'Pass', 'Divya': 'Pass', 'Karthik': 'Pass'}
Swapping Keys and Values
roll_numbers = {"Arun": 101, "Divya": 102, "Karthik": 103}
swapped = {roll: name for name, roll in roll_numbers.items()}
print(swapped)
Output:
{101: 'Arun', 102: 'Divya', 103: 'Karthik'}
Comprehension Types Compared
| Type | Brackets | Builds | Duplicates |
|---|---|---|---|
| List | [] |
list |
Kept |
| Set | {} |
set |
Removed |
| Dictionary | {k: v} |
dict |
Keys overwrite |
[] → LIST
{item} → SET
{k: v} → DICTIONARY
Nested Structures
Why Nest Data Structures
A nested data structure is a data structure that contains other data structures inside it — for example, a list of tuples, a list of dictionaries, or a dictionary of lists. Real applications almost always store data this way, because a single student, order, or record naturally has several related fields.
List of Tuples
students = [("Arun", 78), ("Divya", 92), ("Karthik", 85)]
print(students[0])
print(students[0][0])
print(students[0][1])
Output:
('Arun', 78)
Arun
78
List of Dictionaries
Each student is a dictionary; all students together form a list — this is the most common way real systems represent a table of records.
students = [
{"name": "Arun", "marks": 78, "status": "Pass"},
{"name": "Divya", "marks": 92, "status": "Pass"},
{"name": "Karthik", "marks": 40, "status": "Fail"},
]
for student in students:
print(student["name"], "-", student["marks"], "-", student["status"])
Output:
Arun - 78 - Pass
Divya - 92 - Pass
Karthik - 40 - Fail
[ {...}, {...}, {...} ] → LIST OF DICTIONARIES → TABLE OF RECORDS
Dictionary of Lists
All marks for each subject, grouped under one dictionary:
subject_marks = {
"Maths": [78, 92, 85],
"Science": [60, 74, 88],
}
print(subject_marks["Maths"])
print(subject_marks["Maths"][0])
Output:
[78, 92, 85]
78
Dictionary of Dictionaries
Each student's full record, keyed by name:
students = {
"Arun": {"marks": 78, "status": "Pass"},
"Divya": {"marks": 92, "status": "Pass"},
}
print(students["Arun"])
print(students["Arun"]["marks"])
Output:
{'marks': 78, 'status': 'Pass'}
78
DICT { NAME : DICT { FIELD : VALUE } }
Accessing Nested Values
Each level is accessed with one more [] step, moving from the outer
structure to the inner value.
college = {
"students": [
{"name": "Arun", "marks": [78, 92, 85]},
{"name": "Divya", "marks": [60, 74, 88]},
]
}
print(college["students"][0]["name"])
print(college["students"][0]["marks"][1])
Output:
Arun
92
college["students"][0]["marks"][1]
↑ ↑ ↑ ↑
dict list dict list
Modifying Nested Values
Navigating to the exact nested position also lets a value be updated in place.
college = {
"students": [
{"name": "Arun", "marks": [78, 92, 85]},
]
}
college["students"][0]["marks"][0] = 80
print(college["students"][0]["marks"])
Output:
[80, 92, 85]
Shallow Copy vs Deep Copy of Nested Data
copy() on a dictionary or list only copies the outer structure — any
lists or dictionaries nested inside it are still shared between the original
and the copy.
college = {
"students": [
{"name": "Arun", "marks": [78, 92, 85]},
]
}
college_copy = college.copy()
college_copy["students"][0]["marks"][0] = 100
print(college["students"][0]["marks"])
Output:
[100, 92, 85]
Even though college_copy is a separate dictionary, its inner student
dictionary and marks list are the same objects as in college — this is
called a shallow copy. To copy every nested level independently, use
copy.deepcopy():
college = {
import copy
college_deep = copy.deepcopy(college)
college_deep["students"][0]["marks"][0] = 999
print(college["students"][0]["marks"])
Output:
[100, 92, 85]
copy() → copies only the OUTER structure → SHALLOW COPY
copy.deepcopy() → copies EVERY nested level → DEEP COPY
Real-World Example: Building a Result Report
Combining comprehensions with nested structures is common in real projects — for example, generating a pass/fail report from a list of student records.
students = [
{"name": "Arun", "marks": 78},
{"name": "Divya", "marks": 45},
{"name": "Karthik", "marks": 85},
]
report = {student["name"]: ("Pass" if student["marks"] >= 50 else "Fail") for student in students}
print(report)
Output:
{'Arun': 'Pass', 'Divya': 'Fail', 'Karthik': 'Pass'}
LIST OF DICTS → DICT COMPREHENSION → NAME : RESULT
Common Fresher Mistakes
| ✗ Mistake | Problem | ✓ Correct Understanding |
|---|---|---|
| Unpacking with the wrong number of names | Raises ValueError: too many/not enough values to unpack |
The name count must match the value count |
Forgetting * collects the "rest" into a list, not a tuple |
Code that assumes a tuple result behaves unexpectedly | *rest always produces a list |
| Trying to swap values with a temporary variable out of habit | Adds an unnecessary extra variable and line | Python can swap directly: a, b = b, a |
| Writing a comprehension that is hard to read instead of a normal loop | Reduces readability and makes debugging harder | Prefer a loop when logic has more than one condition or nesting level |
Confusing [], {item}, and {k: v} comprehension syntax |
Builds the wrong collection type by accident | [] → list, {item} → set, {k: v} → dictionary |
| Assuming a set comprehension preserves order or duplicates | Code relying on order or duplicate counts breaks silently | Sets remove duplicates and do not guarantee order |
| Forgetting keys overwrite in a dictionary comprehension | Earlier values for a repeated key are silently lost | A repeated key keeps only the last value seen |
Accessing a nested value with too few or wrong [] steps |
Raises KeyError, IndexError, or TypeError |
Each nesting level needs its own [] step |
| Modifying a nested structure and expecting the original data unaffected | Unexpected side effects appear because the reference is shared | Nested lists/dicts are shared references; changes are visible everywhere |
| Using deeply nested comprehensions "to look advanced" | Makes code hard to read, debug, and review | Clarity matters more than compressing code into one line |
Placement Quick Points
PACKING → Multiple values combined into one object (tuple)
UNPACKING → Splitting an iterable into separate names
*rest → Collects remaining values into a LIST
SWAP → a, b = b, a (no temporary variable needed)
LIST COMP → [expr for item in iterable]
FILTER COMP → [expr for item in iterable if condition]
TRANSFORM → [a if cond else b for item in iterable]
SET COMP → {expr for item in iterable} → removes duplicates
DICT COMP → {key: value for item in iterable}
NESTED DATA → structure containing another structure (list of dicts, dict of lists, etc.)
ACCESS → one [] per nesting level
Interview Questions
- What is packing in Python?
- Packing is combining multiple comma-separated values into a single object, usually a tuple.
- What is unpacking in Python?
- Unpacking is splitting an iterable into separate names, one value per name.
- What happens if the number of names does not match the number of values while unpacking?
- Python raises a
ValueError. - Can unpacking be used with lists and strings, not just tuples?
- Yes. Unpacking works with any iterable.
- How does Python swap two values without a temporary variable?
- Using packing and unpacking together:
a, b = b, a. - What does
*do during unpacking? - It collects the remaining values into a list; it can appear at the start, middle, or end.
- What type of object does
*restproduce? - A list.
- What is the underscore
_used for during unpacking? - It is a convention to indicate that a value from unpacking is intentionally ignored.
- What is a list comprehension?
- A concise way to build a new list from an iterable in a single line, in the form
[expression for item in iterable]. - How is filtering added to a list comprehension?
- By adding
if conditionat the end of the comprehension. - How is a transforming
if...elsewritten in a comprehension, and how is it different from a filteringif? - It is written before the
forclause, as[a if condition else b for item in iterable]. It keeps every item and changes its value, while a filteringifat the end removes items instead. - What is a nested list comprehension?
- A comprehension that contains another
forclause, commonly used to flatten a list of lists. - When should a normal loop be preferred over a comprehension?
- When the logic involves more than one condition or more than one level of nesting, since readability matters more than compressing lines.
- What is a set comprehension and how is its syntax different from a list comprehension?
- A set comprehension builds a set using
{expression for item in iterable}; unlike a list comprehension, duplicate values are automatically removed. - What is a dictionary comprehension?
- A concise way to build a dictionary using
{key: value for item in iterable}. - What happens when a dictionary comprehension produces a repeated key?
- The last value seen for that key is kept; earlier values are overwritten.
- What is a nested data structure?
- A data structure that contains other data structures inside it, such as a list of dictionaries or a dictionary of lists.
- Give an example of a common nested structure used to represent multiple records.
- A list of dictionaries, where each dictionary represents one record (for example, one student).
- How many
[]steps are needed to access a value nested three levels deep? - Three — one step per nesting level.
Practice
Use a College Result Management System.
- Pack a student's name, marks, and status into a tuple in one line.
- Unpack that tuple into three separate names.
- Try unpacking into the wrong number of names and observe the error.
- Unpack a string into individual character names.
- Assign the same value
0to three names in one line. - Assign three different values to three names in one line.
- Swap two mark values without using a temporary variable.
- Use
*restto collect all marks except the first from a list. - Use
*restto collect all marks except the last from a list. - Loop over a list of
(name, marks)tuples using unpacking. - Use
_to ignore the status field while unpacking a student record. - Write a list comprehension that adds 5 bonus marks to every value in a list.
- Write a list comprehension that keeps only marks greater than or equal to 50.
- Write a list comprehension that labels each mark as
"Pass"or"Fail". - Flatten a list of subject-wise mark lists using a nested list comprehension.
- Build a small nested list pairing student names with subject names.
- Write a set comprehension that collects unique grades from a list.
- Write a set comprehension that collects unique subjects from a list of records.
- Write a dictionary comprehension mapping student names to marks using
zip(). - Write a dictionary comprehension that keeps only students who passed.
- Write a dictionary comprehension that swaps keys and values of a roll-number dictionary.
- Create a list of dictionaries, each representing one student's record.
- Create a dictionary of lists, grouping marks by subject.
- Create a dictionary of dictionaries, keyed by student name.
- Access a specific student's marks from a nested
list of dicts inside a dict. - Modify one nested mark value in place and print the updated structure.
- Build a pass/fail report dictionary from a list of student-record dictionaries using a dictionary comprehension.
Predict the output:
For set outputs, the order may vary.
name, marks = "Arun", 78
print(name, marks)
a, *b = [78, 92, 85, 60]
print(a)
print(b)
marks = [78, 45, 92, 30]
result = [m for m in marks if m >= 50]
print(result)
grades = ["A", "B", "A", "C"]
print({g for g in grades})
students = {"Arun": 78, "Divya": 92}
print({v: k for k, v in students.items()})
Find the mistake:
name, marks, status = ("Arun", 78)
students = [{"name": "Arun", "marks": [78, 92]}]
print(students["name"])
marks = {"Arun": 78, "Arun": 92}
print(marks)
Final task: Build a small College Result Management System that stores
several students as a list of dictionaries (name, marks), then use
comprehensions to (a) produce a list of only the passing students' names, (b)
produce a set of unique pass/fail statuses, and (c) produce a dictionary
mapping each student's name to their result status.
Output:
PACK / UNPACK → MOVE VALUES IN AND OUT OF NAMES
COMPREHENSION → BUILD A NEW COLLECTION IN ONE READABLE LINE
NESTED DATA → STRUCTURE INSIDE STRUCTURE, ONE [] PER LEVEL
End of Level 4