L
LLLOS.ai
LLOS.ai
L
Class 11 Computer Science Chapter 5 of 9

Chapter 5 — Working With Data In Python

Overview

Introduction: "Working with Data in Python" introduces how data is represented, stored and manipulated in Python programs. The chapter covers core built-in data types (numbers, strings, booleans), compound data structures (lists, tuples, sets, dictionaries), basic input/output and type conversion, and common operations and methods used to clean, transform and analyze simple datasets. Importance: Understanding these concepts gives students the foundation to solve real problems, prepare data for algorithms, and build larger programs. Mastery of data handling is essential for algorithm development, debugging, and later topics like databases, data science and file handling. Key themes: data types and literals, variables and naming rules, reading and displaying data, type conversion and validation, operators and precedence, string manipulation (indexing, slicing, methods), sequence operations (lists/tuples: creation, indexing, slicing, methods), sets and dictionaries (usage and common operations), iteration over data, and basic data processing patterns (search, aggregate, sort). What the student will learn: students will learn to declare and use variables, distinguish and convert data…

Learning Objectives

  • Define and give examples of built-in Python data types used in working with data (int, float, str, bool, list, tuple, dict, set).
  • Explain indexing and slicing for strings, lists and tuples and trace outputs of code snippets that use them.
  • Distinguish between mutable and immutable sequence types and describe implications for data manipulation.
  • Demonstrate use of common string methods (split, join, strip, replace, upper/lower, find) to process textual data.
  • Apply list and dictionary methods (append, extend, insert, remove, pop, sort, reverse, keys, values, items, get, update) to solve data manipulation tasks.
  • Use list comprehensions and generator expressions to create and transform collections concisely and predict their outputs.
  • Implement nested data structures (lists of lists, dictionaries containing lists/dicts) and write code to access and update nested elements.
  • Write Python programs to read from and write to text files using open(), read(), readline(), readlines(), write(), and with context managers.

Topics in this chapter

20 topics · tap a topic title to jump straight to it.

⚙️1

Introduction to Working with Data

What is data? Data are pieces of information — numbers, text, True/False values or more complex records — that describe events, measurements, observations or transactions.

Why work with data? In computing and everyday life we collect, store, analyze and present data to make decisions (e.g., student marks, sales figures, weather records).

Types of data:

  • Primitive: integers, floats, strings, booleans.
  • Structured: tables (rows and columns), CSV, JSON; unstructured: free text, images.

Common Python containers: list (ordered collection), tuple (ordered immutable), dict (key→value mapping), set (unique items). These are used to store and manipulate data in memory.

Typical data workflow: collection → cleaning/preprocessing → storage → analysis → visualization → interpretation. Cleaning includes handling missing values, converting data types, removing duplicates and validating ranges.

Basic operations: indexing/slicing, iterating (for-loops, comprehensions), aggregations (sum, count, min, max), and summary statistics (mean, median, mode, variance). For files and exchange formats you use CSV or JSON; Python has built-in csv/json modules and higher-level libraries such as pandas for tabular data.

Practical tips: always check data types, handle missing or invalid entries before analysis, and start with simple visualizations to understand distribution and trends.

📌 Examples
  • Student marks table: rows are students, columns are subjects and total marks. Use lists/dicts to store records and compute average marks, highest scorer, grade distribution.
  • Daily temperature readings: a time-series of floats. Use a list of (date, temperature) tuples, compute monthly average, and plot a line chart to show trends.
  • Store sales transactions in CSV: columns like date, product_id, quantity, price. Read CSV, convert quantities/prices to numeric types, compute total revenue per product.
  • Survey responses in JSON: each response as a JSON object. Use Python's json module to load data, count categorical responses, and find common answers.
  • Sensor data stream: real-time numeric values with occasional missing readings. Detect and interpolate or remove missing values before computing statistics.
🧮 Formulas
  1. Mean (average): mean = (x1 + x2 + ... + xn) / n
  2. Median: the middle value when data are sorted (if n is odd), or average of two middle values (if n is even)
  3. Mode: the value that appears most frequently in the dataset
  4. Variance: σ² = (1/n) * Σ (xi - mean)²
  5. Standard deviation: σ = sqrt(variance)
  6. Percentage: percent = (part / whole) × 100
📊 Visual ideas
Histogram — use to show distribution of a numeric variable (e.g., marks). X-axis: value bins; Y-axis: frequency. Suggestion: choose 8–12 bins for moderate-sized datasets.
Bar chart — compare categorical counts (e.g., number of students per grade or sales per product). X-axis: categories; Y-axis: counts or sums (revenue).
Line chart (time series) — show change over time (e.g., daily temperature or monthly sales). X-axis: time; Y-axis: measurement. Useful to display trends and seasonality.
Scatter plot — show relationship between two numeric variables (e.g., study hours vs marks). X-axis: independent variable; Y-axis: dependent variable. Add a trend line to estimate correlation.
💻2

Variables and Identifiers

What is a Variable?
A variable is a name that refers to a value stored in memory. In Python a variable name (identifier) is bound to an object; the object has a value and a type. Python is dynamically typed: the same variable name can refer to objects of different types at different times.

What is an Identifier?
An identifier is the name used to identify a variable, function, class, module, or other object. Identifiers in Python are sequences of letters (a–z, A–Z), digits (0–9) and underscores (_), but must start with a letter or underscore.

Rules for Identifiers

  • Must begin with a letter (a–z, A–Z) or underscore (_).
  • Can contain letters, digits and underscores only.
  • Case-sensitive: age and Age are different identifiers.
  • Cannot be a Python reserved keyword (for, if, while, def, class, True, None, etc.).
  • Should not start with digits and should not contain special characters or spaces.

Naming Conventions (Good Practices)
Use meaningful names, prefer snake_case for variables and functions (example: student_marks), limit length to readable size, avoid single-letter names except for counters (i, j) and avoid names that shadow built-ins (like list, str).

How Variables Work (Conceptual Memory Model)
Think of a variable name as a label attached to an object stored in memory. When you assign x = 10, the name x points to an integer object with value 10. Assigning x = 'hello' rebinds the name x to a string object; the old integer object may be reclaimed by garbage collection if no names refer to it.

Scope and Lifetime (Brief)
Scope determines where an identifier is accessible: global scope (module-level), local scope (inside functions), and nested scopes. Lifetime is how long the binding exists — local variables exist during function execution; globals exist while the program runs (or module is loaded).

Common Operations

# assignment
count = 5

# multiple assignment
a, b = 1, 2

# swapping without temp
a, b = b, a

# augmented assignment
count += 1

# type conversion (casting)
age = int('18')

Errors to Watch For

  • SyntaxError if identifier starts with digit or has illegal character: 2name, first-name.
  • NameError when using an identifier that has not been defined.
  • Shadowing built-ins can cause subtle bugs (for example, assigning list = [1,2] then calling list()).

📌 Examples
  • age = 16 # integer variable name = 'Rahul' # string variable is_enrolled = True # boolean variable
  • x, y, z = 10, 20, 30 # multiple assignment x, y = y, x # swap x and y
  • balance = 1500.75 balance += 500 # augmented assignment; balance becomes 2000.75
  • # invalid identifiers # 2count = 5 # SyntaxError: cannot start with digit # first-name = 'A' # SyntaxError: '-' not allowed in identifier
  • # scope example user = 'global' def greet(): user = 'local' # local variable, different from global user print(user) # greet() prints 'local', global user remains 'global'
🧮 Formulas
  1. Assignment: variable = expression (example: x = 5)
  2. Multiple assignment: a, b, c = v1, v2, v3 (example: a, b = 1, 2)
  3. Swap without temp: a, b = b, a
  4. Augmented assignment: var op= value (example: count += 1, total *= 2)
  5. Type conversion (casting): new_var = type(value) (example: n = int('42'))
  6. Identifier rule summary: starts_with ∈ {letter, underscore} AND all_chars ∈ {letters, digits, underscore} AND not a reserved_keyword
📊 Visual ideas
Memory-box diagram: show variable names as labels pointing to boxes representing objects (value + type). Include two labels referencing the same object to illustrate aliasing.
Flowchart of assignment: evaluate right-hand expression → create/locate object → bind name to object. Show re-binding on reassignment.
Scope diagram: nested boxes showing global scope, function scope, and inner blocks; place identifier examples in appropriate boxes to show visibility.
Type-change timeline: a horizontal timeline for a single identifier (x) showing different types it refers to at different times (int → string → list).
📊3

Data Types and Literals

Data types specify what kind of value a variable can hold and what operations are valid on it. In Python, values are created by writing literals — fixed notations for data (like 10, 'abc', [1,2]).

Main categories:

  • Numeric types
    • int – integers without fractional part (e.g., 42, -7).
    • float – floating-point numbers with decimals (e.g., 3.14, -0.5).
    • complex – complex numbers with real and imaginary parts (e.g., 2+3j).
  • Text
    • str – sequence of characters. Literals can use single, double or triple quotes: 'hi', "hello", '''multi-line'''. Supports indexing, slicing and many methods.
  • Boolean – bool which has two literals: True and False. Used for conditions and logical operations.
  • None – the None literal represents absence of a value.
  • Collection (compound) types
    • list – ordered, changeable sequence written with square brackets: [1, 2, 3] (mutable).
    • tuple – ordered, fixed sequence written with parentheses: (1, 2, 3) (immutable).
    • set – unordered collection of unique elements written with braces: {1, 2, 3} (mutable, no duplicates).
    • dict – key:value mapping using braces: {'name':'Asha', 'age':16} (mutable, keys unique).

Literals are the concrete notation for values. Examples: numeric literals (0, -3.5), string literals ("CBSE"), boolean (True), collection literals ([], (), {}), and None.

Mutability: lists, sets and dicts are mutable (their contents can change). Strings, tuples, integers, floats and complex numbers are immutable. Understanding immutability helps avoid bugs when sharing objects.

Type checking and conversion: use type(x) or isinstance(x, T) to check types. Convert values with int(), float(), str(), bool(), and constructors for collections like list(), tuple(), set(), dict().

Why it matters (real life): choose appropriate types to represent data accurately — e.g., counts as int, prices/measurements as float, names as str, a contact directory as dict. Wrong type choices lead to incorrect computations or inefficient programs.

📌 Examples
  • Counting students: roll = 30 # int literal
  • Price of book: price = 199.99 # float literal
  • Complex number: z = 4 + 2j # complex literal
  • Name: name = 'Rahul' # string literal (single quotes)
  • Multi-line text: text = '''Line1\nLine2''' # triple-quoted string
  • Boolean check: passed = True # boolean literal
🧮 Formulas
  1. type(x) # returns the type of x
  2. isinstance(x, T) # checks if x is of type T
  3. int(s), float(s), str(x), bool(x) # type conversions
  4. len(s) # length of sequence or collection
  5. a + b # addition or concatenation (numbers/strings/lists)
  6. a * n # repeat sequence or multiply numbers
📊 Visual ideas
Type hierarchy diagram: a simple tree showing primitive types (int, float, complex, bool, str, NoneType) and compound types (list, tuple, set, dict) with arrows for 'is-a' relation and notes on mutability.
Memory/mutability illustration: two diagrams side-by-side showing an immutable object (string/tuple) where modifying creates a new object vs a mutable object (list/dict) where in-place updates change the same object (include object id examples).
Literal syntax table: a labeled table graphic showing examples of each literal and syntax (int: 10, float: 3.5, str: 'hi', list: [1,2], tuple: (1,2), set: {1,2}, dict: {'a':1}).
Data-type distribution pie/bar chart: for a sample dataset (e.g., student records), visualize percent of values that are strings, numbers, booleans, etc. Useful to decide storage/processing strategy.
💻4

Type Conversion and Casting

Type conversion (also called casting) in Python is the process of converting a value from one data type to another. This is important when combining values of different types, reading input (which is a string), or preparing data for operations that require specific types.

Two kinds of conversion

  • Implicit conversion (automatic): Python automatically converts one data type to another when it is safe to do so—for example, when an int and a float are used together, the int is converted to float so precision is preserved.
  • Explicit conversion (casting): The programmer converts types using built-in functions such as int(), float(), str(), bool(), etc. This is required when automatic conversion is not possible or when you want a specific result.

Rules & behavior

  • Numeric promotion: int mixed with float → float; float mixed with complex → complex.
  • int(float_value) truncates toward zero (it does not round).
  • Converting from string: the string must represent a valid value for the target type (e.g., "123" → int OK, "12.3" → int raises ValueError unless first converted to float).
  • bool conversions: bool(0) is False; bool(0.0) is False; bool("") is False; any nonzero number or non-empty container is True.
  • Incompatible conversions (like int('abc')) raise ValueError; type mismatches in operations may raise TypeError.
  • Python’s bool is a subclass of int: True == 1 and False == 0, so booleans participate in numeric operations.

Common casting functions

  • int(x) — to integer (truncates floats).
  • float(x) — to floating-point.
  • str(x) — to string.
  • bool(x) — to boolean.
  • complex(a, b) or complex(x) — to complex number.
  • int(string, base) — convert from a string in given base (e.g., base=2 for binary).
  • bin(x), oct(x), hex(x) — convert integer to binary/octal/hex string representation.
  • list(), tuple(), set(), dict() — convert between container types where conversion makes sense.

Short code examples

# implicit conversion
result = 5 + 2.3    # result is 7.3 (int 5 promoted to float)

# explicit conversion
s = '123'
num = int(s)         # 123 (int)
price = 19.95
count = int(price)   # 19 (truncated)

# string + number needs casting
name = 'Alice'
age = 17
greeting = name + ' is ' + str(age) + ' years old'

# base conversion
n = int('1010', 2)   # n == 10

# boolean rules
bool(0)    # False
bool('')   # False
bool([1])  # True

# invalid conversion
# int('12.3')  -> ValueError; first do float('12.3') then int(...)

Understanding when Python converts types automatically and when you must cast explicitly helps avoid errors and data loss (for example, unintended truncation). Use explicit casting when you need a specific type or to validate/clean input data.

📌 Examples
  • Reading numeric input from a user: input() returns a string. Convert to int or float before arithmetic: age = int(input('Age: ')).
  • Concatenating text and numbers: build messages using str(): 'Score: ' + str(score).
  • Currency conversion: multiply float exchange_rate by int quantity (int promoted to float). Example: total = 3 * 74.5 -> 223.5.
  • Sensor data: a temperature sensor returns '23.7' as a string from a device; convert with temp = float(reading) to compute averages.
  • Binary parsing: convert '1101' (binary string) to decimal using int('1101', 2) -> 13.
🧮 Formulas
  1. int(x) — convert x to integer (float -> truncates toward zero; string -> must be integer-format).
  2. float(x) — convert x to float (int -> exact, string -> must be valid float format).
  3. str(x) — convert x to string (useful for concatenation and display).
  4. bool(x) — convert x to boolean (0, 0.0, '', None, empty containers -> False; otherwise True).
  5. complex(a, b) or complex(x) — produce complex numbers (a + bj).
  6. int(s, base) — parse string s as an integer in given base (2..36).
📊 Visual ideas
Type conversion flow diagram: nodes for types (str, int, float, bool, complex, list, tuple) and arrows labeled with functions (int(), float(), str(), bool(), complex(), list(), tuple()). Use different colored arrows for implicit vs explicit conversions.
Bar chart showing 'value before' vs 'value after' when converting floats to ints to illustrate truncation (e.g., 3.9 -> 3, -2.7 -> -2).
Line plot of a float time series vs its int-casted values to show loss of precision over time (e.g., sensor readings rounded down by int).
Pie chart of a dataset by data type (strings, integers, floats, booleans) to visualize how much casting might be needed when cleaning data.
💻5

Operators and Expressions

Overview
In Python, an expression is any combination of values, variables and operators that Python can evaluate to produce another value. Operators are special symbols or keywords that perform operations on one or more operands (values or variables). Understanding operators and expressions is essential for writing computations, making decisions and manipulating data.

Types of operators

  • Arithmetic: +, -, *, /, // (floor division), % (modulus), ** (exponent). These perform numeric calculations. Example: 3 + 4 * 2**2.
  • Comparison (Relational): ==, !=, <, <=, >, >=. They compare values and return True or False.
  • Logical: and, or, not. Combine boolean expressions. Use short-circuit evaluation.
  • Assignment: = assigns a value. Augmented assignment like +=, -=, *= updates a variable: x += 3 is x = x + 3.
  • Bitwise: &, |, ^, ~, <<, >> operate on integer bit representations.
  • Membership: in, not in — check presence in sequences or collections.
  • Identity: is, is not — check whether two names refer to the same object (not just equal values).

Expressions and evaluation
An expression can be simple (5, x) or compound ((a + b) * c). Evaluation follows operator precedence (higher-precedence operators evaluated first) and associativity (left-to-right or right-to-left for operators with same precedence). Python also performs implicit type conversions for some operations (e.g., 1 + 2.0 -> 3.0) and allows explicit conversions via int(), float(), str(), bool().

Precedence highlights
Important precedence order (from higher to lower): parentheses, exponentiation (** right-to-left), unary +/-, *, /, //, %, +, -, bitwise shifts, bitwise AND/OR/XOR, comparison, not, and, or. Use parentheses to force a specific evaluation order.

Short-circuiting
Logical and and or use short-circuit evaluation: for A and B, if A is false, B is not evaluated; for A or B, if A is true, B is not evaluated. This is useful for guarding operations (e.g., x and x.method()).

Operator overloading
Some operators behave differently for different operand types: e.g., + adds numbers but concatenates strings or lists ('a' + 'b' -> 'ab', [1] + [2] -> [1,2]).

Common pitfalls

  • Using is instead of == for value comparison (use == to compare values; is checks identity).
  • Unexpected integer division vs floor division: in Python 3, / produces float, use // for floor division.
  • Relying on implicit type conversion in mixed-type expressions can produce unexpected floats or strings; prefer explicit conversion when needed.

Small Python code examples

# arithmetic and precedence
a, b, c = 2, 3, 4
result = a + b * c        # 2 + (3*4) = 14

# short-circuit example
user = None
name = user and user.name  # user is None -> name is None (user.name not evaluated)

# bitwise example
x = 6        # 0b110
y = 3        # 0b011
z = x & y    # 0b010 -> 2

# membership
s = 'hello'
'in' in s    # True

# augmented assignment
count = 0
count += 5   # 5

# swapping without temp
x, y = y, x

How this applies in real life
Operators and expressions model everyday computations: banking calculations (interest using arithmetic operators and precedence), decision logic in apps (comparison + logical operators to determine eligibility), string manipulation in text processing (concatenation, membership), bitwise operations in low-level hardware control, and short-circuit checks for safe resource access.

📌 Examples
  • Calculator (arithmetic & precedence): expression for final amount: final = principal + interest_rate * principal; precedence ensures multiplication before addition.
  • Eligibility check (comparison + logical): if age >= 18 and has_id: allow entry — both conditions must be True.
  • Safe attribute access (short-circuit): username = user and user.name — avoids accessing user.name when user is None.
  • String concatenation (operator overloading): full_name = first_name + ' ' + last_name.
  • Even/odd check (bitwise): even if (n & 1) == 0 else odd — fast test using bitwise AND.
  • Swapping values (assignment tuple): a, b = b, a — swap without a temporary variable.
🧮 Formulas
  1. Arithmetic identities: a + 0 = a, a * 1 = a, a - 0 = a, a / 1 = a
  2. Modulus: a = b * (a // b) + (a % b) (quotient and remainder relationship)
  3. De Morgan's laws: not (A and B) == (not A) or (not B); not (A or B) == (not A) and (not B)
  4. Operator precedence (important order high→low): parentheses (), exponentiation ** (right-to-left), unary + -, *, /, //, %, + -, << >>, &, ^, |, comparisons, not, and, or
  5. Short-circuit logic: (A and B) returns A if A is falsy, otherwise returns B; (A or B) returns A if A is truthy, otherwise returns B
  6. Type conversion: numeric mixing — int + float -> float; explicit conversions: int(x), float(x), str(x), bool(x)
📊 Visual ideas
Expression tree for (a + b * c): nodes showing '*' as child of '+' and leaves a, b, c — shows order of evaluation; useful as a tree diagram.
Operator precedence chart: horizontal stacked bar or layered pyramid showing precedence levels (top = highest precedence, bottom = lowest) for quick reference.
Flowchart for short-circuit evaluation: decision nodes for left operand truthiness guiding whether to evaluate right operand (useful for and/or behavior).
Venn diagram for logical operators: show A and B (intersection), A or B (union), not A (complement) to visualize boolean combinations.
💻6

Strings

What is a string?
A string is an ordered sequence of characters used to store and manipulate text. In Python a string literal is written between single quotes (') or double quotes (") or triple quotes for multi-line text. Strings are immutable — once created, their characters cannot be changed in place.

Creating and representing strings

  • s = 'Hello' or s = "Hello" or s = '''Multi\nline'''
  • Escape sequences: \n (newline), \t (tab), \\ (backslash), \', \"
  • Raw strings: r'C:\folder\file' treats backslashes literally

Indexing and slicing
Strings are indexed from 0. You can access characters and sub-strings using indexing and slicing.

  • Indexing: s[i] gives the character at position i (negative indices count from the end)
  • Slicing: s[i:j:k] returns a substring from index i up to j (exclusive) with step k; omitted values default to start, end, or step 1

Common operations

  • Concatenation: s + t
  • Repetition: s * n
  • Membership: sub in s checks whether sub occurs in s
  • Length: len(s)
  • Methods: s.upper(), s.lower(), s.strip(), s.split(sep), sep.join(list), s.replace(old,new), s.find(sub), s.count(ch)

Immutability and consequences
Because strings are immutable, operations that appear to modify a string actually create and return a new string. For example, s = s.replace('a','b') assigns a new string to the variable s; the old string is unchanged in memory until garbage-collected.

Formatting and templates
Strings are commonly used to format output and generate templates. You can use str.format(), f-strings (f"Hello {name}"), or % formatting to insert values into text.

Use in data handling
Strings are central in reading/writing text files, parsing CSV or log lines, processing user input, building URLs and JSON, validating input (emails, phone numbers), and producing human-readable reports.

📌 Examples
  • Basic: s = 'Hello World' ; print(s[0]) # 'H' ; print(s[-1]) # 'd'
  • Slicing: s = 'ABCDEFGHI' ; s[1:7:2] # 'BDF'
  • Concatenation & repetition: 'Hi' + '!'*3 # 'Hi!!!'
  • Split & join: line = 'apple,banana,pear' ; parts = line.split(',') ; ','.join(parts)
  • Search & replace: s.find('cat') ; s.replace('cat','dog')
  • Formatting: name = 'Asha' ; age = 16 ; f'Name: {name}, Age: {age}'
🧮 Formulas
  1. Indexing: s[i] # character at index i (0-based). Negative: s[-1] last char
  2. Slicing: s[i:j:k] # substring from i to j-1 with step k. Defaults: i=0, j=len(s), k=1
  3. Length: len(s) # number of characters
  4. Concatenation: s + t # joins two strings
  5. Repetition: s * n # repeats s n times
  6. Membership: sub in s # True if sub appears in s
📊 Visual ideas
Character index line: a horizontal line with positions 0..n-1 and characters above each index to show indexing and negative indices (mirror indices on top/bottom).
Slicing mapping diagram: illustrate start, stop, step selecting positions from the index line, with arrows showing picked characters.
Immutability memory diagram: show original string object and a new string object after a replace/concatenate operation (variables pointing to different objects).
Character frequency bar chart: x-axis characters, y-axis counts — useful for visualizing s.count results and text analysis.
💻7

Lists

What is a list?

A list in Python is an ordered, mutable collection of items. Items can be of different data types (heterogeneous) and duplicates are allowed. Lists are used to store a sequence of values and provide many built-in operations and methods to manipulate that sequence.

Creating lists

# empty list
lst = []
# list with values
nums = [10, 20, 30]
mixed = [1, 'apple', 3.14, True]
# nested list (list of lists)
matrix = [[1,2,3], [4,5,6], [7,8,9]]

Key characteristics

  • Ordered: items have a definite order and each item has an index (starting at 0).
  • Mutable: elements can be changed, added or removed after creation.
  • Heterogeneous: elements of different types can coexist.
  • Allow duplicates: same value can appear multiple times.

Indexing and slicing

Access items by index: lst[0] is first item. Negative indices count from the end: lst[-1] is last item. Slicing returns a sublist: lst[start:stop:step].

nums = [10,20,30,40,50]
nums[0]      # 10
nums[-1]     # 50
nums[1:4]    # [20,30,40]
nums[::2]    # [10,30,50]

Common list operations and methods

  • len(lst) — number of elements.
  • in — membership test: if x in lst:.
  • Concatenation: lst1 + lst2. Repetition: lst * n.
  • append(x) — add x to end.
  • insert(i, x) — insert x at index i.
  • extend(iterable) — add all elements of an iterable.
  • remove(x) — remove first occurrence of x.
  • pop([i]) — remove and return element at index i (default last).
  • index(x), count(x) — locate/count occurrences.
  • sort() and reverse() — in-place sorting and reversal. sorted(lst) returns a new sorted list.
  • copy() or slicing lst[:] — shallow copy. Use copy.deepcopy() for nested lists.

Iteration and enumeration

for item in lst:
    print(item)
# with index
for i, v in enumerate(lst):
    print(i, v)

Nested lists (2D lists / matrices)

Lists can contain other lists. Access by chained indices: matrix[row][col]. Useful to represent tables or matrices.

List comprehensions (concise construction)

squares = [x*x for x in range(1,6)]  # [1,4,9,16,25]
filtered = [x for x in nums if x > 20]

Practical tips

  • Use append for incremental building, extend to merge lists.
  • Prefer sorted() if you need original list unchanged.
  • Beware of aliasing: b = a makes both names refer to same list; use a.copy() or a[:] to copy.
📌 Examples
  • Example 1: Student marks marks = [78, 85, 62, 91, 73] # average = sum(marks) / len(marks) # find highest: max(marks), lowest: min(marks)
  • Example 2: Shopping list (mutable) shopping = ['milk', 'eggs', 'bread'] shopping.append('butter') # add item shopping.remove('eggs') # remove item
  • Example 3: Inventory with counts (parallel lists) items = ['pen', 'notebook', 'eraser'] qty = [20, 10, 5] # quantity of 'notebook' -> qty[items.index('notebook')] # 10
  • Example 4: Sensor readings (time series) readings = [23.4, 24.1, 22.8, 23.9] # moving average, plot readings vs time
  • Example 5: Matrix as nested list matrix = [[1,2,3],[4,5,6],[7,8,9]] # element at row2 col3: matrix[1][2] -> 6
🧮 Formulas
  1. Indexing: lst[i] (i starts from 0); negative: lst[-1] is last element
  2. Slicing: lst[start:stop:step] (returns sublist from start to stop-1)
  3. Length: n = len(lst)
  4. Concatenate: lst3 = lst1 + lst2
  5. Repeat: lst2 = lst * k
  6. Membership: x in lst (True/False)
📊 Visual ideas
Bar chart: frequency of categorical items in a list (e.g., count of fruits in shopping list). X-axis: item categories, Y-axis: counts.
Line plot: time-series values from a numeric list (e.g., sensor readings or student scores over tests). X-axis: time or test number, Y-axis: value.
Pie chart: percentage distribution of categories (e.g., percent of each grade band among students).
Heatmap / matrix grid: visualize a nested list (2D list) as a colored grid (good for matrices or images).
💻8

Tuples

What is a tuple?

A tuple is an ordered, immutable collection of items in Python. Like a list, a tuple can hold heterogeneous elements (different data types) and preserves the insertion order, but once created its elements cannot be changed (no item assignment, insertion, or deletion). Tuples are written with parentheses () or simply comma-separated values.

Syntax & basic examples

# creation
t1 = (1, 2, 3)
# parentheses optional for simple tuples
t2 = 4, 5, 6
# single element tuple needs a trailing comma
single = (10,)
# empty tuple
empty = ()

Properties

  • Ordered: elements have a fixed order and can be accessed by index (0-based).
  • Immutable: you cannot change elements after creation. Any attempt to assign to an index raises an error.
  • Heterogeneous: elements of different types allowed.
  • Hashable (sometimes): a tuple is hashable if all its elements are hashable, so it can be used as a dictionary key.
  • Supports many sequence operations: indexing, slicing, concatenation, repetition, membership test, length, iteration.

Common operations (with examples)

t = (10, 20, 30, 'a')
# indexing
x = t[1]        # 20
# slicing
s = t[1:3]      # (20, 30)
# concatenation
t3 = t + (100,)
# repetition
r = t * 2       # (10,20,30,'a',10,20,30,'a')
# membership
is_in = 20 in t # True
# length
n = len(t)      # 4
# methods: count and index
cnt = t.count(20)
pos = t.index('a')
# unpacking
a, b, c, d = t
# extended unpacking
first, *middle, last = (1,2,3,4,5)

Immutability explained

Because tuples are immutable, operations that seem to change a tuple actually create a new tuple. For example concatenation t + u returns a new tuple; converting to a list, modifying, then converting back is a common workaround.

t = (1,2,3)
# this raises TypeError
# t[0] = 10
# workaround
lst = list(t)
lst[0] = 10
t = tuple(lst)

When to use tuples (advantages)

  • When data should not change (fixed records like coordinates, RGB colors, date components).
  • As keys in dictionaries (if elements are hashable) or elements in sets.
  • Returning multiple values from a function (function returns a tuple by default).
  • Tuples are slightly faster and use less memory than lists for fixed-size sequences.

Examples of real-life mapping

  • Geographic coordinate: (latitude, longitude)
  • RGB color: (R, G, B)
  • Date: (year, month, day)
  • Database record row (id, name, age)

Notes for Class 11 students

  • Understand difference between tuple and list: mutability is the key difference.
  • Learn tuple packing/unpacking and common operations (indexing, slicing, concatenation, repetition, membership).
  • Practice with small programs: returning multiple values, using tuples as keys in dicts, and converting between list & tuple.
📌 Examples
  • Return multiple values from a function: def min_max(numbers): return (min(numbers), max(numbers)) a, b = min_max([3,1,9,4]) # a=1, b=9
  • Coordinate example: point = (12.9716, 77.5946) # (latitude, longitude) lat = point[0] lon = point[1]
  • Using tuple as dict key: location = {(12.9716,77.5946): 'Bengaluru'} print(location[(12.9716,77.5946)])
  • Unpacking with extended form: values = (1,2,3,4,5) first, *middle, last = values # first=1, middle=[2,3,4], last=5
  • Immutability workaround: t = (1,2,3) lst = list(t) lst.append(4) t = tuple(lst) # now (1,2,3,4)
🧮 Formulas
  1. Indexing: element = t[i] where i in 0..len(t)-1
  2. Slicing: subtuple = t[start:stop] returns elements start..stop-1
  3. Concatenation: t3 = t1 + t2 (creates a new tuple)
  4. Repetition: t2 = t1 * n (repeats contents n times)
  5. Length: n = len(t)
  6. Membership: x in t (True if x is one of the elements)
📊 Visual ideas
Box diagram of a tuple: draw a horizontal row of boxes each labeled with the element and index below (0,1,2..) to show order and indexing.
Memory diagram comparing tuple vs list: show tuple pointing to immutable objects and list with arrows to elements and an edit (insert/change) symbol for list but a lock symbol for tuple.
Flowchart for tuple creation and unpacking: creation -> check length -> assign to variables -> handle ValueError if mismatch (demonstrate packing/unpacking).
Bar chart suggestion: compare time/memory for small fixed sequences in tuple vs list (e.g., create and iterate 1 million times) to show tuples slightly faster / less memory.
💻9

Sets

What is a set? A set is an unordered collection of distinct elements. In Python, sets store unique items and support mathematical set operations (union, intersection, difference, etc.). Sets are mutable (you can add/remove elements) but their elements must be immutable (e.g., numbers, strings, tuples).

How to create sets in Python

  • Using curly braces: {1, 2, 3}.
  • Using the constructor: set([1, 2, 3]) or set('abc').
  • Note: {} creates an empty dictionary — use set() for an empty set.

Important properties

  • Uniqueness: duplicates are automatically removed.
  • Unordered: no guaranteed index or order.
  • Mutable container: methods like add, remove, discard, pop, clear are available.
  • Set elements must be hashable (immutable types).

Common operations and methods

  • Union: A | B or A.union(B) — elements in A or B.
  • Intersection: A & B or A.intersection(B) — elements in both A and B.
  • Difference: A - B or A.difference(B) — elements in A but not in B.
  • Symmetric difference: A ^ B or A.symmetric_difference(B) — elements in A or B but not both.
  • Subset / superset checks: A.issubset(B), A.issuperset(B).
  • Membership: x in A (fast, average O(1)).

Why use sets? They are ideal for removing duplicates, membership testing, and performing mathematical set operations efficiently.

Simple Python example (illustration)

students = ['Anita', 'Ravi', 'Anita', 'Sunil']
unique = set(students)   # {'Anita', 'Ravi', 'Sunil'}
unique.add('Meera')
if 'Ravi' in unique:
    unique.remove('Ravi')
📌 Examples
  • Create, add, remove: code: s = {1, 2, 3} s.add(4) # s -> {1,2,3,4} s.discard(2) # s -> {1,3,4} # discard does not raise error if element missing; remove does
  • Union and intersection: code: A = {1, 2, 3} B = {3, 4, 5} A | B # -> {1,2,3,4,5} (union) A & B # -> {3} (intersection) A - B # -> {1,2} (difference) A ^ B # -> {1,2,4,5} (symmetric difference)
  • Remove duplicates from a list: code: nums = [2,2,3,5,3] unique_nums = list(set(nums)) # -> [2,3,5] (order not guaranteed)
  • Real-life scheduling (common free slots): code: alice = {'Mon9','Tue10','Wed11'} bob = {'Tue10','Thu9'} common = alice & bob # -> {'Tue10'} (time slots both are free)
  • Exclusive customers to store A (difference): code: A = {'cust1','cust2','cust3'} B = {'cust2','cust4'} only_A = A - B # -> {'cust1','cust3'}
🧮 Formulas
  1. A ∪ B = {x | x ∈ A or x ∈ B} (union) — Python: A | B or A.union(B)
  2. A ∩ B = {x | x ∈ A and x ∈ B} (intersection) — Python: A & B or A.intersection(B)
  3. A \ B = {x | x ∈ A and x ∉ B} (difference) — Python: A - B or A.difference(B)
  4. A Δ B = (A \ B) ∪ (B \ A) (symmetric difference) — Python: A ^ B or A.symmetric_difference(B)
  5. |A| = number of elements in A (cardinality) — Python: len(A)
  6. Subset: A ⊆ B iff every x ∈ A is also in B — Python: A.issubset(B)
📊 Visual ideas
Venn diagram showing two overlapping circles labeled A and B: shade the union (A ∪ B), intersection (A ∩ B), and each difference (A \ B, B \ A) in separate subfigures for clarity.
Three-set Venn diagram (A, B, C) to illustrate intersections of multiple sets and symmetric differences; label each of the 7 non-empty regions.
Euler diagram to show subset relationship: draw a large circle B and a smaller circle A entirely inside B to represent A ⊆ B.
Bar chart comparing cardinalities: bars for |A|, |B| and |A ∪ B|, |A ∩ B| to visualize how sizes relate after operations.
💻10

Dictionaries

What is a Dictionary?

A dictionary in Python is a built-in collection type that stores data as key:value pairs. Each key maps to a value. Keys must be unique and immutable (for example: strings, numbers, tuples), while values can be any Python object (numbers, strings, lists, other dictionaries, etc.).

Characteristics

  • Mapping type — data accessed by key, not by index.
  • Keys are unique; assigning the same key again updates the value.
  • Mutable — you can add, change or remove items.
  • Insertion order is preserved in Python 3.7+ (implementation detail in 3.6).

Basic operations and syntax

  • Creation: d = {'a': 1, 'b': 2} or d = dict().
  • Access: value = d['a'] (raises KeyError if key missing) or value = d.get('a', default) (safer).
  • Insert / Update: d['c'] = 3.
  • Delete: del d['b'] or d.pop('b', default) or d.popitem().
  • Membership: 'a' in d returns True/False (checks keys).
  • Iteration: for k in d: or for k, v in d.items():.

Common methods

  • d.keys() — view of keys.
  • d.values() — view of values.
  • d.items() — view of (key, value) pairs.
  • d.update(other) — merge another dict (overwrites on same keys).
  • d.clear() — remove all items.
  • d.copy() — shallow copy.
  • dict.fromkeys(seq, value) — create dict with keys from seq and same value.
  • d.setdefault(key, default) — get or set default value for key.

Dictionary comprehensions

Similar to list comprehensions: {k: v for k, v in iterable}. Useful to build transformed dictionaries in one expression.

Nested dictionaries

Dictionaries can contain other dictionaries as values, enabling structured records (e.g., student -> {name, marks}). Access uses multiple key lookups: students['s1']['marks'].

Performance

Dictionaries are implemented with hash tables. Average time complexity for lookup, insert and delete is O(1). In rare worst-case situations (many hash collisions) complexity can degrade toward O(n).

When to use a dictionary?

  • When you need a lookup by a unique key (e.g., phone number by name).
  • When representing records/objects with named fields.
  • When counting/frequency mapping (word counts, item counts).
📌 Examples
  • Phonebook (simple): phone = {'Alice':'9876543210', 'Bob':'9123456780'}; phone['Alice'] gives '9876543210'.
  • Student records (nested): students = {'s101': {'name':'Rahul', 'marks': 85}, 's102': {'name':'Anita', 'marks': 92}}; students['s101']['marks'] -> 85.
  • Inventory update: stock = {'pens': 10, 'notebooks': 5}; stock['pens'] = stock.get('pens',0) - 2 updates quantity; stock.update({'eraser':3}) adds new item.
  • Counting words: text = 'to be or not to be'; freq = {}; for w in text.split(): freq[w] = freq.get(w,0)+1; result: {'to':2, 'be':2, 'or':1, 'not':1}.
  • Merging dictionaries: d3 = {**d1, **d2} (Python 3.5+) or d3 = d1.copy(); d3.update(d2) — values from d2 overwrite d1 on key conflict.
🧮 Formulas
  1. len(d) -> number of key:value pairs
  2. key in d -> True/False (checks presence of key)
  3. d[key] = value -> insert or update the key with value
  4. value = d.get(key, default) -> safe access without KeyError
  5. del d[key] or d.pop(key) -> remove a key and its value
  6. d.keys(), d.values(), d.items() -> views for keys, values, pairs
📊 Visual ideas
Key->Value mapping diagram: boxes for keys with arrows pointing to their values to show pair relationships (good for beginners).
Hash table bucket diagram: show keys hashed to bucket indices, collisions chained or linear-probed — explains why average O(1) lookup works.
Nested dictionary tree: tree diagram with top-level keys branching to inner dictionaries and fields (useful for student records example).
Bar chart from dict values: e.g., plot word frequencies with keys on x-axis and counts on y-axis to visualise distribution.
⚖️11

Sequence Operations, Indexing and Slicing

Overview: In Python a sequence is an ordered collection of items. Common sequence types are strings, lists and tuples. Sequences support indexing (accessing single elements), slicing (extracting subsequences), and several operations such as concatenation, repetition and membership tests.

Indexing

  • Indices are zero-based: the first element has index 0, the second index 1, and so on. Example: seq[0] is the first element.
  • Negative indices access elements from the end: seq[-1] is the last element, seq[-2] is the second last, etc. Formula: seq[-k] == seq[len(seq) - k].
  • Attempting to use an index outside the valid range raises IndexError for lists/tuples/strings.

Slicing

  • General form: seq[start:stop:step]. The slice returns a new sequence of the same type (for strings, lists, tuples) containing elements from index start up to but not including stop, taken every step positions.
  • Defaults: if start is omitted it defaults to 0 (when step > 0). If stop is omitted it defaults to len(seq). If step is omitted it defaults to 1.
  • Negative step reverses direction: seq[::-1] returns the sequence reversed. With a negative step, default start becomes len(seq)-1 and default stop becomes -1 (stop excluded).
  • Slices always produce a new object (a shallow copy for lists). For immutable sequences like strings and tuples you cannot modify elements; for lists you can assign to a slice to change multiple elements at once, e.g. lst[1:3] = [x, y].

Common sequence operations

  • Concatenation: seq1 + seq2 joins two sequences of the same type (strings or lists or tuples).
  • Repetition: seq * n repeats the sequence n times.
  • Membership: x in seq returns True if x appears in seq.
  • Length and extrema: len(seq), min(seq), max(seq) (when elements are comparable).
  • Iteration: for loops iterate elements in order: for item in seq:

Practical notes and pitfalls

  • Remember that slicing stop index is exclusive: seq[1:4] returns elements at indices 1,2,3.
  • Using an out-of-range index for slicing does not raise an error; Python clamps the indices to valid range. But direct indexing with an out-of-range index raises IndexError.
  • Because slicing returns a new object, modifying the returned slice does not change the original sequence (except by assigning back to a slice of a mutable sequence).

Short code summary

s = 'python'
# indexing
s[0]     # 'p'
s[-1]    # 'n'
# slicing
s[1:4]   # 'yth'  (indices 1,2,3)
s[:3]    # 'pyt'
s[3:]    # 'hon'
s[::2]   # 'pto'  (every second char)
s[::-1]  # 'nohtyp' (reversed)

lst = [10,20,30,40,50]
lst[1:4]         # [20,30,40]
lst[1:4:2]       # [20,40]
lst[-3:-1]       # [30,40]
lst + [60,70]    # [10,20,30,40,50,60,70]
lst * 2          # [10,20,30,40,50,10,20,30,40,50]
📌 Examples
  • Example 1 - Days of week: days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'] - days[0] -> 'Mon' - days[-1] -> 'Sun' - days[1:5] -> ['Tue','Wed','Thu','Fri'] (slices exclude stop index) - days[::2] -> ['Mon','Wed','Fri','Sun'] (every second day)
  • Example 2 - Top marks: marks = [72, 85, 90, 65, 88, 91] - sorted marks then take top 3 using slicing: sorted_marks = sorted(marks) - top3 = sorted_marks[-3:] -> last three elements are top scorers
  • Example 3 - Extracting every nth sensor reading: readings = [r0, r1, r2, ...] - sample_every_5 = readings[::5] returns readings at indices 0,5,10,...
  • Example 4 - Reverse a string: name = 'RAHUL' - name[::-1] -> 'LUHAR' (useful for palindromes and reversing sequences)
🧮 Formulas
  1. Indexing: element at position i is seq[i] where i in 0..len(seq)-1; negative index: seq[-k] == seq[len(seq)-k]
  2. Slicing syntax: seq[start:stop:step] returns indices start, start+step, start+2*step, ... up to but not including stop
  3. Default values: if start omitted -> 0 (when step>0); if stop omitted -> len(seq); if step omitted -> 1
  4. Number of items in slice (step>0): count = max(0, ((stop - start + step - 1) // step)) (indices treated after clamping to valid range)
📊 Visual ideas
Index map: horizontal boxes each containing element, with positive indices labeled 0,1,2,... above the boxes and negative indices labeled -n,...,-2,-1 below. Use a colored box to show seq[1] and seq[-1].
Slice window: same index map but highlight start index in green, stop index in red (stop excluded) and shaded boxes for included elements. Add arrows indicating step skipping boxes.
Step visualization: show every kth element highlighted (e.g., every 2nd element) to illustrate seq[::2].
Reverse arrow: show sequence with arrows pointing left (for negative step) and label seq[::-1] producing reversed sequence.
💻12

Mutability, Immutability and Aliasing

Overview

In Python, every value is an object. Objects are either mutable (their content can change after creation) or immutable (their content cannot change; any operation that looks like a change actually creates a new object). Aliasing happens when two or more names (variables) refer to the same object in memory.

Why this matters

  • Mutability affects program behaviour: modifying a mutable object through one name affects all names that refer to it.
  • Immutability gives safety and predictability: you cannot accidentally change an object shared elsewhere.
  • Aliasing can cause bugs if you unintentionally share and then mutate objects.

Mutable vs Immutable (common Python types)

  • Mutable: list, dict, set, bytearray, most custom objects (unless designed otherwise)
  • Immutable: int, float, bool, str, tuple, frozenset, bytes

Identity vs Equality

  • Equality (==) checks whether values are equal.
  • Identity (is) checks whether two names point to the same object (same memory address).
  • id(x) returns the unique identity (address) of object x while it exists.

Typical behaviours

  • Assigning a name to an object just creates a reference (no copy). Example: a = [1, 2]; b = a — both a and b reference the same list.
  • Mutating methods change the object in-place: append, extend, pop, clear, dictionary updates, etc.
  • Operations on immutables produce new objects. For strings and tuples, operations that appear to change them actually bind the name to a new object.

Shallow vs Deep Copy

  • Shallow copy copies the container but keeps references to the same elements (use list.copy(), slicing [:], or copy.copy()).
  • Deep copy recursively copies nested objects so no shared sub-objects remain (use copy.deepcopy()).

Practical advice

  • When you need independent objects, explicitly copy (shallow or deep) instead of relying on assignment.
  • Use immutable objects for keys in dictionaries (e.g., tuple but not list).
  • Use is only to check identity (such as None checks); use == for value equality.

Short code demonstrations

# Mutable aliasing example
a = [1, 2]
b = a    # b aliases the same list as a
b.append(3)
print(a)  # Output: [1, 2, 3]
print(a is b)  # True (same object)

# Immutable behavior
s1 = 'hi'
s2 = s1
s2 += '!'  # creates a new string and binds s2 to it
print(s1)  # 'hi'
print(s2)  # 'hi!'
print(s1 is s2)  # Usually False

# Shallow vs deep copy
import copy
orig = [[1], [2]]
sh = orig.copy()          # shallow copy
dp = copy.deepcopy(orig) # deep copy
sh[0].append(99)
print(orig)  # [[1, 99], [2]]  (inner list shared with shallow copy)
print(dp)    # [[1], [2]]    (deep copy unaffected)
📌 Examples
  • Real-life analogy: A printed flyer (immutable) vs. a whiteboard (mutable). If two people each hold a copy of a flyer, changing one person's flyer doesn’t change the other's. If two people point to the same whiteboard and one erases something, both see the change (aliasing).
  • Python list aliasing: a = [10]; b = a; b.append(20) => a becomes [10, 20] because a and b reference the same mutable list.
  • Python string immutability: s = 'cat'; t = s; t += 's' => s stays 'cat', t becomes 'cats' because concatenation creates a new string.
  • Shallow copy problem: nested = [[1],[2]]; s = nested[:] ; s[0].append(9) changes nested too — inner lists are shared.
  • Avoiding aliasing: make copies when needed: copy_list = original_list.copy() or import copy; safe = copy.deepcopy(original_nested_structure).
🧮 Formulas
  1. Aliasing condition: name1 is name2 => both names refer to the same object (same id).
  2. Identity test: id(a) == id(b) <=> a is b
  3. Mutating operation: mut_obj.mutate(...) modifies the same object in-place; no new id assigned.
  4. Immutable operation: imm_obj + something returns a new object; id(imm_obj) typically changes after reassignment.
  5. Shallow copy: new_container refers to the same elements as old_container (copy only top-level container).
  6. Deep copy: new_container has independent copies of nested elements (no shared sub-objects).
📊 Visual ideas
Diagram 1 (Aliasing of mutable): Draw two variable names 'a' and 'b' as labels; draw a single box labeled 'list [1,2]' with arrows from both 'a' and 'b' pointing to that box. Then an arrow showing b.append(3) and the box becomes 'list [1,2,3]' demonstrating both see the change.
Diagram 2 (Immutable rebind): Draw 's1' pointing to box '"hi"'. Draw 's2' pointing to same '"hi"' box. After s2 += '!', show s2 arrow moved to a new box '"hi!"' while s1 still points to '"hi"'.
Diagram 3 (Shallow copy): Draw 'orig' pointing to an outer box (list) that contains two inner boxes (inner lists). Draw 'shallow' pointing to a separate outer box whose elements are arrows referencing the same inner boxes (show shared inner references).
Diagram 4 (Deep copy): Similar to Diagram 3 but show 'deep' outer box with its own newly drawn inner boxes (no arrows back to orig's inner boxes).
⚖️13

Traversal and Iteration

What is Traversal?
Traversal is the process of visiting each element of a data structure (like a list, tuple, string, set, dictionary or a 2‑D list) so you can read or process the elements. Example uses: counting, searching, summing, filtering.

What is Iteration?
Iteration is the repeated execution of a block of code. In Python iteration is usually done with loops (for, while) or by using iterators and generator objects. When you iterate over an iterable (like a list), the loop visits elements one by one — this is traversal implemented by iteration.

Difference (brief):
Traversal is the goal (visit all elements). Iteration is the mechanism (repeating steps using loops or iterators).

Common iteration constructs in Python

  • for loop: used to repeat a block for each element in an iterable.
  • while loop: repeats while a condition holds; useful when number of steps is not known beforehand.
  • Iterator protocol: objects implement __iter__() and __next__() so they can be used by for and next().
  • Generator expressions and functions: produce values on the fly (memory efficient).

Useful built‑ins and idioms

  • range(n) — sequence of integers 0..n-1, commonly used to get indices.
  • enumerate(iterable) — gives (index, value) pairs while looping.
  • zip(a, b) — iterate two sequences in parallel.
  • items(), keys(), values() — iterate over dictionary entries.
  • List comprehensions — concise way to build lists by iterating and optionally filtering.

Controlling iteration: break (exit loop), continue (skip to next iteration), and pass (do nothing).

Examples of traversal patterns

  • Simple linear traversal: visiting all elements of a list.
  • Indexed traversal: using indices to access or modify elements.
  • Nested traversal: traversing a 2‑D list or matrix using nested loops.
  • Conditional traversal: visit elements that meet a condition (filtering).

Iterator protocol (short)
An object is iterable if it implements __iter__() returning an iterator. An iterator implements __next__() which returns next value or raises StopIteration.

Good practices

  • Prefer for loops over manual index loops when possible (safer, more Pythonic).
  • Use enumerate() instead of maintaining a separate index counter.
  • Avoid modifying a list while traversing it — iterate over a copy (list[:] ) if you must change the original.
  • Use generators for large data to save memory.

Short code examples

# Linear traversal
numbers = [10, 20, 30]
for n in numbers:
    print(n)

# Using enumerate to get index and value
for i, val in enumerate(numbers):
    print(i, val)

# Traversing a matrix (2D list)
matrix = [[1,2,3],[4,5,6]]
for i in range(len(matrix)):
    for j in range(len(matrix[0])):
        print('element at', i, j, 'is', matrix[i][j])

# Using a generator (memory efficient)
def gen_squares(n):
    for i in range(n):
        yield i*i
for sq in gen_squares(5):
    print(sq)

When traversal and iteration matter
For tasks such as searching (linear scan), aggregation (sum, max, average), filtering (select elements that match a condition), transforming (map each element), and combining sequences (zip) — traversal + iteration are the core techniques.

📌 Examples
  • Class register: Traverse a list of student names to mark attendance. Code idea: for name in students: mark_attendance(name).
  • Calculate total marks: Traverse the marks list, accumulate sum using an accumulator variable. total = 0; for m in marks: total += m
  • Find highest score: Iterate through scores and update max_so_far when a larger score is found.
  • Matrix traversal: For a seating plan (2D list), use nested loops to print seat numbers or check empty seats.
  • Filtering: From a list of ages, create a new list of adults using list comprehension: adults = [a for a in ages if a >= 18]
  • Parallel iteration: Combine roll numbers and names using zip: for r, n in zip(rolls, names): print(r, n)
🧮 Formulas
  1. Accumulator pattern (sum): total = 0; for x in A: total = total + x => average = total / len(A)
  2. Count occurrences: count = 0; for x in A: if predicate(x): count += 1
  3. Linear search steps: in worst case, comparisons = n (for n elements) — O(n) time
  4. Nested loops (matrix of size n×m): operations ≈ n * m — O(n*m); for two nested loops over n elements each: O(n^2)
  5. Index mapping for 2D to 1D (row-major): index = i * num_cols + j (useful for visual layouts)
📊 Visual ideas
Flowchart of a for-loop: start → get next element → process element → more elements? → yes → loop → no → end.
Array traversal diagram: a horizontal array with arrows visiting each cell in order, showing current index pointer moving from 0 to n-1.
Matrix traversal visualization: a grid with nested loop arrows (outer loop over rows, inner loop over columns) and highlighted cell (i,j) being visited.
Iterator protocol diagram: Iterable -> __iter__() -> Iterator -> repeated __next__() until StopIteration, with arrows showing data flow.
🧬14

Comprehensions and Generator Expressions

Overview: Comprehensions and generator expressions are concise, readable ways to create new sequences or iterators from existing iterables using a compact syntax. They replace many simple for-loops and make transformations and filtering easy to express.

Types and basic syntax:

  • List comprehension: [expression for item in iterable if condition]
  • Set comprehension: {expression for item in iterable if condition}
  • Dictionary comprehension: {key_expr: value_expr for item in iterable if condition}
  • Generator expression: (expression for item in iterable if condition)

How they work:

- A comprehension evaluates the expression for each item in the input iterable and collects the results into a new container (list, set, dict). This builds the entire result in memory.

- A generator expression produces values on demand (lazy evaluation). It returns a generator object and yields items one by one as you iterate, so it uses far less memory for large streams.

Examples (short):

# List of squares
squares = [x*x for x in range(10)]

# Filtered list: even numbers
evens = [x for x in range(20) if x % 2 == 0]

# Dict: number -> square
sq_map = {x: x*x for x in range(6)}

# Set: unique word lengths from a sentence
words = 'this is a sample sentence'.split()
lengths = {len(w) for w in words}

# Generator: lazy squares for large range
gen = (x*x for x in range(10**7))  # no list created

Key behavioral notes:

  • Comprehensions evaluate immediately and return a concrete container; generator expressions are lazy and return an iterator.
  • Generator expressions can be passed directly to functions that consume iterables (e.g., sum(), any(), max()).
  • Generators are exhausted after iteration; to reuse results you must recreate them or store the results.
  • Comprehensions are generally as fast as equivalent for-loops (often faster in CPython) and usually clearer, but overly complex nested comprehensions hurt readability.

Nesting:

# Flatten a matrix
matrix = [[1,2,3],[4,5],[6]]
flat = [elem for row in matrix for elem in row]

# Nested comprehension with condition
pairs = [(i,j) for i in range(3) for j in range(3) if i != j]

Real-life use cases:

  • Transforming CSV rows into dicts: [dict(zip(headers,row)) for row in rows]
  • Filtering sensor readings: [r for r in readings if r.value > threshold]
  • Streaming large log files and counting matches with a generator: sum(1 for line in open('log') if 'ERROR' in line)

Pitfalls & tips:

  • Do not use huge list comprehensions when a generator can do the job (memory pressure).
  • Prefer comprehensions for simple transformations; use functions or loops if logic is complex for readability.
  • Remember that dict comprehensions require unique keys; later keys overwrite earlier ones.

Summary: Use list/set/dict comprehensions when you need a concrete container produced immediately. Use generator expressions when you want lazy evaluation, reduced memory usage, or to feed an iterable-consuming function.

📌 Examples
  • List comprehension — squares: squares = [x*x for x in range(10)] # result: [0,1,4,9,...,81]
  • Filtered list — evens: evens = [x for x in range(20) if x % 2 == 0] # keeps only even numbers
  • Dict comprehension — mapping: sq_map = {x: x*x for x in range(6)} # {0:0, 1:1, 2:4, ...}
  • Set comprehension — unique lengths: lengths = {len(w) for w in 'this is a sample'.split()} # {1,2,4,6}
  • Nested list comprehension — flatten matrix: flat = [e for row in matrix for e in row] # flattens nested lists
  • Generator expression — memory efficient: gen = (x*x for x in range(10**7)); next(gen) # yields values on demand
🧮 Formulas
  1. General patterns: - List: [expression for item in iterable if condition] - Set: {expression for item in iterable if condition} - Dict: {key_expr: value_expr for item in iterable if condition} - Generator: (expression for item in iterable if condition)
  2. Complexity and memory (big-O): - Time: O(n) to process n items (same as an explicit loop) - Memory: list/set/dict comprehension => O(n) (stores all results) - Generator expression => O(1) extra memory (yields one item at a time, ignoring the memory of items consumed)
  3. Memory comparison (conceptual): - list_size ≈ n * average_item_size - generator_peak ≈ constant_overhead + size_of_current_item
📊 Visual ideas
Memory usage vs. number of items (n): x-axis = n (items), y-axis = memory (MB). Plot two curves: list comprehension (linear growth) and generator expression (flat, near-constant). Caption: 'List consumes memory proportional to n; generator stays constant.'
Throughput over time for a streaming pipeline: x-axis = time, y-axis = processed items/sec. Show pipeline with generator (steady flow, low memory) vs. batch list (bursts with pauses and higher memory spikes). Caption: 'Generators enable steady streaming processing.'
Flow diagram for lazy evaluation: source iterable -> generator expression (yields one item) -> consumer (e.g., loop or sum). Annotate 'no full materialization' at generator stage.
Bar chart comparing creation time and peak memory for small, medium, large n: three groups of bars (create-time, peak-memory) per method (list vs generator). Caption: 'Generators trade tiny creation overhead for huge memory savings at large n.'
💻15

Built‑in Functions and Useful Modules

Overview
Built‑in functions are pre-defined functions available in Python without importing anything (for example: len(), sum(), min()). Useful modules are collections of related functions and classes grouped in standard libraries that you import (for example: math, random, statistics, datetime).

Why they matter
They let you perform common tasks quickly and correctly: numerical operations, statistical summaries, date/time handling, random sampling, file & data handling. Using them avoids reinventing the wheel and makes code shorter and more reliable.

Common built‑in functions (short descriptions)

  • len(obj) — length of sequence or collection.
  • type(x) — type of an object.
  • int(), float(), str() — type conversions.
  • sum(iterable), min(), max(), round(), abs().
  • sorted(iterable), reversed().
  • map(func, iterable), filter(func, iterable), zip(), enumerate().
  • any(), all() — boolean checks across iterables.
  • divmod(a,b), pow(a,b).
  • help(obj), dir(obj), isinstance(obj, Type) — introspection and debugging.

How to use modules
Import a module to access its functions. Example forms: import math, from math import sqrt, pi, import datetime as dt.

Key useful modules (class 11 focus)

  • math — mathematical functions: sqrt(), factorial(), sin/cos, pi, etc.
  • random — random numbers and choices: randint(), choice(), shuffle().
  • statistics — mean, median, mode, variance, stdev: mean(), median().
  • datetime — dates and times: datetime.now(), date arithmetic.
  • json — read/write JSON data (json.load(), json.dump()).
  • os, sys — file and system operations (optional for basic data tasks).

Small code examples

# statistics: mean, median
from statistics import mean, median
marks = [78, 82, 91, 67, 88]
print('Mean=', mean(marks))
print('Median=', median(marks))

# math: factorial and square root
import math
print('5! =', math.factorial(5))
print('sqrt(25)=', math.sqrt(25))

# random: simple sampling
import random
students = ['A', 'B', 'C', 'D']
print('Random pick:', random.choice(students))

# datetime: difference between dates
from datetime import date
d1 = date(2025, 1, 1)
d2 = date(2025, 10, 11)
print('Days between =', (d2 - d1).days)

# map and filter
nums = [1, 2, 3, 4]
sq = list(map(lambda x: x*x, nums))
even = list(filter(lambda x: x%2==0, nums))
print(sq, even)

Best practices

  • Prefer standard library functions (they are tested and efficient).
  • Import only what you need: from module import func or alias with as.
  • Use help() and dir() to explore unfamiliar modules.

Class 11 focus
Understand how to call built‑ins, use map/filter/zip/enumerate with lists, and use the listed modules for solving common data problems: computing statistics, generating random samples, and handling dates.

📌 Examples
  • Compute class test statistics: use statistics.mean(marks), statistics.median(marks), statistics.pstdev/variance to report class performance. Example code: from statistics import mean, median; marks=[78,82,91,67,88]; print(mean(marks), median(marks))
  • Generating lottery-style random numbers: import random; nums = random.sample(range(1,50), 6) — picks 6 unique numbers from 1–49.
  • Attendance percentage and pass/fail quick check: present = 160; total = 200; percent = (present/total)*100; status = 'Pass' if percent>=75 else 'Fail'. Use round(percent,1) to format.
  • Reading and writing simple JSON data (student record): import json; data={'name':'Ria','marks':[85,90]}; json.dump(data, open('rec.json','w')) and later json.load(open('rec.json'))
  • Date difference for event planning: from datetime import date; days_left = (event_date - date.today()).days to show days remaining.
🧮 Formulas
  1. Mean (arithmetic average): mean = (x1 + x2 + ... + xn) / n — Python: statistics.mean(list)
  2. Median: middle value when sorted (or average of two middles) — Python: statistics.median(list)
  3. Mode: most frequent value — Python: statistics.mode(list)
  4. Population variance: σ² = (Σ(xi - μ)²) / n — Python: statistics.pvariance(list) or statistics.variance(list) for sample
  5. Standard deviation: σ = sqrt(variance) — Python: statistics.pstdev(list) or statistics.stdev(list)
  6. Compound formulas using math: e.g., quadratic roots = (-b ± sqrt(b^2-4ac)) / (2a) — use math.sqrt and arithmetic operators
📊 Visual ideas
Histogram of marks (matplotlib.pyplot.hist) — shows distribution of student scores; useful to identify concentration (e.g., many students near 70-80).
Bar chart of subject-wise average marks (matplotlib.pyplot.bar) — compare averages across subjects to find strengths/weaknesses.
Line chart of attendance over time (dates on x-axis using matplotlib.dates) — shows trends in attendance; use datetime objects for x-values.
Pie chart of grade categories (A/B/C/D) — shows composition of class performance by category; use counts of each grade.
📊16

CRUD Operations on Data Structures

CRUD stands for Create, Read, Update and Delete — the four basic operations you perform on data. In Python these operations are carried out on built-in data structures (lists, tuples, sets, dictionaries, strings) using specific methods or operators. Understanding how CRUD maps to each data structure helps you choose the right structure and write efficient code.

Create: make a new container or add new items. Examples: list append/extend, dict assignment, set add.

Read: access elements without changing them. Examples: indexing, slicing, iteration, dict.get, membership (in).

Update: change existing elements or replace containers. Examples: assignment to list index, list insert, dict[key] = value, set operations (union), note that tuples and strings are immutable so you create a new object instead of modifying in place.

Delete: remove elements or entire containers. Examples: del, pop, remove, clear, dict.pop, set.discard.

Mutability notes: Lists, dictionaries and sets are mutable (support in-place update/delete). Tuples and strings are immutable (you must create a new tuple/string when "updating").

Common pitfalls & good practices:

  • Use dicts for key-based CRUD (fast average lookup/change).
  • For ordered collections use lists; insertion/removal in middle is O(n).
  • Check for membership with in before removing to avoid exceptions or use methods like discard for sets.
  • Prefer dict.get(key, default) to avoid KeyError when reading.

Small code examples:

# List (mutable)
items = [10, 20]
# Create
items.append(30)
# Read
x = items[1]        # 20
# Update
items[0] = 15
# Delete
items.remove(20)

# Dictionary (mutable, key-based)
students = {'A001': 'Rita'}
# Create / Update
students['A002'] = 'Vikram'
students['A001'] = 'Rita Sharma'  # update
# Read
name = students.get('A001')
# Delete
students.pop('A002', None)

# Tuple (immutable)
t = (1, 2)
# To "update" create a new tuple
t = t + (3,)

# Set (mutable, unique elements)
s = {2, 3}
s.add(4)
if 3 in s:
    s.remove(3)

By mapping CRUD to appropriate Python methods and being aware of time/space costs, you can design simple data-driven programs (contact lists, inventories, student records) efficiently and safely.

📌 Examples
  • Contact list (dictionary): Use dict where key = phone/email and value = contact info. Create: contacts['raj@example.com'] = {'name':'Raj','phone':9876}. Read: contacts.get('raj@example.com'). Update: contacts['raj@example.com']['phone']=9999. Delete: contacts.pop('raj@example.com', None).
  • Shopping cart (list of dicts): Each cart item is a dict; Create: cart.append({'id':101,'qty':1}). Read: iterate for display. Update: increase qty by modifying the dict in the list. Delete: cart.pop(index) or remove item with matching id.
  • Student marks (list + dict): Store students as list of dicts or dict of dicts keyed by roll number. Use dict for fast read/update by roll no. Create: students['S01']={'name':'Anu','marks':85}. Read: students['S01']['marks']. Update: students['S01']['marks']=90. Delete: del students['S01'].
  • Inventory system (set for SKUs): Use set to store unique product codes. Create: skus.add('P100'); Read: 'P100' in skus; Delete: skus.discard('P100').
🧮 Formulas
  1. List: append(element) — Create (amortized O(1)); index access list[i] — Read O(1); insert(i, x) — Update/insert O(n); remove(x) / pop(i) — Delete O(n) (pop() at end O(1)).
  2. Dictionary: dict[key] = value — Create/Update average O(1); dict.get(key) — Read average O(1); del dict[key] / dict.pop(key) — Delete average O(1).
  3. Set: add(x) — Create average O(1); x in set — Read membership average O(1); remove(x)/discard(x) — Delete average O(1).
  4. Tuple & String: immutable — 'Update' costs creating a new object (O(n) to copy).
  5. Useful method signatures: list.append(x), list.insert(i,x), list.pop(i), list.remove(x); dict.get(k, default), dict.keys(), dict.values(), dict.pop(k, default); set.add(x), set.remove(x), set.discard(x), set.pop().
📊 Visual ideas
Bar chart comparing average time complexity for common CRUD operations across data structures (list vs dict vs set): show O(1) vs O(n) differences for create/read/update/delete scenarios.
Flowchart of a CRUD request: Start -> Check operation type (Create/Read/Update/Delete) -> Validate input -> Choose data structure -> Perform operation -> Return success/error -> End.
State diagram for a data item: (Non-existent) --create--> (Exists) --read/update--> (Exists modified) --delete--> (Non-existent).
Sequence diagram of a simple app (e.g., contact manager): UI -> Controller -> Data structure (dict): Create/Read/Update/Delete calls with success/failure responses.
📊17

Conversion Between Data Structures

What it is: Conversion between data structures in Python means transforming data from one built-in container type to another (list, tuple, set, dict, string) using constructors or methods so that the data can be used in an appropriate way (mutable/immutable, ordered/unordered, unique elements, key-value access).

Why it matters: Different tasks require different properties: lists for ordered mutable sequences, tuples for fixed records, sets for uniqueness and fast membership tests, and dictionaries for lookup by keys. Converting lets you use the best structure for each task.

Basic rules and behavior:

  • list(iterable) — creates a list preserving order of the iterable.
  • tuple(iterable) — creates an immutable tuple from the iterable.
  • set(iterable) — creates an unordered collection with duplicates removed; order not guaranteed.
  • dict(...) — constructs dictionaries. Passing a mapping or an iterable of key-value pairs (e.g., list of tuples) produces a dict. Passing just an iterable (like list of keys) does not by itself form key-value pairs.
  • str.join(list_of_strings) and str.split(...) — convert between strings and lists of substrings.

Important consequences:

  • Converting to set removes duplicates — use to get unique items (but original order is not preserved).
  • Converting a dict to list returns a list of keys. Use dict.items() to get (key, value) pairs and dict.values() for values.
  • Converting a list of two-item tuples to dict maps each first item to the second; duplicate keys get overwritten by the last occurrence.
  • Mutability: converting to tuple makes data immutable; converting from tuple to list allows modification.

Complexities (brief): Most conversions are O(n) where n is the number of elements because each element must be visited.

Typical conversion functions and patterns:

# Examples of constructors and methods used for conversion
list(some_iterable)      # to list
tuple(some_iterable)     # to tuple
set(some_iterable)       # to set (unique elements)
str.join(list_of_strings)  # to string
some_string.split(sep)   # to list of substrings
dict(list_of_pairs)      # from [(key, value), ...]
list(a_dict)             # list of dict keys
list(a_dict.items())     # list of (key, value) tuples
list(a_dict.values())    # list of values
zip(keys, values)        # pairs two iterables; often wrapped with dict()
📌 Examples
  • Example 1 — Remove duplicates from a list (order not guaranteed): input_list = [2, 3, 2, 5, 3] unique_set = set(input_list) # {2, 3, 5} unique_list = list(unique_set) # [2, 3, 5] (order may vary)
  • Example 2 — Convert string to list and back: s = 'apple,banana,cherry' fruits = s.split(',') # ['apple', 'banana', 'cherry'] new_s = ';'.join(fruits) # 'apple;banana;cherry'
  • Example 3 — Convert list of tuples to dictionary (pairs): pairs = [('a', 1), ('b', 2), ('c', 3)] D = dict(pairs) # {'a': 1, 'b': 2, 'c': 3} # If duplicate keys exist, last value wins: dict([('a',1),('a',9)]) -> {'a': 9}
  • Example 4 — Get keys, values, items from dict: D = {'r1': 90, 'r2': 85} keys = list(D) # ['r1', 'r2'] values = list(D.values()) # [90, 85] items = list(D.items()) # [('r1', 90), ('r2', 85)]
  • Example 5 — Convert tuple to list to modify, then back: coords = (10, 20) coords_list = list(coords) coords_list[0] = 15 coords = tuple(coords_list) # (15, 20)
🧮 Formulas
  1. list(iterable) -> list of elements in iterable (order preserved for sequences).
  2. tuple(iterable) -> tuple with same elements (immutable).
  3. set(iterable) -> set of unique elements (duplicates removed, order not guaranteed).
  4. dict(iterable_of_pairs) -> dictionary mapping first->second for each (key, value) pair.
  5. str.split(sep) -> list of substrings; sep.join(list_of_strings) -> single string.
  6. list(a_dict) -> list of keys; list(a_dict.items()) -> list of (key, value) tuples; list(a_dict.values()) -> list of values.
📊 Visual ideas
Flowchart showing conversions with arrows between types: String <-> List (split/join), List <-> Tuple (tuple()/list()), List -> Set (set()), Set -> List (list()), List of pairs -> Dict (dict()). Label each arrow with the function name.
Directed graph with nodes: List, Tuple, Set, Dict, String. On each arrow show effects: 'duplicates removed' on List->Set; 'immutable' on List->Tuple; 'keys only' on Dict->List; 'pairs required' on List->Dict.
Memory diagram (boxes) comparing mutability: show a list box with changeable elements, and an arrow to a tuple box labeled 'tuple(list) — becomes immutable'. Include an example element change that is allowed for the list but not for the tuple.
Pipeline example graph for a real-life task: Raw text -> split(',') -> list of words -> set() to remove duplicates -> sorted(list(set)) -> join(';') -> output string. Show the transformation at each stage with a small sample input.
📊18

Nested Data Structures

What are Nested Data Structures?

Nested data structures are data structures that contain other data structures as their elements. In Python this typically means lists, tuples, sets or dictionaries can hold other lists, tuples, sets or dictionaries. Nesting lets you represent complex, hierarchical data (tables, trees, JSON-like records) naturally.

Common nested types

  • List of lists (2D lists): a matrix or table representation.
  • List of dictionaries: a collection of records (e.g., list of student records).
  • Dictionary of lists: grouped properties under keys (e.g., subjects -> marks list).
  • Dictionary of dictionaries: hierarchical keyed data (e.g., JSON objects).
  • Tuples inside lists or vice-versa: fixed-size records inside a mutable collection.

How to access elements

Accessing nested elements is done by chaining index or key operations. Examples:

# 2D list: matrix[i][j]
matrix = [[1,2],[3,4]]
val = matrix[0][1]  # 2

# nested dict: d['a']['b']
d = {'a': {'b': 10}}
val = d['a']['b']  # 10

# list of dicts: students[0]['name']
students = [{'name': 'Alice', 'age': 15}, {'name': 'Bob', 'age': 16}]
name0 = students[0]['name']  # 'Alice'

Iteration and nested loops

To process nested structures, use nested loops or nested comprehensions. For example, iterating a 2D list uses one loop per dimension:

for row in matrix:
    for item in row:
        print(item)

# Nested comprehension to flatten a 2D list
flat = [item for row in matrix for item in row]

Modification and mutability

Be aware of mutability: lists and dictionaries are mutable; tuples are not. If you store a mutable object (like a list) inside multiple places, modifying it in one place affects all references. Use copy.copy or copy.deepcopy when needed.

Common operations and cautions

  • Flattening: convert nested lists into a single list (via nested loops or comprehensions).
  • Depth (nesting level): keep track to avoid deep recursion; Python has recursion limits.
  • Shallow vs deep copy: shallow copy copies references to inner objects; deep copy duplicates nested contents.
  • Performance: nested loops increase time complexity multiplicatively (see formulas below).

Real-life use-cases

  • Spreadsheet data: 2D lists for rows and columns.
  • JSON APIs: dictionaries with nested dictionaries and lists for structured responses (users → posts → comments).
  • School records: list of dicts where each dict stores name, grades (list), and address (dict).
  • Graphs and trees: adjacency lists (dict of lists) or nested dicts for nodes and properties.
📌 Examples
  • Nested list (2D list / matrix): matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] # access element in row 2, column 3 (0-based): matrix[1][2] -> 6
  • List of dictionaries (records): students = [ {'name': 'Alice', 'age': 15, 'marks': [85, 92]}, {'name': 'Bob', 'age': 16, 'marks': [78, 81]} ] # get Bob's second mark: students[1]['marks'][1] # 81
  • Dictionary of dictionaries (nested mapping): school = { 'Class10': {'teacher': 'Mr Rao', 'students': 32}, 'Class11': {'teacher': 'Ms Iyer', 'students': 28} } # teacher of Class11: school['Class11']['teacher'] # 'Ms Iyer'
  • Nested comprehension (flatten 2D list): flat = [item for row in matrix for item in row] # flat -> [1,2,3,4,5,6,7,8,9]
  • Deep copy vs shallow copy example: import copy outer = [[1,2], [3,4]] sh = outer.copy() # shallow copy: inner lists are same objects sh[0][0] = 99 # outer is now [[99,2],[3,4]] outer = [[1,2],[3,4]] dp = copy.deepcopy(outer) # deep copy: inner lists are independent
🧮 Formulas
  1. Access notation: nested_list[i][j] or nested_dict['key1']['key2'] — chain indices/keys from outer to inner.
  2. Length relation: total_items = sum(len(sub) for sub in outer_list) # for list of lists
  3. Time complexity for basic ops: - Indexing into list by index: O(1) - Access by key in dict: average O(1) - Iterating nested lists: O(product_of_dimensions) e.g., 2D list with n*m elements is O(n*m) - Searching through nested structure by value: O(total elements)
  4. Nested loops complexity: if outer has n items and each inner has m items, nested loops cost O(n * m).
  5. Flattening cost: O(total_elements) where total_elements = sum of lengths of all sub-containers.
  6. Shallow copy vs deep copy: - shallow_copy = container.copy() # copies outer container, inner references shared - deep_copy = copy.deepcopy(container) # duplicates nested contents
📊 Visual ideas
Tree diagram (root node = outer container) showing children nodes for each nested element. Useful for nested dicts/JSON.
Nested boxes visualization: concentric or tiled boxes where each box is a container and inner boxes are contained elements (good for lists of lists).
Matrix/grid diagram for 2D lists: rows and columns labeled; show access path to element at (i, j).
Flowchart showing chained access: start -> outer index/key -> inner index/key -> value; include checks for KeyError/IndexError.
📊19

Error Handling Related to Data Operations

What it is: Error handling related to data operations means writing Python code that anticipates, detects and responds to errors that occur when reading, writing, converting, or validating data (files, user input, CSV/JSON, lists/dicts, etc.). Good error handling keeps programs robust, prevents crashes, and makes failures informative.

Why it matters: Data is often messy or unavailable: files may be missing, numbers may be malformed, fields may be missing, or external resources may be temporarily unreachable. Without handling these situations a program will raise exceptions and stop. Proper handling lets you give defaults, retry, log problems, or fail gracefully.

Common exception types in data operations

  • FileNotFoundError / IOError: file missing or unreadable.
  • ValueError: converting a string to int/float fails (e.g., int("abc")).
  • TypeError: operation applied to wrong type (e.g., adding str and int).
  • IndexError: accessing invalid list index.
  • KeyError: missing key in a dictionary.
  • ZeroDivisionError: division by zero when computing statistics.
  • json.JSONDecodeError / UnicodeDecodeError: malformed JSON or wrong file encoding.

Techniques and patterns

  • Use try/except to catch specific exceptions and handle them. Prefer catching specific exception classes rather than a bare except:.
  • Use else to run code when no exception occurred, and finally to always run cleanup (close resources) if not using with.
  • Use with open(...) to ensure files are closed automatically.
  • Validate and sanitize input before converting: strip whitespace, check numeric pattern, or use helper functions that return defaults on failure.
  • Raise meaningful exceptions with raise ValueError('message') when detecting invalid data inside functions (fail fast).
  • Log errors (using the logging module) so the cause can be diagnosed later rather than silently ignoring problems.
  • Provide fallbacks or retry logic for transient errors (e.g., network read). Keep retries limited and use backoff if needed.

Best practices

  • Catch the narrowest exception you expect.
  • Keep try blocks small — only include the statement(s) that might raise the expected error.
  • Don’t use exceptions for normal control flow.
  • Provide clear error messages and use logging for diagnostics.
  • Validate input and data formats before processing.

Short code patterns

# safe file open with fallback
try:
    with open('data.csv') as f:
        text = f.read()
except FileNotFoundError:
    print('data.csv not found — using default data')
    text = ''

# safe conversion with default
try:
    x = int(user_input)
except ValueError:
    x = 0  # fallback or ask again

# raise when encountering invalid record
def parse_age(s):
    try:
        age = int(s)
    except ValueError:
        raise ValueError(f'Invalid age: {s}')
    if age < 0:
        raise ValueError('Age must be >= 0')
    return age

In sum: anticipate likely problems from external or untrusted data, catch and handle specific exceptions, validate input early, and ensure resources are always cleaned up.

📌 Examples
  • Reading a student marks file where the file may not exist: try: with open('marks.csv') as f: lines = f.readlines() except FileNotFoundError: print('marks.csv not found — please provide the file') lines = []
  • Converting CSV fields to numbers while skipping malformed entries: safe_marks = [] for token in row: try: m = float(token) except ValueError: # log and skip bad value print(f'Bad number: {token} — treated as 0') m = 0.0 safe_marks.append(m)
  • Preventing crash on division by zero when computing average: try: avg = total / count except ZeroDivisionError: avg = 0 # or handle as 'no data' # better: check before dividing if count == 0: avg = 0 else: avg = total / count
  • Accessing dictionary keys safely (KeyError): student = {'name': 'Anita', 'marks': 78} marks = student.get('marks', 0) # returns 0 if key missing # or try: email = student['email'] except KeyError: email = 'not provided'
🧮 Formulas
  1. try -> except <ExceptionType>: -> (optional) else -> (optional) finally (pattern for handling exceptions and cleanup)
  2. with open(filename, mode) as f: (automatic resource management; avoids needing finally to close file)
  3. int(s) # may raise ValueError if s is not numeric; use try/except to catch
  4. raise ValueError('message') # to signal invalid data from inside a function
  5. dict.get(key, default) # safe access to dictionary values without KeyError
📊 Visual ideas
Flowchart of exception flow: Start -> Try block -> (Exception?) -> Yes: Except handler -> Finally -> End; No: Else (optional) -> Finally -> End. (Use decision diamonds for whether exception occurred.)
Bar chart showing counts of common error types encountered in a data pipeline (x-axis: Error types like FileNotFoundError, ValueError, KeyError, ZeroDivisionError; y-axis: frequency). Useful for prioritizing fixes.
Sequence diagram for file read: Program -> File system: open(file) -> (File missing?) -> File system: FileNotFoundError -> Program: handle exception (log / fallback).
Decision tree for input validation: Is field present? -> Is it numeric? -> In valid range? -> Accept / Convert / Reject & report. (Nodes show checks; leaves show actions like accept, default, or raise error.)
💻20

Practical Examples and Problem Solving

'Practical Examples and Problem Solving' in the 'Working with Data in Python' chapter teaches how to apply Python to collect, clean, analyze and present data to solve real-world problems. The process follows these steps:

  • Understand the problem: identify inputs, outputs and constraints.
  • Choose data structures: lists, tuples, dictionaries, sets for different needs (ordered data, key-value lookup, unique elements).
  • Data ingestion: read data from user input, text files or CSV (use the csv module or pandas for larger tasks).
  • Data cleaning & validation: handle missing values, convert types (strings to int/float), strip whitespace.
  • Processing & algorithms: counting, searching, filtering, sorting, aggregations (sum, mean, median), grouping by keys.
  • Output & visualization: textual reports, tables or graphs (bar/histogram/line/scatter) to communicate results.

Key Python constructs to use:

  • Loops and conditionals (for, while, if)
  • Comprehensions (list/dict/set) for concise transformations
  • Functions to modularize tasks
  • File handling (open/read/write) and the csv module for structured data
  • Dictionaries to implement frequency counts and grouping

Small example workflow (student marks analysis): read marks from a CSV, convert to numbers, compute mean/median/mode, find top scorers, and plot distribution.

# Example: read marks, compute mean
import csv
marks = []
with open('marks.csv') as f:
    reader = csv.reader(f)
    next(reader)  # skip header
    for row in reader:
        marks.append(int(row[1]))

mean = sum(marks)/len(marks)

Problem solving tips:

  • Start with small test data and print intermediate results.
  • Break the problem into functions (read_data, clean_data, analyze, visualize).
  • Use dictionaries for counting (e.g., frequency of grades) and sorting with key functions.
  • Handle edge cases (empty files, non-numeric entries) with try/except or validation checks.
📌 Examples
  • 1) Counting frequency of words in a text file: read file line by line, split into words, normalize to lowercase, use a dictionary to count and then sort by count to get most frequent words. Python sketch: from collections import Counter with open('essay.txt') as f: words = [w.strip('.,!?:;"').lower() for line in f for w in line.split()] counts = Counter(words) most_common = counts.most_common(5)
  • 2) Student marks analysis (CSV): read student name and marks, compute total, mean, median, mode, grade distribution and list top 3 students. Sketch: import csv students = [] with open('students.csv') as f: for name, mark in csv.reader(f): students.append((name, int(mark))) # compute mean, sort by marks for top 3
  • 3) Inventory management (dictionary): maintain product:quantity mapping, support add/remove/update, and display low-stock items (quantity < threshold). Sketch: inventory = {'pens': 50, 'books': 12} # update: inventory['pens'] -= 10 low_stock = [p for p,q in inventory.items() if q < 20]
  • 4) Temperature analysis (list): given daily temps, compute average, min, max, count days above average, and plot a line chart of temperature trend. Sketch: temps = [30.5, 32.0, 29.8, ...] avg = sum(temps)/len(temps) days_above = sum(1 for t in temps if t > avg)
  • 5) CSV data aggregation: group sales by region using a dictionary: for each row (region, amount) do totals[region] = totals.get(region, 0) + float(amount); then create a bar chart of totals per region.
🧮 Formulas
  1. Mean (average): mean = sum(x_i) / n Python: mean = sum(values)/len(values)
  2. Median: sort the list; if n odd -> middle element; if n even -> average of two middle elements Python sketch: sorted_vals = sorted(values); median = (sorted_vals[n//2] if n%2 else (sorted_vals[n//2 -1] + sorted_vals[n//2])/2)
  3. Mode: the value(s) with highest frequency Python: from collections import Counter; mode = Counter(values).most_common(1)[0][0]
  4. Variance (population): var = (1/n) * sum((x_i - mean)^2) Standard deviation: sd = sqrt(var) Python sketch: import math; var = sum((x-mean)**2 for x in values)/len(values); sd = math.sqrt(var)
  5. Percentage / proportion: percent = (part / whole) * 100 Useful for grade distribution and category shares.
  6. Sorting key for top-N: use sorted(items, key=lambda x: x[1], reverse=True) to get items sorted by value (e.g., marks or totals).
📊 Visual ideas
Bar chart: Use to show categorical totals (e.g., sales by region, number of students per grade). X-axis = categories, Y-axis = counts/totals. Library: matplotlib.pyplot.bar or pandas.DataFrame.plot.bar
Histogram: Use to show distribution of a numeric variable (e.g., marks distribution). X-axis = bins (ranges of values), Y-axis = frequency. Library: matplotlib.pyplot.hist or pandas.DataFrame.plot.hist
Line chart: Use for time-series or trend data (e.g., temperature over days). X-axis = time (day index or date), Y-axis = measured value. Library: matplotlib.pyplot.plot
Scatter plot: Use to show relationship between two numeric variables (e.g., study hours vs marks). X-axis = predictor, Y-axis = response; can add a trendline. Library: matplotlib.pyplot.scatter

Key Concepts

Data
Raw facts or values (numbers, text, etc.) that can be processed by a program.
Data Type
A classification that specifies the kind of data (e.g., int, float, str, list).
Mutable
Objects whose state or contents can be changed after creation (e.g., lists, dicts, sets).
Immutable
Objects that cannot be altered after creation (e.g., int, float, tuple, str).
List
An ordered, mutable collection of items, written with square brackets [].
Tuple
An ordered, immutable collection of items, written with parentheses ().
Dictionary
An unordered collection of key-value pairs, written with braces {}.
Set
An unordered collection of unique items, written with braces {}, useful for membership tests.
String
A sequence of characters used to store text, enclosed in quotes.
Indexing
Accessing an individual element of a sequence (list, tuple, string) using its position (index).
Slicing
Extracting a subsequence from a sequence using start:stop[:step] notation.
File Handling
Opening, reading, writing and closing files using built-in functions like open(), read(), write(), close().
File Modes
Mode strings used with open(), such as 'r' (read), 'w' (write, overwrite), 'a' (append), 'rb'/'wb' (binary).
CSV (Comma-Separated Values)
A common plain-text format for tabular data; Python's csv module helps read/write CSV files.
JSON
JavaScript Object Notation, a text format for structured data; use Python's json module to parse and serialize.
Exception
An error event that occurs during program execution; handled using try-except blocks.
Iterator
An object representing a stream of data; supports next() to fetch successive items and is obtained via iter().
Generator
A special iterator defined with yield or generator expressions that produces items lazily.
Comprehension
A concise way to create lists, sets or dicts from iterables using a compact syntax (e.g., [expr for var in iter]).
Type Conversion (Casting)
Explicitly converting one data type to another using functions like int(), float(), str(), list(), dict().

End-of-Chapter Trial Paper & Test Questions

Topic-wise questions to test your understanding of every concept in this chapter.

  1. Differentiate between mutable and immutable data types in Python with examples. / पायथन में म्यूटेबल और इम्यूटेबल डेटा प्रकारों में उदाहरण सहित अंतर कीजिए।
    Show answer

    Mutable types can be changed in place (list, dict, set), while immutable types cannot be altered after creation (int, float, str, tuple); modifying an immutable value creates a new object. / म्यूटेबल प्रकार स्थान पर ही बदले जा सकते हैं (list, dict, set), जबकि इम्यूटेबल प्रकार बनने के बाद नहीं बदले जा सकते (int, float, str, tuple); इम्यूटेबल मान बदलने पर एक नई वस्तु बनती है।

  2. Given s = 'python', what is the output of s[1:4] and s[::-1]? / दिया गया है s = 'python', s[1:4] और s[::-1] का आउटपुट क्या है?
    Show answer

    s[1:4] gives 'yth' (indices 1,2,3 with stop exclusive); s[::-1] gives 'nohtyp' (the string reversed). / s[1:4] से 'yth' मिलता है (सूचकांक 1,2,3 जहाँ स्टॉप अपवर्जित है); s[::-1] से 'nohtyp' मिलता है (स्ट्रिंग उल्टी हुई)।

  3. Explain the difference between d['key'] and d.get('key') for dictionary access. / डिक्शनरी एक्सेस के लिए d['key'] और d.get('key') में अंतर समझाइए।
    Show answer

    d['key'] raises a KeyError if the key is missing, whereas d.get('key') returns None (or a given default) safely without raising an error. / यदि कुंजी अनुपस्थित हो तो d['key'] KeyError उठाता है, जबकि d.get('key') बिना त्रुटि उठाए सुरक्षित रूप से None (या दिया गया डिफ़ॉल्ट) लौटाता है।

  4. Why must dictionary keys be immutable while values can be of any type? / डिक्शनरी की कुंजियाँ इम्यूटेबल क्यों होनी चाहिए जबकि मान किसी भी प्रकार के हो सकते हैं?
    Show answer

    Dictionaries use a hash table where keys are hashed for O(1) lookup, so keys must be hashable/immutable to keep a stable hash, but values are not hashed and can be any object. / डिक्शनरी एक हैश टेबल का उपयोग करती है जहाँ कुंजियाँ O(1) लुकअप के लिए हैश की जाती हैं, इसलिए स्थिर हैश बनाए रखने हेतु कुंजियाँ हैशेबल/इम्यूटेबल होनी चाहिए, परंतु मान हैश नहीं होते और कोई भी वस्तु हो सकते हैं।

  5. Write a list comprehension to create a list of squares of numbers from 1 to 5. / 1 से 5 तक की संख्याओं के वर्गों की सूची बनाने के लिए एक लिस्ट कॉम्प्रिहेंशन लिखिए।
    Show answer

    squares = [x*x for x in range(1, 6)] produces [1, 4, 9, 16, 25]. / squares = [x*x for x in range(1, 6)] से [1, 4, 9, 16, 25] बनता है।

  6. Given A = {1,2,3} and B = {3,4,5}, find A | B, A & B and A - B. / दिया गया है A = {1,2,3} और B = {3,4,5}, A | B, A & B और A - B ज्ञात कीजिए।
    Show answer

    A | B (union) = {1,2,3,4,5}; A & B (intersection) = {3}; A - B (difference) = {1,2}. / A | B (संघ) = {1,2,3,4,5}; A & B (प्रतिच्छेदन) = {3}; A - B (अंतर) = {1,2}।

  7. What happens when int('12.3') is executed and how should the conversion be done correctly? / int('12.3') निष्पादित करने पर क्या होता है और रूपांतरण सही तरीके से कैसे किया जाना चाहिए?
    Show answer

    int('12.3') raises a ValueError because the string is not integer-format; convert with float('12.3') first, then int(float('12.3')) to get 12 (truncated). / int('12.3') ValueError उठाता है क्योंकि स्ट्रिंग पूर्णांक-प्रारूप में नहीं है; पहले float('12.3') से बदलें, फिर int(float('12.3')) से 12 (काट-छाँट कर) प्राप्त करें।

  8. Give two reasons to use a tuple instead of a list to store data. / डेटा संग्रहीत करने के लिए लिस्ट के बजाय ट्यूपल का उपयोग करने के दो कारण दीजिए।
    Show answer

    Tuples are used when data should not change (immutability protects fixed records like coordinates) and they can serve as dictionary keys; they are also slightly faster and use less memory than lists. / ट्यूपल का उपयोग तब होता है जब डेटा बदलना नहीं चाहिए (इम्यूटेबिलिटी निर्देशांक जैसे स्थिर रिकॉर्ड की रक्षा करती है) और वे डिक्शनरी की कुंजी बन सकते हैं; ये लिस्ट की तुलना में थोड़े तेज़ भी होते हैं और कम मेमोरी लेते हैं।

Related Laws & Principles

Explore all

Foundational laws & principles behind this chapter. Each one opens a full page — what it says, why it matters, five practice questions and the mistakes to avoid.

Loading related laws…
Sourced from 261 content files · LLOS Learn · browse all chapters