Pages

Analysis of Sorting Algorithms

/* Program: AnalysisOfSortingAlgorithms
 *      By: John P. Spurgeon
 * Created: 21 April 2018
 * Updated: 23 April 2018
 *     URL: http://scribbledecobble.blogspot.com/2018/04/analysis-of-sorting-algorithms.html
 *
 * Some (multi)sets to try to sort.
 *
 * 0. Simple Sets: a b c d e f g h ...
 *
 * 1. A Multiset: c a b d d a b d a d
 * See: TAOCP, vol. 3, sec. 5.1.2.
 *
 * 2. A Set: 503 087 512 061 908 170 897 275 653 426 154 509 612 677 765 703
 * See: TAOCP, vol. 3 (2nd ed.), p. 76.
 */

// With a little help from my friends...
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.List;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;

/* abstracted adj.
 *
 *  1. Drawn off; separate, apart from 1660.
 *  2. Withdrawn from the contemplation of present objects; absent in mind 1643.
 * *3. Separated from the concrete, ideal, abstruse (replaced by ABSTRACT a. 4) - 1823.
 *
 *  The Evil one a. stood From his own evil MILT.
 *
 *  * disinterestedness.
 *
 *  Source: The Sorter Oxford English Dictionary on Historical Principles,
 *          Third Edition, Revised with Addenda (1947).
 */

interface ComparisonCounter
{
    int getComparisonCount();
}
interface ExchangeCounter
{
    int getExchangeCount();
}
interface ExecutionTimer
{
    long getElapsedTime();
}

/* Counting Comparator */

final class CountingComparator<E> implements Comparator<E>, ComparisonCounter
{
    private Comparator<E> cmp;
    private int comparisonCount = 0;

    public CountingComparator(Comparator<E> cmp)
    {
        this.cmp = cmp;
    }

    // lhs: left hand side.
    // rhs: right hand side.

    public final int compare(E lhs, E rhs)
    {
        comparisonCount++;
        return cmp.compare(lhs, rhs);
    }
    public final int getComparisonCount()
    {
        return comparisonCount;
    }
}

/* All Sorts of Sorters */

final class Sorters
{
    public static final String
        PARTIALLY_ABSTRACTED_SORTER = "Partially Abstracted Sorter",
        COUNTING_SORTER = "Counting Sorter",
        EXCHANGE_SORTER = "Exchange Sorter",
        COUNTING_EXCHANGE_SORTER = "Counting Exchange Sorter",
        BAD_BUBBLE_SORTER = "Bad Bubble Sorter",
        BETTER_BUBBLE_SORTER = "Better Bubble Sorter",
        BEST_BUBBLE_SORTER = "Best Bubble Sorter",
        INSERTION_SORTER = "Insertion Sorter",
        MERGE_SORTER = "Merge Sorter",
        QUICK_SORTER = "Quick Sorter",
        COLLECTIONS_SORTER = "Collections Sorter";
}

// Generic, Possibly-Partially-Disinterested (in performance) Sorters

interface Sorter extends ComparisonCounter, ExchangeCounter, ExecutionTimer
{
    <E> void sort(List<E> sequence, Comparator<E> cmp);
    <E extends Comparable<E>> void sort(List<E> sequence);
    <E extends Comparable<E>> void sortReverse(List<E> sequence);

    // Does it actually count?
    boolean countsComparisons();
    boolean countsExchanges();
}

// Certainly partially disinterested...

abstract class PartiallyAbstractedSorter implements Sorter
{
    private long elapsedTime;

    //******************************************//
    // ALL YOU HAVE TO DO IS SUPPLY THE METHOD! //
    //******************************************//
    protected abstract <E> void sortMethod(List<E> sequence, Comparator<E> cmp);
    
    // This Sorter is not entirely abstracted: it pays close attention to time.
    protected final <E> void timeSortMethod(List<E> sequence, Comparator<E> cmp)
    {
        long start = System.nanoTime();
        this.sortMethod(sequence, cmp);
        elapsedTime = System.nanoTime() - start;
    }
    public <E> void sort(List<E> sequence, Comparator<E> cmp)
    {
        timeSortMethod(sequence, cmp);
    }
    public final <E extends Comparable<E>> void sort(List<E> sequence)
    {
        Comparator<E> cmp = (E lhs, E rhs) -> lhs.compareTo(rhs);
        sort(sequence, cmp);
    }
    public final <E extends Comparable<E>> void sortReverse(List<E> sequence)
    {
        Comparator<E> cmp = (E lhs, E rhs) -> rhs.compareTo(lhs);
        sort(sequence, cmp);
    }
    public boolean countsComparisons()
    {
        return false;
    }
    public boolean countsExchanges()
    {
        return false;
    }
    public int getComparisonCount()
    {
        throw new UnsupportedOperationException();
    }
    public int getExchangeCount()
    {
        throw new UnsupportedOperationException();
    }
    public String toString()
    {
        return Sorters.PARTIALLY_ABSTRACTED_SORTER;
    }
    public long getElapsedTime()
    {
        return elapsedTime;
    }
}

abstract class CountingSorter extends PartiallyAbstractedSorter
{
    private int comparisonCount = 0;
    
    public final <E> void sort(List<E> sequence, Comparator<E> cmp)
    {
        CountingComparator<E> cc = new CountingComparator<E>(cmp);
        timeSortMethod(sequence, cc);
        comparisonCount = cc.getComparisonCount();
    }
    public final boolean countsComparisons()
    {
        return true;
    }
    public final int getComparisonCount()
    {
        return comparisonCount;
    }
    public String toString()
    {
        return Sorters.COUNTING_SORTER;
    }
}

abstract class ExchangeSorter extends PartiallyAbstractedSorter
{
    protected <E> void swap(List<E> sequence, int i, int j)
    {
        E tmp = sequence.get(i);
        sequence.set(i, sequence.get(j));
        sequence.set(j, tmp);
    }
    public String toString()
    {
        return Sorters.EXCHANGE_SORTER;
    }
}

abstract class CountingExchangeSorter extends ExchangeSorter
{
    private int comparisonCount = 0, exchangeCount = 0;

    protected final <E> void swap(List<E> sequence, int i, int j)
    {
        super.swap(sequence, i, j);
        exchangeCount++;
    }
    public final <E> void sort(List<E> sequence, Comparator<E> cmp)
    {
        exchangeCount = 0;
        CountingComparator<E> cc = new CountingComparator<E>(cmp);
        timeSortMethod(sequence, cc);
        comparisonCount = cc.getComparisonCount();
    }
    public final boolean countsComparisons()
    {
        return true;
    }
    public final boolean countsExchanges()
    {
        return true;
    }
    public final int getComparisonCount()
    {
        return comparisonCount;
    }
    public final int getExchangeCount()
    {
        return exchangeCount;
    }
    public String toString()
    {
        return Sorters.COUNTING_EXCHANGE_SORTER;
    }
}

// Bubble Sort

class BadBubbleSorter extends CountingExchangeSorter
{
    // Exercise 1a: Fix the problem with the bubble sort implementation below.

    protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp)
    {
        /*
         * This method sorts. But it sorts in the wrong direction!
         */

        final int n = sequence.size();
        final int lastIndex = n - 1;
        for (int count = 0; count < n; count++)
        {
            for (int i = 0; i < lastIndex; i++)
            {
                int j = i + 1;
                E lhs = sequence.get(i);
                E rhs = sequence.get(j);
                if (cmp.compare(lhs, rhs) < 0) swap(sequence, i, j);
            }
        }
    }
    public String toString()
    {
        return Sorters.BAD_BUBBLE_SORTER;
    }
}

class BetterBubbleSorter extends BadBubbleSorter
{
    // Exercise 1b: Implement bubble sort again. Make it better this time.
    // Specifically: On each pass, do one fewer comparison than before.

    protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp)
    {
    }
    public String toString()
    {
        return Sorters.BETTER_BUBBLE_SORTER;
    }
}

// Best: not Bestest, MoreBestest, or MostBestest. (But it's not final.)

class BestBubbleSorter extends BetterBubbleSorter
{
    // Exercise 1c: Implement bubble sort one more time. Make it even better.
    // Specifically: Stop as soon as you know you're done.

    protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp)
    {
    }
    public final String toString()
    {
        return Sorters.BEST_BUBBLE_SORTER;
    }
}

// Insertion Sort

final class InsertionSorter extends CountingExchangeSorter
{
    // Sort an unordered sequence...

    protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp)
    {
        // Exercise 2a: Implement insertion sort.
    }

    // Insert into a sorted multiset...

    public <E> void insertIntoMultiset(E element, List<E> sortedSeq, Comparator<E> cmp)
    {
        // Exercise 2b: Write code that inserts an element into a multiset (duplicates allowed).
        // Assume that the list passed to the method is in sorted order.
    }
    public final <E extends Comparable<E>>
    void insertIntoMultiset(E element, List<E> sortedSeq)
    {
        Comparator<E> cmp = (E lhs, E rhs) -> lhs.compareTo(rhs);
        insertIntoMultiset(element, sortedSeq, cmp);
    }
    public final <E extends Comparable<E>>
    void insertIntoMultisetReverse(E element, List<E> sortedSeq)
    {
        Comparator<E> cmp = (E lhs, E rhs) -> rhs.compareTo(lhs);
        insertIntoMultiset(element, sortedSeq, cmp);
    }

    // Insert into a sorted set...

    public <E> boolean insertIntoSet(E element, List<E> sortedSeq, Comparator<E> cmp)
    {
        // Exercise 2c: Write code that inserts an element into a set (duplicates not allowed).
        // Assume that the list passed to the method is in sorted order.

        return false; // Modify this line if necessary.
    }
    public final <E extends Comparable<E>>
    boolean insertIntoSet(E element, List<E> sortedSeq)
    {
        Comparator<E> cmp = (E lhs, E rhs) -> lhs.compareTo(rhs);
        insertIntoSet(element, sortedSeq, cmp);
        return false;
    }
    public final <E extends Comparable<E>>
    boolean insertIntoSetReverse(E element, List<E> sortedSeq)
    {
        Comparator<E> cmp = (E lhs, E rhs) -> rhs.compareTo(lhs);
        insertIntoSet(element, sortedSeq, cmp);
        return false;
    }

    public String toString()
    {
        return Sorters.INSERTION_SORTER;
    }
}

// Quick Sort  (What about Quicker? Quickest? MostQuickest?)

class QuickSorter extends CountingExchangeSorter
{
    protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp)
    {
        // Exercise 3: Implement quick sort.
    }
    public String toString()
    {
        return Sorters.QUICK_SORTER;
    }
}

// Merge Sort

final class MergeSorter extends CountingSorter
{
    protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp)
    {
        // Exercise 4: Implement merge sort.
    }
    public String toString()
    {
        return Sorters.MERGE_SORTER;
    }
}

// Using Java's Collections.sort method to sort.

final class CollectionsSorter extends CountingSorter
{
    protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp)
    {
        Collections.sort(sequence, cmp);
    }
    public String toString()
    {
        return Sorters.COLLECTIONS_SORTER;
    }
}

/*
 * n. permuter 1. One who permutes.
 */

final class Permuter
{
    public static <E> List<List<E>> getAllPermutations(Set<E> set)
    {
        ArrayList<E> list = new ArrayList<E>(set.size());
        for (E element : set) list.add(element);
        return getAllPermutations(list, 0);
    }
    public static <E> List<List<E>> getAllPermutations(List<E> list)
    {
        return getAllPermutations(list, 0);
    }
    private static <E> List<List<E>> getAllPermutations(List<E> list, int headIndex)
    {
        final int permutationLength = list.size() - headIndex;
        List<List<E>> permutations = new ArrayList<List<E>>();
        if (permutationLength > 0)
        {
            E theHeadElement = list.get(headIndex);
            List<List<E>> permutationsOfTail = getAllPermutations(list, headIndex + 1);
            for (List<E> permutationOfTail : permutationsOfTail)
            {
                final int lastIndex = permutationLength - 1;
                for (int putHeadAtIndex = 0; putHeadAtIndex <= lastIndex; putHeadAtIndex++)
                {
                    List<E> newPermutation = new ArrayList<E>(permutationLength);
                    for (int i = 0; i < lastIndex; i++)
                    {
                        E nextTailElement = permutationOfTail.get(i);
                        if (putHeadAtIndex == i)
                        {
                            newPermutation.add(theHeadElement);
                        }
                        newPermutation.add(nextTailElement);
                    }
                    if (putHeadAtIndex == lastIndex)
                    {
                        newPermutation.add(theHeadElement);
                    }
                    permutations.add(newPermutation);
                }
                
            }
        }
        else
        {
            // There is one way to arrange no elements.
            permutations.add(new ArrayList<E>());
        }
        return permutations;
    }
}

/* A Statistics Utility */

final class Statistics
{
    public static Double getMin(List<Double> values)
    {
        final int n = values.size();
        if (n == 0) return null;
        Double smallest = values.get(0);
        for (int i = 1; i < n; i++)
        {
            Double value = values.get(i);
            if (value < smallest)
                smallest = value;
        }
        return smallest;
    }
    public static Double getMax(List<Double> values)
    {
        final int n = values.size();
        if (n == 0) return null;
        Double largest = values.get(0);
        for (int i = 1; i < n; i++)
        {
            Double value = values.get(i);
            if (value > largest)
                largest = value;
        }
        return largest;
    }
    public static Double getSum(List<Double> values)
    {
        Double sum = 0.0;
        for (Double value : values) sum += value;
        return sum;
    }
    public static Double getAvg(List<Double> values)
    {
        return getSum(values) / values.size();
    }
    public static Double getStdDev(List<Double> values, Double avg)
    {
        final List<Double> diffsSquared = new ArrayList<Double>(values.size());
        for (Double value : values )
        {
            Double diff = avg - value;
            Double diffSquared = diff * diff;
            diffsSquared.add(diffSquared);
        }
        return Math.sqrt(getAvg(diffsSquared));
    }
    public static Double getStdDev(List<Double> values)
    {
        return getStdDev(values, getAvg(values));
    }
}

/* Count, Min, Max, Avg, SD (standard deviation)
 *
 * Count is not one of the Gang of Four statistics, but it's good to know.
 *
 * SEE: Stanford Lecture - Don Knuth: The Analysis of Algorithms
 *      Donald Knuth recreates his very first lecture taught at Stanford University.
 *      https://www.youtube.com/watch?v=jmcSzzN1gkc (JUMP TO 25:45)
 */

final class DataSet
{
    private final String description;
    private final List<Double> values;
    private Double min = null, max = null, avg = null, stdDev = null, sum = 0.0;
    private boolean statsAreCurrent = true;
    
    public DataSet(String description, int initialCapacity)
    {
        this.description = description;
        values = new ArrayList<Double>(initialCapacity);
    }
    public DataSet(String description)
    {
        this.description = description;
        values = new LinkedList<Double>();
    }
    public final void add(Integer value)
    {
        add(value.doubleValue());
    }
    public final void add(Double value)
    {
        statsAreCurrent = false;
        values.add(value);
        sum += value;
        if (values.size() == 1)
        {
            min = value;
            max = value;
        }
        else
        {
            if (value < min) min = value;
            if (value > max) max = value;
        }
    }
    private void analyzeThis()
    {
        if (!statsAreCurrent)
        {
            avg = sum / values.size();
            stdDev = Statistics.getStdDev(values, avg);
            statsAreCurrent = true;
        }
    }
    public final String toString()
    {
        List<DataSet> listOfThis = new LinkedList<DataSet>();
        listOfThis.add(this);
        return DataSet.toString(listOfThis);
    }
    private static String toFixed(Double d)
    {
        return String.format("%.1f", d);
    }
    private static String toFixed(String string)
    {
        final int length = 6;
        return String.format("%1$" + length + "s", string);
    }
    public static String toString(List<DataSet> dataSets)
    {
        String description = "";
        final String SEP = " ";
        for (DataSet ds : dataSets)
        {
            description = ds.description;
            ds.analyzeThis();
        }
        StringBuilder sb = new StringBuilder();
        sb.append("Dataset:" + SEP + description + "\n");
        sb.append("\n  Count:");
        for (DataSet ds : dataSets) sb.append(SEP + toFixed(((Integer)ds.values.size()).toString()));
        sb.append("\n    Min:");
        for (DataSet ds : dataSets) sb.append(SEP + toFixed(toFixed(ds.min)));
        sb.append("\n    Max:");
        for (DataSet ds : dataSets) sb.append(SEP + toFixed(toFixed(ds.max)));
        sb.append("\n    Avg:");
        for (DataSet ds : dataSets) sb.append(SEP + toFixed(toFixed(ds.avg)));
        sb.append("\n     SD:");
        for (DataSet ds : dataSets) sb.append(SEP + toFixed(toFixed(ds.stdDev)));
        sb.append("\n");
        return sb.toString();
    }
}

/* Mathematicians */

interface MathProfessor
{
    <E extends Comparable<E>> void analyze(List<E> sequence);
    String getAnalysis();
    void nextDataSet();
}

class ExecutionTimeAnalyst implements MathProfessor
{
    private final String HR = "********************************\n";
    private DataSet executionTimes;
    private final List<DataSet> executionTimesDataSets;
    private final Sorter sorter;

    public ExecutionTimeAnalyst(Sorter sorter)
    {
        this.sorter = sorter;
        executionTimesDataSets = new LinkedList<DataSet>();
    }
    public <E extends Comparable<E>> void analyze(List<E> sequence)
    {
        sorter.sort(sequence);
        Long time = sorter.getElapsedTime();
        executionTimes.add(time.doubleValue() / 1000);
    }
    public void nextDataSet()
    {
        executionTimes = new DataSet("Execution Time (microseconds)");
        executionTimesDataSets.add(executionTimes);
    }
    protected final int getComparisonCount()
    {
        return sorter.getComparisonCount();
    }
    protected final int getExchangeCount()
    {
        return sorter.getExchangeCount();
    }
    public String getAnalysis()
    {
        return HR + sorter.toString() + "\n\n" +
               DataSet.toString(executionTimesDataSets) + "\n";
    }
}

class ComparisonCountAnalyst extends ExecutionTimeAnalyst
{
    private DataSet comparisons;
    private final List<DataSet> comparisonsDataSets;

    public ComparisonCountAnalyst(Sorter sorter)
    {
        super(sorter);
        comparisonsDataSets = new LinkedList<DataSet>();
    }
    public <E extends Comparable<E>> void analyze(List<E> sequence)
    {
        super.analyze(sequence);
        comparisons.add(getComparisonCount());
    }
    public void nextDataSet()
    {
        super.nextDataSet();
        comparisons = new DataSet("Number of Comparisons");
        comparisonsDataSets.add(comparisons);
    }
    public String getAnalysis()
    {
        return super.getAnalysis() +
               DataSet.toString(comparisonsDataSets) + "\n";
    }
}

final class ExchangeCountAnalyst extends ComparisonCountAnalyst
{
    private DataSet exchanges;
    private final List<DataSet> exchangesDataSets;

    public ExchangeCountAnalyst(Sorter sorter)
    {
        super(sorter);
        exchangesDataSets = new LinkedList<DataSet>();
    }
    public <E extends Comparable<E>> void analyze(List<E> sequence)
    {
        super.analyze(sequence);
        exchanges.add(getExchangeCount());
    }
    public void nextDataSet()
    {
        super.nextDataSet();
        exchanges = new DataSet("Number of Exchanges");
        exchangesDataSets.add(exchanges);
    }
    public final String getAnalysis()
    {
        return super.getAnalysis() +
               DataSet.toString(exchangesDataSets) + "\n";
    }
}

/* Educational Entities */

final class StanfordUniversity
{
    public static MathProfessor getProfessor(Sorter sorter)
    {
        if (sorter.countsComparisons())
        {
            if (sorter.countsExchanges())
                return new ExchangeCountAnalyst(sorter);
            else
                return new ComparisonCountAnalyst(sorter);
        }
        else
        {
            return new ExecutionTimeAnalyst(sorter);
        }
    }
}

final class ComputerScienceStudent
{
    private Sorter sorter;
    private final MathProfessor donKnuth;

    public ComputerScienceStudent(Sorter sorter)
    {
        this.sorter = sorter;
        donKnuth = StanfordUniversity.getProfessor(sorter);
    }
    public final void analyze(List<List<String>> permutations)
    {
        donKnuth.nextDataSet();
        for (List<String> permutation : permutations)
        {
            // Analyzing a permutation PERMUTES the permutation, and we
            // will need to use permutations again, so make a copy and
            // analyze the copy instead of the original permutation.

            List<String> copy = new ArrayList<String>(permutation.size());
            for (String element : permutation)
            {
                copy.add(element);
            }
            donKnuth.analyze(copy);
        }
    }
    public final String getResults()
    {
        return donKnuth.getAnalysis();
    }
}

final class TeachingAssistant
{
    private final List<List<List<String>>> setsOfAllPermutations;
    private final SortedSet<String> sortedSet = new TreeSet<String>();

    public TeachingAssistant(String[] args)
    {
        final int n = args.length;
        setsOfAllPermutations = new ArrayList<List<List<String>>>(n);
        List<List<String>> permutations;

        for (String string : args)
        {
            sortedSet.add(string);
            permutations = Permuter.getAllPermutations(sortedSet);
            setsOfAllPermutations.add(permutations);
        }
    }
    public String getSuperset()
    {
        String superset = "SUPERSET:";
        for (String element : sortedSet)
        {
            superset += " " + element;
        }
        return superset;
    }
    public final void createExercisesFor(List<ComputerScienceStudent> students)
    {
        for (List<List<String>> setOfAllPermutations : setsOfAllPermutations)
        {
            for (ComputerScienceStudent student : students)
            {
                student.analyze(setOfAllPermutations);
            }
        }
    }
}

// The Main Program: It's about time...

final public class AnalaysisOfSortingAlgorithms
{
    // What kind of class doesn't have students?
    private static List<ComputerScienceStudent> students;

    // All sorts of sorters for students to study.
    private static Sorter[] allSortsOfSorters =
    {
        new CollectionsSorter(),
        new BadBubbleSorter(), new BetterBubbleSorter(), new BestBubbleSorter(),
        new InsertionSorter(), new MergeSorter(), new QuickSorter(),
    };

    public static void main(String[] args)
    {        
        // Recruit the students.

        students = new LinkedList<ComputerScienceStudent>();

        for (Sorter sorter : allSortsOfSorters)
        {
            // Assign a sorter to each student.
            students.add(new ComputerScienceStudent(sorter));
        }

        // Hire a TA and put her to work creating exercises for the students.

        TeachingAssistant annaKarlin = new TeachingAssistant(args);
        annaKarlin.createExercisesFor(students);

        // Print the results.

        System.out.println(annaKarlin.getSuperset() + "\n");
        for (ComputerScienceStudent student : students)
        {
            System.out.println(student.getResults());
        }

        // finis ... (Retire early and write books.)
    }
}