├── README.md ├── .gitignore ├── LICENSE ├── Insertion.java ├── Merge.java ├── output.txt ├── Quick.java ├── answers.txt └── Bench.java /README.md: -------------------------------------------------------------------------------- 1 | # Sorting, Complexity 2 | 3 | A complexity exercise in the *Datastructures and Algorithms* course through Gothenburg University. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This is a gitignore file. 2 | # It tells git which files and directories it should avoid tracking. 3 | # For an explanation of the format, see the following link: 4 | # https://git-scm.com/docs/gitignore 5 | # At the moment, it has been configured so that IDE-specific files are not considered for tracking. 6 | # If all members of your group use the same IDE and you wish to share some project configuration files, you must edit this file as appropriate before git allows you to track them. 7 | 8 | ### General ### 9 | 10 | # Backup files 11 | *~ 12 | \#*# 13 | *.bak 14 | 15 | # Vim swap files 16 | *.swo 17 | *.swp 18 | 19 | ### Java ### 20 | 21 | # Compiled class files 22 | *.class 23 | 24 | # Log files 25 | *.log 26 | 27 | # Virtual machine crash logs 28 | hs_err_pid* 29 | 30 | ### Eclipse ### 31 | 32 | /.classpath 33 | /.project 34 | /.settings/ 35 | 36 | ### IntelliJ ### 37 | 38 | # Configuration files 39 | /.idea/ 40 | /*.ipr 41 | /*.iml 42 | 43 | # Output directory 44 | /out/ 45 | 46 | ### NetBeans ### 47 | 48 | # Configuration files 49 | /nbproject/ 50 | 51 | # Build and distribution files 52 | /build/ 53 | /nbbuild/ 54 | /dist/ 55 | /nbdist/ 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The files: 2 | - Merge.java 3 | - Quick.java 4 | - Insertion.java 5 | are modified versions of files of the same name from algs4.jar by Robert Sedgewick and Kevin Wayne. 6 | They are licensed under the GPL3. 7 | The following notice applies to them. 8 | 9 | /****************************************************************************** 10 | * Copyright 2002-2018, Robert Sedgewick and Kevin Wayne. 11 | * 12 | * This file is part of algs4.jar, which accompanies the textbook 13 | * 14 | * Algorithms, 4th edition by Robert Sedgewick and Kevin Wayne, 15 | * Addison-Wesley Professional, 2011, ISBN 0-321-57351-X. 16 | * http://algs4.cs.princeton.edu 17 | * 18 | * 19 | * algs4.jar is free software: you can redistribute it and/or modify 20 | * it under the terms of the GNU General Public License as published by 21 | * the Free Software Foundation, either version 3 of the License, or 22 | * (at your option) any later version. 23 | * 24 | * algs4.jar is distributed in the hope that it will be useful, 25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 | * GNU General Public License for more details. 28 | * 29 | * You should have received a copy of the GNU General Public License 30 | * along with algs4.jar. If not, see http://www.gnu.org/licenses. 31 | ******************************************************************************/ 32 | -------------------------------------------------------------------------------- /Insertion.java: -------------------------------------------------------------------------------- 1 | /** 2 | * For additional documentation on this implementation of insertion sort, 3 | * see Section 2.1 4 | * of Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne. 5 | */ 6 | public class Insertion { 7 | /** 8 | * Rearranges the array in ascending order, using the natural order. 9 | * @param a the array to be sorted 10 | */ 11 | public static final void sort(int[] a) { 12 | sort(a, 0, a.length-1); 13 | } 14 | 15 | /** 16 | * Rearranges the subarray a[lo..hi] in ascending order, using the natural order. 17 | * @param a the array to be sorted 18 | * @param lo left endpoint (inclusive) 19 | * @param hi right endpoint (inclusive) 20 | */ 21 | public static final void sort(int[] a, int lo, int hi) { 22 | for (int i = lo; i <= hi; i++) { 23 | // Insert a[i] into a[lo..i-1]. 24 | int value = a[i]; 25 | int j = i; 26 | while (j > lo && a[j-1] > value) { 27 | a[j] = a[j-1]; 28 | j--; 29 | } 30 | a[j] = value; 31 | } 32 | 33 | assert(isSorted(a, lo, hi)); 34 | } 35 | 36 | /*************************************************************************** 37 | * Check if array is sorted - useful for debugging. 38 | ***************************************************************************/ 39 | 40 | public static boolean isSorted(int[] a) { 41 | return isSorted(a, 0, a.length - 1); 42 | } 43 | 44 | public static boolean isSorted(int[] a, int lo, int hi) { 45 | for (int i = lo; i < hi; i++) 46 | if (!(a[i + 1] >= a[i])) 47 | return false; 48 | return true; 49 | } 50 | 51 | // This class should not be instantiated. 52 | private Insertion() { } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Merge.java: -------------------------------------------------------------------------------- 1 | /** 2 | * For additional documentation on this implementation of merge sort, 3 | * see Section 2.2 4 | * of Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne. 5 | */ 6 | public class Merge { 7 | /** 8 | * Rearranges the array in ascending order, using the natural order. 9 | * @param a the array to be sorted 10 | */ 11 | public static final void sort(int[] a) { 12 | int[] aux = new int[a.length]; 13 | sort(a, aux, 0, a.length-1); 14 | assert Insertion.isSorted(a); 15 | } 16 | 17 | // mergesort a[lo..hi] using auxiliary array aux[lo..hi] 18 | private static final void sort(int[] a, int[] aux, int lo, int hi) { 19 | if (hi <= lo) return; 20 | 21 | int mid = lo + (hi - lo) / 2; 22 | sort(a, aux, lo, mid); 23 | sort(a, aux, mid + 1, hi); 24 | 25 | merge(a, aux, lo, mid, hi); 26 | } 27 | 28 | // stably merge a[lo..mid] with a[mid+1..hi] using aux[lo..hi] 29 | private static final void merge(int[] a, int[] aux, int lo, int mid, int hi) { 30 | // precondition: a[lo..mid] and a[mid+1..hi] are sorted subarrays 31 | assert Insertion.isSorted(a, lo, mid); 32 | assert Insertion.isSorted(a, mid+1, hi); 33 | 34 | // copy to aux[] 35 | for (int k = lo; k <= hi; k++) { 36 | aux[k] = a[k]; 37 | } 38 | 39 | // merge back to a[] 40 | int i = lo, j = mid+1; 41 | for (int k = lo; k <= hi; k++) { 42 | if (i > mid) a[k] = aux[j++]; 43 | else if (j > hi) a[k] = aux[i++]; 44 | else if (aux[j] < aux[i]) a[k] = aux[j++]; 45 | else a[k] = aux[i++]; 46 | } 47 | 48 | // postcondition: a[lo..hi] is sorted 49 | assert Insertion.isSorted(a, lo, hi); 50 | } 51 | 52 | // This class should not be instantiated. 53 | private Merge() { } 54 | } 55 | -------------------------------------------------------------------------------- /output.txt: -------------------------------------------------------------------------------- 1 | ======================================================= 2 | Quick.java: quicksort (times in ms) 3 | ======================================================= 4 | Size | Random | 95% sorted | Sorted 5 | 10 | 0.000042 | 0.000039 | 0.000049 6 | 30 | 0.000148 | 0.000331 | 0.000334 7 | 100 | 0.000831 | 0.002335 | 0.003264 8 | 300 | 0.003295 | 0.006307 | 0.025445 9 | 1000 | 0.012835 | 0.022377 | 0.271050 10 | 3000 | 0.122960 | 0.204902 | 2.351488 11 | 10000 | 0.491067 | 0.646217 | 25.977400 12 | 30000 | 1.651520 | 3.082567 | 235.097200 13 | 100000 | 6.112050 | 9.638250 | 2620.616400 14 | 15 | ======================================================= 16 | Quick.java: quicksort with all improvements (times in ms) 17 | ======================================================= 18 | Size | Random | 95% sorted | Sorted 19 | 10 | 0.000135 | 0.000110 | 0.000100 20 | 30 | 0.000548 | 0.000443 | 0.000449 21 | 100 | 0.000913 | 0.000774 | 0.000710 22 | 300 | 0.003508 | 0.002809 | 0.002550 23 | 1000 | 0.016046 | 0.012107 | 0.010574 24 | 3000 | 0.125962 | 0.056746 | 0.034815 25 | 10000 | 0.511777 | 0.264746 | 0.156820 26 | 30000 | 1.733560 | 0.868810 | 0.527483 27 | 100000 | 6.399850 | 3.133500 | 1.848950 28 | 29 | ======================================================= 30 | Insertion.java: insertion sort (times in ms) 31 | ======================================================= 32 | Size | Random | 95% sorted | Sorted 33 | 10 | 0.000021 | 0.000017 | 0.000013 34 | 30 | 0.000127 | 0.000035 | 0.000035 35 | 100 | 0.001228 | 0.000195 | 0.000131 36 | 300 | 0.008649 | 0.001008 | 0.000413 37 | 1000 | 0.086346 | 0.007035 | 0.001079 38 | 3000 | 0.661363 | 0.062296 | 0.003096 39 | 10000 | 8.441850 | 0.593423 | 0.009779 40 | 30000 | 80.557200 | 5.501050 | 0.030049 41 | 100000 | 941.525000 | 56.874500 | 0.096487 42 | 43 | ======================================================= 44 | Merge.java: merge sort (times in ms) 45 | ======================================================= 46 | Size | Random | 95% sorted | Sorted 47 | 10 | 0.000141 | 0.000143 | 0.000157 48 | 30 | 0.000501 | 0.000456 | 0.000464 49 | 100 | 0.002093 | 0.001867 | 0.001863 50 | 300 | 0.007178 | 0.006555 | 0.005943 51 | 1000 | 0.041904 | 0.024423 | 0.021266 52 | 3000 | 0.187100 | 0.082477 | 0.069207 53 | 10000 | 0.717640 | 0.303288 | 0.245048 54 | 30000 | 2.372588 | 1.011680 | 0.841305 55 | 100000 | 8.746450 | 3.723775 | 2.995300 -------------------------------------------------------------------------------- /Quick.java: -------------------------------------------------------------------------------- 1 | import java.util.Random; 2 | 3 | /** 4 | * For additional documentation on this implementation of quicksort, 5 | * see Section 2.3 6 | * of Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne. 7 | */ 8 | public class Quick { 9 | 10 | /** 11 | * Instantiate the quicksort algorithm with the given options. 12 | * @param shuffleFirst If true, shuffle the array before quicksorting it. 13 | * @param useMedianOfThree If true, use the median-of-three technique for pivot selection. 14 | * In the recursive call for quicksorting a[lo..hi], 15 | * the first element a[lo] is used as pivot by default. 16 | * Instead, this uses the median of the first, middle, and last element of a[lo..hi]. 17 | * @param insertionSortCutoff Switch to insertion sort in the recursive call for quicksorting a[lo..hi] 18 | * once the size of a[lo..hi] is less than the given cutoff value. 19 | */ 20 | public Quick(boolean shuffleFirst, boolean useMedianOfThree, int insertionSortCutoff) { 21 | this.shuffleFirst = shuffleFirst; 22 | this.useMedianOfThree = useMedianOfThree; 23 | this.insertionSortCutoff = insertionSortCutoff; 24 | } 25 | 26 | private boolean shuffleFirst; 27 | private boolean useMedianOfThree; 28 | private int insertionSortCutoff; 29 | 30 | /** 31 | * Rearranges the array in ascending order, using the natural order. 32 | * @param a the array to be sorted 33 | */ 34 | public void sort(int[] a) { 35 | if (shuffleFirst) { 36 | // TODO: Randomise the array before sorting. 37 | shuffle(a); //added 38 | // Hint: There is a static method shuffle. 39 | //throw new UnsupportedOperationException("to be implemented"); 40 | } 41 | 42 | sort(a, 0, a.length - 1); 43 | assert Insertion.isSorted(a); 44 | } 45 | 46 | // Quicksort the subarray a[lo..hi]. 47 | // This is the recursive workhorse of the algorithm. 48 | private void sort(int[] a, int lo, int hi) { 49 | if (hi <= lo) return; 50 | 51 | // TODO: check if the size of a[lo..hi] is below the cutoff value 52 | if ((hi - lo) < insertionSortCutoff) { 53 | // TODO: Switch to insertion sort. 54 | Insertion.sort(a, lo, hi); 55 | return; 56 | // throw new UnsupportedOperationException("to be implemented"); 57 | } 58 | 59 | int j = partition(a, lo, hi); 60 | sort(a, lo, j-1); 61 | sort(a, j+1, hi); 62 | assert Insertion.isSorted(a, lo, hi); 63 | } 64 | 65 | // Partition the subarray a[lo..hi] so that 66 | // a[lo..j-1] <= a[j] <= a[j+1..hi] 67 | // and return the index j. 68 | private int partition(int[] a, int lo, int hi) { 69 | if (useMedianOfThree) { 70 | // TODO: Find the median of the first, last and middle 71 | // elements of a[lo..hi], and swap it with a[lo]. 72 | // Hint: Use the static methods medianOfThree and exchange. 73 | int median = medianOfThree(a, lo, hi, (lo+hi)/2); 74 | exchange(a, lo, median); 75 | // throw new UnsupportedOperationException("to be implemented"); 76 | } 77 | 78 | int i = lo; 79 | int j = hi + 1; 80 | 81 | // a[lo] is used as pivot. 82 | int pivot = a[lo]; 83 | 84 | // a[lo] is unique largest element. 85 | while (a[++i] < pivot) 86 | if (i == hi) { 87 | exchange(a, lo, hi); 88 | return hi; 89 | } 90 | 91 | // a[lo] is unique smallest element. 92 | while (pivot < a[--j]) 93 | if (j == lo + 1) 94 | return lo; 95 | 96 | // the main loop 97 | while (i < j) { 98 | exchange(a, i, j); 99 | while (a[++i] < pivot); 100 | while (pivot < a[--j]); 101 | } 102 | 103 | // Put pivot item v at a[j]. 104 | exchange(a, lo, j); 105 | 106 | // Now, a[lo .. j-1] <= a[j] <= a[j+1 .. hi]. 107 | return j; 108 | } 109 | 110 | /*************************************************************************** 111 | * Helper sorting functions. 112 | ***************************************************************************/ 113 | 114 | // Exchange a[i] and a[j]. 115 | private static void exchange(int[] a, int i, int j) { 116 | int swap = a[i]; 117 | a[i] = a[j]; 118 | a[j] = swap; 119 | } 120 | 121 | // Return the index of the median element among a[i], a[j], and a[k]. 122 | // For example, if a[j] <= a[k] <= a[i], then return k. 123 | // (The median of some numbers is the middle number if the numbers were sorted.) 124 | // TODO: implement! 125 | private static int medianOfThree(int[] a, int i, int j, int k) { 126 | // Hint: don't try to do anything smart, but just list out all 127 | // the possible cases. E.g. if a[j] <= a[k] <= a[i], return k. 128 | int returnValue = 0; 129 | if(j>=i && i>=k || k>=i && i>=j){ 130 | returnValue = i; 131 | } 132 | if(i>=j && j>=k || k>=j && j>=i){ 133 | returnValue = j; 134 | } 135 | if(i>=k && k>=j || j>=k && k>=i){ 136 | returnValue = k; 137 | } 138 | // throw new UnsupportedOperationException("to be implemented"); 139 | return returnValue; 140 | } 141 | 142 | // Shuffle an array, putting its contents in a random order. 143 | private static void shuffle(int[] a) { 144 | for (int i = 0; i < a.length; i++) { 145 | int j = i + random.nextInt(a.length - i); // uniformly distributed in [i, a.length) 146 | exchange(a, i, j); 147 | } 148 | } 149 | 150 | // Use a fixed random number seed to make testing reproducible. 151 | private static Random random = new Random(314159265); 152 | 153 | } 154 | -------------------------------------------------------------------------------- /answers.txt: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | ** DIT181 Datastrukturer och algoritmer, LP3 2021 3 | ** Lab 1: Sorting, Complexity 4 | *******************************************************************************/ 5 | 6 | Group members: 7 | - [Drake Axelrod] 8 | - [Vernita Gouws] 9 | - [Axel Lindmark] 10 | 11 | /****************************************************************************** 12 | ** Task: Figuring out the complexity 13 | ** 14 | ** 1. What is the complexity of running each of the following algorithms 15 | ** on each of the following kinds of inputs? 16 | ** You only have to decide between quadratic or faster than quadratic. 17 | ******************************************************************************/ 18 | 19 | Insertion.java on: 20 | 21 | - random inputs: Quadratic 22 | - 95% sorted inputs: Quadratic 23 | - sorted inputs: Faster than quadratic 24 | 25 | Quick.java on: 26 | 27 | - random inputs: Faster than quadratic 28 | - 95% sorted inputs: Faster than quadratic 29 | - sorted inputs: Quadratic 30 | 31 | Merge.java on: 32 | 33 | - random inputs: Faster than quadratic 34 | - 95% sorted inputs: Faster than quadratic 35 | - sorted inputs: Faster than quadratic 36 | 37 | /****************************************************************************** 38 | ** 2. How did you check if an algorithm had quadratic complexity or not? 39 | ******************************************************************************/ 40 | 41 | [We used deductive reasoning and mathematics. When a sorting algorithm increased quadratically as the number of inputs grew, 42 | we reasoned that it would be following a quadratic style of growth. For example, when there are 10,000 elements and the runtime is 1second, 43 | then when the elements increase to 30,000 elements, we expect the runtime to be around 9seconds. 44 | Quicksort - The complexity is N^2 for the sorted array because it is using the first element as the pivot, which is the lowest number in a sorted array. 45 | The complexity for the Random and 95% sorted arrays are faster than quadratic as these arrays use more efficient pivots. 46 | Insertion sort - The complexity is N^2 for Random and 95% Sorted arrays, because the algorithm iterates over the entire array multiple times in order to sort the array. 47 | However, the best case scenario is that the array is already sorted, in which case there will only be one iteration of the entire array where the algorithm would 48 | accept the order and complete its run - Making the complexity for the sorted array O(N). 49 | Merge Sort - For all arrays in Merge Sort the runtime is faster than quadratic O(NlogN). This is because the algorithm selects a median index in the array, 50 | which leads to an increased equality in the split sections. Naturally the increased equality improves the efficiency of the algorithm, which is 51 | what Quicksort tries to emulate with the 'median-of-three' concept. 52 | ] 53 | 54 | /****************************************************************************** 55 | ** Task: Improving quicksort 56 | ** 57 | ** 3. Do the following changes affect the complexity of quicksort 58 | ** on any kind of input data? If so, what is it that changes? 59 | ******************************************************************************/ 60 | 61 | Shuffling the array first: 62 | [yes] 63 | According to our test runs, shuffling the arrays before sorting them increased the runtime slightly on the Random array - but it remains faster than quadratic 64 | Approximately tripled the time on 95% sorted, but still faster than quadratic 65 | Dramatically increased the time on the the Sorted array, which was already quadratic, and thus remains so. 66 | 67 | Median-of-three pivot selection: 68 | [yes] 69 | On the Random array, the runtime increases with a slight amount, keeping to faster than quadratic runtime. 70 | On 95% sorted array, the runtime improves a lot, keeping a faster than quadratic runtime. 71 | On Sorted arrays, the runtime increases dramatically, making its runtime faster than quadratic. 72 | 73 | 74 | Insertion sort for small arrays: 75 | [no] 76 | 77 | /****************************************************************************** 78 | ** 4. What is a good cutoff to use for insertion sort? 79 | ** Explain briefly how you came to this answer. 80 | ** Remember that you should try to find the answer in a systematic way. 81 | ******************************************************************************/ 82 | 83 | A good cutoff would be around 50 elements as insertion sort more efficient on smaller arrays we came 84 | to this answer by systematically testing values and incrementing or decrementing until we arrived at 50. 85 | When you start using a cutoff point higher than 100 then smaller array sorting get slower. 86 | 87 | /****************************************************************************** 88 | ** 5. Which combination of improvements gives the best performance? 89 | ******************************************************************************/ 90 | 91 | Shuffle first: off 92 | Median of three: on 93 | Cutoff: 50 94 | ExecutionTimeReport("Quick.java: quicksort with all improvements", new Quick(false, true, 50)::sort); 95 | 96 | /****************************************************************************** 97 | ** Appendix: General information 98 | ** 99 | ** A. Approximately how many hours did you spend on the assignment? 100 | ******************************************************************************/ 101 | 102 | [Drake]: [3] 103 | [Vernita]: [3] 104 | [Axel]: [3] 105 | 106 | 107 | /****************************************************************************** 108 | ** B. Are there any known bugs / limitations? 109 | ******************************************************************************/ 110 | 111 | We had bugs when we attempted add to functions because we sent values instead of indexes. 112 | There were no bugs in submission that we could find. 113 | 114 | /****************************************************************************** 115 | ** C. Did you collaborate with any other students on this lab? 116 | ** If so, please write in what way you collaborated and with whom. 117 | ** Also include any resources (including the web) that you may 118 | ** may have used in creating your design. 119 | ******************************************************************************/ 120 | 121 | We were in a zoom call the entire time working together, so we think that it was an 122 | 100% collaborative effort. 123 | We used https://code4coding.com/java-program-to-find-middle-of-three-numbers/ 124 | to help expedite the process of the if statements for the median sort. 125 | 126 | /****************************************************************************** 127 | ** D. Describe any serious problems you encountered. 128 | ******************************************************************************/ 129 | 130 | We did not have any serious problems 131 | 132 | /****************************************************************************** 133 | ** E. List any other comments here. 134 | ** Feel free to provide any feedback on how much you learned 135 | ** from doing the assignment, and whether you enjoyed it. 136 | ******************************************************************************/ 137 | 138 | no other comments 139 | -------------------------------------------------------------------------------- /Bench.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.util.function.*; 3 | import java.util.stream.Collectors; 4 | import java.util.stream.Stream; 5 | 6 | public class Bench { 7 | 8 | /** 9 | * Main function 10 | * 11 | * Change this method freely. 12 | * You can choose which sorting algorithms to run and benchmark. 13 | */ 14 | public static void main(final String[] args) { 15 | executionTimeReport("Quick.java: quicksort with all improvements", new Quick(false, true, 50)::sort); 16 | executionTimeReport("Quick.java: quicksort", new Quick(false, false, 0)::sort); 17 | executionTimeReport("Insertion.java: insertion sort", Insertion::sort); 18 | executionTimeReport("Merge.java: merge sort", Merge::sort); 19 | 20 | // If you want to compare against an industrial-strength algorithm: 21 | //executionTimeReport("Arrays.sort: Java built-in sorting", Arrays::sort); 22 | } 23 | 24 | // The sample sizes and kinds randomness (0-100) the benchmarking program uses. 25 | // You can play around with different values! 26 | private static final int[] SAMPLE_SIZES = new int[] {10, 30, 100, 300, 1000, 3000, 10000, 30000, 100000}; 27 | private static final int[] RANDOMNESS = new int[] {100, 5, 0}; 28 | 29 | // 30 | // HERE BE DRAGONS! 31 | // 32 | // You don't have to look at the rest of this file. 33 | // It's just the testing and benchmarking program. 34 | // 35 | 36 | /** Test data generator **/ 37 | 38 | // Generates a random array of size 'size'. 39 | // Part of the array is sorted, while the rest is chosen uniformly 40 | // at random; the 'randomness' parameter sets what percent of the 41 | // array is chosen at random. 42 | public static int[] generateSample(int size, int randomness) { 43 | int[] sample = new int[size]; 44 | 45 | Random random = new Random(12345678 * size); 46 | int previousElement = 0; 47 | for (int i = 0; i < size; i++) { 48 | if (random.nextInt(100) >= randomness) { 49 | int randomOffset = random.nextInt(3); 50 | int currentElement = previousElement + randomOffset; 51 | sample[i] = currentElement; 52 | previousElement = currentElement; 53 | } else { 54 | sample[i] = random.nextInt(size); 55 | } 56 | } 57 | 58 | return sample; 59 | } 60 | 61 | public static String getRandomnessDescription(int randomness) { 62 | switch (randomness) { 63 | case 0: return "Sorted"; 64 | case 100: return "Random"; 65 | default: return (100 - randomness) + "% sorted"; 66 | } 67 | } 68 | 69 | /** Code to test the correctness of a sorting algorithm **/ 70 | 71 | @SuppressWarnings("serial") 72 | private static class TestException extends Exception { 73 | } 74 | 75 | private static void testAlgorithm(Consumer algorithm) throws TestException { 76 | for (int size = 0; size <= 1000; ++size) 77 | for (int randomness : new int[] {100, 5, 0}) 78 | check(generateSample(size, randomness), algorithm); 79 | } 80 | 81 | private static void check(final int[] array, Consumer algorithm) throws TestException { 82 | final int[] reference = array.clone(); 83 | Arrays.sort(reference); 84 | 85 | // We don't catch exceptions so as not to disturb debuggers. 86 | int[] result = array.clone(); 87 | withExceptionHandler(() -> algorithm.accept(result), e -> { 88 | if (!(e instanceof UnsupportedOperationException)) 89 | failed(array, reference); 90 | 91 | System.out.println("Threw exception:"); 92 | e.printStackTrace(System.out); 93 | }); 94 | 95 | if (!Arrays.equals(result, reference)) { 96 | failed(array, reference); 97 | System.out.println("Actual answer: " + show(result)); 98 | throw new TestException(); 99 | } 100 | } 101 | 102 | private static void withExceptionHandler(Runnable f, Consumer handler) { 103 | Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 104 | public void uncaughtException(Thread thread, Throwable e) { 105 | handler.accept(e); 106 | } 107 | }); 108 | f.run(); 109 | Thread.currentThread().setUncaughtExceptionHandler(null); 110 | } 111 | 112 | private static void failed(int[] array, int[] reference) { 113 | System.out.println("Test failed! There is a bug in the sorting algorithm."); 114 | System.out.println("Input array: " + show(array)); 115 | System.out.println("Expected answer: " + show(reference)); 116 | } 117 | 118 | private static String show(int[] array) { 119 | return Arrays.stream(array).mapToObj(Integer::toString).collect(Collectors.joining(", ", "{", "}")); 120 | } 121 | 122 | /** Code to measure the performance of a sorting algorithm **/ 123 | 124 | // Execute an algorithm on an input and return its runtime. 125 | private static String execute(Consumer algorithm, int[] input) { 126 | // To get accurate results even for small inputs, we repeat 127 | // the algorithm several times in a row and count the total time. 128 | // We pick the number of repetitions automatically so that 129 | // the total time is at least 10ms. 130 | // 131 | // To pick the number of repetitions, we start by assuming 132 | // that one repetition will be enough. We then execute the 133 | // algorithm and measure how long it takes. If it took less 134 | // than 10ms, we scale up the number of repetitions by 135 | // an appropriate factor. E.g., if the algorithm only took 136 | // 1ms, we will multiply the number of repetitions by 10. 137 | // We then repeat this whole process with the new number of 138 | // repetitions. 139 | // 140 | // Once the repetitions take more than 10ms, we try it three 141 | // times and take the smallest measured runtime. This avoids 142 | // freakish results due to e.g. the garbage collector kicking 143 | // in at the wrong time. 144 | 145 | // Minimum acceptable value for total time. 146 | final long target = 10000000; 147 | // How many times to re-measure the algorithm once it hits the 148 | // target time. 149 | final int MAX_LIVES = 3; 150 | // How many repetitions we guess will be enough. 151 | int repetitions = 1; 152 | // The lowest runtime we saw with the current number of repetitions. 153 | long runtime = Long.MAX_VALUE; 154 | // How many times we've measured after hitting the target time. 155 | int lives = MAX_LIVES; 156 | while(true) { 157 | // Build the input arrays in advance to avoid memory 158 | // allocation during testing. 159 | int[][] inputs = new int[repetitions][]; 160 | for (int i = 0; i < repetitions; i++) 161 | inputs[i] = Arrays.copyOf(input, input.length); 162 | // Try to reduce unpredictability 163 | System.gc(); 164 | Thread.yield(); 165 | 166 | // Run the algorithm 167 | long startTime = System.nanoTime(); 168 | for (int i = 0; i < repetitions; i++) 169 | algorithm.accept(inputs[i]); 170 | long endTime = System.nanoTime(); 171 | runtime = Math.min(runtime, endTime - startTime); 172 | 173 | // If the algorithm is really slow, we don't 174 | // need to measure too carefully 175 | if (repetitions == 1 && runtime >= 30*target) 176 | break; 177 | if (runtime >= target) { 178 | // Ran for long enough - reduce number of lives by one. 179 | if (lives == 0) break; else lives--; 180 | } else { 181 | // Didn't run for long enough. 182 | // Increase number of repetitions to try to hit 183 | // target - but at least double it, and at most 184 | // times by 5. 185 | if (runtime == 0) 186 | repetitions *= 5; 187 | else { 188 | double factor = target / runtime; 189 | if (factor < 2) factor = 2; 190 | if (factor > 5) factor = 5; 191 | repetitions *= factor; 192 | } 193 | runtime = Long.MAX_VALUE; 194 | lives = MAX_LIVES; 195 | } 196 | } 197 | return String.format("%6f", (double)runtime / ((long)repetitions * 1000000)); 198 | } 199 | 200 | private static void runWithLargeStack(int stackSize, Runnable f) { 201 | Thread t = new Thread(null, f, "large stack thread", stackSize); 202 | t.start(); 203 | try { 204 | t.join(); 205 | } catch (InterruptedException e) { 206 | e.printStackTrace(); 207 | System.exit(-1); 208 | } 209 | } 210 | 211 | private static void executionTimeReport(String name, Consumer algorithm) { 212 | final int sizeLength = 7; 213 | final int timeLength = 13; 214 | 215 | Function> pad = 216 | n -> x -> String.format("%" + n + "s", x); 217 | BiConsumer> printLine = (delim, s) -> System.out.println(s.collect(Collectors.joining(delim))); 218 | Runnable printSep = () -> printLine.accept("===", 219 | Stream.concat(Stream.of(sizeLength), Collections.nCopies(RANDOMNESS.length, timeLength).stream()) 220 | .map(n -> String.join("", Collections.nCopies(n, "=")))); 221 | BiConsumer> printRow = (size, stream) -> printLine.accept(" | ", 222 | Stream.concat(Arrays.asList(size).stream().map(pad.apply(sizeLength)), stream.map(pad.apply(timeLength)))); 223 | 224 | printSep.run(); 225 | System.out.printf(" %s (times in ms)%n", name); 226 | printSep.run(); 227 | 228 | runWithLargeStack(32 * 1024 * 1024, () -> { 229 | try { 230 | testAlgorithm(algorithm); 231 | printRow.accept("Size", Arrays.stream(RANDOMNESS).mapToObj(r -> getRandomnessDescription(r))); 232 | for (int size : SAMPLE_SIZES) 233 | printRow.accept(size, Arrays.stream(RANDOMNESS).mapToObj(r -> execute(algorithm, generateSample(size, r)))); 234 | } catch (TestException e) { 235 | } 236 | }); 237 | 238 | System.out.println(); 239 | } 240 | } 241 | --------------------------------------------------------------------------------