An Introduction to the Art of Computer Programming Using Python in the Age of Generative AI

XVII. Introduction to Algorithms and Efficiency

There should be one-- and preferably only one --obvious way to do it.
Tim Peters, Zen of Python

An algorithm is a step-by-step procedure or formula for solving a problem. In Python, algorithms are often expressed as functions that take inputs, perform calculations or data manipulation, and return outputs. Understanding the efficiency and scalability of these algorithms is critical, and that's where Big-O notation comes in.

Big-O Notation

Big-O notation is a mathematical representation used to describe the upper bound of an algorithm's runtime or space requirements, which helps to understand the worst-case scenario of its efficiency. It essentially answers the question: "At most, how complex is the algorithm as the size of its input data set grows?" Big-O ignores constant factors and lower-order terms, focusing only on the most important factors that affect complexity.

O(1) - Constant Time

Constant time complexity, also known as O(1) complexity, is when the time required to complete an operation remains the same regardless of the size of the input data set.


fruits: list = ["apple", "banana", "orange"]

def get_first_element(fruits):
    if fruits:
        return fruits[0]
    else:
        return None
        

O(log n) - Logarithmic Time

Logarithmic time complexity occurs when the execution time grows logarithmically as the input data set grows. This is common in algorithms that reduce the problem size by a constant factor each step, such as binary search.


def binary_search(example_list, item):
    first = 0
    last = len(fruits) - 1
    found = False

    while first <= last and not found:
        midpoint = (first + last) // 2
        if fruits[midpoint] == item:
            found = True
        else:
            if item < fruits[midpoint]:
                last = midpoint - 1
            else:
                first = midpoint + 1
    return found

fruits = ["apple", "banana", "orange"]
item = "banana"
print(binary_search(fruits, item))
        

O(n) - Linear Time

Linear time complexity occurs when the execution time grows linearly with the size of the input data set. This is common in algorithms that must examine each element of the data set once.


def sum_list(example_list):
    total = 0
    for element in example_list:
        total += element
    return total

numbers = [1,3,5,7]

print(sum_list(numbers))
        

O(n^2) - Quadratic Time

Quadratic time complexity is common in algorithms that involve nested iterations over the data set. The execution time grows proportionally to the square of the input data set size.


def bubble_sort(example_list):
    n = len(example_list)
    for i in range(n):
        for j in range(0, n-i-1):
            if example_list[j] > example_list[j+1]:
                example_list[j], example_list[j+1] = example_list[j+1], example_list[j]
    return example_list

unordered_numbers = [55, 2, -23, 6, 24]

print(bubble_sort(unordered_numbers))
        

O(2^n) - Exponential Time

Exponential time complexity occurs when the growth doubles with each addition to the input data set. Algorithms with this complexity are often infeasible for large inputs.


def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(5))
        

Prompting Generative AI for Efficient Code and Algorithms

Start by specifically defining the problem you want to solve, including the necessary details. For example, instead of asking "How do I sort a list?", ask "How can I implement a bubble sorting algorithm to sort a list of integers in Python?" Use clear and unambiguous language to describe your requirements, and avoid vague terms. It is also important to provide enough context about the task. For example, mention relevant background information or related algorithms, such as "I have a list of integers that may contain duplicates. How can I efficiently sort this list using a bubble sorting algorithm?"

Example Prompt:
Write a functional program for merge sort, including functions to split the list and merge sorted sublists.

Resulting AI-generated code:


def merge_sort(lst):
    if len(lst) <= 1:
        return lst
    mid = len(lst) // 2
    left = merge_sort(lst[:mid])
    right = merge_sort(lst[mid:])
    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])
    result.extend(right[j:])
    return result

unordered_numbers = [55, 2, -23, 6, 24]
sorted_numbers = merge_sort(unordered_numbers)
print(sorted_numbers)
Back to Home