Chapter 02 Advanced Data Structures and Functions
Chapter 02 Advanced Data Structures and Functions
Data Structure
A particular way of organizing data in a computer
Collection is a single “variable” used to store multiple values,
1- List = [ ] ordered and (mutable) changeable ; duplicates are allowed
2- Set = { } unordered and mutable, No duplicates
3- Tuple = ( ) ordered and immutable, duplicates allowed, Faster
4- Dictionary = { key : value} pairs, ordered and changeable, No duplicates
2
Very Useful Tip
# print all the attributes and methods of this data
structure
print(dir(collection_name))
# in details
print(help(collection_name))
3
What are LISTS ?
my_list = [ 1,2,9,4]
• The elements of a list do not have to be of the
same type.
my_list =["Hello", 5, 3.8, [6,8] , (45,68)]
print(my_list)
5
What are LISTS ?
• Accessing Lists
my_integers = [2,4,8,16,32]
To retrieve the first element :
myFirstElement = my_integers[0]
To retrieve the second element :
mySecondElement = my_integers[1]
6
What are LISTS ?
• Accessing Lists
my_integers = [2,4,8,16,32]
One way to get last element :
myLastElement = my_integers[-1]
7
What are LISTS ?
• Accessing Lists
myList = ['P', 'Y', 'T', 'H', 'O', 'N']
Index
Index
my_integers.append(46)
my_integers = my_integers + [46]
my_integers += [46]
10
What are LISTS ?
Modifying my Lists
my_integers = [2, 4, 8, 16, 32,46]
To change an element :
my_integers[5] = 64
To delete an element (using an index) :
del my_integers[3]
To remove a value in the list :
my_integers.remove(2) 11
What are LISTS ?
Modifying my Lists
my_integers = [2, 4, 8, 16, 32,46]
To pop a value from the list :
Value= my_integers.pop() # can be used with append()
to create a stack (LIFO last in first out)
print(my_integers) # prints [2, 4, 8, 16, 32]
Example:
my_integers.insert(0, 10) # [10,2, 4, 8, 16, 32,46]
13
What are LISTS ?
words=["moon","sun","tree","world","ice"]
words.sort() # result ?
words.sort(key=len) # result ?
14
Functions
append Indexing e.g., L[i]
insert Slicing e.g., L[1:5]
index
count
Concatenation e.g., L + L
sort Repetition e.g., L * 5
reverse
Membership test e.g., ‘a’ in L
remove
pop Length e.g., len(L)
extend
List_name.function(arguments) 15
s = [1,2,3]
t = ['begin', s, 'end']
>>> t
['begin', [1, 2, 3], 'end']
>>> t[1][1]
2
16
# Traditional way
squares = []
for x in range(10):
squares.append(x**2)
17
Use conditional expressions within comprehensions.
even_squares = [x**2 for x in range(10) if x % 2 == 0]
Old method:
flattened=[]
for sublist in nested:
for item in sublist:
flattened.append(item)
19
20
Tuple is a collection which is ordered and immutable
Duplicates are allowed, Faster data structure
a, b = (1, 2)
print(a) # Output: 1
# Swapping variables
a, b = b, a
print(a) # Output: 2
22
a= [1,2,3,4,5]
a[1]=6 # not allowed Error
23
Operations in Tuple
Indexing e.g., T[i]
Slicing e.g., T[1:5]
Concatenation e.g., T + T
Repetition e.g., T * 5
Membership test e.g., ‘a’ in T
Length e.g., len(T)
24
Operations in Tuple
Indexing e.g., T[i]
Slicing e.g., T[1:5]
Concatenation e.g., T + T
Repetition e.g., T * 5
Membership test e.g., ‘a’ in T
Length e.g., len(T)
25
Operations in Tuple
What do you think ?
a= ([1,5,66],2,"hello",4,5)
print(a[2][1]) # ?
print(a[1][1]) # ?
26
Operations in Tuple
What do you think ?
a= ([1,5,66],2,"hello",4,5)
print(a[2][1]) # ?
print(a[1][1]) # ?
27
Tuple’s pre-built functions
Tuple_name.count(value)
Tuple_name.index(value)
a= ([1,5,66],2,"hello",5,4,5)
print(a.count(5)) # answer is 2
print(a.index(5)) # answer is 3
28
Tuple with loops
a= ([1,5,66],2,"hello",5,4,5)
for item in a:
print(item)
29
Named Tubles
Will be discussed in OOP chapter
# Python code to demonstrate namedtuple()
from collections import namedtuple
# Declaring namedtuple()
Student = namedtuple('Student', ['name', 'age', 'DOB'])
# Adding values
S = Student('Nandini', '19', '2541997')
def get_min_max(numbers):
return min(numbers), max(numbers)
31
SETs
32
Sets
A set is a collection of elements
33
Sets
Why use sets?
Fast membership testing
Efficient operations for union, intersection, and difference
accepted_set = {1, 2, 3}
rejected_set = {1, [2,6], 3}# error no lists allowed
empty_set = set()
34
Sets
Basic Set Operations
Adding Elements:
my_set.add(4)
Removing Elements:
my_set.remove(2) # Raises KeyError if not found
my_set.discard(2) # Safe remove
35
Sets
Set’s Methods
Membership Testing: print( 3 in my_set)
if 3 in my_set:
print("Found")
Clear all elements: my_set.clear()
Copying a Set: new_set = my_set.copy()
Remove and return the first element: elem=my_set.pop()
36
Built-in Set Methods
Mathematical Set Operations
Union: set1.union(set2) # OR set1 | set2
Intersection: set1.intersection(set2) # OR set1 & set2
Difference: set1.difference(set2) # OR set1 - set2
37
Built-in Set Methods
Mathematical Set Operations
TODO by you: Try and understand the use of these
methods
set1.update
set1.difference_update
set1.intersection_update
set1.symmetric_difference
set1.symmetric_difference_update
set1.issubset(set2)
set1.issuperset(set2) 38
Sets are useful for duplication removal
39
A100% immutable variation of Sets
Frozen Sets
frozen_set = frozenset([1, 2, 3])
frozen_set: has NO add, remove, discard,clear or pop methods
40
41
Dictionaries
42
Dictionaries
Creating a Dictionary
Syntax
dict = {'key1': 'value1', 'key2': 'value2'}
43
Dictionaries
Access by Key:
value = dict['key1'] # returns an error if « key not found »
44
Dictionaries
Modifying a Dictionary
Removing Elements:
Using del: del my_dict['key1']
Using pop() (returns the removed value):
val=my_dict.pop('key2') # we can remove val=
45
Methods in Dictionary
Common Methods:
keys(): Returns all keys.
values(): Returns all values.
items(): Returns all key-value pairs.
keys = dict.keys()
values = dict.values()
items = dict.items()
46
Methods in Dictionary
has_key(key)
clear()
copy() makes a copy of the dictionary
get(key[,x])
setdefault(key[,x]) Returns the value of a key; if the key does not exist, inserts
the key with a default value ex: dict.setdefault('key4', 'default_value')
update(D) Updates the dictionary with elements from another dictionary or
iterable.
popitem() removes and return the last inserted item ( key:value)
example: key, value = dict.popitem() 47
Methods in Dictionary
Checking for Keys
Using in Operator:
if 'key1' in dict:
print("Key found")
48
Methods in Dictionary
Iterating Over a Dictionary
To extract Keys only
for key in dict:
print(key)
50
Methods in Dictionary
Iterating Over a Dictionary
To extract keys and values:
51
Nested Dictionaries
Creating a Nested Dictionary:
nested_dict = {
'dict1': {'keyA': 'valueA'},
'dict2': {'keyB': 'valueB'}
}
52
Nested Dictionaries
Merging Dictionaries (Python 3.9+):
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
merged_dict = dict1 | dict2 # Output: {'a': 1, 'b': 3, 'c': 4}
53
Nested Dictionaries
How ?
54
55
What is a Function
result = square(5)
print(result) # Output: 25
60
Functions: Return Values
Returning Multiple Values
You can return multiple values by separating them with commas.
def calculate(x, y):
sum_ = x + y
product = x * y
return sum_, product
62
Functions: Function Arguments
Positional and Keyword Arguments
Keyword arguments allow you to pass arguments by explicitly naming them.
63
Functions: Function Arguments
Default Arguments
Default values can be assigned to function parameters.
def greet(name="Stranger"):
print(f"Hello, {name}!")
64
Functions: Function Arguments
Arbitrary Arguments (*args, **kwargs)
*args allows you to pass a variable number of positional arguments.
**kwargs allows you to pass a variable number of keyword arguments.
def total_sum(*args):
return sum(args)
67
Functions: Lambda Functions
Lambda in filter(), map() and reduce()
filter(function,iterable) filters elements from an iterable
based on a condition.
nums = [1, 2, 3, 4, 5, 6]
even_nums = list(filter(lambda x: x % 2 == 0, nums))
print(even_nums) # Output: [2, 4, 6]
68
Functions: Lambda Functions
Lambda in filter(), map() and reduce()
map(function,iterable) applies a function to all items in an
iterable:
nums = [1, 2, 3, 4]
squared = list(map(lambda x: x ** 2, nums))
print(squared) # Output: [1, 4, 9, 16]
69
Functions: Lambda Functions
Lambda in filter(), map() and reduce()
The reduce(function,iterable) function from functools
reduces an iterable to a single value by applying a function
cumulatively.
from functools import reduce
nums = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, nums)
print(result) # Output: 24
70
Functions: Lambda Functions
Lambda in filter(), map() and reduce()
from functools import reduce
def add(a,b):
return a+b
With a normal function
nums = [1, 2, 3, 4]
result = reduce(add, nums)
print(result) # Output: 24
With lambda function
from functools import reduce
nums = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, nums)
print(result) # Output: 24 71
Special and Advanced Functions
Recursive Functions
A recursive function is a function that calls itself to
solve a problem.
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n - 1)
73
Special and Advanced Functions
Higher-Order Functions
A function that takes another function as an argument or
returns a function is called a higher-order function.
def outer_function(message):
def inner_function():
print(message)
return inner_function
75
Special and Advanced Functions
Decorators
def decorator_function(func):
def wrapper():
# Extend or modify behavior
print("Before function call")
func()
Output:
print("After function call") Before function call
return wrapper
Hello!
@decorator_function After function call
def say_hello():
print("Hello!")
say_hello() 76
Special and Advanced Functions
Function Arguments in Decorators
• Decorators can also handle functions with arguments.
def decorator_with_args(func):
def wrapper(*args, **kwargs):
print(f"Arguments passed: {args}")
return func(*args, **kwargs)
return wrapper
@decorator_with_args
def add(a, b):
return a + b
result = add(3, 5)
77
print(result) # Output: 8
Special and Advanced Functions
Chaining Multiple Decorators
• You can apply multiple decorators to a single function by stacking
@decorator_name lines.
@decorator_one
@decorator_two
def my_function():
pass # Placeholder
78
Special and Advanced Functions
Closures
Key Characteristics of a Closure:
Nested function: There is a function defined inside another
function.
Enclosing scope: The inner function captures the variables from the
outer function's scope.
Returned function: The outer function returns the inner function,
which can still access the variables of the outer function.
79
Special and Advanced Functions
Closures
80
Special and Advanced Functions
Closures are useful when:
• keeping track of function state, and more.
Example: Creating a counter with closure:
def counter():
count = 0
def increment():
nonlocal count nonlocal ?
count += 1
return count
return increment
my_counter = counter()
print(my_counter()) # Output: 1
81
print(my_counter()) # Output: 2
Special and Advanced Functions
Closures
Closures are useful when:
• You want to delay execution but still "remember" the state of
variables.
• You want to encapsulate logic in a function and maintain access to
data without using global variables.
• Closures are commonly used in decorators, callbacks, and when
building function factories.
82
Special and Advanced Functions
Currying Functions
• Currying is the process of transforming a function with multiple
arguments into a series of functions that each take one argument.
def multiply(a):
def by(b):
return a * b Instead of multiply(2,5)
return by
doubler = multiply(2)
print(doubler(5)) # Output: 10
?
84
Special and Advanced Functions
Function Composition
Function Composition is a fundamental concept in functional
programming where multiple functions are combined into a
single function. The output of one function becomes the input
of another. This technique allows you to build complex
operations by composing simpler functions. ( Sounds like
pipline ?)
85
Special and Advanced Functions
Function Composition
def add(x):
return x + 2
def multiply(x):
return x * 3
def filter_data(data):
return [x for x in data if x]
def process_data(data):
return [x.upper() for x in data]
result = asyncio.run(fetch_data())
print(result) 90
Special and Advanced Functions
Coroutines with Multiple Await Calls
You can chain multiple asynchronous operations using await inside a coroutine. For
example, let’s extend the fetch_data() coroutine to simulate multiple steps in an
asynchronous workflow.
import asyncio
print("Processing data...")
await asyncio.sleep(1) # Simulate processing delay
data["processed"] = True
return data
result = asyncio.run(fetch_data()) 91
print(result)
Special and Advanced Functions
Coroutines with Multiple Tasks
92
Special and Advanced Functions
Coroutines with Multiple Tasks
Example: Running Multiple Coroutines Concurrently
import asyncio
101
Predefined Functions and Libraries
Useful Libraries for Functions
• math: Provides mathematical functions.
• itertools: Contains useful tools for iteration, such as
chain(), combinations(), permutations().
• functools: Provides higher-order functions like reduce().
import math
print(math.sqrt(16)) # Output: 4.0
103