├── 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