Beginner Python Project: A Checked Report and Quicksort

PYTHON Published Updated 4 mins read By Leon Wei
Beginner Python Project: A Checked Report and Quicksort cover image

Quick summary

Summarize this blog with AI

A useful beginner Python project has a clear input, a checked output, and a few explicit failure cases. This lesson builds a small sorting report and then implements quicksort to explain the algorithm. Use Python’s built-in sorting tools for ordinary application work; the recursive implementation is for learning on small inputs.

Begin with a data contract

Python strings are sequences of Unicode code points, not arrays of encoded bytes. Encoding a string produces bytes for a specified encoding. Lists are mutable sequences; assigning another name to a list does not copy it. These distinctions matter when a transformation is expected to preserve its input.

For the report, each fictional row has a task name and a nonnegative integer duration. Zero is valid. Missing durations, booleans, and other types are rejected rather than silently becoming numbers.

def duration_report(rows):
    checked = []
    for row in rows:
        if set(row) != {"task", "minutes"}:
            raise ValueError("each row needs task and minutes")
        if not isinstance(row["task"], str) or not row["task"].strip():
            raise ValueError("task must be a nonempty string")
        if type(row["minutes"]) is not int or row["minutes"] < 0:
            raise ValueError("minutes must be a nonnegative integer")
        checked.append(dict(row))
    ordered = sorted(checked, key=lambda row: (row["minutes"], row["task"]))
    return {"count": len(ordered), "total_minutes": sum(r["minutes"] for r in ordered),
            "ordered_tasks": [r["task"] for r in ordered]}

tasks = [{"task": "clean", "minutes": 12},
         {"task": "check", "minutes": 0},
         {"task": "export", "minutes": 8}]
print(duration_report(tasks))
assert tasks[0] == {"task": "clean", "minutes": 12}

The result is count 3, total_minutes 20, and ordered_tasks ['check', 'export', 'clean']. The function accepts an empty iterable and returns zero counts with an empty task list. For this exercise, input rows are dictionaries; a production ingestion layer would also validate that outer structure before calling the function.

Understand the sorting API

sorted(values) produces a new list; values.sort() modifies an existing list and returns None. Both support a key function and stable sorting. The tuple key above sorts by duration, then task name. Stability retains input order for equal complete keys. Python’s sorting guide documents these behaviors.

Do not assign the result of list.sort() and expect a sorted list. Also do not assume converting every value to text preserves numeric order: for example, the text "10" sorts before "2" lexicographically.

Implement quicksort without consuming the caller’s list

This version validates integer inputs, chooses a middle-position pivot, and partitions into less-than, equal, and greater-than lists. Keeping an equal partition avoids repeatedly recursing through duplicate pivot values.

def quick_sort(values):
    items = list(values)
    if any(type(value) is not int for value in items):
        raise TypeError("this teaching version accepts integers only")

    def sort_part(part):
        if len(part) < 2:
            return part
        pivot = part[len(part) // 2]
        lower = [value for value in part if value < pivot]
        equal = [value for value in part if value == pivot]
        higher = [value for value in part if value > pivot]
        return sort_part(lower) + equal + sort_part(higher)

    return sort_part(items)

source = [3, 1, 3, 0, -2]
print(quick_sort(source))
assert source == [3, 1, 3, 0, -2]
assert quick_sort([]) == []
assert quick_sort([2, 2, 2]) == [2, 2, 2]

The result is [-2, 0, 1, 3, 3]. The original tutorial used pop() on the caller’s list, which removed an input element. This version copies the input before partitioning and never mutates the original list.

Explain the limits without promising speed

Quicksort has O(n log n) work when partitions remain reasonably balanced, but O(n²) worst-case work. Choosing the element at the middle position does not guarantee a median value or balanced partitions. This implementation allocates new lists at each recursive step and is not in-place. Recursion depth can still become excessive for adversarial inputs; use it on small teaching fixtures.

Python does not automatically eliminate tail recursion, and adding a few clever pivot rules does not guarantee peak performance. Built-in sorting is the appropriate default for the report above. Profile an actual workload before changing an implementation for speed.

Test and extend one requirement at a time

Compare the teaching sorter with sorted() for empty, singleton, duplicate, negative, ascending, and descending integer lists. Test that invalid data raises an explicit error and that inputs remain unchanged. For the report, test a blank task name, a missing field, a negative duration, and a tied duration.

A useful next project is a CSV-backed version with a documented schema and an output written to a new file. Keep parsing, validation, transformation, and reporting separate. Do not automatically delete missing rows or collect private social-media data just to make a beginner portfolio look realistic; synthetic data is enough to demonstrate a reliable workflow.

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles