Welcome to Chapter 2 ā the deep dive into Python's most powerful data structures! This chapter builds directly on Tour I. Master these and you'll ace both the theory and practical papers.
> [!TIP]
> **How to use these notes:** Each section has a concept explanation, code example, and at least one Board Exam Tip. Don't just read ā **type every code snippet yourself** in Python. Muscle memory is your best friend before boards!
---
## 2.1 Introduction
In Python Revision Tour I, we covered the basics ā tokens, variables, data types, and flow control. **Tour II goes deeper** into Python's built-in **data structures** ā the workhorses of real-world programming.
::: grid
::: card š¤ | Strings | Ordered, immutable sequences of characters | `"Hello"`, `'Python'`
::: card š | Lists | Ordered, mutable sequences ā can store any data type | `[1, "two", 3.0]`
::: card š¦ | Tuples | Ordered, immutable sequences ā fixed after creation | `(10, 20, 30)`
::: card š | Dictionaries | Unordered key-value pairs ā fast lookups by key | `{"name": "Arjun"}`
:::
> [!IMPORTANT]
> **Board Exam Tip**
> A very common 1-mark question: *"Name the four main sequence/collection data types in Python."* Answer: **String, List, Tuple, Dictionary**. Know which are **mutable** (List, Dictionary) and which are **immutable** (String, Tuple).
---
## 2.2 Strings in Python
A **string** is an ordered, immutable sequence of characters enclosed in single `' '`, double `" "`, or triple `''' '''` quotes. Strings are one of Python's most-used data types.
```python
# Creating strings
s1 = 'Hello'
s2 = "World"
s3 = '''This is a
multi-line string'''
print(type(s1)) # <class 'str'>
print(len(s1)) # 5
```
**Indexing in Strings ā Both Directions!**
| Index | H | e | l | l | o |
|:---:|:---:|:---:|:---:|:---:|:---:|
| Positive | 0 | 1 | 2 | 3 | 4 |
| Negative | -5 | -4 | -3 | -2 | -1 |
```python
s = "Hello"
print(s[0]) # H (first character)
print(s[4]) # o (last character)
print(s[-1]) # o (last character, negative index)
print(s[-5]) # H (first character, negative index)
```
---
### 2.2.1 Item Assignment Not Supported
Strings are **immutable** ā once created, individual characters **cannot be changed**. This is one of the most important properties of strings.
### Code Example: string_immutable.py
```python
# string_immutable.py
s = "Hello"
# Trying to change a character ā WILL CRASH!
# s[0] = "J" ā TypeError: 'str' object does not support item assignment
# ā
CORRECT WAY ā create a NEW string
s = "J" + s[1:]
print(s) # Jello
# Another correct method
s = "Hello"
s = s.replace("H", "J")
print(s) # Jello
```
> [!IMPORTANT]
> **Board Exam Tip**
> "Why can't we change a character of a string directly?" is a guaranteed 1-mark question. Answer: Strings are **immutable** in Python. Item assignment (`s[0] = 'X'`) raises a `TypeError`. To "change" a string, you must create a **new string**.
> [!WARNING]
> **Classic Trap**
> Beginners often try `s[0] = 'X'` and get confused by the TypeError. Remember: you can **read** from a string using index (`s[0]`) but you can **never write** to it!
---
### 2.2.2 Traversing a String
**Traversal** means visiting each character of a string one by one. Python gives you two clean ways to do this.
### Code Example: string_traversal.py
```python
# string_traversal.py
name = "Python"
# Method 1: for loop (PREFERRED ā clean and Pythonic)
print("Method 1: for loop")
for ch in name:
print(ch, end=" ") # P y t h o n
print()
# Method 2: while loop with index
print("Method 2: while loop")
i = 0
while i < len(name):
print(name[i], end=" ") # P y t h o n
i += 1
print()
# Method 3: for loop with range and index
print("Method 3: for with range")
for i in range(len(name)):
print(f"Index {i}: {name[i]}")
```
**Output:**
```
Method 1: for loop
P y t h o n
Method 2: while loop
P y t h o n
Method 3: for with range
Index 0: P
Index 1: y
Index 2: t
Index 3: h
Index 4: o
Index 5: n
```
> [!IMPORTANT]
> **Board Exam Tip**
> Programs to count vowels/consonants, count specific characters, or check palindrome all use string traversal. Know **both** the `for ch in s` and `while i < len(s)` methods ā both appear in practical exams.
---
### 2.2.3 String Operators
Python supports several operators that work directly on strings.
| Operator | Name | Example | Result |
|:---:|:---|:---|:---|
| `+` | Concatenation | `"Hello" + " World"` | `"Hello World"` |
| `*` | Repetition | `"Ha" * 3` | `"HaHaHa"` |
| `in` | Membership | `"ell" in "Hello"` | `True` |
| `not in` | Non-membership | `"xyz" not in "Hello"` | `True` |
| `==` | Equal | `"abc" == "abc"` | `True` |
| `<` `>` | Comparison | `"apple" < "banana"` | `True` (lexicographic) |
### Code Example: string_operators.py
```python
# string_operators.py
s1 = "Hello"
s2 = " World"
# Concatenation
print(s1 + s2) # Hello World
# Repetition
print("Ha" * 3) # HaHaHa
print(3 * "-") # --- (order doesn't matter!)
# Membership
print("ell" in s1) # True
print("xyz" in s1) # False
print("xyz" not in s1)# True
# Comparison (lexicographic ā based on ASCII values)
print("apple" < "banana") # True (a < b)
print("Apple" < "apple") # True (A=65 < a=97)
print("abc" == "abc") # True
```
> [!WARNING]
> **String Comparison Trap!**
> Python compares strings **lexicographically** (like a dictionary). `"Apple" < "apple"` is `True` because uppercase letters have **lower ASCII values** than lowercase (`A=65`, `a=97`). This often surprises students!
---
### 2.2.4 String Slices
**Slicing** extracts a portion (substring) of a string. It's one of the most powerful string features in Python.
**Syntax:** `string[start : stop : step]`
- `start` ā Index to begin (inclusive). Default: `0`
- `stop` ā Index to end (**exclusive** ā NOT included). Default: `len(s)`
- `step` ā Jump between characters. Default: `1`
### Code Example: string_slicing.py
```python
# string_slicing.py
s = "PYTHON"
# 0 1 2 3 4 5 (positive indices)
# -6-5-4-3-2-1 (negative indices)
# Basic slices
print(s[1:4]) # YTH (index 1,2,3 ā stop=4 is excluded)
print(s[:3]) # PYT (start defaults to 0)
print(s[3:]) # HON (stop defaults to end)
print(s[:]) # PYTHON (full string)
# Step slicing
print(s[::2]) # PTO (every 2nd character)
print(s[1::2]) # YHN (start at 1, every 2nd)
# Negative step ā REVERSAL!
print(s[::-1]) # NOHTYP (reverse the string!)
print(s[4:1:-1]) # OHT (from index 4 to 2, going backwards)
# Using negative indices
print(s[-3:]) # HON (last 3 characters)
print(s[:-3]) # PYT (everything except last 3)
```
> [!IMPORTANT]
> **Board Exam Tip ā The Reversal Trick**
> `s[::-1]` reverses a string ā this is asked **every year** in both MCQ and programs. Also know: `s[::2]` gives alternate characters, `s[1::2]` gives alternate characters starting from index 1. These are 1-mark MCQ favourites!
> [!NOTE]
> **Memory Aid for Slicing**
> Think of slicing as a **fence post** problem. `s[1:4]` means: "Start at fence post 1, stop at fence post 4" ā you get the characters between posts 1ā2, 2ā3, and 3ā4, giving you 3 characters (indices 1, 2, 3).
---
### 2.2.5 String Functions
Python's `str` type comes loaded with built-in functions and methods. These are exam gold!
**Case Functions:**
| Function | What it does | Example |
|:---|:---|:---|
| `upper()` | All uppercase | `"hello".upper()` ā `"HELLO"` |
| `lower()` | All lowercase | `"HELLO".lower()` ā `"hello"` |
| `capitalize()` | First letter upper, rest lower | `"hELLO".capitalize()` ā `"Hello"` |
| `title()` | First letter of each word upper | `"hello world".title()` ā `"Hello World"` |
| `swapcase()` | Flip case of every character | `"HeLLo".swapcase()` ā `"hEllO"` |
**Search & Replace:**
| Function | What it does | Example |
|:---|:---|:---|
| `find(sub)` | First index of sub; `-1` if not found | `"Hello".find("ll")` ā `2` |
| `index(sub)` | Like `find()` but raises ValueError if not found | `"Hello".index("ll")` ā `2` |
| `count(sub)` | How many times sub appears | `"Hello".count("l")` ā `2` |
| `replace(old, new)` | Replace all occurrences | `"Hello".replace("l","r")` ā `"Herro"` |
| `startswith(s)` | Returns True if string starts with s | `"Python".startswith("Py")` ā `True` |
| `endswith(s)` | Returns True if string ends with s | `"Python".endswith("on")` ā `True` |
**Split & Join:**
| Function | What it does | Example |
|:---|:---|:---|
| `split(sep)` | Split string into list | `"a,b,c".split(",")` ā `['a','b','c']` |
| `join(iterable)` | Join iterable with string as separator | `",".join(['a','b','c'])` ā `"a,b,c"` |
**Strip Functions:**
| Function | What it does | Example |
|:---|:---|:---|
| `strip()` | Remove whitespace from both ends | `" Hi ".strip()` ā `"Hi"` |
| `lstrip()` | Remove whitespace from left end | `" Hi ".lstrip()` ā `"Hi "` |
| `rstrip()` | Remove whitespace from right end | `" Hi ".rstrip()` ā `" Hi"` |
**Check Functions (Return True/False):**
| Function | Returns True when... |
|:---|:---|
| `isalpha()` | All characters are letters |
| `isdigit()` | All characters are digits |
| `isalnum()` | All characters are letters or digits |
| `isspace()` | All characters are whitespace |
| `isupper()` | All letters are uppercase |
| `islower()` | All letters are lowercase |
**Other Useful Functions:**
| Function | What it does | Example |
|:---|:---|:---|
| `len(s)` | Length of string | `len("Hello")` ā `5` |
| `ord(ch)` | ASCII value of character | `ord('A')` ā `65` |
| `chr(n)` | Character for ASCII value | `chr(65)` ā `'A'` |
### Code Example: string_functions.py
```python
# string_functions.py
s = " Hello, World! "
# Case operations
print(s.upper()) # " HELLO, WORLD! "
print(s.lower()) # " hello, world! "
print("hello world".title()) # "Hello World"
# Strip whitespace
print(s.strip()) # "Hello, World!"
clean = s.strip()
# Find and Count
print(clean.find("l")) # 2
print(clean.count("l")) # 3
print(clean.find("xyz"))# -1 (not found ā no crash!)
# Replace
print(clean.replace("World", "Python")) # "Hello, Python!"
# Split and Join
sentence = "apple,banana,cherry"
fruits = sentence.split(",")
print(fruits) # ['apple', 'banana', 'cherry']
print(" | ".join(fruits)) # apple | banana | cherry
# Check functions
print("Hello123".isalnum()) # True
print("Hello".isalpha()) # True
print("12345".isdigit()) # True
print(" ".isspace()) # True
# ord() and chr()
print(ord('A')) # 65
print(ord('a')) # 97
print(chr(65)) # A
print(chr(90)) # Z
```
> [!IMPORTANT]
> **Board Exam Tip ā find() vs index()**
> Both find the position of a substring, BUT: `find()` returns **-1** if not found (safe!), while `index()` raises a **ValueError** exception. In programs, prefer `find()` unless you want an error on failure. This difference is a popular 2-mark question!
> [!IMPORTANT]
> **Board Exam Tip ā ord() and chr()**
> These functions are used in encoding/decoding programs. Remember: `ord('A')=65`, `ord('a')=97`, `ord('0')=48`. The gap between uppercase and lowercase is always **32** (`ord('a') - ord('A') == 32`). This is used in Caesar cipher programs.
---
## 2.3 Lists in Python
A **list** is an ordered, mutable (changeable) sequence that can hold items of **different data types**. Lists are the most versatile data structure in Python.
> [!IMPORTANT]
> **Board Exam Tip**
> "What is a list in Python?" ā Answer: A list is an **ordered, mutable sequence** of elements enclosed in square brackets `[ ]`. Elements can be of different data types. Lists support indexing, slicing, and many built-in methods.
---
### 2.3.1 Creating Lists
### Code Example: creating_lists.py
```python
# creating_lists.py
# Empty list
empty = []
print(empty) # []
# List of integers
numbers = [10, 20, 30, 40, 50]
print(numbers) # [10, 20, 30, 40, 50]
# Mixed data types ā Lists can hold anything!
mixed = [42, "Python", 3.14, True, None]
print(mixed) # [42, 'Python', 3.14, True, None]
# Nested list (list of lists)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(matrix[1][2]) # 6 (row 1, col 2)
# Using list() constructor
chars = list("CBSE")
print(chars) # ['C', 'B', 'S', 'E']
nums = list(range(1, 6))
print(nums) # [1, 2, 3, 4, 5]
```
**Indexing in Lists ā Same as Strings:**
| Index | 10 | 20 | 30 | 40 | 50 |
|:---:|:---:|:---:|:---:|:---:|:---:|
| Positive | 0 | 1 | 2 | 3 | 4 |
| Negative | -5 | -4 | -3 | -2 | -1 |
```python
lst = [10, 20, 30, 40, 50]
print(lst[0]) # 10
print(lst[-1]) # 50
print(lst[2]) # 30
```
---
### 2.3.2 Lists vs. Strings
Both strings and lists are sequences, but they have key differences:
| Feature | String | List |
|:---|:---:|:---:|
| Mutability | ā Immutable | ā
Mutable |
| Data types stored | Characters only | Any data type |
| Syntax | `"Hello"` | `[1, 2, 3]` |
| Item assignment | ā TypeError | ā
Allowed |
| Concatenation (`+`) | ā
| ā
|
| Repetition (`*`) | ā
| ā
|
| Indexing | ā
| ā
|
| Slicing | ā
| ā
|
### Code Example: list_vs_string.py
```python
# list_vs_string.py
# String ā IMMUTABLE
s = "Hello"
# s[0] = "J" ā TypeError!
# List ā MUTABLE
lst = ["H", "e", "l", "l", "o"]
lst[0] = "J" # ā
Allowed!
print(lst) # ['J', 'e', 'l', 'l', 'o']
# Both support slicing
print(s[1:4]) # ell
print(lst[1:4]) # ['e', 'l', 'l']
# Both support membership
print("e" in s) # True
print("e" in lst) # True
```
> [!IMPORTANT]
> **Board Exam Tip**
> "Differentiate between lists and strings in Python" ā This is a **3-mark question** that appears regularly. Always mention: mutability, data types stored, and syntax. The key difference is **mutability**.
---
### 2.3.3 List Operations
| Operation | Operator | Example | Result |
|:---|:---:|:---|:---|
| Concatenation | `+` | `[1,2] + [3,4]` | `[1, 2, 3, 4]` |
| Repetition | `*` | `[0] * 3` | `[0, 0, 0]` |
| Membership | `in` | `3 in [1,2,3]` | `True` |
| Non-membership | `not in` | `5 not in [1,2,3]` | `True` |
| Comparison | `==` | `[1,2] == [1,2]` | `True` |
### Code Example: list_operations.py
```python
# list_operations.py
a = [1, 2, 3]
b = [4, 5, 6]
# Concatenation ā creates a NEW list
c = a + b
print(c) # [1, 2, 3, 4, 5, 6]
# Repetition
zeros = [0] * 4
print(zeros) # [0, 0, 0, 0]
# Membership
print(2 in a) # True
print(10 in a) # False
print(10 not in a) # True
# Comparison
print([1,2,3] == [1,2,3]) # True
print([1,2,3] == [3,2,1]) # False (order matters!)
# Slicing a list ā same syntax as strings
lst = [10, 20, 30, 40, 50]
print(lst[1:4]) # [20, 30, 40]
print(lst[::-1]) # [50, 40, 30, 20, 10] (reversed!)
print(lst[::2]) # [10, 30, 50]
```
---
### 2.3.4 List Manipulation
Since lists are mutable, we can directly modify their elements ā unlike strings!
### Code Example: list_manipulation.py
```python
# list_manipulation.py
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
# ā Updating an element
fruits[1] = "blueberry"
print(fruits) # ['apple', 'blueberry', 'cherry', 'date', 'elderberry']
# ā” Updating a slice (replace multiple elements at once)
fruits[2:4] = ["coconut", "durian"]
print(fruits) # ['apple', 'blueberry', 'coconut', 'durian', 'elderberry']
# ⢠Deleting an element by index
del fruits[0]
print(fruits) # ['blueberry', 'coconut', 'durian', 'elderberry']
# ⣠Deleting a slice
del fruits[1:3]
print(fruits) # ['blueberry', 'elderberry']
# ⤠Inserting in the middle via slice assignment
fruits[1:1] = ["fig", "grape"]
print(fruits) # ['blueberry', 'fig', 'grape', 'elderberry']
```
> [!WARNING]
> **Common Mistake**
> `fruits[1:3] = ["mango"]` does NOT just change elements 1 and 2 ā it **replaces that slice** with the new list. The length of the list may change! Slice assignment is powerful but needs care.
---
### 2.3.5 Making True Copy of a List
This is one of the **trickiest topics** for students ā and a favourite exam question!
**The Problem: Shallow (Alias) Copy**
When you write `b = a`, Python does NOT create a new list. Both `a` and `b` point to the **same list object**. Changing one changes both!
### Code Example: list_copy.py
```python
# list_copy.py
# ā ALIAS (NOT a copy!)
original = [1, 2, 3, 4, 5]
alias = original # Both point to SAME list!
alias[0] = 999
print(original) # [999, 2, 3, 4, 5] ā Original CHANGED!
print(alias) # [999, 2, 3, 4, 5]
# ā
TRUE COPY ā Method 1: Slicing
original = [1, 2, 3, 4, 5]
copy1 = original[:]
copy1[0] = 999
print(original) # [1, 2, 3, 4, 5] ā Original SAFE!
print(copy1) # [999, 2, 3, 4, 5]
# ā
TRUE COPY ā Method 2: list() constructor
copy2 = list(original)
copy2[0] = 888
print(original) # [1, 2, 3, 4, 5] ā Original SAFE!
# ā
TRUE COPY ā Method 3: .copy() method
copy3 = original.copy()
copy3[0] = 777
print(original) # [1, 2, 3, 4, 5] ā Original SAFE!
```
> [!IMPORTANT]
> **Board Exam Tip ā Most Important Concept!**
> "What is the difference between `b = a` and `b = a[:]` for lists?" ā This is a **guaranteed 3-mark question** in many board papers. Always explain:
> - `b = a` creates an **alias** (both refer to same object)
> - `b = a[:]` creates a **true copy** (independent objects)
> Use `id()` to prove they are different: `id(original) != id(copy1)`.
> [!NOTE]
> **id() Verification**
> ```python
> a = [1, 2, 3]
> b = a # alias
> c = a[:] # true copy
> print(id(a) == id(b)) # True ā same object!
> print(id(a) == id(c)) # False ā different objects!
> ```
---
### 2.3.6 List Functions
Python provides a rich set of list functions and methods. Know them all!
**Methods that MODIFY the list (in-place):**
| Method | What it does | Example |
|:---|:---|:---|
| `append(x)` | Add x at the END | `lst.append(6)` |
| `extend(iterable)` | Add all items of iterable at end | `lst.extend([7,8])` |
| `insert(i, x)` | Insert x at index i | `lst.insert(2, 99)` |
| `remove(x)` | Remove first occurrence of x | `lst.remove(99)` |
| `pop(i)` | Remove & return item at index i (default: last) | `lst.pop()` |
| `sort()` | Sort list in place (ascending) | `lst.sort()` |
| `sort(reverse=True)` | Sort in descending order | `lst.sort(reverse=True)` |
| `reverse()` | Reverse the list in place | `lst.reverse()` |
| `clear()` | Remove all elements | `lst.clear()` |
**Methods/Functions that return a value (do NOT modify):**
| Function | What it does | Example |
|:---|:---|:---|
| `len(lst)` | Number of elements | `len([1,2,3])` ā `3` |
| `count(x)` | Count occurrences of x | `[1,2,2,3].count(2)` ā `2` |
| `index(x)` | Index of first occurrence of x | `[1,2,3].index(2)` ā `1` |
| `min(lst)` | Minimum element | `min([5,1,3])` ā `1` |
| `max(lst)` | Maximum element | `max([5,1,3])` ā `5` |
| `sum(lst)` | Sum of all elements | `sum([1,2,3])` ā `6` |
| `sorted(lst)` | Returns new sorted list | `sorted([3,1,2])` ā `[1,2,3]` |
### Code Example: list_functions.py
```python
# list_functions.py
lst = [30, 10, 50, 20, 40]
print("Original:", lst) # [30, 10, 50, 20, 40]
# append ā adds ONE element to end
lst.append(60)
print("After append(60):", lst) # [30, 10, 50, 20, 40, 60]
# extend ā adds MULTIPLE elements to end
lst.extend([70, 80])
print("After extend:", lst) # [30, 10, 50, 20, 40, 60, 70, 80]
# insert ā at specific position
lst.insert(1, 5)
print("After insert(1,5):", lst) # [30, 5, 10, 50, 20, 40, 60, 70, 80]
# remove ā first occurrence
lst.remove(5)
print("After remove(5):", lst) # [30, 10, 50, 20, 40, 60, 70, 80]
# pop ā removes and returns last element
popped = lst.pop()
print("Popped:", popped) # 80
print("After pop():", lst) # [30, 10, 50, 20, 40, 60, 70]
# pop(index) ā removes and returns specific index
popped_at = lst.pop(2)
print("Popped at [2]:", popped_at) # 50
# sort and reverse
lst.sort()
print("After sort():", lst) # [10, 20, 30, 40, 60, 70]
lst.reverse()
print("After reverse():", lst) # [70, 60, 40, 30, 20, 10]
# Statistics
print("min:", min(lst)) # 10
print("max:", max(lst)) # 70
print("sum:", sum(lst)) # 230
print("len:", len(lst)) # 6
```
> [!IMPORTANT]
> **Board Exam Tip ā append() vs extend()**
> This is asked every year!
> - `append([4,5])` ā adds the list as a single element: `[1,2,3,[4,5]]`
> - `extend([4,5])` ā adds each item separately: `[1,2,3,4,5]`
> After `lst.append([4,5])`, `len(lst)` increases by **1**. After `lst.extend([4,5])`, it increases by **2**.
> [!WARNING]
> **sort() vs sorted()**
> - `lst.sort()` ā **modifies** the original list, returns `None`
> - `sorted(lst)` ā **does NOT modify** original list, returns a **new** sorted list
> Writing `lst = lst.sort()` is a classic bug ā `lst` becomes `None`!
---
## 2.4 Tuples in Python
A **tuple** is an ordered, **immutable** sequence of elements. Think of a tuple as a "read-only list" ā once created, it cannot be changed.
> [!IMPORTANT]
> **Board Exam Tip**
> "When would you use a tuple instead of a list?" ā Answer: When data should **not change** (e.g., days of week, RGB color values, database records), use a tuple. Tuples are also **faster** than lists and can be used as **dictionary keys** (lists cannot).
---
### 2.4.1 Creating Tuples
### Code Example: creating_tuples.py
```python
# creating_tuples.py
# Empty tuple
t0 = ()
print(t0) # ()
# Tuple of integers
t1 = (10, 20, 30)
print(t1) # (10, 20, 30)
# Mixed data types
t2 = (42, "Python", 3.14, True)
print(t2)
# ā ļø CRITICAL: Single-element tuple ā the comma is MANDATORY!
single_wrong = (42) # This is just an int, NOT a tuple!
single_right = (42,) # This IS a tuple ā note the comma!
print(type(single_wrong)) # <class 'int'>
print(type(single_right)) # <class 'tuple'>
# Tuple without parentheses (tuple packing)
t3 = 1, 2, 3
print(t3) # (1, 2, 3)
print(type(t3)) # <class 'tuple'>
# Using tuple() constructor
t4 = tuple([1, 2, 3]) # from list
t5 = tuple("CBSE") # from string
print(t4) # (1, 2, 3)
print(t5) # ('C', 'B', 'S', 'E')
```
> [!WARNING]
> **The Single-Element Tuple Trap ā Exam Favourite!**
> `t = (42)` is an **integer**, NOT a tuple! Python reads the parentheses as just grouping.
> `t = (42,)` is a **tuple** ā the trailing comma makes all the difference.
> This is a 1-mark MCQ that trips up almost everyone. Remember: **ONE item ā must have a comma!**
---
### 2.4.2 Tuples vs. Lists
| Feature | Tuple | List |
|:---|:---:|:---:|
| Mutability | ā Immutable | ā
Mutable |
| Syntax | `( )` | `[ ]` |
| Speed | ā
Faster | Slower |
| Memory | Less | More |
| Can be dict key | ā
Yes | ā No |
| Has all list methods | ā No | ā
Yes |
| Use case | Fixed data | Dynamic data |
### Code Example: tuple_vs_list.py
```python
# tuple_vs_list.py
t = (1, 2, 3)
lst = [1, 2, 3]
# ā Tuple item assignment raises TypeError
# t[0] = 99 ā TypeError: 'tuple' object does not support item assignment
# ā
List item assignment works
lst[0] = 99
print(lst) # [99, 2, 3]
# Tuples can be dict keys, lists CANNOT
my_dict = {(1, 2): "point A", (3, 4): "point B"}
print(my_dict[(1, 2)]) # point A
# This would CRASH:
# bad_dict = {[1,2]: "point"} ā TypeError: unhashable type: 'list'
```
---
### 2.4.3 Tuple Operations
Tuples support all **read-only** operations that lists do:
| Operation | Example | Result |
|:---|:---|:---|
| Concatenation | `(1,2) + (3,4)` | `(1, 2, 3, 4)` |
| Repetition | `(0,) * 3` | `(0, 0, 0)` |
| Membership | `2 in (1,2,3)` | `True` |
| Indexing | `t[1]` | Second element |
| Slicing | `t[1:3]` | Sub-tuple |
| Comparison | `(1,2) == (1,2)` | `True` |
### Code Example: tuple_operations.py
```python
# tuple_operations.py
t1 = (10, 20, 30)
t2 = (40, 50, 60)
# Concatenation ā creates NEW tuple
t3 = t1 + t2
print(t3) # (10, 20, 30, 40, 50, 60)
# Repetition
t4 = (0,) * 4
print(t4) # (0, 0, 0, 0)
# Membership
print(20 in t1) # True
print(99 in t1) # False
# Indexing
print(t1[0]) # 10
print(t1[-1]) # 30
# Slicing
print(t3[2:5]) # (30, 40, 50)
print(t3[::-1]) # (60, 50, 40, 30, 20, 10)
# Tuple unpacking ā very Pythonic!
a, b, c = t1
print(a, b, c) # 10 20 30
```
> [!TIP]
> **Tuple Unpacking** is a clean Python technique ā assign a tuple's values to multiple variables in one line: `x, y, z = (1, 2, 3)`. This works because Python's multiple assignment is based on tuple packing/unpacking.
---
### 2.4.4 Tuple Functions and Methods
Tuples have only **2 methods** (because they're immutable ā can't add, remove, or sort):
| Method | What it does | Example |
|:---|:---|:---|
| `count(x)` | Count occurrences of x | `(1,2,2,3).count(2)` ā `2` |
| `index(x)` | Index of first occurrence of x | `(10,20,30).index(20)` ā `1` |
**Built-in functions that work on tuples:**
| Function | What it does |
|:---|:---|
| `len(t)` | Number of elements |
| `min(t)` | Minimum element |
| `max(t)` | Maximum element |
| `sum(t)` | Sum of all numeric elements |
| `sorted(t)` | Returns a new sorted **list** (not tuple!) |
| `tuple(seq)` | Convert sequence to tuple |
### Code Example: tuple_functions.py
```python
# tuple_functions.py
marks = (85, 92, 78, 92, 88, 70)
print("Length:", len(marks)) # 6
print("Maximum:", max(marks)) # 92
print("Minimum:", min(marks)) # 70
print("Sum:", sum(marks)) # 505
print("Count of 92:", marks.count(92)) # 2 ā count() is a tuple method
print("Index of 78:", marks.index(78)) # 2
# sorted() returns a LIST, not a tuple!
sorted_marks = sorted(marks)
print("Sorted:", sorted_marks) # [70, 78, 85, 88, 92, 92]
print("Type:", type(sorted_marks)) # <class 'list'>
# If you need a sorted tuple:
sorted_tuple = tuple(sorted(marks))
print("Sorted tuple:", sorted_tuple) # (70, 78, 85, 88, 92, 92)
```
> [!IMPORTANT]
> **Board Exam Tip**
> `sorted(t)` on a tuple returns a **list**, not a tuple! This is a frequent 1-mark MCQ. Also: tuples have only `count()` and `index()` methods ā they do NOT have `append()`, `remove()`, `sort()`, etc.
---
## 2.5 Dictionaries in Python
A **dictionary** is an unordered (Python 3.6 insertion-ordered) collection of **key-value pairs**. It's like a real dictionary ā you look up a word (key) to find its definition (value).
> [!IMPORTANT]
> **Board Exam Tip**
> "Define dictionary in Python" ā Answer: A dictionary is a **mutable, unordered** collection of **key-value pairs** enclosed in curly braces `{ }`. Each key must be **unique** and **immutable** (string, number, or tuple). Values can be of any type.
---
### 2.5.1 Creating a Dictionary
### Code Example: creating_dict.py
```python
# creating_dict.py
# Empty dictionary
d0 = {}
print(d0) # {}
print(type(d0)) # <class 'dict'>
# Dictionary with initial values
student = {
"name": "Arjun",
"age": 17,
"marks": 95,
"passed": True
}
print(student)
# Mixed key types (keys must be immutable)
mixed = {
1: "one",
"two": 2,
(3, 4): "tuple key"
}
print(mixed[1]) # one
print(mixed["two"]) # 2
print(mixed[(3, 4)]) # tuple key
# Using dict() constructor
d2 = dict(name="Priya", age=16, city="Delhi")
print(d2) # {'name': 'Priya', 'age': 16, 'city': 'Delhi'}
# Creating from list of tuples
d3 = dict([("a", 1), ("b", 2), ("c", 3)])
print(d3) # {'a': 1, 'b': 2, 'c': 3}
```
---
### 2.5.2 Accessing Elements of a Dictionary
### Code Example: accessing_dict.py
```python
# accessing_dict.py
student = {"name": "Arjun", "age": 17, "marks": 95}
# Method 1: Direct key access ā FAST
print(student["name"]) # Arjun
print(student["marks"]) # 95
# ā Key error if key doesn't exist
# print(student["grade"]) ā KeyError: 'grade'
# ā
Method 2: get() ā SAFE (returns None or default if key not found)
print(student.get("grade")) # None (no crash!)
print(student.get("grade", "N/A")) # N/A (custom default value)
# Checking if key exists
if "age" in student:
print("Age found:", student["age"]) # Age found: 17
```
> [!IMPORTANT]
> **Board Exam Tip ā [] vs get()**
> - `d["key"]` ā **raises KeyError** if key doesn't exist
> - `d.get("key")` ā returns **None** (or custom default) if key doesn't exist
> In safe programs, always use `get()`. This difference is tested in both MCQ and programming questions!
---
### 2.5.3 Characteristics of a Dictionary
::: grid
::: card š | Unique Keys | Each key appears EXACTLY once ā duplicate keys overwrite | `{"a":1, "a":2}` ā `{"a":2}`
::: card š | Immutable Keys | Keys must be immutable: strings, numbers, tuples ā NOT lists | Use `"name"` not `["name"]`
::: card āļø | Mutable Values | Values can be any data type and can be changed | Values can even be lists or dicts!
::: card š | Ordered (3.7+) | From Python 3.7, dicts maintain insertion order | Items appear in order you added them
:::
### Code Example: dict_characteristics.py
```python
# dict_characteristics.py
# Duplicate keys ā LAST VALUE WINS
d = {"a": 1, "b": 2, "a": 99}
print(d) # {'a': 99, 'b': 2} ā first "a":1 is overwritten!
# Values can be any type ā even lists, dicts!
complex_dict = {
"name": "Arjun",
"scores": [85, 90, 88],
"address": {"city": "Delhi", "pin": 110001}
}
print(complex_dict["scores"]) # [85, 90, 88]
print(complex_dict["address"]["city"]) # Delhi
# Lists CANNOT be keys (unhashable!)
# bad = {[1,2]: "value"} ā TypeError: unhashable type: 'list'
# Tuples CAN be keys
good = {(1, 2): "coordinates"}
print(good[(1, 2)]) # coordinates
```
---
### 2.5.4 Dictionary Operations
### Code Example: dict_operations.py
```python
# dict_operations.py
student = {"name": "Arjun", "age": 17}
# ā Adding a new key-value pair
student["marks"] = 95
print(student) # {'name': 'Arjun', 'age': 17, 'marks': 95}
# ā” Updating an existing value
student["age"] = 18
print(student) # {'name': 'Arjun', 'age': 18, 'marks': 95}
# ⢠Deleting a key-value pair using del
del student["age"]
print(student) # {'name': 'Arjun', 'marks': 95}
# ⣠Deleting using pop() ā also returns the value
removed = student.pop("marks")
print("Removed value:", removed) # 95
print(student) # {'name': 'Arjun'}
# ⤠Checking membership (checks KEYS only!)
student = {"name": "Arjun", "age": 17, "marks": 95}
print("name" in student) # True
print("Arjun" in student) # False ā "Arjun" is a VALUE, not a KEY!
print(17 in student) # False ā 17 is a VALUE!
# ā„ Traversal
for key in student:
print(key, ":", student[key])
```
> [!WARNING]
> **Membership Check on Dictionaries**
> `"key" in d` checks only **keys**, NOT values! `"Arjun" in student` is `False` even though "Arjun" is in the dictionary (as a value). Use `"Arjun" in student.values()` to search values.
---
### 2.5.5 Dictionary Functions and Methods
| Method | What it does | Returns |
|:---|:---|:---|
| `keys()` | All keys | `dict_keys` view |
| `values()` | All values | `dict_values` view |
| `items()` | All key-value pairs as tuples | `dict_items` view |
| `get(key, default)` | Value for key; default if not found | Value or default |
| `update(d2)` | Add/update with another dict | None (in-place) |
| `pop(key)` | Remove and return value for key | Value |
| `popitem()` | Remove and return last inserted pair | (key, value) tuple |
| `clear()` | Remove all items | None |
| `copy()` | Shallow copy | New dict |
| `setdefault(key, val)` | Return value; set default if key absent | Value |
| `fromkeys(seq, val)` | Create dict with keys from seq | New dict |
| `len(d)` | Number of key-value pairs | Integer |
### Code Example: dict_methods.py
```python
# dict_methods.py
student = {"name": "Arjun", "age": 17, "marks": 95, "grade": "A"}
# keys(), values(), items()
print(student.keys()) # dict_keys(['name', 'age', 'marks', 'grade'])
print(student.values()) # dict_values(['Arjun', 17, 95, 'A'])
print(student.items()) # dict_items([('name', 'Arjun'), ...])
# Convert to lists if needed
key_list = list(student.keys())
print(key_list) # ['name', 'age', 'marks', 'grade']
# update() ā adds/modifies multiple items at once
student.update({"city": "Delhi", "age": 18})
print(student) # marks updated, city added
# Traversal using items()
print("\nStudent Report:")
for key, value in student.items():
print(f" {key:10}: {value}")
# fromkeys() ā create dict with fixed value
subjects = ["Physics", "Chemistry", "Maths", "CS"]
marks_template = dict.fromkeys(subjects, 0)
print(marks_template)
# {'Physics': 0, 'Chemistry': 0, 'Maths': 0, 'CS': 0}
# popitem() ā removes last inserted item
last = student.popitem()
print("Removed:", last) # ('age', 18) ā last inserted
# Nested dict traversal
school = {
"class12": {"strength": 40, "subjects": 5},
"class11": {"strength": 35, "subjects": 5}
}
for cls, info in school.items():
print(f"{cls}: {info['strength']} students")
```
> [!IMPORTANT]
> **Board Exam Tip ā keys(), values(), items()**
> These three methods are asked every year. Know that they return **view objects**, not lists. Convert to list with `list(d.keys())` if you need a list. Also know that traversal using `for k, v in d.items():` is the most Pythonic way to iterate over a dict.
---
## 2.6 Sorting Techniques
Sorting means arranging elements in a specific order (ascending or descending). CBSE Class 12 requires knowledge of two classic algorithms: **Bubble Sort** and **Insertion Sort**.
> [!IMPORTANT]
> **Board Exam Tip**
> Sorting algorithms are almost always a **5-mark programming question** in boards. You must be able to: (a) write the complete program, (b) trace through a given array step by step, and (c) state the time complexity.
---
### 2.6.1 Bubble Sort
**Concept:** In each **pass**, compare adjacent pairs of elements and **swap** if they are in the wrong order. After each pass, the largest unsorted element "bubbles up" to its correct position.
**How it works (ascending order):**
- Pass 1: Largest element reaches last position
- Pass 2: 2nd largest reaches 2nd last
- After n-1 passes: Entire array is sorted
**Tracing Bubble Sort on `[64, 34, 25, 12, 22]`:**
| Pass | Array after pass | Element Settled |
|:---:|:---|:---:|
| Start | `[64, 34, 25, 12, 22]` | ā |
| Pass 1 | `[34, 25, 12, 22, 64]` | 64 ā position 4 |
| Pass 2 | `[25, 12, 22, 34, 64]` | 34 ā position 3 |
| Pass 3 | `[12, 22, 25, 34, 64]` | 25 ā position 2 |
| Pass 4 | `[12, 22, 25, 34, 64]` | 22 ā position 1 |
### Code Example: bubble_sort.py
```python
# bubble_sort.py
def bubble_sort(arr):
n = len(arr)
# Outer loop: n-1 passes
for i in range(n - 1):
# Inner loop: compare adjacent pairs
# Each pass, last i elements are already sorted
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]: # If out of order...
arr[j], arr[j+1] = arr[j+1], arr[j] # ...swap!
print(f"After pass {i+1}: {arr}") # Show progress
# Main
data = [64, 34, 25, 12, 22]
print("Original:", data)
bubble_sort(data)
print("Sorted: ", data)
```
**Output:**
```
Original: [64, 34, 25, 12, 22]
After pass 1: [34, 25, 12, 22, 64]
After pass 2: [25, 12, 22, 34, 64]
After pass 3: [12, 22, 25, 34, 64]
After pass 4: [12, 22, 25, 34, 64]
Sorted: [12, 22, 25, 34, 64]
```
**Optimized Bubble Sort (Early Exit):**
```python
def bubble_sort_optimized(arr):
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped: # If no swap in this pass, already sorted!
print(f"Early exit at pass {i+1} ā already sorted!")
break
```
**Descending Order:** Change `>` to `<` in the comparison.
::: grid
::: card Ⱡ| Time Complexity | Best: O(n) with optimization | Worst/Average: O(n²)
::: card š¾ | Space Complexity | O(1) ā in-place sorting | No extra array needed
::: card ā
| Stable Sort | Yes ā equal elements maintain relative order | Safe for records
::: card š | Best For | Small datasets, teaching/exams | NOT efficient for large data
:::
> [!IMPORTANT]
> **Board Exam Tip ā Write the Algorithm Too!**
> If asked to "write an algorithm for bubble sort": Step 1 ā Start from index 0; Step 2 ā Compare adjacent elements; Step 3 ā Swap if out of order; Step 4 ā Repeat for n-1 passes. Always mention **O(n²)** time complexity.
---
### 2.6.2 Insertion Sort
**Concept:** Build a sorted portion of the list one element at a time. Take each element from the **unsorted** part and **insert** it into its correct position in the **sorted** part ā like sorting playing cards in your hand!
**How it works:**
- Start: First element is trivially "sorted"
- Step 2: Take 2nd element, insert in correct position among first 1
- Step 3: Take 3rd element, insert in correct position among first 2
- Continue until all elements are placed
**Tracing Insertion Sort on `[29, 10, 14, 37, 13]`:**
| Step | Sorted Part | Key | Result |
|:---:|:---|:---:|:---|
| Start | `[29]` | ā | ā |
| Step 2 | `[29]` | 10 | `[10, 29]` |
| Step 3 | `[10, 29]` | 14 | `[10, 14, 29]` |
| Step 4 | `[10, 14, 29]` | 37 | `[10, 14, 29, 37]` |
| Step 5 | `[10, 14, 29, 37]` | 13 | `[10, 13, 14, 29, 37]` |
### Code Example: insertion_sort.py
```python
# insertion_sort.py
def insertion_sort(arr):
n = len(arr)
# Start from index 1 (index 0 is trivially sorted)
for i in range(1, n):
key = arr[i] # The element to be placed
j = i - 1 # Start comparing from just before
# Shift elements rightward to make room for key
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j] # Shift right
j -= 1
arr[j + 1] = key # Place key in correct position
print(f"After step {i}: {arr}")
# Main
data = [29, 10, 14, 37, 13]
print("Original:", data)
insertion_sort(data)
print("Sorted: ", data)
```
**Output:**
```
Original: [29, 10, 14, 37, 13]
After step 1: [10, 29, 14, 37, 13]
After step 2: [10, 14, 29, 37, 13]
After step 3: [10, 14, 29, 37, 13]
After step 4: [10, 13, 14, 29, 37]
Sorted: [10, 13, 14, 29, 37]
```
::: grid
::: card Ⱡ| Time Complexity | Best: O(n) for nearly-sorted | Worst/Average: O(n²)
::: card š¾ | Space Complexity | O(1) ā in-place | No extra memory needed
::: card ā
| Stable Sort | Yes ā equal elements keep their relative order | Reliable
::: card š | Best For | Nearly-sorted data, small arrays, live data streams | Beats Bubble Sort in practice
:::
**Bubble Sort vs. Insertion Sort ā Comparison:**
| Feature | Bubble Sort | Insertion Sort |
|:---|:---:|:---:|
| Comparisons (worst case) | More | Fewer |
| Swaps (worst case) | More | Fewer (shifts instead) |
| Performance on nearly-sorted | Faster with optimization | Faster naturally |
| Practical use | Rarely used | Used in hybrid algorithms |
| Time Complexity | O(n²) | O(n²) |
| Space Complexity | O(1) | O(1) |
> [!IMPORTANT]
> **Board Exam Tip ā Trace the Algorithm!**
> The board exam often gives you an unsorted array and asks you to "show the array after each pass of bubble sort" or "show each step of insertion sort." Practice tracing manually on paper ā don't just run code! Traceable arrays of 5-6 elements are the standard exam format.
> [!NOTE]
> **Memory Trick ā Which is Which?**
> š«§ **Bubble** = big elements **bubble up** to the top (like bubbles rising) ā compare and swap adjacent.
> š **Insertion** = like **inserting a card** in your hand at the right spot ā take one, find its place in the sorted left portion.
---
## Practice Problems
Test your mastery of Tour II topics with these board-style problems:
**Strings:**
1. Write a program to count the number of vowels and consonants in a string
2. Write a program to check if a string is a palindrome (without using slicing)
3. Write a program to reverse words in a sentence: `"Hello World"` ā `"World Hello"`
4. Write a program to count occurrences of each character in a string using a dictionary
5. Write a program using `ord()` to encode a message (shift each letter by 3 ā Caesar cipher)
**Lists & Tuples:**
6. Write a program to find the second-largest element in a list (without sorting)
7. Write a program to remove duplicate elements from a list while preserving order
8. Write a program to find the frequency of each element in a list using a dictionary
9. Demonstrate the difference between alias copy and true copy of a list
10. Write a program to flatten a list of lists: `[[1,2],[3,4],[5,6]]` ā `[1,2,3,4,5,6]`
**Dictionaries:**
11. Write a program to merge two dictionaries
12. Write a program to create a frequency dictionary from a list of words
13. Write a program to find the key with the maximum value in a dictionary
14. Write a program to invert a dictionary (swap keys and values)
**Sorting:**
15. Implement bubble sort and show the array after each pass for `[5, 1, 4, 2, 8]`
16. Implement insertion sort for a list of student names (alphabetical order)
17. Modify bubble sort to sort in descending order
18. For `[8, 4, 1, 3, 6]`, perform insertion sort step by step and show each state
---
## Quick Reference
**Strings ā Key Facts:**
- Immutable ā `s[0] = 'X'` raises TypeError
- Slicing: `s[start:stop:step]`, reverse: `s[::-1]`
- `find()` ā `-1` if not found | `index()` ā ValueError if not found
- `split()` ā string to list | `join()` ā list to string
- `ord()` ā ASCII value | `chr()` ā character from ASCII
**Lists ā Key Facts:**
- Mutable ā `lst[0] = 99` is allowed
- True copy: `lst[:]` or `lst.copy()` or `list(lst)` ā NOT `b = a`
- `append(x)` ā adds 1 item | `extend([x,y])` ā adds multiple
- `sort()` ā modifies original | `sorted()` ā returns new list
- `pop()` ā removes last | `pop(i)` ā removes at index i
**Tuples ā Key Facts:**
- Immutable ā cannot change after creation
- Single-element: `(42,)` ā comma is mandatory!
- Only 2 methods: `count()` and `index()`
- `sorted(t)` returns a **list**, not a tuple
- Can be used as dictionary keys (lists cannot)
**Dictionaries ā Key Facts:**
- Key-value pairs: `{key: value}`
- Keys must be unique and immutable
- `d[key]` ā KeyError if missing | `d.get(key)` ā None if missing
- `in` checks keys, NOT values
- `keys()`, `values()`, `items()` ā view objects
**Sorting Complexities:**
| Algorithm | Best | Average | Worst | Space |
|:---|:---:|:---:|:---:|:---:|
| Bubble Sort | O(n) | O(n²) | O(n²) | O(1) |
| Insertion Sort | O(n) | O(n²) | O(n²) | O(1) |
Back to List
Calculating...