uccser/cs-field-guide

View on GitHub
csfieldguide/static/files/selection-quicksort-python3.py

Summary

Maintainability
F
6 days
Test Coverage
"""
Measures the relative speeds of quicksort and selection sort.

The current output is human readable, but for large-scale experiments you will
want to modify it so that the output can be graphed
(e.g. generate CSV to put in a spreadsheet, or introduce a plotting library).
You should also consider generating special cases of lists,
such as already-sorted lists and reverse-sorted lists.
Note that Quicksort can perform very poorly in some cases,
and if this happens you may need to limit the list size to around 500
if it won't run, but in normal circumstances you can try it with tens of
thousands of items to sort.

The following constants should be modified to run the experiments
on a wider range of data:
NUMBER_OF_KEYS
NUMBER_OF_REPEATED_EXPERIMENTS

This is for Python version 3.
Tim Bell, August 2012
Modified by Courtney Bracefield, June 2020
"""

from random import shuffle
import time

# Each sorting method will be evaluated for lists of the following sizes
NUMBER_OF_KEYS = [10, 1000]
# The experiments will be repeated this many times
NUMBER_OF_REPEATED_EXPERIMENTS = 10


def selection_sort_count(sample_list):
    """
    Perform min selection sort on values in sample_list.

    Returns the number of comparisons required.
    """
    key_comparisons_made = 0
    for i in range(0, len(sample_list) - 1):
        min_position_so_far = i
        for j in range(i + 1, len(sample_list)):
            key_comparisons_made += 1
            if sample_list[j] < sample_list[min_position_so_far]:
                min_position_so_far = j
        temp = sample_list[min_position_so_far]
        sample_list[min_position_so_far] = sample_list[i]
        sample_list[i] = temp
    return key_comparisons_made


def quick_sort_count(sample_list):
    """
    Perform quicksort on values in sample_list.

    Returns the number of comparisons required.
    Based on code from "Problem Solving with Algorithms and Data Structures"
    By Brad Miller and David Ranum, runestoneinteractive.org
    """
    return quicksort_partial_list(sample_list, 0, len(sample_list) - 1)


def quicksort_partial_list(sample_list, first, last):
    """Recursively quicksort sample_list between first and last inclusive."""
    key_comparisons_made = 0
    if first < last:
        partition_point = partition(sample_list, first, last)
        # partition compares one less than items in list
        key_comparisons_made += (last - first)
        left_key_comps = quicksort_partial_list(
            sample_list,
            first,
            partition_point - 1
        )
        right_key_comps = quicksort_partial_list(
            sample_list,
            partition_point + 1,
            last
        )
        key_comparisons_made += left_key_comps + right_key_comps
        return key_comparisons_made
    else:
        return 0  # no comparisons as sublist is empty


def partition(alist, first, last):
    """
    Partition alist into smaller and larger values.

    Returns pivot position.
    """
    pivot_value = alist[first]
    left_to_right, right_to_left = first + 1, last
    done = False
    while not done:
        while (left_to_right <= right_to_left and
                alist[left_to_right] <= pivot_value):
            left_to_right = left_to_right + 1
        while (alist[right_to_left] >= pivot_value and
                left_to_right <= right_to_left):
            right_to_left = right_to_left - 1
        if right_to_left < left_to_right:
            done = True
        else:
            temp = alist[left_to_right]
            alist[left_to_right] = alist[right_to_left]
            alist[right_to_left] = temp
    alist[first], alist[right_to_left] = alist[right_to_left], alist[first]
    return right_to_left


def test_selection_sort(n, show_list):
    """Measure the performance of selection sort on a random list."""
    sample_list = list(range(n))  # create a sorted list of n keys
    shuffle(sample_list)  # shuffle them
    print("\nSelection sorting", n, "keys")
    if show_list:
        print("Test list is", sample_list)
    start = time.time()
    key_comparisons_made = selection_sort_count(sample_list)
    end = time.time()
    if show_list:
        print("Selection sort output is", sample_list)

    result = "For selection sort of {} items, {} comparisons of keys were used"
    time_taken = "Time taken: {:.4f} milliseconds elapsed"
    print(result.format(n, key_comparisons_made))
    print(time_taken.format((end - start) * 1000))


def test_quick_sort(n, show_list):
    """Create a random list and measure the performance of quicksort on it."""
    sample_list = list(range(n))  # create a sorted list of n keys
    # commenting out the line below overflows the recursion limit
    # when the list is large
    shuffle(sample_list)  # shuffle them
    print("\nQuick sorting", n, "keys")
    if show_list:
        print("Test list is", sample_list)
    start = time.time()
    key_comparisons_made = quick_sort_count(sample_list)
    end = time.time()
    if show_list:
        print("Quicksort output is", sample_list)

    result = "For quicksort of {} items, {} comparisons of keys were used"
    time_taken = "Time taken: {:.4f} milliseconds elapsed"
    print(result.format(n, key_comparisons_made))
    print(time_taken.format((end - start) * 1000))


# This is an example of how to run an experiment.
# For thorough results, experiments should be run for a larger range of values
# and experiments should be repeated multiple times
for number_of_keys in NUMBER_OF_KEYS:
    for repeat_of_experiment in range(NUMBER_OF_REPEATED_EXPERIMENTS):
        test_selection_sort(number_of_keys, False)
    for repeat_of_experiment in range(NUMBER_OF_REPEATED_EXPERIMENTS):
        test_quick_sort(number_of_keys, False)