Randomized Partition
Introduction
This is part of a series about the Partition algorithm that starts with this post.
Imports and Setup
# python
# For python 3.9 and newer you want to import
# the typing classes from collections.abc
# But Ubuntu 21.10 has pypy 3.7 which needs the version from typing
# for what we want here
from functools import partial
from typing import Callable, MutableSequence, TypeVar
import random
# pypi
from expects import be_true, contain_exactly, equal, expect
import altair
import pandas
# software under test
from bowling.sort.quick import (clrs_partition,
levitins_partition,
randomized_partition)
# monkey
from graeae.visualization.altair_helpers import output_path, save_chart
SLUG = "randomized-partition"
OUTPUT_PATH = output_path(SLUG)
save_it = partial(save_chart, output_path=OUTPUT_PATH)
Orderable = TypeVar("Orderable")
The Algorithm
The randomized partition just swaps the pivot element for a random element, but since the CLRS version uses the last element as the pivot and the Levitin version uses the first element (and Hoare used the middle) I'm going to pass in the pivot and the function explicitly so I don't have to implement different versions of the randomized partition.
For the pseudocode I'll just assume that the partition function is in the global space instead of passing it in.
\begin{algorithm} \caption{Randomized Partition} \begin{algorithmic} \INPUT An array and left, right, and pivot locations \OUTPUT The sub-array from left to right is partitioned and the partition location is returned \PROCEDURE{RandomizedPartition}{A, left, right, pivot} \STATE i $\gets$ \textsc{Random}(left, right) \STATE \textsc{Swap}(A[i], A[pivot]) \RETURN \textsc{Partition}(A, left, right) \ENDPROCEDURE \end{algorithmic} \end{algorithm}
The downside to this approach is that to use it you have to remember where the pivot is. Maybe I'll make separate ones for the different algorithms later…
The Implementation
def partition_randomized(collection: MutableSequence[Orderable],
left: int, right: int,
pivot: int,
partition: Partition) -> int:
"""Partitions the collection around a random element in the list
Args:
collection: the list to partition
left: index of the first element in the sub-list to partition
right: index of the last element in the sub-list to partition
pivot: location of the pivot element
partition: the partition function to use
Returns:
the index of the pivot element
"""
random_index = random.randrange(left, right + 1)
collection[pivot], collection[random_index] = (collection[random_index],
collection[pivot])
return partition(collection, left, right)
Some Checks
randomized_clrs = partial(randomized_partition, pivot=-1,
partition=clrs_partition)
randomized_levitin = partial(randomized_partition, pivot=0,
partition=levitins_partition)
def test_partitioner(partitioner: Callable[[MutableSequence[Orderable], int, int], int],
input_size: int=10**5) -> None:
elements = list(range(input_size))
random.shuffle(elements)
pivot = partitioner(elements, left=0, right=input_size-1)
pivot_element = elements[pivot]
expect(all(element < pivot_element
for element in elements[:pivot])).to(be_true)
expect(all(element > pivot_element
for element in elements[pivot + 1:])).to(be_true)
expect(len(elements)).to(equal(input_size))
return
test_partitioner(randomized_clrs)
test_partitioner(randomized_levitin)