Unit 2: Control Flow, Functions, and Problem-Solving

CSR101 — Python Programming 9 min read

I. Orientation

Python programs follow a sequence of statements, but control flow allows that sequence to make decisions, repeat actions, and delegate work to reusable functions. Problem-solving combines these mechanisms with text processing, pattern matching, and algorithmic planning.

  • Governing principle: A program transforms input into output through ordered operations, decisions, repetition, and abstraction.
  • Boolean foundation: Conditions evaluate to True or False; comparisons include ==, !=, <, >, <=, and >=.
  • Block convention: Indentation, normally four spaces, defines the statements controlled by if, loops, and functions.
  • Function convention: A function receives arguments, performs a task, and may produce a return value.
  • String convention: Strings are immutable sequences indexed from 0; slicing creates a new string.
  • Efficiency convention: A correct algorithm should also use reasonable time and memory, especially when loops are nested.

II. Conditional Statements — Choosing Between Alternatives

Conditional statements execute different blocks according to Boolean tests. They are the basic mechanism for expressing alternatives.

A. Conditional statements (if-else)

An if statement tests a condition, while elif and else provide alternative paths.

  • Basic form: The indented block after if condition: runs only when condition is true.
PYTHON
age = 20
if age >= 18:
    status = "adult"
else:
    status = "minor"
  • Multiple cases: elif tests another condition only if earlier conditions were false.
  • Truth values: Empty strings, 0, None, and empty collections are false-like; non-empty values are generally true-like.
  • Logical combination: and requires both conditions, or requires at least one, and not reverses a Boolean.
  • Ordering: Conditions should be arranged from specific to general when one case could match another.

III. Loops — Repeating Computation

Loops execute a block repeatedly, reducing duplicated code and supporting accumulation, searching, and validation.

A. Loops (for, while)

A for loop visits items in an iterable; a while loop continues while its condition remains true.

  • for behavior: Each item is assigned to the loop variable.
PYTHON
total = 0
for number in [3, 5, 7]:
    total += number
  • while behavior: The condition is checked before each iteration, so the loop may run zero times.
PYTHON
attempts = 0
while attempts < 3:
    attempts += 1
  • Termination: A for loop ends when its iterable is exhausted; a while loop needs a condition that eventually becomes false.
  • Selection principle: Use for when processing known items and while when repetition depends on a changing condition.

B. Nested loops

Nested loops place one loop inside another and are useful for tables, grids, and pair comparisons.

  • Execution pattern: For each iteration of the outer loop, the complete inner loop runs.
  • Concrete count: A 3 × 4 nested loop performs 3 * 4 = 12 inner-body executions.
  • Complexity: If both loops process n items, the work is commonly proportional to .
  • Indentation: The inner loop must be indented inside the outer loop.

C. Break and continue

break stops the nearest loop immediately, while continue skips to its next iteration.

  • break use: Stop searching after finding the first matching value.
PYTHON
for value in [4, 8, 11, 15]:
    if value > 10:
        break
  • continue use: Ignore unwanted values while preserving the loop.
PYTHON
for value in range(6):
    if value % 2 == 0:
        continue
    print(value)
  • Scope: These statements affect only the nearest enclosing loop, not an outer loop or the whole function.

D. Counting

Counting tracks occurrences or iterations using an accumulator initialized before the loop.

  • Counter rule: Increment by one when an event occurs; initialize with count = 0.
PYTHON
count = 0
for character in "banana":
    if character == "a":
        count += 1
  • Accumulator distinction: A counter stores quantities, whereas a sum accumulator stores a running total such as total += price.
  • Invariant: After each iteration, count should equal the number of qualifying items processed so far.

E. range() function

range() produces an arithmetic sequence of integers, commonly used for controlled iteration.

  • Forms: range(stop), range(start, stop), and range(start, stop, step) are available.
  • Exclusion rule: The stop value is not included; range(2, 5) produces 2, 3, 4.
  • Direction: A negative step counts downward, as in range(5, 0, -1).
  • Memory behavior: In Python 3, range represents the sequence compactly rather than constructing a full list.

IV. Functions — Reusable Units of Logic

Functions package a named operation so it can be called repeatedly with different data.

A. Function definitions

A function definition uses def, a name, parameters, a colon, and an indented body.

  • Purpose: Encapsulation separates a task from the main program and reduces repetition.
PYTHON
def square(number):
    return number * number
  • Parameters: number is a local name receiving input when square is called.
  • Local scope: Variables created inside a function normally exist only during that call.
  • Documentation: A docstring immediately inside the function can describe its purpose and expected inputs.

B. Arguments

Arguments are the actual values supplied to a function’s parameters.

  • Positional arguments: square(4) assigns 4 to the first parameter by position.
  • Keyword arguments: power(base=2, exponent=3) assigns values by parameter name.
  • Default values: def greet(name="Guest"): allows a call with no argument.
  • Mutable caution: Default mutable objects such as lists can retain changes between calls; use None when a fresh list is needed.

C. Return values

return ends a function call and sends a value back to the caller.

  • Expression result: return number * number produces 16 for square(4).
  • No explicit result: A function reaching its end returns None.
  • Multiple values: return x, y returns one tuple containing both values.
  • Separation: Returning a result is generally more reusable than printing it, because callers can store or further process the value.

D. Recursion basics

Recursion occurs when a function calls itself on a smaller version of the problem.

  • Base case: A condition such as if n == 0: return 1 stops further calls.
  • Recursive case: Each call must move toward the base case.
PYTHON
def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n - 1)
  • Trace: factorial(3) becomes 3 * factorial(2), then 2 * factorial(1), then 1 * factorial(0).
  • Limitation: Excessive depth can cause a recursion error; iteration is often more memory-efficient for simple repetition.

E. Lambda function

A lambda is a short anonymous function containing one expression.

  • Syntax: lambda parameter: expression creates a function object.
  • Example: sorted(words, key=lambda word: len(word)) sorts by word length.
  • Restriction: Lambda bodies cannot contain ordinary statements such as assignments or loops.
  • Use principle: Use lambda for brief, local transformations; use def when logic needs explanation or multiple steps.

V. Strings — Sequence and Text Processing

Strings are immutable ordered collections of characters, supporting indexing, slicing, searching, transformation, and combination.

A. String slicing

String slicing extracts a portion using text[start:stop:step].

  • Bounds: The start is included and the stop is excluded; "Python"[1:4] produces "yth".
  • Defaults: text[:3] starts at the beginning, and text[3:] continues to the end.
  • Negative indices: text[-1] selects the final character.
  • Reversal: text[::-1] creates a reversed copy.

B. Advanced string formatting

Advanced formatting controls alignment, width, precision, signs, and numeric presentation.

  • F-string expression: f"{price:.2f}" formats price to two digits after the decimal point.
  • Width and alignment: f"{name:>10}" right-aligns name in a field of width 10.
  • Numeric formats: f"{value:,}" inserts thousands separators; f"{ratio:.1%}" displays a percentage.
  • Evaluation: Expressions inside braces are evaluated before formatting, such as f"{total / count:.2f}".

C. String methods

String methods return transformed strings or information without changing the original immutable string.

  • Case methods: "Hello".lower() gives "hello"; .upper() gives "HELLO; .title() capitalizes words.
  • Validation: .isdigit() checks digit characters, while .startswith("Py") checks a prefix.
  • Searching: .find("cat") returns the starting index or -1; .count("a") counts occurrences.
  • Replacement: "red red".replace("red", "blue") produces "blue blue".

D. Splitting and joining strings

split() converts a string into a list, while join() combines iterable items into one string.

  • Whitespace split: "one two".split() produces ["one", "two"].
  • Delimiter split: "a,b,c".split(",") separates at commas.
  • Joining rule: ", ".join(["a", "b", "c"]) produces "a, b, c".
  • Type requirement: Every item passed to join() must be a string; convert numbers with str() first.

E. String format method

The format() method inserts positional or named arguments into brace fields.

  • Positional fields: "{} + {} = {}".format(2, 3, 5) fills fields from left to right.
  • Named fields: "Hello, {name}".format(name="Mira") improves readability.
  • Formatting specification: "{:.2f}".format(3.14159) produces "3.14".
  • Comparison: F-strings are usually clearer for immediate values, while format() is useful for reusable templates.

VI. Regular Expressions — Pattern-Based Text Matching

Regular expressions describe text patterns and are implemented in Python through the re module.

A. Regular expressions

A regular expression can search, validate, split, or replace text according to a pattern.

  • Compilation: re.compile(r"\d+") creates a pattern matching one or more digits; the raw string prevents accidental escape processing.
  • Searching: re.search(pattern, text) finds a match anywhere; re.fullmatch() requires the entire string to match.
  • Common symbols: \d means a digit, \w a word character, . any character, and * zero or more repetitions.
  • Groups: Parentheses capture components, so r"(\d{4})-(\d{2})" separates year and month in "2025-06".
  • Limitation: Regular expressions are powerful for structured text but can become difficult to read; simple string methods are preferable for simple searches.

VII. Algorithmic Thinking — Designing Reliable Solutions

Algorithmic thinking translates a problem into precise, finite steps before implementation.

A. Algorithmic thinking

An algorithm is a defined procedure that maps inputs to outputs and terminates under its stated conditions.

  • Decomposition: Break “process a class report” into read marks, calculate totals, compute averages, and classify results.
  • Pattern recognition: Notice repeated structures such as counting, accumulation, filtering, and maximum selection.
  • State tracking: Identify variables such as total, count, largest, or index and define what each means after every iteration.
  • Complexity awareness: One pass through n items is usually O(n); comparing every pair commonly produces O(n²).
  • Validation: Consider empty input, invalid values, boundary indices, and zero divisors before coding.

B. Writing Python code for common problem-solving patterns

Common patterns provide dependable templates for turning algorithms into readable Python.

  • Linear search: Examine each item and stop when target == item; this takes at most n comparisons for n items.
  • Filtering: Build a result containing only items satisfying a condition, such as [x for x in numbers if x > 0].
  • Maximum tracking: Initialize from the first item, then replace largest whenever a larger value appears.
  • Frequency counting: Use a dictionary to map each item to its count.
PYTHON
frequencies = {}
for word in words:
    frequencies[word] = frequencies.get(word, 0) + 1
  • Input-process-output structure: Parse input first, perform the algorithm second, and format output last; this makes testing and debugging clearer.
  • Correctness check: Test normal data, empty collections, one-item collections, duplicates, and boundary values such as 0 or the final index.