/* SortsOfSorters * By: John P. Spurgeon * Updated: 21 April 2018 * - Bug fix: Instance variable swapCount needs to be reset in sort method. * - Change: Removed the prefix "actually" from method names. * At: http://scribbledecobble.blogspot.com/2018/04/sorts-of-sorters.html * * Classic Sequences to Sort * * 1. A Multiset * Sequence: c a b d d a b d a d * See: TAOCP, vol. 3, sec. 5.1.2 * * 2. A Set * Sequence: 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.List; import java.util.LinkedList; import java.util.ArrayList; import java.util.Comparator; import java.util.Collections; /* Counters */ interface ComparisonCounter { int getComparisonCount(); } interface ExchangeCounter { int getExchangeCount(); } // meter: n. a device that measures and records the quantity, degree, or rate of something. interface SortMeter extends ComparisonCounter, ExchangeCounter { boolean countsComparisons(); boolean countsExchanges(); }
// Note: lhs => left hand side, rhs => right hand side. final class CountingComparator<E> implements Comparator<E>, ComparisonCounter { private Comparator<E> cmp; private int comparisonCount = 0; public CountingComparator(Comparator<E> cmp) { this.cmp = cmp; } public final int compare(E lhs, E rhs) { comparisonCount++; return cmp.compare(lhs, rhs); } public final int getComparisonCount() { return comparisonCount; } } /* 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). */ class Sorters { public static final String ABSTRACTED_SORTER = "Abstracted Sorter", ABSTRACTED_EXCHANGE_SORTER = "Abstracted Exchange Sorter", BAD_BUBBLE_SORTER = "Bad Bubble Sorter", BETTER_BUBBLE_SORTER = "Better Bubble Sorter", BEST_BUBBLE_SORTER = "Best Bubble Sorter", COLLECTIONS_SORTER = "Collections Sorter", COMPARISON_COUNTING_SORTER = "Comparison Counting Sorter", COMPARISON_EXCHANGE_COUNTING_SORTER = "Comparison/Exchange Counting Sorter", INSERTION_SORTER = "Insertion Sorter", MERGE_SORTER = "Merge Sorter", QUICK_SORTER = "Quick Sorter"; }
/* Abstract Sorters */ abstract class Sorter implements SortMeter { protected abstract <E> void sortMethod(List<E> sequence, Comparator<E> cmp); public <E> void sort(List<E> sequence, Comparator<E> cmp) { this.sortMethod(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); } // Sorter is abstracted (withdrawn from the contemplation of present objects), // because although it implements the SortMeter interface, it doesn't actually // count comparisons or exchanges; it has a habitually forgetful disposition. public boolean countsComparisons() { return false; } public boolean countsExchanges() { return false; } public int getComparisonCount() { return -1; } public int getExchangeCount() { return -1; } public String toString() { return Sorters.ABSTRACTED_SORTER; } }
abstract class ComparisonCountingSorter extends Sorter { private int comparisonCount = 0; public final <E> void sort(List<E> sequence, Comparator<E> cmp) { CountingComparator<E> cc = new CountingComparator<E>(cmp); this.sortMethod(sequence, cc); comparisonCount = cc.getComparisonCount(); } public final boolean countsComparisons() { return true; } public final int getComparisonCount() { return comparisonCount; } public String toString() { return Sorters.COMPARISON_COUNTING_SORTER; } } abstract class ExchangeSorter extends Sorter { 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.ABSTRACTED_EXCHANGE_SORTER; } }
abstract class ComparisonExchangeCountingSorter extends ExchangeSorter { private int comparisonCount = 0, swapCount = 0; protected final <E> void swap(List<E> sequence, int i, int j) { super.swap(sequence, i, j); swapCount++; } public final <E> void sort(List<E> sequence, Comparator<E> cmp) { swapCount = 0; CountingComparator<E> cc = new CountingComparator<E>(cmp); this.sortMethod(sequence, cc); comparisonCount = cc.getComparisonCount(); } public final boolean countsComparisons() { return true; } public final int getComparisonCount() { return comparisonCount; } public final boolean countsExchanges() { return true; } public final int getExchangeCount() { return swapCount; } public String toString() { return Sorters.COMPARISON_EXCHANGE_COUNTING_SORTER; } }
/* Bubble Sort */ class BadBubbleSorter extends ComparisonExchangeCountingSorter { // 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) {final int n = sequence.size(); 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); } lastIndex--; }} public String toString() { return Sorters.BETTER_BUBBLE_SORTER; } }
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) {final int n = sequence.size(); int lastIndex = n - 1; for (int count = 0; count < n; count++) { int exchangeCount = getExchangeCount(); 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); } if (getExchangeCount() == exchangeCount) break; lastIndex--; }} public final String toString() { return Sorters.BEST_BUBBLE_SORTER; } } /* Insertion Sort */ final class InsertionSorter extends ComparisonExchangeCountingSorter { /* Sorting an unordered sequence. */ protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp) { // Exercise 2a: Implement insertion sort.final int n = sequence.size(); for (int i = 1; i < n; i++) { for (int j = i; j > 0; j--) { int k = j - 1; E rhs = sequence.get(j); E lhs = sequence.get(k); if (cmp.compare(lhs, rhs) > 0) swap(sequence, k, j); } }} /* Inserting 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. int i = 0; for (E nextElement : sortedSeq) { if (cmp.compare(element, nextElement) < 0) i++; else if (cmp.compare(element, nextElement) >= 0) break; } sortedSeq.add(i, element); } 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); }
/* Inserting 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. int i = 0; for (E nextElement : sortedSeq) { if (cmp.compare(element, nextElement) < 0) i++; else if (cmp.compare(element, nextElement) == 0) return false; else break; } sortedSeq.add(i, element); return true; } 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 */ final class QuickSorter extends ComparisonExchangeCountingSorter { // lower is inclusive, upper is exclusive private <E> void quickSort(List<E> sequence, Comparator<E> cmp, int lower, int upper) { if (upper - lower <= 1) return; int pivot = lower; final E pivotValue = sequence.get(pivot); int j = pivot + 1; for (int i = j; i < upper; i++) { final int compareResult = cmp.compare(sequence.get(i), pivotValue); if (compareResult <= 0) { if (i > j) swap(sequence, i, j); if (compareResult < 0) swap(sequence, pivot, j); pivot = j++; } } quickSort(sequence, cmp, lower, pivot); quickSort(sequence, cmp, j, upper); } protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp) { // Exercise 3: Implement quick sort. quickSort(sequence, cmp, 0, sequence.size()); } public String toString() { return Sorters.QUICK_SORTER; } } /* Merge Sort */ final class MergeSorter extends ComparisonCountingSorter { private <E> void merge(List<E> list, Comparator<E> cmp, int lower, int middle, int upper) { final int n = upper - lower; List<E> buffer = new ArrayList<E>(n); int i, j, k; for (i = 0; i < n; i++) buffer.add(null); i = lower; j = middle; k = 0; while (i < middle && j < upper) { int compareResult = cmp.compare(list.get(i), list.get(j)); if (compareResult <= 0) buffer.set(k++, list.get(i++)); if (compareResult >= 0) buffer.set(k++, list.get(j++)); } while (i < middle) buffer.set(k++, list.get(i++)); while (j < upper) buffer.set(k++, list.get(j++)); for (i = 0; i < n; i++) list.set(lower + i, buffer.get(i)); } private <E> void mergeSort(List<E> list, Comparator<E> cmp, int lower, int upper) { final int n = upper - lower; if (n <= 1) return; int middle = lower + (n / 2); mergeSort(list, cmp, lower, middle); mergeSort(list, cmp, middle, upper); merge(list, cmp, lower, middle, upper); } protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp) { // Exercise 4: Implement merge sort. mergeSort(sequence, cmp, 0, sequence.size()); } public String toString() { return Sorters.MERGE_SORTER; } } /* A sorter that uses Java's Collections.sort method to sort. */ final class CollectionsSorter extends ComparisonCountingSorter { protected <E> void sortMethod(List<E> sequence, Comparator<E> cmp) { Collections.sort(sequence, cmp); } public String toString() { return Sorters.COLLECTIONS_SORTER; } }
/* String Utilities */ class StringTester { public static boolean isNumeric(String s) { try { double d = Double.parseDouble(s); } catch(NumberFormatException nfe) { return false; } return true; } public static boolean areNumeric(String[] strings) { if (strings == null || strings.length == 0) return false; else for (String s : strings) if (!isNumeric(s)) return false; return true; } }
class StringSorter { public static List<Double> sortAsNumbers(String[] strings, Sorter sorter) { List<Double> list = new LinkedList<Double>(); for (String s : strings) list.add(Double.parseDouble(s)); sorter.sort(list); return list; } public static List<Double> sortAsNumbersReverse(String[] strings, Sorter sorter) { List<Double> list = new ArrayList<Double>(); for (String s : strings) list.add(Double.parseDouble(s)); sorter.sortReverse(list); return list; } public static List<String> sortAsStrings(String[] strings, Sorter sorter) { List<String> list = new ArrayList<String>(); for (String s : strings) list.add(s); sorter.sort(list); return list; } public static List<String> sortAsStringsReverse(String[] strings, Sorter sorter) { List<String> list = new LinkedList<String>(); for (String s : strings) list.add(s); sorter.sortReverse(list); return list; } public static List<Double> insertNumbersIntoSet(String[] strings, InsertionSorter sorter) { List<Double> list = new LinkedList<Double>(); for (String s : strings) sorter.insertIntoSet(Double.parseDouble(s), list); return list; } public static List<Double> insertNumbersIntoMultiset(String[] strings, InsertionSorter sorter) { List<Double> list = new ArrayList<Double>(); for (String s : strings) sorter.insertIntoMultiset(Double.parseDouble(s), list); return list; } public static List<String> insertStringsIntoSet(String[] strings, InsertionSorter sorter) { List<String> list = new ArrayList<String>(); for (String s : strings) sorter.insertIntoSet(s, list); return list; } public static List<String> insertStringsIntoMultiset(String[] strings, InsertionSorter sorter) { List<String> list = new LinkedList<String>(); for (String s : strings) sorter.insertIntoMultiset(s, list); return list; } }
/* Main Program: SortsOfSorters */ public class SortsOfSorters { private static <E> void print(List<E> sequence, SortMeter counter, String label) { System.out.println(counter); System.out.print(label + ":"); for (E element : sequence) System.out.print(" " + element); System.out.println(); if (counter.countsComparisons()) System.out.println("Comparisons: " + counter.getComparisonCount()); if (counter.countsExchanges()) System.out.println("Exchanges: " + counter.getExchangeCount()); System.out.println(); } public static void main(String[] args) { Sorter[] sortsOfSorters = { new BadBubbleSorter(), new BetterBubbleSorter(), new BestBubbleSorter(), new InsertionSorter(), new MergeSorter(), new QuickSorter(), new CollectionsSorter(), }; if (StringTester.areNumeric(args)) { for (Sorter sorter : sortsOfSorters) { print(StringSorter.sortAsNumbers(args, sorter), sorter, "Sorted"); print(StringSorter.sortAsNumbersReverse(args, sorter), sorter, "Reversed"); if (sorter.toString().equals(Sorters.INSERTION_SORTER)) { InsertionSorter iSrtr = (InsertionSorter) sorter; print(StringSorter.insertNumbersIntoSet(args, iSrtr), iSrtr, "Set"); print(StringSorter.insertNumbersIntoMultiset(args, iSrtr), iSrtr, "Multiset"); } } } else { for (Sorter sorter : sortsOfSorters) { print(StringSorter.sortAsStrings(args, sorter), sorter, "Sorted"); print(StringSorter.sortAsStringsReverse(args, sorter), sorter, "Reversed"); if (sorter.toString().equals(Sorters.INSERTION_SORTER)) { InsertionSorter iSrtr = (InsertionSorter) sorter; print(StringSorter.insertStringsIntoSet(args, iSrtr), iSrtr, "Set"); print(StringSorter.insertStringsIntoMultiset(args, iSrtr), iSrtr, "Multiset"); } } } } }