Algorithmic Thinking with Python. Module 3, control flow, functions, and python collections.

UCEST105 · 29 pages · Your place is saved in this browser

Contents · Module 3 overview
Text size18

Page 1 of 29

Module 3 overview

This module develops the Python skills needed to control a program, divide its work into functions, and organize data in collections.

TopicWhat you should be able to do
Control flowChoose branches, repeat actions, and trace loop execution
FunctionsDefine parameters, pass arguments, return results, and separate tasks
Strings and sequencesIndex, slice, traverse, and transform ordered data
Lists and tuplesChoose between mutable and immutable sequences
NumPy arraysInspect shapes, select elements, reshape, and perform matrix operations
Sets and dictionariesWork with unique values and key–value associations

The examples use Python 3. Code blocks contain runnable programs; output comments show the expected result. NumPy examples require the separately installed numpy package.

Key idea. Choose a collection by how you need to use it: positions suggest a sequence, unique membership suggests a set, and lookup by a meaningful key suggests a dictionary.

Page 2 of 29

Sequence and one-way selection

Sequence means executing statements in order. Selection chooses which statements to execute. Repetition executes a block repeatedly. Python uses indentation to mark the blocks belonging to conditions, loops, and functions.

Python
a = 20
b = 10
print("Difference:", a - b)  # Difference: 10
print("Sum:", a + b)         # Sum: 30
print("Product:", a * b)     # Product: 200

A one-way if runs its body only when its condition is true. Execution then continues after the block, whether or not the body ran.

Python
number = 8
if 0 < number < 10:
    print("Positive single-digit number")
print("Check complete")

The condition deliberately excludes 0 because it asks for a positive single-digit integer. Assume number is an integer. Use lowercase if, finish the header with a colon, and indent its body consistently.

Page 3 of 29

Two-way and multi-way selection

An if/else selects exactly one of two branches. This exercise classifies ages below 18 as minors; assume age is non-negative.

Python
age = 12
if age >= 18:
    print("Adult")
else:
    print("Minor")  # Minor

An age decision sends the true branch to Adult and the false branch to Minor.

Open full-size SVG

An if/elif/else chain checks conditions from top to bottom and runs the first matching branch. The example below uses illustrative grade bands, not an institutional grading policy.

Python
def grade_for(mark):
    if not 0 <= mark <= 100:
        return "Invalid mark"
    if mark >= 90:
        return "S"
    elif mark >= 80:
        return "A"
    elif mark >= 70:
        return "B"
    elif mark >= 60:
        return "C"
    elif mark >= 50:
        return "D"
    elif mark >= 40:
        return "E"
    else:
        return "F"

print(grade_for(98))  # S
print(grade_for(45))  # E
print(grade_for(39))  # F

Use and to combine Boolean conditions, or write a chained comparison such as 80 <= mark < 90. The & operator is bitwise AND and is not a substitute for and in these scalar comparisons. Here, descending thresholds make a second upper-bound check unnecessary.

Check boundaries: 100 and 90 give S, 89 gives A, 40 gives E, and 39 gives F. Values outside 0–100 are invalid.

Page 4 of 29

For loops, range, and while loops

A for loop visits each item in an iterable. Use range() for an integer counter sequence. Its stop value is excluded.

Python
for number in range(5):
    print(number, end=" ")
print()  # The preceding line prints: 0 1 2 3 4

print(list(range(2, 6)))      # [2, 3, 4, 5]
print(list(range(1, 10, 2)))  # [1, 3, 5, 7, 9]
print(list(range(10, 0, -2))) # [10, 8, 6, 4, 2]
FormMeaning
range(stop)Start at 0, step by 1
range(start, stop)Use the given start, step by 1
range(start, stop, step)Use an explicit positive or negative step

A zero step raises ValueError. A range is empty when its step points away from the stop value; for example, range(1, 5, -1).

A while loop checks its condition before every iteration. It can run zero times and is useful when the repetition count is not known in advance.

Python
count = 3
while count > 0:
    print(count)
    count -= 1
print("Done")
# Output, one value per line: 3, 2, 1, Done

Always check how the loop makes progress. Removing count -= 1 from this example would prevent termination.

Page 5 of 29

Nested loops and loop-control statements

A nested loop runs an inner loop during each iteration of an outer loop. If both ranges have fixed lengths, the total number of inner-body executions is their product.

Python
for row in range(3):
    for column in range(1, 5):
        print(column, end=" ")
    print()
# Three rows, each containing: 1 2 3 4
StatementEffect
breakExit the innermost enclosing loop immediately
continueSkip the remaining statements of the current iteration
passDo nothing; it is a placeholder, not a loop jump
Python
for number in range(1, 6):
    if number == 3:
        break
    print(number, end=" ")
print()  # Preceding line: 1 2

for number in range(1, 6):
    if number % 2 == 0:
        continue
    print(number, end=" ")
print()  # Preceding line: 1 3 5

for number in range(1, 4):
    if number == 2:
        pass
    print(number, end=" ")
print()  # Preceding line: 1 2 3

In nested loops, break exits the inner loop containing it, not every enclosing loop. In a while loop, ensure that continue does not skip the update needed for termination.

Page 6 of 29

Short-circuit evaluation

Python evaluates logical operands from left to right. For and, it skips the right operand when the left is false. For or, it skips the right operand when the left is true.

Python
numerator = 10
denominator = 0
if denominator != 0 and numerator / denominator > 0:
    print("Positive quotient")
else:
    print("Zero denominator or non-positive quotient")

The division is skipped when denominator is zero. Testing denominator > 0 would also avoid zero division, but would unnecessarily reject negative non-zero denominators. Put a required safety check before the operation it protects.

Python
values = []
if values and values[0] > 0:
    print("First value is positive")

name = ""
display_name = name or "Guest"
print(display_name)  # Guest

and and or return one of their operands, which is not necessarily a Boolean. Use bool(...) when an explicit Boolean result is required. Short-circuiting can save work, but the order must first preserve the intended logic and required side effects.

Page 7 of 29

Functions and modular design

A function is a named, reusable block of code. Decomposition breaks a larger problem into smaller tasks. Modular design organizes those tasks behind clear interfaces so that each part can be understood and checked separately.

Functions reduce duplicated logic, hide implementation detail, and make individual tasks easier to reuse, debug, and test.

Python
def add_numbers(first, second):
    """Return the sum of two numbers."""
    total = first + second
    return total

answer = add_numbers(6, 8)
print(answer)  # 14
PartExamplePurpose
Definition keyworddefCreate a function definition
Nameadd_numbersIdentify the function
Parametersfirst, secondNames receiving the call's values
BodyIndented statementsPerform the task
Returnreturn totalExit this call and give a result to its caller

A Python module is usually a .py file containing definitions and statements. It may contain several functions; module and function are not interchangeable terms.

A definition must execute before the function is called. A function may contain multiple return statements, and a return need not be the last written line of the function.

Page 8 of 29

Arguments, parameters, and return values

A parameter is a name in a function definition. An argument is a value supplied in a call. A function can receive arguments, return a result, do both, or do neither explicitly.

Python
def greet():
    print("Hello")

def show_sum(first, second):
    print(first + second)

def default_limit():
    return 10

def add_numbers(first, second):
    return first + second

greet()                     # Hello
show_sum(3, 5)              # 8
print(default_limit())     # 10
print(add_numbers(10, 15))  # 25
FunctionTakes arguments?Explicitly returns a useful value?
greet()NoNo
show_sum(first, second)YesNo
default_limit()NoYes
add_numbers(first, second)YesYes

Every Python function returns a value. Without an explicit return value, it returns None. Printing displays something; it does not pass that displayed value back to the caller.

Python
def display_total(first, second):
    print(first + second)

result = display_total(2, 3)  # Prints 5
print(result)                # None

Prefer returning results when the caller may need them for further calculations. Avoid naming a function sum, which hides Python's built-in sum() within the same scope.

Page 9 of 29

Function calls and worked examples

A caller supplies arguments, the function computes its result, and control returns to the caller. Parameters and variables assigned inside a function are normally local to that function.

A caller sends two arguments to add_numbers, receives a total, then prints the result.

Open full-size SVG

Python
def absolute_value(number):
    if number < 0:
        return -number
    return number

def is_even(number):
    return number % 2 == 0

print(absolute_value(-7))  # 7
print(absolute_value(0))   # 0
print(is_even(8))          # True
print(is_even(-3))         # False

A pattern-printing function needs to be called; defining it alone produces no pattern.

Python
def print_odd_pattern(rows):
    for row in range(1, rows + 1):
        for number in range(1, 2 * row, 2):
            print(number, end=" ")
        print()

print_odd_pattern(4)
# 1
# 1 3
# 1 3 5
# 1 3 5 7

Assume rows is a non-negative integer. A value of 0 prints no rows. Avoid names such as abs for variables because they hide useful built-in functions.

Page 10 of 29

Sequences and Unicode strings

A sequence is ordered: its items have positions and can be accessed by index. Strings, lists, and tuples are common sequence types; range is another sequence type.

TypeHoldsCan its items be reassigned?
strUnicode text charactersNo
listReferences to arbitrary Python objectsYes
tupleReferences to arbitrary Python objectsNo

A Python string is an immutable sequence of Unicode code points. Python code does not need a terminating null character to determine its length. A character is not necessarily one byte; encoded byte length depends on the encoding and the text.

Python
text = "Hello World"
print(len(text))      # 11
print(text[0])        # H
print(text[-1])       # d
print(len("café"))    # 4

Hello World indexed from 0 to 10 and from -11 to -1; the space has its own position.

Open full-size SVG

This is an index diagram, not a physical memory layout. len() counts code points, which can differ from the number of user-perceived characters when combining marks or multi-code-point emoji are used.

Page 11 of 29

String indexing, traversal, and immutability

For a string of length nn, valid non-negative indices run from 0 to n−1n-1. Negative indices count from the end, starting at -1.

Python
text = "Try out!"
print(text[-1])  # !
print(text[-3])  # u
print(len(text)) # 8
# text[-9] would raise IndexError: the index is out of range.

An out-of-range access is an indexing error; it is unrelated to whether a string is mutable. Immutability means that assigning to an existing character, such as text[0] = "t", raises TypeError.

Create a new string when a change is needed:

Python
text = "python"
changed = "P" + text[1:]
print(changed)  # Python
print(text)     # python

for character in "Hi there":
    print(repr(character), end=" ")
print()
# 'H' 'i' ' ' 't' 'h' 'e' 'r' 'e'

Traversal includes spaces and punctuation. repr() makes an otherwise hard-to-see space visible in the output.

Python
print("Python" + " " + "Program")  # Python Program
print("Order!" * 3)                 # Order!Order!Order!

Concatenation does not add spaces automatically. Repetition requires an integer multiplier.

Page 12 of 29

String slicing, comparison, and membership

The slice text[start:stop:step] selects characters from the inclusive start up to the exclusive stop. Start and stop can be omitted; step defaults to 1 and cannot be zero.

Python
text = "Python, Java and C++"
print(text[:6])    # Python
print(text[8:12])  # Java
print(text[-3:])   # C++
print(text[::-1])  # ++C dna avaJ ,nohtyP
print(repr(text[4:4]))  # ''
print(text[100:])       # Empty string; slicing clips out-of-range bounds

With a positive step, a slice whose start is not before its stop is empty. With a negative step, selection runs backwards. An invalid individual index raises IndexError, whereas an out-of-range slice boundary is clipped.

Strings compare lexicographically by Unicode code point, not by language-specific dictionary order. Comparison is case-sensitive.

Python
print("python" == "Python")  # False
print("C" < "Java")          # True
print("A" < "a")             # True
print("Java" in "Python, Java and C++")  # True
print("Java" in "Python")               # False

Use casefold() when a case-insensitive text comparison is appropriate. The string membership operator checks for a substring, not only a single character.

Page 13 of 29

String methods

String methods are called directly on a string. They do not require import string. Because strings are immutable, transformation methods return new strings.

MethodResult
s.count(sub[, start[, end]])Count non-overlapping occurrences of a substring
s.find(sub[, start[, end]])First matching index, or -1 when no match exists
s.replace(old, new[, count])A copy with replacements; optionally limit their count
s.isalpha()True for a non-empty string containing only letters
s.isdigit()True for a non-empty string containing only digit characters
s.lower(), s.upper()A copy with case changed
s.strip()A copy with leading and trailing whitespace removed

Square brackets in the signatures indicate optional arguments; do not type them in a call.

Python
text = "malayalam"
print(text.find("al"))        # 1
print(text.find("al", 2))     # 5
print(text.find("al", 2, 5))  # -1
print(text.count("al"))       # 2
print(text.replace("la", "pq"))     # mapqyapqm
print(text.replace("la", "pq", 1))  # mapqyalam
print(text)                  # malayalam
print("STR2".isalpha())       # False
print("".isdigit())           # False

isalpha() and isdigit() recognize more than ASCII letters and digits. A true isdigit() result does not guarantee that every such Unicode character is accepted by int(); use conversion with error handling when validating numeric input.

Page 14 of 29

String constants and counting characters

The separate string module supplies constants such as ASCII letters and digits. These constants are useful when a problem specifically restricts its input to ASCII.

Python
import string

print(string.ascii_lowercase)  # abcdefghijklmnopqrstuvwxyz
print(string.ascii_uppercase)  # ABCDEFGHIJKLMNOPQRSTUVWXYZ
print(string.digits)           # 0123456789
print("p" in string.ascii_lowercase)  # True
print("é" in string.ascii_letters)    # False
print("é".isalpha())                  # True

To count a single character without calling count(), traverse the string and increment a counter for each match.

Python
def count_character(text, target):
    if len(target) != 1:
        raise ValueError("target must contain exactly one character")
    total = 0
    for character in text:
        if character == target:
            total += 1
    return total

print(count_character("banana", "a"))  # 3
print(count_character("banana", "z"))  # 0
print(count_character("", "a"))        # 0

This function counts one character at a time. Counting a multi-character substring requires a different approach, such as text.count(substring).

Page 15 of 29

Creating, indexing, and traversing lists

A list is an ordered, mutable sequence. Items may have different types, and a list may contain other lists.

Python
items = [10, "hi", 3.14]
nested = [10, 20, [25, 30], 40]
print(len(items))    # 3
print(len(nested))   # 4; the nested list counts as one item
print(nested[2])     # [25, 30]
print(nested[2][1])  # 30
print(nested[-1])    # 40
print(list(range(10, 30, 5)))  # [10, 15, 20, 25]

List indices must be integers or slices. An expression such as nested[5 - 4] is valid because it evaluates to the integer 1. nested[1.0] raises TypeError, even though 1.0 is numerically equal to 1.

Python
primes = [2, 3, 5, 7, 11]
for value in primes:
    print(value, end=" ")
print()  # Preceding line: 2 3 5 7 11

for index, value in enumerate(primes):
    print(index, value)

Use enumerate() when both positions and values are needed. An empty list is [], and len([]) is 0. Avoid changing a list's size while traversing it unless the algorithm deliberately accounts for that change.

Page 16 of 29

List comprehensions and operations

A list comprehension builds a new list by evaluating an expression for items in an iterable, optionally filtering with a condition. It need not start from an existing list.

Python
numbers = [1, 28, 73, 4, 100, 358, 75, 208]
odd = [number for number in numbers if number % 2 != 0]
squares = [number ** 2 for number in range(1, 5)]
print(odd)      # [1, 73, 75]
print(squares)  # [1, 4, 9, 16]
print(list(range(10, 20, 2)))  # [10, 12, 14, 16, 18]
OperationExampleMeaning
Concatenation[2, 3] + [5, 7]New list combining both sequences
Repetition[0, 1] * 2New list [0, 1, 0, 1]
Equality[2, 4] == [2, 4]Same values in the same order
Membership4 in [2, 4, 6]Whether a value occurs
Ordering[2, 3, 7] < [2, 4, 6]Lexicographic comparison

Lists compare element by element, stopping at the first unequal pair. If one is a prefix of the other, the shorter list comes first. Ordering elements of incompatible types, such as an integer and a string, can raise TypeError.

Watch out. Repetition copies references, not nested objects. [[]] * 3 contains three references to the same inner list. Use [[] for _ in range(3)] when independent inner lists are required.

Page 17 of 29

List slices, mutation, and copying

Lists use the same start-inclusive, stop-exclusive slicing rules as strings. A list slice creates a new list, but it is a shallow copy: references to nested objects are shared.

Python
values = [10, 20, 30, 40]
print(values[1:3])  # [20, 30]
values[2] = 35
print(values)       # [10, 20, 35, 40]

alias = values
copy = values[:]
alias[0] = 99
print(values)  # [99, 20, 35, 40]
print(copy)    # [10, 20, 35, 40]

Assignment creates an alias to one list, whereas slicing creates a separate outer list.

Open full-size SVG

Python
original = [[1], [2]]
shallow = original[:]
shallow[0].append(9)
print(original)  # [[1, 9], [2]]

Reassigning an element in a shallow copy changes that copy's slot; mutating a shared nested object is visible through both lists. Use copy.deepcopy() when the task requires independently copied nested structures.

Page 18 of 29

Replacing, inserting, and deleting list items

An indexed assignment replaces one item. A slice assignment replaces a range with values from an iterable and may change the list's length.

Python
values = [10, 20, 30, 40, 50]
values[1:3] = [25, 35]
print(values)  # [10, 25, 35, 40, 50]

values[2:2] = [27, 29]
print(values)  # [10, 25, 27, 29, 35, 40, 50]

values[1:4] = []
print(values)  # [10, 35, 40, 50]

del values[1]
print(values)  # [10, 40, 50]
del values[1:]
print(values)  # [10]

An empty slice such as [2:2] inserts before position 2. Assigning [] to a slice removes the selected range. In contrast, values[0] = [] stores an empty list as an item; it does not remove that position.

The right side of a slice assignment must be iterable. values[0:1] = 7 raises TypeError; use [7] instead. Deleting one out-of-range index raises IndexError, while deleting an out-of-range slice safely clips its bounds.

Page 19 of 29

List methods

Use this text reference in place of a screenshot so method names and arguments remain selectable.

MethodEffect or result
items.index(value)First matching index; ValueError if absent
items.insert(index, value)Insert before an index; an index past the end appends
items.append(value)Add one object at the end
items.extend(iterable)Append each item from an iterable
items.remove(value)Remove the first match; ValueError if absent
items.pop() / items.pop(index)Remove and return an item; IndexError for an invalid index or empty list
items.count(value)Number of matching items
items.sort(key=..., reverse=...)Sort the list in place; return None
items.reverse()Reverse the current order in place; return None
items.clear()Remove every item
items.copy()Make a shallow copy
Python
values = [3, 1, 3]
print(values.index(3))  # 0
values.insert(1, 9)
values.append(5)
values.extend([7, 8])
print(values)          # [3, 9, 1, 3, 5, 7, 8]
print(values.pop())    # 8
values.remove(3)
print(values)          # [9, 1, 3, 5, 7]
print(values.count(3)) # 1
values.sort()
print(values)          # [1, 3, 5, 7, 9]
values.reverse()
print(values)          # [9, 7, 5, 3, 1]

append([7, 8]) adds a nested list as one item; extend([7, 8]) adds two items. Use sorted(items) to obtain a sorted copy. Do not write items = items.sort(), which replaces the variable with None.

Page 20 of 29

Lists with functions, split, and join

A function can return a list. Returning a container of values is different from printing those values. This function returns up to three largest values without changing its input:

Python
def top_three(values):
    return sorted(values, reverse=True)[:3]

numbers = [23, 1, 78, 50, 100, -5]
print(top_three(numbers))  # [100, 78, 50]
print(numbers)             # [23, 1, 78, 50, 100, -5]
print(top_three([4]))      # [4]
print(top_three([]))       # []

A function receives references to the supplied objects. Mutating a list parameter can change the caller's list; merely rebinding that parameter to another list does not rebind the caller's variable.

Python
text = "two words"
print(list(text))    # ['t', 'w', 'o', ' ', 'w', 'o', 'r', 'd', 's']
print(text.split()) # ['two', 'words']
print(text.split("w"))  # ['t', 'o ', 'ords']
print(text.split("wo")) # ['t', ' ', 'rds']

words = ["three", "word", "string"]
print(" ".join(words))  # three word string
print("-".join(words))  # three-word-string

split() without an argument groups consecutive whitespace and drops leading or trailing whitespace. An explicit separator follows different rules and can produce empty items: "a,,b".split(",") gives ['a', '', 'b']. join() requires string items and inserts its separator between them.

Page 21 of 29

Tuples and unpacking

A tuple is an immutable sequence. Commas create tuple structure; parentheses are often used for clarity.

Python
record = ("Ram", 18, "S1")
single = ("a",)
not_a_tuple = ("a")
print(type(single).__name__)       # tuple
print(type(not_a_tuple).__name__)  # str
print(tuple("python"))            # ('p', 'y', 't', 'h', 'o', 'n')
print(tuple())                    # ()
print(record[0])                  # Ram
print(record[1:])                 # (18, 'S1')

name, age, semester = record
print(name, age, semester)         # Ram 18 S1
a, b = 5, 10
a, b = b, a
print(a, b)                       # 10 5

The number of targets must match the number of values unless starred unpacking is used. A trailing comma makes a one-item tuple; (4) is simply the integer 4.

Tuples do not support item assignment. However, an object referenced by a tuple may itself be mutable:

Python
record = ("marks", [10, 20])
record[1].append(30)
print(record)  # ('marks', [10, 20, 30])

A tuple is hashable only if all its elements are hashable. A tuple containing a list cannot be a dictionary key or set element.

Page 22 of 29

NumPy arrays, dimensions, and shape

NumPy provides the ndarray type for numerical data. It is a separate package and is conventionally imported as np. A standard numeric array has one data type; mixed inputs may be converted to a common type.

Python
import numpy as np

values = np.array([1, 2, 3, 4, 5, 6])
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(values.ndim)   # 1
print(values.shape)  # (6,)
print(matrix.ndim)   # 2
print(matrix.shape)  # (2, 3)
print(matrix.size)   # 6
AttributeMeaning
ndimNumber of axes
shapeTuple of lengths along those axes
sizeTotal number of elements
dtypeElement data type

A two-row, three-column array with its row and column indices.

Open full-size SVG

(6,) is a one-element shape tuple. (2, 3) means two rows and three columns. Shape must match the actual data: an array containing five values has shape (5,), not (6,). Object-dtype arrays are an exception to the usual homogeneous numeric-value description.

Page 23 of 29

Array indexing, traversal, and views

NumPy supports one index per axis. Basic slicing usually shares data with the original array, unlike a list slice's new outer list.

Python
import numpy as np

values = np.array([1, 2, 3, 4, 5, 6])
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(values[3])       # 4
print(values[2:5])     # [3 4 5]
print(values[1:5:2])   # [2 4]
print(matrix[0, 2])    # 3
print(matrix[-1, -1])  # 6
print(matrix[:, 1])   # [2 5]

for row in matrix:
    for value in row:
        print(value, end=" ")
    print()
# Rows: 1 2 3 and 4 5 6
Python
import numpy as np

values = np.array([10, 20, 30, 40])
view = values[1:3]
view[0] = 99
print(values)  # [10 99 30 40]

independent = values[1:3].copy()
independent[0] = -1
print(values)  # [10 99 30 40]

Use .copy() when the selected data must be independent. In matrix[row, column], both indices refer to axes; a list of lists instead normally uses nested[row][column].

Page 24 of 29

Reshaping arrays and matrix operations

Reshaping reorganizes the axes while preserving the number of elements. It returns an array with the requested shape; assigning that return value does not modify the original object's shape.

Python
import numpy as np

values = np.array([1, 2, 3, 4, 5, 6])
matrix = values.reshape(2, 3)
print(matrix.shape)  # (2, 3)
print(values.shape)  # (6,)
# np.array([1, 2, 3, 4, 5]).reshape(2, 3) would raise ValueError.

The product of the new dimensions must equal the element count. reshape() returns a view where possible and otherwise may copy; it does not guarantee independent data.

OperationExpressionMeaning
Additiona + b / np.add(a, b)Elementwise sum
Subtractiona - b / np.subtract(a, b)Elementwise difference
Elementwise multiplicationa * bMultiply corresponding elements
Matrix multiplicationa @ b / np.matmul(a, b)Row-by-column multiplication
Transposea.TSwap the axes of a 2-D array
Python
import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
print((a + b).tolist())  # [[6, 8], [10, 12]]
print((a - b).tolist())  # [[-4, -4], [-4, -4]]
print((a * b).tolist())  # [[5, 12], [21, 32]]
print((a @ b).tolist())  # [[19, 22], [43, 50]]
print(a.T.tolist())     # [[1, 3], [2, 4]]

For 2-D matrix multiplication, the left array's column count must equal the right array's row count. The examples use equally shaped arrays for elementwise operations; NumPy also supports broadcasting for compatible shapes.

Page 25 of 29

Creating, traversing, and changing sets

A set is an unordered collection of distinct, hashable objects. It supports membership checks but not positional indexing. Unordered does not mean the order randomly changes on every print; it means you must not rely on an order being defined.

Python
values = {0, 1, 1, 2, 3, 5}
print(sorted(values))  # [0, 1, 2, 3, 5]
print(3 in values)    # True
print(set())          # set(); an empty set
print(type({}).__name__)  # dict; braces alone create a dictionary

vowels = set("aeiou")
for vowel in sorted(vowels):
    print(vowel, end=" ")
print()  # Preceding line: a e i o u

You can traverse a set, or obtain an arbitrary item with next(iter(values)) when it is non-empty. values[0] raises TypeError because sets have no index.

Python
primes = {3, 5, 7, 9, 11}
primes.add(2)
primes.update([13, 17])
primes.remove(9)
primes.discard(99)  # No error when 99 is absent
print(sorted(primes))  # [2, 3, 5, 7, 11, 13, 17]

add() adds one hashable object; update() reads items from an iterable. remove() raises KeyError when an item is missing, whereas discard() does not. Lists and ordinary sets are unhashable and cannot themselves be set elements; frozenset is an immutable, hashable alternative.

Page 26 of 29

Set relationships and removal methods

Set comparisons describe inclusion relationships rather than lexicographic order. Neither a < b nor a > b needs to be true when the sets overlap without containing one another.

ExpressionInterpretation
a == bSame elements
a != bDifferent elements
a <= bEvery element of a is in b: subset
a < ba is a subset of b and is not equal to b: proper subset
a >= ba contains every element of b: superset
a > ba is a proper superset of b
a.isdisjoint(b)No common elements
Python
digits = set(range(10))
odd_digits = {1, 3, 5, 7, 9}
print(odd_digits < digits)    # True
print(digits >= odd_digits)   # True
print({1, 2} <= {1, 2})       # True
print({1, 2} < {1, 2})        # False
print({1, 2}.isdisjoint({3})) # True
Method or statementEffect
s.remove(value)Remove value; raise KeyError if missing
s.discard(value)Remove value if present
s.pop()Remove and return an arbitrary item; KeyError if empty
s.clear()Remove all items, leaving an empty set
del sDelete this name; the object may still have other references

del s removes a binding rather than guaranteeing that an object is destroyed. Accessing an undefined name raises NameError.

Page 27 of 29

Set algebra and in-place updates

New-set operationEquivalent operatorMeaning
a.union(b)a | bIn either set
a.intersection(b)a & bIn both sets
a.difference(b)a - bIn a but not b
a.symmetric_difference(b)a ^ bIn exactly one set
Python
a = {1, 2, 3}
b = {3, 4}
print(sorted(a | b))  # [1, 2, 3, 4]
print(sorted(a & b))  # [3]
print(sorted(a - b))  # [1, 2]
print(sorted(a ^ b))  # [1, 2, 4]
print(sorted(a))      # [1, 2, 3]; original unchanged

The four algebra methods in the first table return a new set. Their corresponding update methods change the existing set and return None.

In-place methodEquivalent assignment
a.update(b)`a
a.intersection_update(b)a &= b
a.difference_update(b)a -= b
a.symmetric_difference_update(b)a ^= b
Python
numbers = {-3, -2, -1, 0, 1, 2, 3}
natural = {1, 2, 3, 4, 5}
result = numbers.symmetric_difference(natural)
print(sorted(result))   # [-3, -2, -1, 0, 4, 5]
print(sorted(numbers))  # [-3, -2, -1, 0, 1, 2, 3]

numbers.symmetric_difference_update(natural)
print(sorted(numbers))  # [-3, -2, -1, 0, 4, 5]

Use sorted output only when the elements are mutually orderable. Sets may contain different types, so sorted() is not a universal display solution.

Page 28 of 29

Dictionaries: keys, values, and insertion order

A dictionary maps unique, hashable keys to values. Values need not be unique or hashable. A pair such as "name": "Ram" is an item.

In Python 3.7 and later, dictionaries preserve insertion order. Updating an existing key keeps its position; inserting a new key adds it at the end. A dictionary is still accessed by keys rather than by positional indices.

Python
student = {"name": "Ram", "age": 18, "semester": "S1"}
print(student["name"])  # Ram
student["age"] = 19
student["college"] = "Engineering college"
print(list(student))  # ['name', 'age', 'semester', 'college']
print(len(student))   # 4
print("age" in student) # True; membership tests keys

for key, value in student.items():
    print(key, value)

del student["semester"]
print(list(student))  # ['name', 'age', 'college']

Reading or deleting a missing key with brackets raises KeyError. Use get() for a default-valued lookup or pop(key, default) for removal when absence is expected.

If a dictionary literal repeats a key, the later value replaces the earlier value. Strings, numbers, and tuples of hashable items can be keys; lists cannot. Avoid adding or removing keys while directly iterating the dictionary's live views.

Page 29 of 29

Dictionary methods and revision checklist

MethodResult or effect
d.get(key, default)Value or default; default is None when omitted
d.pop(key, default)Remove and return a value, or return the supplied default
d.keys()Live view of keys
d.values()Live view of values
d.items()Live view of (key, value) pairs
d.update(other)Insert or replace entries from another mapping or iterable of pairs
d.clear()Remove all entries

pop() raises KeyError for a missing key if no default is supplied. Dictionary views are not lists; use list(d.keys()) when a list is needed. A view reflects later changes to the dictionary.

Python
student = {"name": "Ram", "age": 18}
keys = student.keys()
print(student.get("college"))           # None
print(student.get("college", "Unknown")) # Unknown
print(student.pop("college", "Absent"))  # Absent
print(student.pop("age"))               # 18
student["semester"] = "S1"
print(list(keys))      # ['name', 'semester']
print(list(student.items()))  # [('name', 'Ram'), ('semester', 'S1')]
student.clear()
print(student)         # {}

Check your understanding

  • Why does return differ from print()?
  • Why does a string slice tolerate an oversized stop while an individual index may fail?
  • When does a copied list still share nested objects?
  • Why are NumPy * and @ different?
  • Which set methods mutate their receiver?
  • Why is a dictionary view different from a snapshot made with list(...)?

References

You’ve reached the end of S1 CSE Algorithmic Thinking with Python · Module 3.

Source: KTU UCEST105 study notes; explanations and worked examples edited for clarity. Check the original material if a step looks unclear.