Skip to content

arthurafarias/snippets-python-algorithm-quicksort

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

If Python Was C++

Every programmer eventually encounters the same problem: writing elegant and efficient code.

Recently, while implementing a generic sorting algorithm in Python, I found myself wishing Python had borrowed one more idea from modern C++.

Generic Algorithms in Python

Suppose we want to write a generic sorting algorithm that accepts a comparator, allowing it to sort any type.

def quicksort(iterable, comparator):
    ...

During the partitioning step, we naturally want to classify every element relative to the pivot:

left = [x for x in iterable if comparator(x, pivot) < 0]
right = [x for x in iterable if comparator(x, pivot) > 0]
middle = [x for x in iterable if comparator(x, pivot) == 0]

The code is concise, expressive, and immediately understandable.

Unfortunately, it is also inefficient.

The iterable is traversed three times, and worse, the comparator is evaluated up to three times for every element. If the comparator performs expensive work—string collation, locale-aware comparison, geometric predicates, database-backed objects, or custom business logic—those extra evaluations become noticeable.

The "Pythonic" solution is to abandon the elegant comprehensions and write the classic imperative loop:

left, middle, right = [], [], []

for x in iterable:
    result = comparator(x, pivot)

    if result < 0:
        left.append(x)
    elif result > 0:
        right.append(x)
    else:
        middle.append(x)

Now the iterable is visited exactly once, and each comparison is computed once.

It is faster.

It is also considerably less declarative.

How C++ Solves This

Modern C++ approaches this problem differently.

Algorithms are separated from containers through iterators, projections, and comparators. A sorting algorithm doesn't care whether it is sorting vectors, lists, spans, or custom containers—it only needs an ordering relation.

More importantly, C++20 introduced Ranges, allowing algorithms to be expressed as lazy pipelines:

auto filtered =
    values
    | std::views::filter(...)
    | std::views::transform(...);

Nothing executes immediately.

Each stage simply describes a transformation.

The actual iteration happens only when the pipeline is consumed.

This design has an important consequence: multiple operations are fused into a single traversal, eliminating temporary containers and unnecessary work.

The Missing Piece

Now imagine extending that idea one step further.

Instead of creating three independent comprehensions, imagine expressing three consumers over the same lazy sequence:

iterable
    |> partition(comparator, pivot)

Conceptually, one traversal would feed three outputs:

  • left
  • middle
  • right

Each element would be evaluated exactly once.

Each comparison would happen exactly once.

Each destination would receive the element immediately.

No repeated traversals.

No repeated predicate evaluations.

This is precisely the kind of optimization that lazy pipelines make possible.

Why Python Cannot Do This

Python generators are lazy, but they are independent iterators.

Writing:

(x for x in iterable if ...)

does not build part of a larger execution graph.

It creates a completely separate iterator.

Creating three generator expressions means creating three consumers.

Each consumer walks the iterable independently.

Python has no optimizer capable of recognizing that all three consumers originate from the same source and could be fused into one pass.

Even itertools.tee() does not solve this problem. It duplicates iteration state by buffering values; it does not merge computations or share predicate evaluations.

In other words, Python supports lazy pipelines, but not lazy graphs.

The Cost of Readability

This highlights one of the philosophical differences between Python and modern C++.

Python tends to favor explicit execution. When performance matters, the language expects developers to write the loop themselves.

Modern C++ increasingly allows developers to describe what they want while enabling the library and compiler to determine how to execute it efficiently.

The irony is that the most elegant Python solution is also the least efficient one, while the fastest solution is the least declarative.

If Python ever gained a true composable lazy pipeline capable of supporting multiple consumers from a single traversal, many performance-sensitive algorithms could remain both expressive and efficient.

Until then, sometimes the fastest Python code is still just a well-written for loop.


Have you encountered similar situations where writing the "Pythonic" version meant sacrificing performance? I'd be interested to hear where you've seen this trade-off in real projects.

About

A simple snippet in quicksort in Python

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors