From c59a468dd5b58d2ec7c7076f3ebf3d7c787f85a3 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Tue, 7 Nov 2023 02:54:01 -0500 Subject: [PATCH 01/27] create Java 11 copypasta sort --- .../github/arrayv/sorts/cabin/Java11Sort.java | 870 ++++++++++++++++++ 1 file changed, 870 insertions(+) create mode 100644 src/main/java/io/github/arrayv/sorts/cabin/Java11Sort.java diff --git a/src/main/java/io/github/arrayv/sorts/cabin/Java11Sort.java b/src/main/java/io/github/arrayv/sorts/cabin/Java11Sort.java new file mode 100644 index 00000000..ba23f8fb --- /dev/null +++ b/src/main/java/io/github/arrayv/sorts/cabin/Java11Sort.java @@ -0,0 +1,870 @@ +package io.github.arrayv.sorts.cabin; + +import io.github.arrayv.main.ArrayVisualizer; +import io.github.arrayv.sortdata.SortMeta; +import io.github.arrayv.sorts.templates.Sort; + +import java.util.Arrays; +import java.util.Optional; + +/** + * JDK 11's dual-pivot Quicksort, which I've painstakingly adapted line by line from DualPivotQuicksort.java to call + * into the ArrayV hooks. Although I gutted all that multithreading crap, because who cares? + *

+ * Unfortunately, this janked out copypasta is the only way to observe the standard sort. I thought of adding hooks into + * the ArrayV code in a List implementor, but the collections framework actually dumps the List into an array and calls + * Arrays::sort when you call List::sort. + *

+ * Overview
+ * The algorithm is an Introsort variant at heart, but with so many safeguards, it's basically invincible. + *

+ * The core algorithm is a dual-pivot Quicksort. It selects the pivots using a weird median of 5 thing which is based + * on the golden ratio, because of course it is. Safeguard #1: If any two of them are equal, it switches to a + * single-pivot Quicksort for that range. + *

+ * Safeguard #2: Before trying Quicksort, it tries to find and merge runs. A run is defined as an ascending, + * descending, or constant sequence of values (a constant sequence could technically be considered ascending and + * descending, but it is handled slightly differently here). A descending sequence is reversed on the spot. Then, if + * all the sequences are long enough, it will attempt to do an N-way merge on them. Otherwise, it will leave them alone + * and defer to the core Quicksort loop. + *

+ * Safeguard #3: If the recursion delves too greedily and too deep, it will call Heapsort on that range. This is, + * of course, a classic Introsort optimization. Interestingly, they set a huge depth on it, likely impossible to + * reach without millions of elements. + *

+ * Safeguard #4: If called on a small enough range, it will call insertion sort. Another classic Introsort + * optimization. But there are two versions of it... one is a regular insertion sort like you're used to. The other is a + * so-called "mixed" insertion sort, which uses the pivot to do some sort of double-ended thing that helps cut down on + * swaps. I find this fascinating. + *

+ * Java 14 supposedly adds even more interesting stuff, if you feel like taking that on. + *

+ * Suggested settings: + *

+ */ +@SuppressWarnings("StatementWithEmptyBody") +@SortMeta( + name = "Java 11", + runName = "Java 11", + category = "Custom" +) +public class Java11Sort extends Sort { + + private static final double INSERTION_SORT_SLEEP = 0.5; + private static final double QUICK_SORT_SLEEP = 0.5; + private static final double RUN_MERGE_SLEEP = 0.5; + private static final boolean includeRunIndicesInVisuals = false; + + public Java11Sort(ArrayVisualizer arrayVisualizer) { + super(arrayVisualizer); + } + + @Override + public void runSort(int[] array, int sortLength, int bucketCount) throws Exception { + sort(array, 0, sortLength); + } + +// ============ ADAPTED FROM DualPivotQuicksort.java (jdk11), minus all the parallel crap. ============ \\ + + /** + * Max array size to use insertion sort. + */ + private static final int MAX_INSERTION_SORT_SIZE = 44; + + /** + * Max array size to use mixed insertion sort. + */ + private static final int MAX_MIXED_INSERTION_SORT_SIZE = 65; + + /** + * Min array size to try merging of runs. + */ + private static final int MIN_TRY_MERGE_SIZE = 4 << 10; + + /** + * Min size of the first run to continue with scanning. + */ + private static final int MIN_FIRST_RUN_SIZE = 16; + + /** + * Min factor for the first runs to continue scanning. + */ + private static final int MIN_FIRST_RUNS_FACTOR = 7; + + /** + * Max capacity of the index array for tracking runs. + */ + private static final int MAX_RUN_CAPACITY = 5 << 10; + + /** + * Threshold of mixed insertion sort is incremented by this value. + */ + private static final int DELTA = 3 << 1; + + /** + * Max recursive partitioning depth before using heap sort. + */ + private static final int MAX_RECURSION_DEPTH = 64 * DELTA; + + /** + * Sorts the specified range of the array using parallel merge + * sort and/or Dual-Pivot Quicksort. + * + * To balance the faster splitting and parallelism of merge sort + * with the faster element partitioning of Quicksort, ranges are + * subdivided in tiers such that, if there is enough parallelism, + * the four-way parallel merge is started, still ensuring enough + * parallelism to process the partitions. + * + * @param a the array to be sorted + * @param low the index of the first element, inclusive, to be sorted + * @param high the index of the last element, exclusive, to be sorted + */ + private void sort(int[] a, int low, int high) throws InterruptedException { + sort(a, 0, low, high); + } + + /** + * Sorts the specified array using the Dual-Pivot Quicksort and/or + * other sorts in special-cases, possibly with parallel partitions. + * + * @param a the array to be sorted + * @param bits the combination of recursion depth and bit flag, where + * the right bit "0" indicates that array is the leftmost part + * @param low the index of the first element, inclusive, to be sorted + * @param high the index of the last element, exclusive, to be sorted + */ + private void sort(int[] a, int bits, int low, int high) throws InterruptedException { + while (true) { + int end = high - 1, size = high - low; + + /* + * Run mixed insertion sort on small non-leftmost parts. + */ + if (size < MAX_MIXED_INSERTION_SORT_SIZE + bits && (bits & 1) > 0) { + mixedInsertionSort(a, low, high - 3 * ((size >> 5) << 3), high); + return; + } + + /* + * Invoke insertion sort on small leftmost part. + */ + if (size < MAX_INSERTION_SORT_SIZE) { + insertionSort(a, low, high); + return; + } + + /* + * Check if the whole array or large non-leftmost + * parts are nearly sorted and then merge runs. + */ + if ((bits == 0 || size > MIN_TRY_MERGE_SIZE && (bits & 1) > 0) + && tryMergeRuns(a, low, size)) { + return; + } + + /* + * Switch to heap sort if execution + * time is becoming quadratic. + */ + if ((bits += DELTA) > MAX_RECURSION_DEPTH) { + heapSort(a, low, high); + return; + } + + /* + * Use an inexpensive approximation of the golden ratio + * to select five sample elements and determine pivots. + */ + int step = (size >> 3) * 3 + 3; + + /* + * Five elements around (and including) the central element + * will be used for pivot selection as described below. The + * unequal choice of spacing these elements was empirically + * determined to work well on a wide variety of inputs. + */ + int e1 = low + step; + int e5 = end - step; + int e3 = (e1 + e5) >>> 1; + int e2 = (e1 + e3) >>> 1; + int e4 = (e3 + e5) >>> 1; + int a3 = a[e3]; + + // Markers can have sparse IDs, and the positions can be unordered. + // With that in mind, it's best to leave 1 and 2 for reads/writes/swaps and start indexing at 3. + + // Mark endpoints + Highlights.markArray(3, low); + Highlights.markArray(4, high - 1); + + // Mark pivot considerations + Highlights.markArray(5, e1); + Highlights.markArray(6, e2); + Highlights.markArray(7, e3); + Highlights.markArray(8, e4); + Highlights.markArray(9, e5); + + /* + * Sort these elements in place by the combination + * of 4-element sorting network and insertion sort. + * + * 5 ------o-----------o------------ + * | | + * 4 ------|-----o-----o-----o------ + * | | | + * 2 ------o-----|-----o-----o------ + * | | + * 1 ------------o-----o------------ + */ + if (Reads.compareIndices(a, e5, e2, QUICK_SORT_SLEEP, true) < 0) + Writes.swap(a, e5, e2, QUICK_SORT_SLEEP, true, false); + if (Reads.compareIndices(a, e4, e1, QUICK_SORT_SLEEP, true) < 0) + Writes.swap(a, e4, e1, QUICK_SORT_SLEEP, true, false); + if (Reads.compareIndices(a, e5, e4, QUICK_SORT_SLEEP, true) < 0) + Writes.swap(a, e5, e4, QUICK_SORT_SLEEP, true, false); + if (Reads.compareIndices(a, e2, e1, QUICK_SORT_SLEEP, true) < 0) + Writes.swap(a, e2, e1, QUICK_SORT_SLEEP, true, false); + if (Reads.compareIndices(a, e4, e2, QUICK_SORT_SLEEP, true) < 0) + Writes.swap(a, e4, e2, QUICK_SORT_SLEEP, true, false); + + // TODO: compareIndices(e3, e2)? + if (Reads.compareValueIndex(a, a3, e2, QUICK_SORT_SLEEP, true) < 0) { + if (Reads.compareValueIndex(a, a3, e1, QUICK_SORT_SLEEP, true) < 0) { + Writes.write(a, e3, a[e2], QUICK_SORT_SLEEP, true, false); + Writes.write(a, e2, a[e1], QUICK_SORT_SLEEP, true, false); + Writes.write(a, e1, a3, QUICK_SORT_SLEEP, true, false); + } else { + Writes.swap(a, e2, e3, QUICK_SORT_SLEEP, true, false); + } + } else if (Reads.compareValueIndex(a, a3, e4, QUICK_SORT_SLEEP, true) > 0) { + if (Reads.compareValueIndex(a, a3, e5, QUICK_SORT_SLEEP, true) > 0) { + Writes.write(a, e3, a[e4], QUICK_SORT_SLEEP, true, false); + Writes.write(a, e4, a[e5], QUICK_SORT_SLEEP, true, false); + Writes.write(a, e5, a3, QUICK_SORT_SLEEP, true, false); + } else { + Writes.swap(a, e3, e4, QUICK_SORT_SLEEP, true, false); + } + } + + // Unmark pivot considerations + Highlights.clearMark(5); + Highlights.clearMark(6); + Highlights.clearMark(7); + Highlights.clearMark(8); + Highlights.clearMark(9); + + // Pointers + int lower = low; // The index of the last element of the left part + int upper = end; // The index of the first element of the right part + + /* + * Partitioning with 2 pivots in case of different elements. + */ + if ( Reads.compareIndices(a, e1, e2, QUICK_SORT_SLEEP, true) < 0 + && Reads.compareIndices(a, e2, e3, QUICK_SORT_SLEEP, true) < 0 + && Reads.compareIndices(a, e3, e4, QUICK_SORT_SLEEP, true) < 0 + && Reads.compareIndices(a, e4, e5, QUICK_SORT_SLEEP, true) < 0 + ) { + + + /* + * Use the first and fifth of the five sorted elements as + * the pivots. These values are inexpensive approximation + * of tertiles. Note, that pivot1 < pivot2. + */ + int pivot1 = a[e1]; + int pivot2 = a[e5]; + + /* + * The first and the last elements to be sorted are moved + * to the locations formerly occupied by the pivots. When + * partitioning is completed, the pivots are swapped back + * into their final positions, and excluded from the next + * subsequent sorting. + */ + Writes.write(a, e1, a[lower], QUICK_SORT_SLEEP, true, false); + Writes.write(a, e5, a[upper], QUICK_SORT_SLEEP, true, false); + + /* + * Skip elements, which are less or greater than the pivots. + */ + while (Reads.compareIndexValue(a, ++lower, pivot1, QUICK_SORT_SLEEP, true) < 0); + while (Reads.compareIndexValue(a, --upper, pivot2, QUICK_SORT_SLEEP, true) > 0); + + Highlights.markArray(3, lower); + Highlights.markArray(4, upper - 1); + + /* + * Backward 3-interval partitioning + * + * left part central part right part + * +------------------------------------------------------------+ + * | < pivot1 | ? | pivot1 <= && <= pivot2 | > pivot2 | + * +------------------------------------------------------------+ + * ^ ^ ^ + * | | | + * lower k upper + * + * Invariants: + * + * all in (low, lower] < pivot1 + * pivot1 <= all in (k, upper) <= pivot2 + * all in [upper, end) > pivot2 + * + * Pointer k is the last index of ?-part + */ + for (int unused = --lower, k = ++upper; --k > lower; ) { + int ak = a[k]; + + if (Reads.compareIndexValue(a, k, pivot1, QUICK_SORT_SLEEP, true) < 0) { // Move a[k] to the left side + while (lower < k) { + if (Reads.compareIndexValue(a, ++lower, pivot1, QUICK_SORT_SLEEP, true) >= 0) { + if (Reads.compareIndexValue(a, lower, pivot2, QUICK_SORT_SLEEP, true) > 0) { + Writes.write(a, k, a[--upper], QUICK_SORT_SLEEP, true, false); + Writes.write(a, upper, a[lower], QUICK_SORT_SLEEP, true, false); + } else { + Writes.write(a, k, a[lower], QUICK_SORT_SLEEP, true, false); + } + Writes.write(a, lower, ak, QUICK_SORT_SLEEP, true, false); + break; + } + } + } else if (Reads.compareIndexValue(a, k, pivot2, QUICK_SORT_SLEEP, true) > 0) { // Move a[k] to the right side + Writes.write(a, k, a[--upper], QUICK_SORT_SLEEP, true, false); + Writes.write(a, upper, ak, QUICK_SORT_SLEEP, true, false); + } + } + + /* + * Swap the pivots into their final positions. + */ + // TODO: swap(low, lower)? + Writes.write(a, low, a[lower], QUICK_SORT_SLEEP, true, false); + Writes.write(a, lower, pivot1, QUICK_SORT_SLEEP, true, false); + + // TODO: swap(end, upper)? + Writes.write(a, end, a[upper], QUICK_SORT_SLEEP, true, false); + Writes.write(a, upper, pivot2, QUICK_SORT_SLEEP, true, false); + + Highlights.clearAllMarks(); + + /* + * Sort non-left parts recursively, excluding known pivots. + */ + sort(a, bits | 1, lower + 1, upper); + sort(a, bits | 1, upper + 1, high); + + } else { // Use single pivot in case of many equal elements + /* + * Use the third of the five sorted elements as the pivot. + * This value is inexpensive approximation of the median. + */ + int pivot = a[e3]; + + /* + * The first element to be sorted is moved to the + * location formerly occupied by the pivot. After + * completion of partitioning the pivot is swapped + * back into its final position, and excluded from + * the next subsequent sorting. + */ + a[e3] = a[lower]; + + /* + * Traditional 3-way (Dutch National Flag) partitioning + * + * left part central part right part + * +------------------------------------------------------+ + * | < pivot | ? | == pivot | > pivot | + * +------------------------------------------------------+ + * ^ ^ ^ + * | | | + * lower k upper + * + * Invariants: + * + * all in (low, lower] < pivot + * all in (k, upper) == pivot + * all in [upper, end] > pivot + * + * Pointer k is the last index of ?-part + */ + for (int k = ++upper; --k > lower; ) { + int ak = a[k]; + + if (Reads.compareIndexValue(a, k, pivot, QUICK_SORT_SLEEP, true) != 0) { + a[k] = pivot; + + if (Reads.compareValues(ak, pivot) < 0) { // Move a[k] to the left side + while (Reads.compareIndexValue(a, ++lower, pivot) < 0); + + if (Reads.compareIndexValue(a, lower, pivot, QUICK_SORT_SLEEP, true) > 0) { + Writes.write(a, --upper, a[lower], QUICK_SORT_SLEEP, true, false); + } + Writes.write(a, lower, ak, QUICK_SORT_SLEEP, true, false); + } else { // ak > pivot - Move a[k] to the right side + Writes.write(a, --upper, ak, QUICK_SORT_SLEEP, true, false); + } + } + } + + /* + * Swap the pivot into its final position. + */ + // TODO: swap(low, lower)? + Writes.write(a, low, a[lower], QUICK_SORT_SLEEP, true, false); + Writes.write(a, lower, pivot, QUICK_SORT_SLEEP, true, false); + + Highlights.clearAllMarks(); + + /* + * Sort the right part, excluding known pivot. + * All elements from the central part are + * equal and therefore already sorted. + */ + sort(a, bits | 1, upper, high); + } + high = lower; // Iterate along the left part + } + } + + /** + * Sorts the specified range of the array using mixed insertion sort. + * + * Mixed insertion sort is combination of simple insertion sort, + * pin insertion sort and pair insertion sort. + * + * In the context of Dual-Pivot Quicksort, the pivot element + * from the left part plays the role of sentinel, because it + * is less than any elements from the given part. Therefore, + * expensive check of the left range can be skipped on each + * iteration unless it is the leftmost call. + * + * @param a the array to be sorted + * @param low the index of the first element, inclusive, to be sorted + * @param end the index of the last element for simple insertion sort + * @param high the index of the last element, exclusive, to be sorted + */ + private void mixedInsertionSort(int[] a, int low, int end, int high) throws InterruptedException { + + Highlights.markArray(3, low); + Highlights.markArray(4, end - 1); + Highlights.markArray(5, high - 1); + + if (end == high) { + + /* + * Invoke simple insertion sort on tiny array. + */ + for (int i; ++low < end; ) { + int ai = a[i = low]; + + // TODO: Reads.compareIndices(low, --i)? Would make a more intuitive marker. + while (Reads.compareValueIndex(a, ai, --i, INSERTION_SORT_SLEEP, true) < 0) { + Writes.write(a, i + 1, a[i], INSERTION_SORT_SLEEP, true, false); + } + Writes.write(a, i + 1, ai, INSERTION_SORT_SLEEP, true, false); + } + } else { + + /* + * Start with pin insertion sort on small part. + * + * Pin insertion sort is extended simple insertion sort. + * The main idea of this sort is to put elements larger + * than an element called pin to the end of array (the + * proper area for such elements). It avoids expensive + * movements of these elements through the whole array. + */ + int pin = a[end]; + + for (int i, p = high; ++low < end; ) { + int ai = a[i = low]; + + // TODO: Reads.compareIndices(low, i - 1)? Would make a more intuitive marker. + if (Reads.compareValueIndex(a, ai, i - 1, INSERTION_SORT_SLEEP, true) < 0) { // Small element + + /* + * Insert small element into sorted part. + */ + Writes.write(a, i, a[--i], INSERTION_SORT_SLEEP, true, false); + + // TODO: Reads.compareIndices(low, i - 1)? Would make a more intuitive marker. + while (Reads.compareValueIndex(a, ai, --i, INSERTION_SORT_SLEEP, true) < 0) { + Writes.write(a, i + 1, a[i], INSERTION_SORT_SLEEP, true, false); + } + Writes.write(a, i + 1, ai, INSERTION_SORT_SLEEP, true, false); + + } else if (p > i && Reads.compareValues(ai, pin) > 0) { // Large element + // TODO: Reads.compareIndices(low, end)? + + /* + * Find element smaller than pin. + */ + while (Reads.compareIndexValue(a, --p, pin, INSERTION_SORT_SLEEP, true) > 0); + // TODO: Reads.compareIndices(--p, end)? + + /* + * Swap it with large element. + */ + if (p > i) { + ai = a[p]; + Writes.write(a, p, a[i], INSERTION_SORT_SLEEP, true, false); + } + + /* + * Insert small element into sorted part. + */ + while (Reads.compareValueIndex(a, ai, --i, INSERTION_SORT_SLEEP, true) < 0) { + // NOTE: Can't do Reads.compareIndices because ai may have been reassigned (can't tell if + // it would be p or low). + Writes.write(a, i + 1, a[i], INSERTION_SORT_SLEEP, true, false); + } + Writes.write(a, i + 1, ai, INSERTION_SORT_SLEEP, true, false); + } + } + + /* + * Continue with pair insertion sort on remain part. + */ + for (int i; low < high; ++low) { + final int a1 = a[i = low], a2 = a[++low]; + + /* + * Insert two elements per iteration: at first, insert the + * larger element and then insert the smaller element, but + * from the position where the larger element was inserted. + */ + if (Reads.compareValues(a1, a2) > 0) { + // TODO: Reads.compareIndices(low - 1, low)? + + while (Reads.compareValueIndex(a, a1, --i, INSERTION_SORT_SLEEP, true) < 0) { + // TODO: Reads.compareIndices(low - 1, --i)? + Writes.write(a, i + 2, a[i], INSERTION_SORT_SLEEP, true, false); + } + Writes.write(a, ++i + 1, a1, INSERTION_SORT_SLEEP, true, false); + + while (Reads.compareValueIndex(a, a2, --i, INSERTION_SORT_SLEEP, true) < 0) { + // TODO: Reads.compareIndices(low, --i)? + Writes.write(a, i + 1, a[i], INSERTION_SORT_SLEEP, true, false); + } + Writes.write(a, i + 1, a2, INSERTION_SORT_SLEEP, true, false); + + } else if (Reads.compareValueIndex(a, a1, i - 1, INSERTION_SORT_SLEEP, true) < 0) { + // TODO: Reads.compareIndices(low - 1, i - 1)? + + while (Reads.compareValueIndex(a, a2, --i, INSERTION_SORT_SLEEP, true) < 0) { + Writes.write(a, i + 2, a[i], INSERTION_SORT_SLEEP, true, false); + } + Writes.write(a, ++i + 1, a2, INSERTION_SORT_SLEEP, true, false); + + while (Reads.compareValueIndex(a, a1, --i, INSERTION_SORT_SLEEP, true) < 0) { + Writes.write(a, i + 1, a[i], INSERTION_SORT_SLEEP, true, false); + } + Writes.write(a, i + 1, a1, INSERTION_SORT_SLEEP, true, false); + } + } + } + + Highlights.clearMark(3); + Highlights.clearMark(4); + Highlights.clearMark(5); + } + + /** + * Sorts the specified range of the array using insertion sort. + * + * @param a the array to be sorted + * @param low the index of the first element, inclusive, to be sorted + * @param high the index of the last element, exclusive, to be sorted + */ + private void insertionSort(int[] a, int low, int high) { + Highlights.markArray(3, low); + Highlights.markArray(4, Math.max(low, high - 1)); + + for (int i, k = low; ++k < high; ) { + int ai = a[i = k]; + + if (Reads.compareValueIndex(a, ai, i - 1, INSERTION_SORT_SLEEP, true) < 0) { + while (--i >= low && Reads.compareValueIndex(a, ai, i, INSERTION_SORT_SLEEP, true) < 0) { + Writes.write(a, i + 1, a[i], INSERTION_SORT_SLEEP, true, false); + } + Writes.write(a, i + 1, ai, INSERTION_SORT_SLEEP, true, false); + } + } + + Highlights.clearMark(3); + Highlights.clearMark(4); + } + + /** + * Tries to sort the specified range of the array. + * + * @param a the array to be sorted + * @param low the index of the first element to be sorted + * @param size the array size + * @return true if finally sorted, false otherwise + */ + private boolean tryMergeRuns(int[] a, int low, int size) { + + /* + * The run array is constructed only if initial runs are + * long enough to continue, run[i] then holds start index + * of the i-th sequence of elements in non-descending order. + */ + int[] run = null; + int high = low + size; + int count = 1, last = low; + + /* + * Identify all possible runs. + */ + for (int k = low + 1; k < high; ) { + + /* + * Find the end index of the current run. + */ + if (Reads.compareIndices(a, k - 1, k, RUN_MERGE_SLEEP, true) < 0) { + + // Identify ascending sequence + while (++k < high && Reads.compareIndices(a, k - 1, k, RUN_MERGE_SLEEP, true) <= 0); + + } else if (Reads.compareIndices(a, k - 1, k, RUN_MERGE_SLEEP, true) > 0) { + + // Identify descending sequence + while (++k < high && Reads.compareIndices(a, k - 1, k, RUN_MERGE_SLEEP, true) >= 0); + + // Reverse into ascending order + Writes.changeReversals(1); + for (int i = last - 1, j = k; ++i < --j && Reads.compareIndices(a, i, j, RUN_MERGE_SLEEP, true) > 0; ) { + Writes.swap(a, i, j, RUN_MERGE_SLEEP, true, false); + } + } else { // Identify constant sequence + for (int ak = a[k]; ++k < high && Reads.compareValueIndex(a, ak, k, RUN_MERGE_SLEEP, true) == 0; ); + + if (k < high) { + continue; + } + } + + /* + * Check special cases. + */ + if (run == null) { + if (k == high) { + + /* + * The array is monotonous sequence, + * and therefore already sorted. + */ + return true; + } + + if (k - low < MIN_FIRST_RUN_SIZE) { + + /* + * The first run is too small + * to proceed with scanning. + */ + return false; + } + + // Some weird bitwise rounding and clamping magic for the size here. + // Conditionally reserving the array through bootleg malloc because I wasn't quite + // sure if I wanted the indices showing up in the visualization. + run = includeRunIndicesInVisuals + ? Writes.createExternalArray(((size >> 10) | 0x7F) & 0x3FF) + : new int[((size >> 10) | 0x7F) & 0x3FF]; + Writes.write(run, 0, low, RUN_MERGE_SLEEP, true, true); + + } else if (Reads.compareIndices(a, last - 1, last, RUN_MERGE_SLEEP, true) > 0) { + + if (count > (k - low) >> MIN_FIRST_RUNS_FACTOR) { + + /* + * The first runs are not long + * enough to continue scanning. + */ + if (includeRunIndicesInVisuals) Writes.deleteExternalArray(run); + return false; + } + + if (++count == MAX_RUN_CAPACITY) { + + /* + * Array is not highly structured. + */ + if (includeRunIndicesInVisuals) Writes.deleteExternalArray(run); + return false; + } + + if (count == run.length) { + + /* + * Increase capacity of index array. + */ + if (includeRunIndicesInVisuals) { + int[] copy = Writes.copyOfArray(run, count << 1); + Writes.deleteExternalArray(run); + run = copy; + } else { + run = Arrays.copyOf(run, count << 1); + } + } + } + Writes.write(run, count, (last = k), RUN_MERGE_SLEEP, true, true); + } + + /* + * Merge runs of highly structured array. + */ + if (count > 1) { + int[] b = Writes.createExternalArray(size); + + mergeRuns(a, b, low, 1, run, 0, count); + Writes.deleteExternalArray(b); + } + + if (includeRunIndicesInVisuals) Optional.ofNullable(run).ifPresent(Writes::deleteExternalArray); + return true; + } + + /** + * Merges the specified runs. + * + * @param a the source array + * @param b the temporary buffer used in merging + * @param offset the start index in the source, inclusive + * @param aim specifies merging: to source ( > 0), buffer ( < 0) or any ( == 0) + * @param run the start indexes of the runs, inclusive + * @param lo the start index of the first run, inclusive + * @param hi the start index of the last run, inclusive + * @return the destination where runs are merged + */ + private int[] mergeRuns(int[] a, int[] b, int offset, + int aim, int[] run, int lo, int hi) { + + if (hi - lo == 1) { + if (aim >= 0) { + return a; + } + for (int i = run[hi], j = i - offset, low = run[lo]; Reads.compareValues(i, low) > 0; + Writes.write(b, --j, a[--i], RUN_MERGE_SLEEP, true, true) + ); + return b; + } + + /* + * Split into approximately equal parts. + */ + int mi = lo, rmi = (run[lo] + run[hi]) >>> 1; + while (Reads.compareIndexValue(run, ++mi + 1, rmi, RUN_MERGE_SLEEP, false) <= 0); + + /* + * Merge the left and right parts. + */ + int[] a1, a2; + + a1 = mergeRuns(a, b, offset, -aim, run, lo, mi); + a2 = mergeRuns(a, b, offset, 0, run, mi, hi); + + int[] dst = a1 == a ? b : a; + + int k = a1 == a ? run[lo] - offset : run[lo]; + int lo1 = a1 == b ? run[lo] - offset : run[lo]; + int hi1 = a1 == b ? run[mi] - offset : run[mi]; + int lo2 = a2 == b ? run[mi] - offset : run[mi]; + int hi2 = a2 == b ? run[hi] - offset : run[hi]; + + mergeParts(dst, k, a1, lo1, hi1, a2, lo2, hi2); + return dst; + } + + /** + * Merges the sorted parts. + * + * @param dst the destination where parts are merged + * @param k the start index of the destination, inclusive + * @param a1 the first part + * @param lo1 the start index of the first part, inclusive + * @param hi1 the end index of the first part, exclusive + * @param a2 the second part + * @param lo2 the start index of the second part, inclusive + * @param hi2 the end index of the second part, exclusive + */ + private void mergeParts(int[] dst, int k, + int[] a1, int lo1, int hi1, int[] a2, int lo2, int hi2) { + + /* + * Merge small parts sequentially. + * TODO: Not sure if the highlighting will make sense, here, since it always marks them in the main array. + * Also need to do some sort of dst == original for the auxwrite flag. + */ + while (lo1 < hi1 && lo2 < hi2) { + Writes.write(dst, k++, Reads.compareValues(a1[lo1], a2[lo2]) < 0 ? a1[lo1++] : a2[lo2++], RUN_MERGE_SLEEP, true, true); + } + if (dst != a1 || k < lo1) { + while (lo1 < hi1) { + Writes.write(dst, k++, a1[lo1++], RUN_MERGE_SLEEP, true, true); + } + } + if (dst != a2 || k < lo2) { + while (lo2 < hi2) { + Writes.write(dst, k++, a2[lo2++], RUN_MERGE_SLEEP, true, true); + } + } + } + + /** + * Sorts the specified range of the array using heap sort. + * + * @param a the array to be sorted + * @param low the index of the first element, inclusive, to be sorted + * @param high the index of the last element, exclusive, to be sorted + */ + private static void heapSort(int[] a, int low, int high) { + + if (true) throw new UnsupportedOperationException("Degeneracy detected. Heapsort not implemented."); + + for (int k = (low + high) >>> 1; k > low; ) { + pushDown(a, --k, a[k], low, high); + } + while (--high > low) { + int max = a[low]; + pushDown(a, low, a[high], low, high); + a[high] = max; + } + } + + /** + * Pushes specified element down during heap sort. + * + * @param a the given array + * @param p the start index + * @param value the given element + * @param low the index of the first element, inclusive, to be sorted + * @param high the index of the last element, exclusive, to be sorted + */ + private static void pushDown(int[] a, int p, int value, int low, int high) { + for (int k ;; a[p] = a[p = k]) { + k = (p << 1) - low + 2; // Index of the right child + + if (k > high) { + break; + } + if (k == high || a[k] < a[k - 1]) { + --k; + } + if (a[k] <= value) { + break; + } + } + a[p] = value; + } +} From 267ac7595ed97aa8cc3de9506bb8df6a23105295 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Wed, 8 Nov 2023 16:31:30 -0500 Subject: [PATCH 02/27] rename to Java14Sort I grabbed DualPivotQuickSort by ctrl+clicking into Arrays.sort. Turns out I had my IDE settings pointing to Java 14 for this project, even though maven was set to use Java 11. Why is IntelliJ like this? --- .../{Java11Sort.java => Java14Sort.java} | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) rename src/main/java/io/github/arrayv/sorts/cabin/{Java11Sort.java => Java14Sort.java} (95%) diff --git a/src/main/java/io/github/arrayv/sorts/cabin/Java11Sort.java b/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java similarity index 95% rename from src/main/java/io/github/arrayv/sorts/cabin/Java11Sort.java rename to src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java index ba23f8fb..e4962938 100644 --- a/src/main/java/io/github/arrayv/sorts/cabin/Java11Sort.java +++ b/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java @@ -8,14 +8,20 @@ import java.util.Optional; /** - * JDK 11's dual-pivot Quicksort, which I've painstakingly adapted line by line from DualPivotQuicksort.java to call - * into the ArrayV hooks. Although I gutted all that multithreading crap, because who cares? + * JDK 14's dual-pivot Quicksort, which I've painstakingly adapted line by line from DualPivotQuicksort.java to call + * into the ArrayV hooks. Although I've gutted all that parallel merge stuff, because it essentially calls into the main + * algorithm on smaller segments in parallel, and merges them back in parallel. I'd rather just watch the main algorithm. + * While this parallel merge piece is ostensibly what sets JDK 14's sort apart from its predecessors, there are still a + * few little optimizations compared to JDK 11, including pivot selection and a new heapsort fallback (which I haven't + * been able to trigger in ArrayV without changing the tuning constants). *

* Unfortunately, this janked out copypasta is the only way to observe the standard sort. I thought of adding hooks into * the ArrayV code in a List implementor, but the collections framework actually dumps the List into an array and calls - * Arrays::sort when you call List::sort. + * Arrays::sort when you call List::sort. Plus, it uses a whole different algorithm for Comparables as opposed + * to primitives. + *

+ * Overview: *

- * Overview
* The algorithm is an Introsort variant at heart, but with so many safeguards, it's basically invincible. *

* The core algorithm is a dual-pivot Quicksort. It selects the pivots using a weird median of 5 thing which is based @@ -37,8 +43,6 @@ * so-called "mixed" insertion sort, which uses the pivot to do some sort of double-ended thing that helps cut down on * swaps. I find this fascinating. *

- * Java 14 supposedly adds even more interesting stuff, if you feel like taking that on. - *

* Suggested settings: *

*/ @SuppressWarnings("StatementWithEmptyBody") @@ -64,15 +65,31 @@ public class Java14Sort extends Sort { private static final double RUN_MERGE_SLEEP = 0.5; private static final boolean includeRunIndicesInVisuals = false; + private int sortLength; + public Java14Sort(ArrayVisualizer arrayVisualizer) { super(arrayVisualizer); } @Override public void runSort(int[] array, int sortLength, int bucketCount) throws Exception { + this.sortLength = sortLength; sort(array, 0, sortLength); } + // For mixedInsertionSort, which often receives awkward bounds. + private int clamp(int i) { + if (i < 0) { + (new IndexOutOfBoundsException("" + i)).printStackTrace(); + return 0; + } else if (i >= sortLength) { + (new IndexOutOfBoundsException("" + i)).printStackTrace(); + return sortLength - 1; + } + // return Math.min(Math.max(i, 0), sortLength - 1); + return i; + } + // ============ ADAPTED FROM DualPivotQuicksort.java (jdk14), minus all the parallel crap. ============ \\ // ================= It doesn't use any fancy language features, so no problems here. ================= \\ // ================================= Original comments have been left intact. ========================= \\ @@ -459,9 +476,9 @@ && tryMergeRuns(a, low, size)) { */ private void mixedInsertionSort(int[] a, int low, int end, int high) { - Highlights.markArray(3, low); - Highlights.markArray(4, end - 1); - Highlights.markArray(5, high - 1); + Highlights.markArray(3, clamp(low)); + Highlights.markArray(4, clamp(end - 1)); + Highlights.markArray(5, clamp(high - 1)); if (end == high) { diff --git a/src/main/java/io/github/arrayv/sorts/tests/IndexOobTest.java b/src/main/java/io/github/arrayv/sorts/tests/IndexOobTest.java new file mode 100644 index 00000000..c6441633 --- /dev/null +++ b/src/main/java/io/github/arrayv/sorts/tests/IndexOobTest.java @@ -0,0 +1,30 @@ +package io.github.arrayv.sorts.tests; + +import io.github.arrayv.main.ArrayVisualizer; +import io.github.arrayv.sortdata.SortMeta; +import io.github.arrayv.sorts.templates.Sort; + +@SortMeta( + name = "Index OOB Test" +) +public class IndexOobTest extends Sort { + + public IndexOobTest(ArrayVisualizer arrayVisualizer) { + super(arrayVisualizer); + } + + @Override + public void runSort(int[] array, int sortLength, int param) throws Exception { + /* + If sortLength == 32768 (the maximum), markArray works just fine, but clearAllMarks will crash. + After that point, it crashes every time it attempts to redraw the screen. + This is because markArray only checks for negative marker positions, not ones that exceed the array length. + This slips by undetected in most cases because the array is always 32768 long, so you can do sneaky OOB + writes in clearAllMarks without causing any problems if the length is any smaller. + + Once this bug is fixed, this test should ALWAYS crash, no matter the sortLength. + */ + Highlights.markArray(1, sortLength); + Highlights.clearAllMarks(); + } +} From a2ff108e37a3fca42cb8fd481d594fba077814b5 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 11 Nov 2023 01:55:26 -0500 Subject: [PATCH 04/27] add more bounds checking to Highlights.markArray() --- .../java/io/github/arrayv/utils/Highlights.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/arrayv/utils/Highlights.java b/src/main/java/io/github/arrayv/utils/Highlights.java index bca1537e..d6c9c7ce 100644 --- a/src/main/java/io/github/arrayv/utils/Highlights.java +++ b/src/main/java/io/github/arrayv/utils/Highlights.java @@ -158,12 +158,23 @@ public boolean containsPosition(int arrayPosition) { if (arrayPosition >= markCounts.length) return false; return this.markCounts[arrayPosition] != 0; } + + /** + * Point marker at given array index. Markers can be sparse and unordered; that is, marker #3 -> position 5, marker #8 -> position 2 + * is totally fine. + *

+ * Markers #1 and #2 are used by the Reads and Writes methods, so you might want to start at #3 if you're invoking + * this method yourself. + * + * @param marker Marker ID + * @param markPosition Index into the array that should be marked + */ public synchronized void markArray(int marker, int markPosition) { try { - if (markPosition < 0) { + if (markPosition < 0 || markPosition >= arrayVisualizer.getCurrentLength()) { if (markPosition == -1) throw new Exception("Highlights.markArray(): Invalid position! -1 is reserved for the clearMark method."); else if (markPosition == -5) throw new Exception("Highlights.markArray(): Invalid position! -5 was the constant originally used to unmark numbers in the array. Instead, use the clearMark method."); - else throw new Exception("Highlights.markArray(): Invalid position!"); + else throw new Exception("Highlights.markArray(): Invalid position " + markPosition + "!"); } else { if (highlights[marker] == markPosition) { return; @@ -181,6 +192,7 @@ public synchronized void markArray(int marker, int markPosition) { } } catch (Exception e) { e.printStackTrace(); + throw new RuntimeException(e); } arrayVisualizer.updateNow(); Delays.enableStepping(); From 661c77d7f4ac2936a7c829120538340464ec9fe2 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 11 Nov 2023 01:58:20 -0500 Subject: [PATCH 05/27] just throw IndexOutOfBoundsExceptions --- .../io/github/arrayv/utils/Highlights.java | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/github/arrayv/utils/Highlights.java b/src/main/java/io/github/arrayv/utils/Highlights.java index d6c9c7ce..284f352c 100644 --- a/src/main/java/io/github/arrayv/utils/Highlights.java +++ b/src/main/java/io/github/arrayv/utils/Highlights.java @@ -170,29 +170,24 @@ public boolean containsPosition(int arrayPosition) { * @param markPosition Index into the array that should be marked */ public synchronized void markArray(int marker, int markPosition) { - try { - if (markPosition < 0 || markPosition >= arrayVisualizer.getCurrentLength()) { - if (markPosition == -1) throw new Exception("Highlights.markArray(): Invalid position! -1 is reserved for the clearMark method."); - else if (markPosition == -5) throw new Exception("Highlights.markArray(): Invalid position! -5 was the constant originally used to unmark numbers in the array. Instead, use the clearMark method."); - else throw new Exception("Highlights.markArray(): Invalid position " + markPosition + "!"); - } else { - if (highlights[marker] == markPosition) { - return; - } - Delays.disableStepping(); - if (highlights[marker] != -1) { - decrementIndexMarkCount(highlights[marker]); - } - highlights[marker] = markPosition; - incrementIndexMarkCount(markPosition); + if (markPosition < 0 || markPosition >= arrayVisualizer.getCurrentLength()) { + if (markPosition == -1) throw new IndexOutOfBoundsException("Highlights.markArray(): Invalid position! -1 is reserved for the clearMark method."); + else if (markPosition == -5) throw new IndexOutOfBoundsException("Highlights.markArray(): Invalid position! -5 was the constant originally used to unmark numbers in the array. Instead, use the clearMark method."); + else throw new IndexOutOfBoundsException("Highlights.markArray(): Invalid position " + markPosition + "!"); + } else { + if (highlights[marker] == markPosition) { + return; + } + Delays.disableStepping(); + if (highlights[marker] != -1) { + decrementIndexMarkCount(highlights[marker]); + } + highlights[marker] = markPosition; + incrementIndexMarkCount(markPosition); - if (marker >= this.maxHighlightMarked) { - this.maxHighlightMarked = marker + 1; - } + if (marker >= this.maxHighlightMarked) { + this.maxHighlightMarked = marker + 1; } - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(e); } arrayVisualizer.updateNow(); Delays.enableStepping(); From 519dd2d964b63fb43496e8946cf7b000506a932e Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 11 Nov 2023 03:05:51 -0500 Subject: [PATCH 06/27] add SMB3 Shuffle --- .../java/io/github/arrayv/utils/Shuffles.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/main/java/io/github/arrayv/utils/Shuffles.java b/src/main/java/io/github/arrayv/utils/Shuffles.java index 0b87beb8..fdfce038 100644 --- a/src/main/java/io/github/arrayv/utils/Shuffles.java +++ b/src/main/java/io/github/arrayv/utils/Shuffles.java @@ -1447,8 +1447,35 @@ private int pow2lte(int value) { for (val = 1; val <= value; val <<= 1); return val >> 1; } + }, + SMB3 { + @Override + public String getName() { + return "SMB3 Matching Game"; + } + @Override + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + // Multiply length by 5/6 to simulate that bottom right corner easter egg + smb3Shuffle(array, (int) (arrayVisualizer.getCurrentLength() * 5.0 / 6.0), false, + arrayVisualizer.shuffleEnabled() ? 0.5 : 0, Writes); + } + }, + SMB3_FIXED { + @Override + public String getName() { + return "SMB3 Matching Game (Fixed)"; + } + @Override + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + // Multiply length by 5/6 to simulate that bottom right corner easter egg + smb3Shuffle(array, (int) (arrayVisualizer.getCurrentLength() * 5.0 / 6.0), true, + arrayVisualizer.shuffleEnabled() ? 0.5 : 0, Writes); + } }; + // TODO: Common fields and constructor for the RNG and various visualization context objects + + // counting sort public void sort(int[] array, int start, int end, double sleep, Writes Writes) { int min = array[start], max = min; for (int i = start+1; i < end; i++) { @@ -1479,6 +1506,85 @@ public void shuffle(int[] array, int start, int end, double sleep, Writes Writes } } + /** + * Did you know the card matching minigame in Super Mario Bros. 3 only has 8 possible layouts? It's true! + * But did you know that's not an intentional feature, but the result of an + * incredibly sloppy shuffle routine? + *

+ * This method emulates that shuffle, with the option to fix or preserve the bugs that resulted in only 8 outcomes. + * It was only ever designed to operate on a list of exactly 15 elements, but I've adapted it to work for any length. + * + * @param array + * @param length + * @param fixed True to fix the bugs and allow for more possibilities. False for a more accurate experience with + * only 8 possible outcomes. + * @param sleep Sleep amount for visualizations + * @param Writes + */ + protected void smb3Shuffle(int[] array, int length, boolean fixed, double sleep, Writes Writes) { + + Random rng = new Random(); + + // There were always 3 loops of the main shuffle algorithm, + // which did a rotation and a sequence of "triple swaps." + for (int numShuffles = 0; numShuffles < 3; numShuffles++) { + + int numRotates = fixed + ? rng.nextInt(length) // It was supposed to be any number from 0 to 14, + : rng.nextInt(2) * 2 + 1; // But with bugs, it was always 1 or 3 + // It's supposed to be a rightRotate, but all I have is this leftRotate function, so numRotates needs to be + // adapted. It doesn't really matter in the fixed version, but it does for the broken version. + leftRotate(array, length - numRotates, length, sleep, Writes); + + if (length <= 10) return; + + int x = fixed + ? rng.nextInt(length - 10) // It had this weird loop that did "triple swaps" down from a random starting point, + : 0; // But with bugs, it always started at 0. + for (; x >= 0; x -= 2) { + // Triple swap + Writes.swap(array, x, x + 5, sleep, true, false); + Writes.swap(array, x + 5, x + 10, sleep, true, false); + } + } + + } + + // https://www.geeksforgeeks.org/array-rotation/# + // This code is clever, but explained horribly. I will redo the comments. + // TODO: HALF_ROTATE may be interested in this function. + + /*Function to left rotate arr[] of size n by d*/ + protected void leftRotate(int arr[], int d, int n, double sleep, Writes Writes) { + /* To handle if d >= n */ + d = d % n; + int i, j, k, temp; + int g_c_d = gcd(d, n); + for (i = 0; i < g_c_d; i++) { + /* move i-th values of blocks */ + temp = arr[i]; + j = i; + while (true) { + k = j + d; + if (k >= n) + k = k - n; + if (k == i) + break; + Writes.write(arr, j, arr[k], sleep, true, false); + j = k; + } + Writes.write(arr, j, temp, sleep, true, false); + } + } + + protected int gcd(int a, int b) { + if (b == 0) + return a; + else + return gcd(b, a % b); + } + + // TODO: display while shuffling public abstract String getName(); public abstract void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes); } From e9773761c8681a0e75a3f483b9af75893cbb9aae Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 11 Nov 2023 04:08:23 -0500 Subject: [PATCH 07/27] document leftRotation --- .../java/io/github/arrayv/utils/Shuffles.java | 64 ++++++++++++------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/src/main/java/io/github/arrayv/utils/Shuffles.java b/src/main/java/io/github/arrayv/utils/Shuffles.java index fdfce038..7ab69e68 100644 --- a/src/main/java/io/github/arrayv/utils/Shuffles.java +++ b/src/main/java/io/github/arrayv/utils/Shuffles.java @@ -1534,7 +1534,7 @@ protected void smb3Shuffle(int[] array, int length, boolean fixed, double sleep, : rng.nextInt(2) * 2 + 1; // But with bugs, it was always 1 or 3 // It's supposed to be a rightRotate, but all I have is this leftRotate function, so numRotates needs to be // adapted. It doesn't really matter in the fixed version, but it does for the broken version. - leftRotate(array, length - numRotates, length, sleep, Writes); + leftRotate(array, length, length - numRotates, sleep, Writes); if (length <= 10) return; @@ -1550,38 +1550,54 @@ protected void smb3Shuffle(int[] array, int length, boolean fixed, double sleep, } - // https://www.geeksforgeeks.org/array-rotation/# - // This code is clever, but explained horribly. I will redo the comments. // TODO: HALF_ROTATE may be interested in this function. - - /*Function to left rotate arr[] of size n by d*/ - protected void leftRotate(int arr[], int d, int n, double sleep, Writes Writes) { - /* To handle if d >= n */ - d = d % n; - int i, j, k, temp; - int g_c_d = gcd(d, n); - for (i = 0; i < g_c_d; i++) { - /* move i-th values of blocks */ - temp = arr[i]; - j = i; + /** + * Does a left rotation with a minimal number of swaps and O(1) extra memory, based on the observation that a + * rotation can be broken up into one or more independent cycles of swaps. The number of cycles is gcd(length, rotation). + * If you don't do it this way, you end up stumbling over yourself and overwriting values that aren't meant to be + * written yet. + *

+ * Adapted from Geeksforgeeks. I had to fix it up a + * little cause those comments were stanky. + *

+ * Examples: + *

+ * Length = 9, rotation = 2:
+ * 0 -> 2 -> 4 -> 6 -> 8 -> 1 -> 3 -> 5 -> 7 -> 0 + *

+ * Length = 10, rotation = 2:
+ * 0 -> 2 -> 4 -> 6 -> 8 -> 0
+ * 1 -> 3 -> 5 -> 7 -> 9 -> 1 + * + * @param array + * @param length + * @param rotation Rotation amount + * @param sleep Sleep amount for visualizations + * @param Writes + */ + protected void leftRotate(int[] array, int length, int rotation, double sleep, Writes Writes) { + rotation %= length; // To handle if rotation >= length + int cycle, j, k, temp; + int gcd = gcd(rotation, length); + for (cycle = 0; cycle < gcd; cycle++) { + // Rotate cycle + temp = array[cycle]; + j = cycle; while (true) { - k = j + d; - if (k >= n) - k = k - n; - if (k == i) + k = (j + rotation) % length; + if (k == cycle) break; - Writes.write(arr, j, arr[k], sleep, true, false); + Writes.write(array, j, array[k], sleep, true, false); j = k; } - Writes.write(arr, j, temp, sleep, true, false); + Writes.write(array, j, temp, sleep, true, false); } } protected int gcd(int a, int b) { - if (b == 0) - return a; - else - return gcd(b, a % b); + return b == 0 + ? a + : gcd(b, a % b); } // TODO: display while shuffling From 272beabf1026f452976f7587815fb29cc68b0195 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 11 Nov 2023 05:03:25 -0500 Subject: [PATCH 08/27] first pass at minimizing parameters --- .../io/github/arrayv/utils/ShuffleInfo.java | 5 +- .../java/io/github/arrayv/utils/Shuffles.java | 310 ++++++++++-------- 2 files changed, 176 insertions(+), 139 deletions(-) diff --git a/src/main/java/io/github/arrayv/utils/ShuffleInfo.java b/src/main/java/io/github/arrayv/utils/ShuffleInfo.java index 7451f7a0..d9ab95e9 100644 --- a/src/main/java/io/github/arrayv/utils/ShuffleInfo.java +++ b/src/main/java/io/github/arrayv/utils/ShuffleInfo.java @@ -115,10 +115,7 @@ public void shuffle(int[] array, ArrayVisualizer arrayVisualizer) { } } else { assert shuffle != null; - Delays Delays = arrayVisualizer.getDelays(); - Highlights Highlights = arrayVisualizer.getHighlights(); - Writes Writes = arrayVisualizer.getWrites(); - this.shuffle.shuffleArray(array, arrayVisualizer, Delays, Highlights, Writes); + this.shuffle.shuffleArray(array, arrayVisualizer); } } } diff --git a/src/main/java/io/github/arrayv/utils/Shuffles.java b/src/main/java/io/github/arrayv/utils/Shuffles.java index 7ab69e68..23181e2c 100644 --- a/src/main/java/io/github/arrayv/utils/Shuffles.java +++ b/src/main/java/io/github/arrayv/utils/Shuffles.java @@ -48,10 +48,11 @@ public String getName() { return "Randomly"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - shuffle(array, 0, currentLen, delay ? 1 : 0, Writes); + shuffle(array, 0, currentLen, delay ? 1 : 0); } }, REVERSE { @@ -59,7 +60,8 @@ public String getName() { return "Backwards"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); Writes.reversal(array, 0, currentLen-1, delay ? 1 : 0, true, false); @@ -70,9 +72,9 @@ public String getName() { return "Slight Shuffle"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); - Random random = new Random(); for (int i = 0; i < Math.max(currentLen / 20, 1); i++){ Writes.swap(array, random.nextInt(currentLen), random.nextInt(currentLen), 0, true, false); @@ -86,7 +88,8 @@ public String getName() { return "No Shuffle"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); for (int i = 0; i < currentLen; i++) { Highlights.markArray(1, i); @@ -99,10 +102,11 @@ public String getName() { return "Sorted"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - this.sort(array, 0, currentLen, delay ? 1 : 0, Writes); + this.sort(array, 0, currentLen, delay ? 1 : 0); } }, NAIVE { @@ -110,10 +114,10 @@ public String getName() { return "Naive Randomly"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - Random random = new Random(); for (int i = 0; i < currentLen; i++) Writes.swap(array, i, random.nextInt(currentLen), delay ? 1 : 0, true, false); @@ -124,11 +128,11 @@ public String getName() { return "Scrambled Tail"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - Random random = new Random(); int[] aux = new int[currentLen]; int i = 0, j = 0, k = 0; while (i < currentLen) { @@ -139,7 +143,7 @@ public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays De Writes.write(array, j++, array[i++], delay ? 1 : 0, true, false); } Writes.arraycopy(aux, 0, array, j, k, delay ? 1 : 0, true, false); - shuffle(array, j, currentLen, delay ? 2 : 0, Writes); + shuffle(array, j, currentLen, delay ? 2 : 0); } }, SHUFFLED_HEAD { @@ -147,11 +151,11 @@ public String getName() { return "Scrambled Head"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - Random random = new Random(); int[] aux = new int[currentLen]; int i = currentLen - 1, j = currentLen - 1, k = 0; while (i >= 0) { @@ -162,7 +166,7 @@ public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays De Writes.write(array, j--, array[i--], delay ? 1 : 0, true, false); } Writes.arraycopy(aux, 0, array, 0, k, delay ? 1 : 0, true, false); - shuffle(array, 0, j, delay ? 2 : 0, Writes); + shuffle(array, 0, j, delay ? 2 : 0); } }, MOVED_ELEMENT { @@ -170,10 +174,10 @@ public String getName() { return "Shifted Element"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - Random random = new Random(); int start = random.nextInt(currentLen); int dest = random.nextInt(currentLen); @@ -189,15 +193,15 @@ public String getName() { return "Noisy"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - Random random = new Random(); int i, size = Math.max(4, (int)(Math.sqrt(currentLen)/2)); for (i = 0; i+size <= currentLen; i += random.nextInt(size-1)+1) - shuffle(array, i, i+size, delay ? 0.5 : 0, Writes); - shuffle(array, i, currentLen, delay ? 0.5 : 0, Writes); + shuffle(array, i, i+size, delay ? 0.5 : 0); + shuffle(array, i, currentLen, delay ? 0.5 : 0); } }, SHUFFLED_ODDS { @@ -205,9 +209,9 @@ public String getName() { return "Scrambled Odds"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); - Random random = new Random(); for (int i = 1; i < currentLen; i += 2){ int randomIndex = (((random.nextInt(currentLen - i) / 2)) * 2) + i; @@ -222,7 +226,8 @@ public String getName() { return "Final Merge Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); int count = 2; @@ -243,14 +248,15 @@ public String getName() { return "Shuffled Final Merge"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - this.shuffle(array, 0, currentLen, delay ? 0.5 : 0, Writes); + this.shuffle(array, 0, currentLen, delay ? 0.5 : 0); Highlights.clearMark(2); - this.sort(array, 0, currentLen / 2, delay ? 0.5 : 0, Writes); - this.sort(array, currentLen / 2, currentLen, delay ? 0.5 : 0, Writes); + this.sort(array, 0, currentLen / 2, delay ? 0.5 : 0); + this.sort(array, currentLen / 2, currentLen, delay ? 0.5 : 0); } }, SHUFFLED_HALF { @@ -258,13 +264,14 @@ public String getName() { return "Shuffled Half"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - this.shuffle(array, 0, currentLen, delay ? 2/3d : 0, Writes); + this.shuffle(array, 0, currentLen, delay ? 2/3d : 0); Highlights.clearMark(2); - this.sort(array, 0, currentLen / 2, delay ? 2/3d : 0, Writes); + this.sort(array, 0, currentLen / 2, delay ? 2/3d : 0); } }, PARTITIONED { @@ -272,14 +279,15 @@ public String getName() { return "Partitioned"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - this.sort(array, 0, currentLen, delay ? 0.5 : 0, Writes); + this.sort(array, 0, currentLen, delay ? 0.5 : 0); Highlights.clearMark(2); - this.shuffle(array, 0, currentLen/2, delay ? 0.5 : 0, Writes); - this.shuffle(array, currentLen/2, currentLen, delay ? 0.5 : 0, Writes); + this.shuffle(array, 0, currentLen/2, delay ? 0.5 : 0); + this.shuffle(array, currentLen/2, currentLen, delay ? 0.5 : 0); } }, SAWTOOTH { @@ -287,7 +295,8 @@ public String getName() { return "Sawtooth"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); int count = 4; @@ -308,7 +317,8 @@ public String getName() { return "Pipe Organ"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -329,7 +339,8 @@ public String getName() { return "Final Bitonic Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -352,7 +363,8 @@ public String getName() { return "Interlaced"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -376,7 +388,8 @@ public String getName() { return "Double Layered"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); for (int i = 0; i < currentLen / 2; i += 2) { @@ -390,7 +403,8 @@ public String getName() { return "Final Radix"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -412,7 +426,8 @@ public String getName() { return "Real Final Radix"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); int mask = 0; @@ -440,14 +455,15 @@ public String getName() { return "Recursive Final Radix"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - weaveRec(array, 0, currentLen, 1, delay ? 0.5 : 0, Writes); + weaveRec(array, 0, currentLen, 1, delay ? 0.5 : 0); } - public void weaveRec(int[] array, int pos, int length, int gap, double delay, Writes Writes) { + public void weaveRec(int[] array, int pos, int length, int gap, double delay) { if (length < 2) return; int mod2 = length % 2; @@ -463,8 +479,8 @@ public void weaveRec(int[] array, int pos, int length, int gap, double delay, Wr Writes.write(array, j+gap, temp[k], delay, true, false); } - weaveRec(array, pos, mid+mod2, 2*gap, delay/2, Writes); - weaveRec(array, pos+gap, mid, 2*gap, delay/2, Writes); + weaveRec(array, pos, mid+mod2, 2*gap, delay/2); + weaveRec(array, pos+gap, mid, 2*gap, delay/2); } }, HALF_ROTATION { @@ -472,7 +488,8 @@ public String getName() { return "Half Rotation"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -496,7 +513,8 @@ public String getName() { return "Half Reversed"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -509,7 +527,8 @@ public String getName() { return "BST Traversal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); int[] temp = Arrays.copyOf(array, currentLen); @@ -546,7 +565,8 @@ public String getName() { return "Inverted BST"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -588,7 +608,8 @@ public String getName() { return "Logarithmic Slopes"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -610,7 +631,8 @@ public String getName() { return "Heapified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -623,7 +645,8 @@ public String getName() { return "Smoothified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); SmoothSort smoothSort = new SmoothSort(arrayVisualizer); @@ -635,7 +658,8 @@ public String getName() { return "Poplarified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); PoplarHeapSort poplarHeapSort = new PoplarHeapSort(arrayVisualizer); @@ -647,7 +671,8 @@ public String getName() { return "Triangular Heapified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -666,21 +691,21 @@ public String getName() { return "First Circle Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - Reads Reads = arrayVisualizer.getReads(); - shuffle(array, 0, currentLen, delay ? 0.5 : 0, Writes); + shuffle(array, 0, currentLen, delay ? 0.5 : 0); int n = 1; //noinspection StatementWithEmptyBody for (; n < currentLen; n*=2); - circleSortRoutine(array, 0, n-1, currentLen, delay ? 0.5 : 0, Reads, Writes); + circleSortRoutine(array, 0, n-1, currentLen, delay ? 0.5 : 0); } - public void circleSortRoutine(int[] array, int lo, int hi, int end, double sleep, Reads Reads, Writes Writes) { + public void circleSortRoutine(int[] array, int lo, int hi, int end, double sleep) { if (lo == hi) return; int high = hi; @@ -695,8 +720,8 @@ public void circleSortRoutine(int[] array, int lo, int hi, int end, double sleep hi--; } - circleSortRoutine(array, low, low + mid, end, sleep/2, Reads, Writes); - if (low + mid + 1 < end) circleSortRoutine(array, low + mid + 1, high, end, sleep/2, Reads, Writes); + circleSortRoutine(array, low, low + mid, end, sleep/2); + if (low + mid + 1 < end) circleSortRoutine(array, low + mid + 1, high, end, sleep/2); } }, PAIRWISE { @@ -704,12 +729,13 @@ public String getName() { return "Final Pairwise Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); Reads Reads = arrayVisualizer.getReads(); - shuffle(array, 0, currentLen, delay ? 0.5 : 0, Writes); + shuffle(array, 0, currentLen, delay ? 0.5 : 0); //create pairs for (int i = 1; i < currentLen; i+=2) @@ -743,21 +769,22 @@ public String getName() { return "Recursive Reversal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - reversalRec(array, 0, currentLen, delay ? 1 : 0, Writes); + reversalRec(array, 0, currentLen, delay ? 1 : 0); } - public void reversalRec(int[] array, int a, int b, double sleep, Writes Writes) { + public void reversalRec(int[] array, int a, int b, double sleep) { if (b-a < 2) return; Writes.reversal(array, a, b-1, sleep, true, false); int m = (a+b)/2; - this.reversalRec(array, a, m, sleep/2, Writes); - this.reversalRec(array, m, b, sleep/2, Writes); + this.reversalRec(array, a, m, sleep/2); + this.reversalRec(array, m, b, sleep/2); } }, GRAY_CODE { @@ -765,14 +792,15 @@ public String getName() { return "Gray Code Fractal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); - reversalRec(array, 0, currentLen, false, delay ? 1 : 0, Writes); + reversalRec(array, 0, currentLen, false, delay ? 1 : 0); } - public void reversalRec(int[] array, int a, int b, boolean bw, double sleep, Writes Writes) { + public void reversalRec(int[] array, int a, int b, boolean bw, double sleep) { if (b-a < 3) return; int m = (a+b)/2; @@ -780,8 +808,8 @@ public void reversalRec(int[] array, int a, int b, boolean bw, double sleep, Wri if (bw) Writes.reversal(array, a, m-1, sleep, true, false); else Writes.reversal(array, m, b-1, sleep, true, false); - this.reversalRec(array, a, m, false, sleep/2, Writes); - this.reversalRec(array, m, b, true, sleep/2, Writes); + this.reversalRec(array, a, m, false, sleep/2); + this.reversalRec(array, m, b, true, sleep/2); } }, SIERPINSKI { @@ -789,7 +817,8 @@ public String getName() { return "Sierpinski Triangle"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); int[] triangle = new int[currentLen]; triangleRec(triangle, 0, currentLen); @@ -820,7 +849,8 @@ public String getName() { return "Triangular"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); int[] triangle = new int[currentLen]; @@ -857,7 +887,8 @@ public String getName() { return "Quicksort Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -866,9 +897,6 @@ public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays De } }, PDQ_BAD { - Reads Reads; - Writes Writes; - Highlights Highlights; boolean delay; double sleep; @@ -898,13 +926,11 @@ public String getName() { return "PDQ Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); delay = arrayVisualizer.shuffleEnabled(); sleep = delay ? 1 : 0; - this.Reads = arrayVisualizer.getReads(); - this.Writes = Writes; - this.Highlights = Highlights; int[] copy = new int[currentLen]; @@ -918,7 +944,7 @@ public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays De Writes.write(temp, i, gas, sleep, true, true); } - pdqLoop(array, 0, currentLen, false, PDQSorting.pdqLog(currentLen)); + pdqLoop(array, 0, currentLen, PDQSorting.pdqLog(currentLen)); for (int i = 0; i < currentLen; i++) { Writes.write(array, i, copy[temp[i] - 1], sleep, true, false); @@ -955,7 +981,7 @@ protected int compare(int ap, int bp) { return Integer.compare(temp[a], temp[b]); } - protected void pdqLoop(int[] array, int begin, int end, boolean branchless, int badAllowed) { + protected void pdqLoop(int[] array, int begin, int end, int badAllowed) { boolean leftmost = true; while (true) { @@ -1030,7 +1056,7 @@ && pdqPartialInsertSort(array, pivotPos + 1, end)) return; } - this.pdqLoop(array, begin, pivotPos, branchless, badAllowed); + this.pdqLoop(array, begin, pivotPos, badAllowed); begin = pivotPos + 1; leftmost = false; } @@ -1050,7 +1076,7 @@ private void siftDown(int[] array, int root, int dist, int start, double sleep, Highlights.markArray(1, start + root - 1); Highlights.markArray(2, start + leaf - 1); if (compare(array[start + root - 1], array[start + leaf - 1]) == compareVal) { - Writes.swap(array, start + root - 1, start + leaf - 1, 0, true, false); + Writes.swap(array, start + root - 1, start + leaf - 1, sleep, true, false); root = leaf; } else break; } @@ -1215,7 +1241,8 @@ public String getName() { return "Grailsort Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -1227,23 +1254,23 @@ public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays De int numKeys = (currentLen - 1) / blockLen + 1; int keys = blockLen + numKeys; - shuffle(array, 0, currentLen, delay ? 0.25 : 0, Writes); - sort(array, 0, keys, delay ? 0.25 : 0, Writes); + shuffle(array, 0, currentLen, delay ? 0.25 : 0); + sort(array, 0, keys, delay ? 0.25 : 0); Writes.reversal(array, 0, keys-1, delay ? 0.25 : 0, true, false); Highlights.clearMark(2); - sort(array, keys, currentLen, delay ? 0.25 : 0, Writes); + sort(array, keys, currentLen, delay ? 0.25 : 0); - push(array, keys, currentLen, blockLen, delay ? 0.25 : 0, Writes); + push(array, keys, currentLen, blockLen, delay ? 0.25 : 0); } } - public void rotate(int[] array, int a, int m, int b, double sleep, Writes Writes) { + public void rotate(int[] array, int a, int m, int b, double sleep) { Writes.reversal(array, a, m-1, sleep, true, false); Writes.reversal(array, m, b-1, sleep, true, false); Writes.reversal(array, a, b-1, sleep, true, false); } - public void push(int[] array, int a, int b, int bLen, double sleep, Writes Writes) { + public void push(int[] array, int a, int b, int bLen, double sleep) { int len = b-a, b1 = b - len%bLen, len1 = b1-a; if (len1 <= 2*bLen) return; @@ -1252,16 +1279,16 @@ public void push(int[] array, int a, int b, int bLen, double sleep, Writes Write while (2*m < len) m *= 2; m += a; - if (b1-m < bLen) push(array, a, m, bLen, sleep, Writes); + if (b1-m < bLen) push(array, a, m, bLen, sleep); else { m = a+b1-m; - rotate(array, m-(bLen-2), b1-(bLen-1), b1, sleep, Writes); + rotate(array, m-(bLen-2), b1-(bLen-1), b1, sleep); Writes.multiSwap(array, a, m, sleep/2, true, false); - rotate(array, a, m, b1, sleep, Writes); + rotate(array, a, m, b1, sleep); m = a+b1-m; - push(array, a, m, bLen, sleep/2, Writes); - push(array, m, b, bLen, sleep/2, Writes); + push(array, a, m, bLen, sleep/2); + push(array, m, b, bLen, sleep/2); } } }, @@ -1270,7 +1297,8 @@ public String getName() { return "Shuffle Merge Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int n = arrayVisualizer.getCurrentLength(); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -1294,23 +1322,23 @@ public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays De dec -= d; k++; } - shuffleMergeBad(array, tmp, i, j, k, delay ? sleep : 0, Writes); + shuffleMergeBad(array, tmp, i, j, k, delay ? sleep : 0); i = k; } d *= 2; } } - public void shuffleMergeBad(int[] array, int[] tmp, int a, int m, int b, double sleep, Writes Writes) { + public void shuffleMergeBad(int[] array, int[] tmp, int a, int m, int b, double sleep) { if ((b-a)%2 == 1) { if (m-a > b-m) a++; else b--; } - shuffleBad(array, tmp, a, b, sleep, Writes); + shuffleBad(array, tmp, a, b, sleep); } //length is always even - public void shuffleBad(int[] array, int[] tmp, int a, int b, double sleep, Writes Writes) { + public void shuffleBad(int[] array, int[] tmp, int a, int b, double sleep) { if (b-a < 2) return; int m = (a+b)/2; @@ -1334,7 +1362,8 @@ public String getName() { return "Bit Reversal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); int len = 1 << (int)(Math.log(arrayVisualizer.getCurrentLength())/Math.log(2)); boolean delay = arrayVisualizer.shuffleEnabled(); @@ -1387,21 +1416,21 @@ public String getName() { return "Randomly w/ Blocks"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); int blockSize = pow2lte((int)Math.sqrt(currentLen)); currentLen -= currentLen%blockSize; boolean delay = arrayVisualizer.shuffleEnabled(); double sleep = delay ? 1 : 0; - Random random = new Random(); for (int i = 0; i < currentLen; i += blockSize) { int randomIndex = random.nextInt((currentLen - i) / blockSize) * blockSize + i; - blockSwap(array, i, randomIndex, blockSize, Writes, sleep); + blockSwap(array, i, randomIndex, blockSize, sleep); } } - private void blockSwap(int[] array, int a, int b, int len, Writes Writes, double sleep) { + private void blockSwap(int[] array, int a, int b, int len, double sleep) { for (int i = 0; i < len; i++) { Writes.swap(array, a + i, b + i, sleep, true, false); } @@ -1420,7 +1449,8 @@ public String getName() { return "Block Reverse"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); int currentLen = arrayVisualizer.getCurrentLength(); int blockSize = pow2lte((int)Math.sqrt(currentLen)); currentLen -= currentLen % blockSize; @@ -1429,13 +1459,13 @@ public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays De int i = 0, j = currentLen - blockSize; while (i < j) { - blockSwap(array, i, j, blockSize, Writes, sleep); + blockSwap(array, i, j, blockSize, sleep); i += blockSize; j -= blockSize; } } - private void blockSwap(int[] array, int a, int b, int len, Writes Writes, double sleep) { + private void blockSwap(int[] array, int a, int b, int len, double sleep) { for (int i = 0; i < len; i++) { Writes.swap(array, a + i, b + i, sleep, true, false); } @@ -1454,10 +1484,11 @@ public String getName() { return "SMB3 Matching Game"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); // Multiply length by 5/6 to simulate that bottom right corner easter egg smb3Shuffle(array, (int) (arrayVisualizer.getCurrentLength() * 5.0 / 6.0), false, - arrayVisualizer.shuffleEnabled() ? 0.5 : 0, Writes); + arrayVisualizer.shuffleEnabled() ? 0.5 : 0); } }, SMB3_FIXED { @@ -1466,17 +1497,31 @@ public String getName() { return "SMB3 Matching Game (Fixed)"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + init(arrayVisualizer); // Multiply length by 5/6 to simulate that bottom right corner easter egg smb3Shuffle(array, (int) (arrayVisualizer.getCurrentLength() * 5.0 / 6.0), true, - arrayVisualizer.shuffleEnabled() ? 0.5 : 0, Writes); + arrayVisualizer.shuffleEnabled() ? 0.5 : 0); } }; - // TODO: Common fields and constructor for the RNG and various visualization context objects + protected Delays Delays; + protected Highlights Highlights; + protected Writes Writes; + protected Reads Reads; + + protected Random random = new Random(); + + // Could make this a public method and force the user to call it? + protected void init(ArrayVisualizer arrayVisualizer) { + Delays = arrayVisualizer.getDelays(); + Highlights = arrayVisualizer.getHighlights(); + Writes = arrayVisualizer.getWrites(); + Reads = arrayVisualizer.getReads(); + } // counting sort - public void sort(int[] array, int start, int end, double sleep, Writes Writes) { + public void sort(int[] array, int start, int end, double sleep) { int min = array[start], max = min; for (int i = start+1; i < end; i++) { if (array[i] < min) min = array[i]; @@ -1498,8 +1543,7 @@ public void sort(int[] array, int start, int end, double sleep, Writes Writes) { } } - public void shuffle(int[] array, int start, int end, double sleep, Writes Writes) { - Random random = new Random(); + public void shuffle(int[] array, int start, int end, double sleep) { for (int i = start; i < end; i++){ int randomIndex = random.nextInt(end - i) + i; Writes.swap(array, i, randomIndex, sleep, true, false); @@ -1519,28 +1563,25 @@ public void shuffle(int[] array, int start, int end, double sleep, Writes Writes * @param fixed True to fix the bugs and allow for more possibilities. False for a more accurate experience with * only 8 possible outcomes. * @param sleep Sleep amount for visualizations - * @param Writes */ - protected void smb3Shuffle(int[] array, int length, boolean fixed, double sleep, Writes Writes) { - - Random rng = new Random(); + protected void smb3Shuffle(int[] array, int length, boolean fixed, double sleep) { // There were always 3 loops of the main shuffle algorithm, // which did a rotation and a sequence of "triple swaps." for (int numShuffles = 0; numShuffles < 3; numShuffles++) { int numRotates = fixed - ? rng.nextInt(length) // It was supposed to be any number from 0 to 14, - : rng.nextInt(2) * 2 + 1; // But with bugs, it was always 1 or 3 + ? random.nextInt(length) // It was supposed to be any number from 0 to 14, + : random.nextInt(2) * 2 + 1; // But with bugs, it was always 1 or 3 // It's supposed to be a rightRotate, but all I have is this leftRotate function, so numRotates needs to be // adapted. It doesn't really matter in the fixed version, but it does for the broken version. - leftRotate(array, length, length - numRotates, sleep, Writes); + leftRotate(array, length, length - numRotates, sleep); if (length <= 10) return; int x = fixed - ? rng.nextInt(length - 10) // It had this weird loop that did "triple swaps" down from a random starting point, - : 0; // But with bugs, it always started at 0. + ? random.nextInt(length - 10) // It had this weird loop that did "triple swaps" down from a random starting point, + : 0; // But with bugs, it always started at 0. for (; x >= 0; x -= 2) { // Triple swap Writes.swap(array, x, x + 5, sleep, true, false); @@ -1573,9 +1614,8 @@ protected void smb3Shuffle(int[] array, int length, boolean fixed, double sleep, * @param length * @param rotation Rotation amount * @param sleep Sleep amount for visualizations - * @param Writes */ - protected void leftRotate(int[] array, int length, int rotation, double sleep, Writes Writes) { + protected void leftRotate(int[] array, int length, int rotation, double sleep) { rotation %= length; // To handle if rotation >= length int cycle, j, k, temp; int gcd = gcd(rotation, length); @@ -1602,5 +1642,5 @@ protected int gcd(int a, int b) { // TODO: display while shuffling public abstract String getName(); - public abstract void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes); + public abstract void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer); } From 65bd221c984444bfcaad8c25c8503c0f6beaf9d2 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 11 Nov 2023 05:44:14 -0500 Subject: [PATCH 09/27] second pass at minimizing parameter clutter --- .../io/github/arrayv/utils/ShuffleInfo.java | 3 +- .../java/io/github/arrayv/utils/Shuffles.java | 205 +++++------------- 2 files changed, 56 insertions(+), 152 deletions(-) diff --git a/src/main/java/io/github/arrayv/utils/ShuffleInfo.java b/src/main/java/io/github/arrayv/utils/ShuffleInfo.java index d9ab95e9..42bcc2bd 100644 --- a/src/main/java/io/github/arrayv/utils/ShuffleInfo.java +++ b/src/main/java/io/github/arrayv/utils/ShuffleInfo.java @@ -115,7 +115,8 @@ public void shuffle(int[] array, ArrayVisualizer arrayVisualizer) { } } else { assert shuffle != null; - this.shuffle.shuffleArray(array, arrayVisualizer); + shuffle.init(arrayVisualizer); + shuffle.shuffleArray(array, arrayVisualizer.getCurrentLength()); } } } diff --git a/src/main/java/io/github/arrayv/utils/Shuffles.java b/src/main/java/io/github/arrayv/utils/Shuffles.java index 23181e2c..cbd33fd0 100644 --- a/src/main/java/io/github/arrayv/utils/Shuffles.java +++ b/src/main/java/io/github/arrayv/utils/Shuffles.java @@ -48,9 +48,7 @@ public String getName() { return "Randomly"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); shuffle(array, 0, currentLen, delay ? 1 : 0); } @@ -60,9 +58,7 @@ public String getName() { return "Backwards"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); Writes.reversal(array, 0, currentLen-1, delay ? 1 : 0, true, false); } @@ -72,10 +68,7 @@ public String getName() { return "Slight Shuffle"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); - + public void shuffleArray(int[] array, int currentLen) { for (int i = 0; i < Math.max(currentLen / 20, 1); i++){ Writes.swap(array, random.nextInt(currentLen), random.nextInt(currentLen), 0, true, false); @@ -88,9 +81,7 @@ public String getName() { return "No Shuffle"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { for (int i = 0; i < currentLen; i++) { Highlights.markArray(1, i); if (arrayVisualizer.shuffleEnabled()) Delays.sleep(1); @@ -102,9 +93,7 @@ public String getName() { return "Sorted"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); this.sort(array, 0, currentLen, delay ? 1 : 0); } @@ -114,9 +103,7 @@ public String getName() { return "Naive Randomly"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); for (int i = 0; i < currentLen; i++) @@ -128,9 +115,7 @@ public String getName() { return "Scrambled Tail"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] aux = new int[currentLen]; @@ -151,9 +136,7 @@ public String getName() { return "Scrambled Head"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] aux = new int[currentLen]; @@ -174,9 +157,7 @@ public String getName() { return "Shifted Element"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int start = random.nextInt(currentLen); @@ -193,9 +174,7 @@ public String getName() { return "Noisy"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int i, size = Math.max(4, (int)(Math.sqrt(currentLen)/2)); @@ -209,10 +188,7 @@ public String getName() { return "Scrambled Odds"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); - + public void shuffleArray(int[] array, int currentLen) { for (int i = 1; i < currentLen; i += 2){ int randomIndex = (((random.nextInt(currentLen - i) / 2)) * 2) + i; Writes.swap(array, i, randomIndex, 0, true, false); @@ -226,9 +202,7 @@ public String getName() { return "Final Merge Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int count = 2; @@ -248,9 +222,7 @@ public String getName() { return "Shuffled Final Merge"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); this.shuffle(array, 0, currentLen, delay ? 0.5 : 0); @@ -264,9 +236,7 @@ public String getName() { return "Shuffled Half"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); this.shuffle(array, 0, currentLen, delay ? 2/3d : 0); @@ -279,9 +249,7 @@ public String getName() { return "Partitioned"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); this.sort(array, 0, currentLen, delay ? 0.5 : 0); @@ -295,9 +263,7 @@ public String getName() { return "Sawtooth"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int count = 4; @@ -317,9 +283,7 @@ public String getName() { return "Pipe Organ"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -339,9 +303,7 @@ public String getName() { return "Final Bitonic Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -363,9 +325,7 @@ public String getName() { return "Interlaced"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] referenceArray = new int[currentLen]; @@ -388,10 +348,7 @@ public String getName() { return "Double Layered"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); - + public void shuffleArray(int[] array, int currentLen) { for (int i = 0; i < currentLen / 2; i += 2) { Writes.swap(array, i, currentLen - i - 1, 0, true, false); if (arrayVisualizer.shuffleEnabled()) Delays.sleep(1); @@ -403,9 +360,7 @@ public String getName() { return "Final Radix"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); currentLen -= currentLen % 2; @@ -426,10 +381,7 @@ public String getName() { return "Real Final Radix"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); - + public void shuffleArray(int[] array, int currentLen) { int mask = 0; for (int i = 0; i < currentLen; i++) while (mask < array[i]) mask = (mask << 1) + 1; @@ -455,9 +407,7 @@ public String getName() { return "Recursive Final Radix"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); weaveRec(array, 0, currentLen, 1, delay ? 0.5 : 0); @@ -488,9 +438,7 @@ public String getName() { return "Half Rotation"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int a = 0, m = (currentLen + 1) / 2; @@ -513,9 +461,7 @@ public String getName() { return "Half Reversed"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); Writes.reversal(array, 0, currentLen-1, delay ? 1 : 0, true, false); @@ -527,9 +473,7 @@ public String getName() { return "BST Traversal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { int[] temp = Arrays.copyOf(array, currentLen); // credit to sam walko/anon @@ -565,9 +509,7 @@ public String getName() { return "Inverted BST"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -608,9 +550,7 @@ public String getName() { return "Logarithmic Slopes"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -631,9 +571,7 @@ public String getName() { return "Heapified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); MaxHeapSort heapSort = new MaxHeapSort(arrayVisualizer); @@ -645,10 +583,7 @@ public String getName() { return "Smoothified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); - + public void shuffleArray(int[] array, int currentLen) { SmoothSort smoothSort = new SmoothSort(arrayVisualizer); smoothSort.smoothHeapify(array, currentLen); } @@ -658,10 +593,7 @@ public String getName() { return "Poplarified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); - + public void shuffleArray(int[] array, int currentLen) { PoplarHeapSort poplarHeapSort = new PoplarHeapSort(arrayVisualizer); poplarHeapSort.poplarHeapify(array, 0, currentLen); } @@ -671,9 +603,7 @@ public String getName() { return "Triangular Heapified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); if (delay) Delays.setSleepRatio(Delays.getSleepRatio()*10); @@ -691,9 +621,7 @@ public String getName() { return "First Circle Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); shuffle(array, 0, currentLen, delay ? 0.5 : 0); @@ -729,9 +657,7 @@ public String getName() { return "Final Pairwise Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); Reads Reads = arrayVisualizer.getReads(); @@ -769,9 +695,7 @@ public String getName() { return "Recursive Reversal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); reversalRec(array, 0, currentLen, delay ? 1 : 0); @@ -792,9 +716,7 @@ public String getName() { return "Gray Code Fractal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); reversalRec(array, 0, currentLen, false, delay ? 1 : 0); @@ -817,9 +739,7 @@ public String getName() { return "Sierpinski Triangle"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { int[] triangle = new int[currentLen]; triangleRec(triangle, 0, currentLen); @@ -849,9 +769,7 @@ public String getName() { return "Triangular"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] triangle = new int[currentLen]; @@ -887,9 +805,7 @@ public String getName() { return "Quicksort Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); for (int j = currentLen-currentLen%2-2, i = j-1; i >= 0; i-=2, j--) @@ -926,9 +842,7 @@ public String getName() { return "PDQ Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { delay = arrayVisualizer.shuffleEnabled(); sleep = delay ? 1 : 0; @@ -1241,9 +1155,7 @@ public String getName() { return "Grailsort Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); if (currentLen <= 16) Writes.reversal(array, 0, currentLen-1, delay ? 1 : 0, true, false); @@ -1297,9 +1209,7 @@ public String getName() { return "Shuffle Merge Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int n = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int n) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] tmp = new int[n]; @@ -1362,10 +1272,8 @@ public String getName() { return "Bit Reversal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); - int len = 1 << (int)(Math.log(arrayVisualizer.getCurrentLength())/Math.log(2)); + public void shuffleArray(int[] array, int currentLen) { + int len = 1 << (int)(Math.log(currentLen)/Math.log(2)); boolean delay = arrayVisualizer.shuffleEnabled(); boolean pow2 = len == currentLen; @@ -1416,9 +1324,7 @@ public String getName() { return "Randomly w/ Blocks"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { int blockSize = pow2lte((int)Math.sqrt(currentLen)); currentLen -= currentLen%blockSize; boolean delay = arrayVisualizer.shuffleEnabled(); @@ -1449,9 +1355,7 @@ public String getName() { return "Block Reverse"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { int blockSize = pow2lte((int)Math.sqrt(currentLen)); currentLen -= currentLen % blockSize; boolean delay = arrayVisualizer.shuffleEnabled(); @@ -1484,10 +1388,9 @@ public String getName() { return "SMB3 Matching Game"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); + public void shuffleArray(int[] array, int currentLen) { // Multiply length by 5/6 to simulate that bottom right corner easter egg - smb3Shuffle(array, (int) (arrayVisualizer.getCurrentLength() * 5.0 / 6.0), false, + smb3Shuffle(array, (int) (currentLen * 5.0 / 6.0), false, arrayVisualizer.shuffleEnabled() ? 0.5 : 0); } }, @@ -1497,14 +1400,14 @@ public String getName() { return "SMB3 Matching Game (Fixed)"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { - init(arrayVisualizer); + public void shuffleArray(int[] array, int currentLen) { // Multiply length by 5/6 to simulate that bottom right corner easter egg - smb3Shuffle(array, (int) (arrayVisualizer.getCurrentLength() * 5.0 / 6.0), true, + smb3Shuffle(array, (int) (currentLen * 5.0 / 6.0), true, arrayVisualizer.shuffleEnabled() ? 0.5 : 0); } }; + protected ArrayVisualizer arrayVisualizer; protected Delays Delays; protected Highlights Highlights; protected Writes Writes; @@ -1512,8 +1415,8 @@ public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { protected Random random = new Random(); - // Could make this a public method and force the user to call it? - protected void init(ArrayVisualizer arrayVisualizer) { + public final void init(ArrayVisualizer arrayVisualizer) { + this.arrayVisualizer = arrayVisualizer; Delays = arrayVisualizer.getDelays(); Highlights = arrayVisualizer.getHighlights(); Writes = arrayVisualizer.getWrites(); @@ -1642,5 +1545,5 @@ protected int gcd(int a, int b) { // TODO: display while shuffling public abstract String getName(); - public abstract void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer); + public abstract void shuffleArray(int[] array, int currentLen); } From 36e090eea7589317afb98b03ea5dc949a0cadd18 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Wed, 15 Nov 2023 04:53:05 -0500 Subject: [PATCH 10/27] "Use default" no longer accepts your input UNINITIALIZED_VALUE means the textbox was empty. This method never actually checked which button was clicked. --- .../arrayv/panes/JEnhancedOptionPane.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/arrayv/panes/JEnhancedOptionPane.java b/src/main/java/io/github/arrayv/panes/JEnhancedOptionPane.java index e1f58bc6..f514ee24 100644 --- a/src/main/java/io/github/arrayv/panes/JEnhancedOptionPane.java +++ b/src/main/java/io/github/arrayv/panes/JEnhancedOptionPane.java @@ -8,6 +8,16 @@ public final class JEnhancedOptionPane extends JOptionPane { private static final long serialVersionUID = 1L; + /** + * Prompts the user with a textbox and returns their answer, or null if they didn't confirm. + * The buttons are customizable, but option 0 is always defined as the Confirm action. + * + * @param title Title bar + * @param message Prompt message + * @param options Buttons to be clicked. You usually just want to pass a String[] of labels here, e.g. ["OK", "Cancel"]. + * + * @return The user input, or null if they closed out or picked a secondary option. + */ public static String showInputDialog(final String title, final Object message, final Object[] options) throws HeadlessException { final JOptionPane pane = new JOptionPane(message, QUESTION_MESSAGE, @@ -17,10 +27,14 @@ public static String showInputDialog(final String title, final Object message, f pane.setComponentOrientation((getRootFrame()).getComponentOrientation()); pane.setMessageType(QUESTION_MESSAGE); pane.selectInitialValue(); + final JDialog dialog = pane.createDialog(null, title); dialog.setVisible(true); dialog.dispose(); - final Object value = pane.getInputValue(); - return (value == UNINITIALIZED_VALUE) ? null : (String) value; + + final Object input = pane.getInputValue(); + final Object button = pane.getValue(); + + return button == options[0] ? (String) input : null; } } From 383c3e65e528fdf3dbad29b1fca3ba906ec5afbb Mon Sep 17 00:00:00 2001 From: warmCabin Date: Wed, 15 Nov 2023 05:01:58 -0500 Subject: [PATCH 11/27] fix Distribution naming issues --- src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java | 2 +- src/main/java/io/github/arrayv/utils/Distributions.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java b/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java index e0da3ac8..101a8c91 100644 --- a/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java +++ b/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java @@ -81,7 +81,7 @@ public ShuffleDialog(ArrayManager arrayManager, JFrame frame) { } distributions = Arrays.stream(arrayManager.getDistributions()) - .filter(dist -> !dist.getName().equals("Custom")) + .filter(dist -> dist != Distributions.CUSTOM) .collect(Collectors.toList()); Object[] distributionNames = distributions.stream() .map(Distributions::getName).toArray(); diff --git a/src/main/java/io/github/arrayv/utils/Distributions.java b/src/main/java/io/github/arrayv/utils/Distributions.java index c7c44d65..d67365f2 100644 --- a/src/main/java/io/github/arrayv/utils/Distributions.java +++ b/src/main/java/io/github/arrayv/utils/Distributions.java @@ -436,7 +436,7 @@ public int sumDivisors(int n) { }, FSD {// fly straight dangit (OEIS A133058) public String getName() { - return "Fly Straight, Damnit!"; + return "Fly Straight, Dammit!"; } @Override public void initializeArray(int[] array, ArrayVisualizer arrayVisualizer) { @@ -526,7 +526,7 @@ public void initializeArray(int[] array, ArrayVisualizer arrayVisualizer) { private int[] refarray; private int length; public String getName() { - return "Custom"; + return "Custom..."; } @Override public void selectDistribution(int[] array, ArrayVisualizer arrayVisualizer) { From b8fb9d3b8ccbd6555ac6f716827bd678b9acc401 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Wed, 15 Nov 2023 16:15:41 -0500 Subject: [PATCH 12/27] improve error handling for custom distributions --- .../io/github/arrayv/main/ArrayManager.java | 9 ++--- .../io/github/arrayv/utils/Distributions.java | 35 +++++++++++++------ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/main/java/io/github/arrayv/main/ArrayManager.java b/src/main/java/io/github/arrayv/main/ArrayManager.java index 83415c1c..e552b44d 100644 --- a/src/main/java/io/github/arrayv/main/ArrayManager.java +++ b/src/main/java/io/github/arrayv/main/ArrayManager.java @@ -154,10 +154,11 @@ public Distributions getDistribution() { return this.distribution; } public void setDistribution(Distributions choice) { - this.distribution = choice; - this.distribution.selectDistribution(arrayVisualizer.getArray(), arrayVisualizer); - if (!arrayVisualizer.isActive()) - this.initializeArray(arrayVisualizer.getArray()); + if (choice.selectDistribution(arrayVisualizer.getArray(), arrayVisualizer)) { + this.distribution = choice; + if (!arrayVisualizer.isActive()) + this.initializeArray(arrayVisualizer.getArray()); + } } public boolean containsShuffle(Shuffles shuffle) { diff --git a/src/main/java/io/github/arrayv/utils/Distributions.java b/src/main/java/io/github/arrayv/utils/Distributions.java index d67365f2..caa5ff20 100644 --- a/src/main/java/io/github/arrayv/utils/Distributions.java +++ b/src/main/java/io/github/arrayv/utils/Distributions.java @@ -529,24 +529,36 @@ public String getName() { return "Custom..."; } @Override - public void selectDistribution(int[] array, ArrayVisualizer arrayVisualizer) { + public boolean selectDistribution(int[] array, ArrayVisualizer arrayVisualizer) { LoadCustomDistributionDialog dialog = new LoadCustomDistributionDialog(); File file = dialog.getFile(); + if (file == null) { + return false; + } Scanner scanner; try { scanner = new Scanner(file); } catch (FileNotFoundException e) { - JErrorPane.invokeErrorMessage(e); - return; + JErrorPane.invokeCustomErrorMessage("File not found: " + e.getMessage()); + return false; } - scanner.useDelimiter("\\s+"); - this.refarray = new int[arrayVisualizer.getMaximumLength()]; - int current = 0; - while (scanner.hasNext()) { - this.refarray[current++] = Integer.parseInt(scanner.next()); + try { + scanner.useDelimiter("\\s+"); + this.refarray = new int[arrayVisualizer.getMaximumLength()]; + int current = 0; + while (scanner.hasNext()) { + // This gives better error messages than scanner.nextInt() + this.refarray[current++] = Integer.parseInt(scanner.next()); + } + this.length = current; + + return true; + } catch (NumberFormatException e) { + JErrorPane.invokeCustomErrorMessage("Malformed custom sequence: " + e.getMessage()); + return false; + } finally { + scanner.close(); } - this.length = current; - scanner.close(); } @Override public void initializeArray(int[] array, ArrayVisualizer arrayVisualizer) { @@ -559,7 +571,8 @@ public void initializeArray(int[] array, ArrayVisualizer arrayVisualizer) { }; public abstract String getName(); - public void selectDistribution(int[] array, ArrayVisualizer arrayVisualizer) { + public boolean selectDistribution(int[] array, ArrayVisualizer arrayVisualizer) { + return true; } public abstract void initializeArray(int[] array, ArrayVisualizer arrayVisualizer); } From 67e15ff6f60c19d7995918d780db3bf8cb470ee8 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Wed, 15 Nov 2023 16:17:20 -0500 Subject: [PATCH 13/27] fix double update bug in shuffle selection --- .../io/github/arrayv/prompts/ShufflePrompt.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java index bd4cbb52..46f866cd 100644 --- a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java +++ b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java @@ -133,17 +133,19 @@ private void initComponents() { jScrollPane1.setViewportView(this.jList1); jScrollPane2.setViewportView(this.jList2); + jList1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList1.addListSelectionListener(evt -> { try { - jList1ValueChanged(); + jList1ValueChanged(evt); } catch (Exception e) { JErrorPane.invokeErrorMessage(e); } }); + jList2.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList2.addListSelectionListener(evt -> { try { - jList2ValueChanged(); + jList2ValueChanged(evt); } catch (Exception e) { JErrorPane.invokeErrorMessage(e); } @@ -205,8 +207,8 @@ private void jButton1ActionPerformed() {//GEN-FIRST:event_jList1ValueChanged new ShuffleDialog(arrayManager, this); }//GEN-LAST:event_jList1ValueChanged - private void jList1ValueChanged() {//GEN-FIRST:event_jList1ValueChanged - if (initializing) + private void jList1ValueChanged(ListSelectionEvent evt) {//GEN-FIRST:event_jList1ValueChanged + if (initializing || evt.getValueIsAdjusting()) return; int selection = jList1.getSelectedIndex(); Distributions[] distributions = arrayManager.getDistributions(); @@ -214,8 +216,8 @@ private void jList1ValueChanged() {//GEN-FIRST:event_jList1ValueChanged arrayManager.setDistribution(distributions[selection]); }//GEN-LAST:event_jList1ValueChanged - private void jList2ValueChanged() {//GEN-FIRST:event_jList1ValueChanged - if (initializing) + private void jList2ValueChanged(ListSelectionEvent evt) {//GEN-FIRST:event_jList1ValueChanged + if (initializing || evt.getValueIsAdjusting()) return; int selection = jList2.getSelectedIndex(); if (shuffleModel.getElementAt(0).equals("Advanced")) { From 982c8c7106eefc18d635ba0947bec22c188cc384 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Wed, 15 Nov 2023 16:28:49 -0500 Subject: [PATCH 14/27] Revert "fix double update bug in shuffle selection" This reverts commit 67e15ff6f60c19d7995918d780db3bf8cb470ee8. --- .../io/github/arrayv/prompts/ShufflePrompt.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java index 46f866cd..bd4cbb52 100644 --- a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java +++ b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java @@ -133,19 +133,17 @@ private void initComponents() { jScrollPane1.setViewportView(this.jList1); jScrollPane2.setViewportView(this.jList2); - jList1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList1.addListSelectionListener(evt -> { try { - jList1ValueChanged(evt); + jList1ValueChanged(); } catch (Exception e) { JErrorPane.invokeErrorMessage(e); } }); - jList2.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList2.addListSelectionListener(evt -> { try { - jList2ValueChanged(evt); + jList2ValueChanged(); } catch (Exception e) { JErrorPane.invokeErrorMessage(e); } @@ -207,8 +205,8 @@ private void jButton1ActionPerformed() {//GEN-FIRST:event_jList1ValueChanged new ShuffleDialog(arrayManager, this); }//GEN-LAST:event_jList1ValueChanged - private void jList1ValueChanged(ListSelectionEvent evt) {//GEN-FIRST:event_jList1ValueChanged - if (initializing || evt.getValueIsAdjusting()) + private void jList1ValueChanged() {//GEN-FIRST:event_jList1ValueChanged + if (initializing) return; int selection = jList1.getSelectedIndex(); Distributions[] distributions = arrayManager.getDistributions(); @@ -216,8 +214,8 @@ private void jList1ValueChanged(ListSelectionEvent evt) {//GEN-FIRST:event_jList arrayManager.setDistribution(distributions[selection]); }//GEN-LAST:event_jList1ValueChanged - private void jList2ValueChanged(ListSelectionEvent evt) {//GEN-FIRST:event_jList1ValueChanged - if (initializing || evt.getValueIsAdjusting()) + private void jList2ValueChanged() {//GEN-FIRST:event_jList1ValueChanged + if (initializing) return; int selection = jList2.getSelectedIndex(); if (shuffleModel.getElementAt(0).equals("Advanced")) { From 711a5d1bdaad083cc78ca40efccb7a40e734f8fd Mon Sep 17 00:00:00 2001 From: warmCabin Date: Wed, 15 Nov 2023 16:39:57 -0500 Subject: [PATCH 15/27] fix double update bug in shuffle selection, for real Didn't realize I was working around generated code here at first! --- src/main/java/io/github/arrayv/prompts/ShufflePrompt.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java index bd4cbb52..308ed9d3 100644 --- a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java +++ b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java @@ -62,6 +62,7 @@ public ShufflePrompt(ArrayManager arrayManager, JFrame frame, UtilFrame utilFram initComponents(); initializing = true; + jList1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList1.setListData(arrayManager.getDistributionIDs()); for (int i = 0; i < arrayManager.getDistributions().length; i++) { if (arrayManager.getDistribution().equals(arrayManager.getDistributions()[i])) { @@ -70,6 +71,7 @@ public ShufflePrompt(ArrayManager arrayManager, JFrame frame, UtilFrame utilFram } } shuffleModel = new DefaultListModel<>(); + jList2.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList2.setModel(shuffleModel); Arrays.stream(arrayManager.getShuffleIDs()).forEach(shuffleModel::addElement); if (arrayManager.getShuffle().size() > 1) { @@ -206,7 +208,7 @@ private void jButton1ActionPerformed() {//GEN-FIRST:event_jList1ValueChanged }//GEN-LAST:event_jList1ValueChanged private void jList1ValueChanged() {//GEN-FIRST:event_jList1ValueChanged - if (initializing) + if (initializing || jList1.getValueIsAdjusting()) return; int selection = jList1.getSelectedIndex(); Distributions[] distributions = arrayManager.getDistributions(); @@ -215,7 +217,7 @@ private void jList1ValueChanged() {//GEN-FIRST:event_jList1ValueChanged }//GEN-LAST:event_jList1ValueChanged private void jList2ValueChanged() {//GEN-FIRST:event_jList1ValueChanged - if (initializing) + if (initializing || jList2.getValueIsAdjusting()) return; int selection = jList2.getSelectedIndex(); if (shuffleModel.getElementAt(0).equals("Advanced")) { From 4dada9cbd93166805ab71035ed6214f7c536ef0e Mon Sep 17 00:00:00 2001 From: warmCabin Date: Wed, 15 Nov 2023 17:01:35 -0500 Subject: [PATCH 16/27] put parentheses around the "Advanced" ghost option --- .../java/io/github/arrayv/prompts/ShufflePrompt.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java index 308ed9d3..b285ac6e 100644 --- a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java +++ b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java @@ -40,6 +40,7 @@ of this software and associated documentation files (the "Software"), to deal public final class ShufflePrompt extends javax.swing.JFrame implements AppFrame { private static final long serialVersionUID = 1L; + private static final String ADVANCED = "(Advanced)"; private final ArrayManager arrayManager; private final JFrame frame; @@ -74,8 +75,9 @@ public ShufflePrompt(ArrayManager arrayManager, JFrame frame, UtilFrame utilFram jList2.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList2.setModel(shuffleModel); Arrays.stream(arrayManager.getShuffleIDs()).forEach(shuffleModel::addElement); + // Add ghost option if advanced shuffle graph is active if (arrayManager.getShuffle().size() > 1) { - shuffleModel.add(0, "Advanced"); + shuffleModel.add(0, ADVANCED); jList2.setSelectedIndex(0); } else { for (int i = 0; i < arrayManager.getShuffles().length; i++) { @@ -85,7 +87,7 @@ public ShufflePrompt(ArrayManager arrayManager, JFrame frame, UtilFrame utilFram } } if (jList2.getSelectedIndex() == -1) { - shuffleModel.add(0, "Advanced"); + shuffleModel.add(0, ADVANCED); jList2.setSelectedIndex(0); } } @@ -220,7 +222,8 @@ private void jList2ValueChanged() {//GEN-FIRST:event_jList1ValueChanged if (initializing || jList2.getValueIsAdjusting()) return; int selection = jList2.getSelectedIndex(); - if (shuffleModel.getElementAt(0).equals("Advanced")) { + // Remove ghost option if something else has been selected + if (shuffleModel.getElementAt(0).equals(ADVANCED)) { if (selection == 0) return; shuffleModel.remove(0); selection--; From ada0cebb532ae6004443098f1b9cc8667a4afd0c Mon Sep 17 00:00:00 2001 From: warmCabin Date: Thu, 16 Nov 2023 02:43:02 -0500 Subject: [PATCH 17/27] fix the same darn thing in ShuffleDialog --- .../io/github/arrayv/dialogs/ShuffleDialog.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java b/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java index 101a8c91..75a2ad26 100644 --- a/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java +++ b/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java @@ -72,6 +72,7 @@ public ShuffleDialog(ArrayManager arrayManager, JFrame frame) { bypassEvents = true; this.shuffleEditor.setShuffle(arrayManager.getShuffle()); + jList4.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList4.setListData(arrayManager.getDistributionIDs()); for (int i = 0; i < arrayManager.getDistributions().length; i++) { if (arrayManager.getDistribution().equals(arrayManager.getDistributions()[i])) { @@ -85,9 +86,12 @@ public ShuffleDialog(ArrayManager arrayManager, JFrame frame) { .collect(Collectors.toList()); Object[] distributionNames = distributions.stream() .map(Distributions::getName).toArray(); + jList1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList1.setListData(distributionNames); + jList3.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList3.setListData(distributionNames); + jList2.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jList2.setListData(arrayManager.getShuffleIDs()); jTextField1.setText(Double.toString( @@ -376,8 +380,9 @@ private void addToGraph(ShuffleInfo shuffle) { shuffleEditor.getShuffle().addDisconnected(shuffle, safePos.x, safePos.y); } + // Base Distribution private void jList4ValueChanged() {//GEN-FIRST:event_jList1ValueChanged - if (bypassEvents) + if (bypassEvents || jList4.getValueIsAdjusting()) return; int selection = jList4.getSelectedIndex(); Distributions[] distributions = arrayManager.getDistributions(); @@ -385,8 +390,9 @@ private void jList4ValueChanged() {//GEN-FIRST:event_jList1ValueChanged arrayManager.setDistribution(distributions[selection]); }//GEN-LAST:event_jList1ValueChanged + // Distribution Stretch private void jList1ValueChanged() {//GEN-FIRST:event_jList1ValueChanged - if (bypassEvents) + if (bypassEvents || jList1.getValueIsAdjusting()) return; String selection = (String)jList1.getSelectedValue(); distributions.stream() @@ -398,8 +404,9 @@ private void jList1ValueChanged() {//GEN-FIRST:event_jList1ValueChanged bypassEvents = false; }//GEN-LAST:event_jList1ValueChanged + // Distribution Warp private void jList3ValueChanged() {//GEN-FIRST:event_jList1ValueChanged - if (bypassEvents) + if (bypassEvents || jList3.getValueIsAdjusting()) return; String selection = (String)jList3.getSelectedValue(); distributions.stream() @@ -411,8 +418,9 @@ private void jList3ValueChanged() {//GEN-FIRST:event_jList1ValueChanged bypassEvents = false; }//GEN-LAST:event_jList1ValueChanged + // Shuffle private void jList2ValueChanged() {//GEN-FIRST:event_jList1ValueChanged - if (bypassEvents) + if (bypassEvents || jList2.getValueIsAdjusting()) return; int selection = jList2.getSelectedIndex(); Shuffles[] shuffles = arrayManager.getShuffles(); From 721808553763343ed7f54a37aa0de63a69ffe9e7 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 18 Nov 2023 18:16:15 -0500 Subject: [PATCH 18/27] return boolean from ArrayManager so we can revert list selection on failure --- .../io/github/arrayv/dialogs/ShuffleDialog.java | 14 ++++++++++++-- .../java/io/github/arrayv/main/ArrayManager.java | 4 +++- .../io/github/arrayv/prompts/ShufflePrompt.java | 14 ++++++++++++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java b/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java index 75a2ad26..397fbaed 100644 --- a/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java +++ b/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java @@ -380,14 +380,24 @@ private void addToGraph(ShuffleInfo shuffle) { shuffleEditor.getShuffle().addDisconnected(shuffle, safePos.x, safePos.y); } + private int jList4PrevSelection; + // Base Distribution private void jList4ValueChanged() {//GEN-FIRST:event_jList1ValueChanged if (bypassEvents || jList4.getValueIsAdjusting()) return; int selection = jList4.getSelectedIndex(); Distributions[] distributions = arrayManager.getDistributions(); - if (selection >= 0 && selection < distributions.length) - arrayManager.setDistribution(distributions[selection]); + if (selection >= 0 && selection < distributions.length) { + if (arrayManager.setDistribution(distributions[selection])) { + jList4PrevSelection = selection; + } else { + // Selection failed for whatever reason. Need to revert to the previous selection. + bypassEvents = true; + jList4.setSelectedIndex(jList4PrevSelection); + bypassEvents = false; + } + } }//GEN-LAST:event_jList1ValueChanged // Distribution Stretch diff --git a/src/main/java/io/github/arrayv/main/ArrayManager.java b/src/main/java/io/github/arrayv/main/ArrayManager.java index e552b44d..3cdb2d45 100644 --- a/src/main/java/io/github/arrayv/main/ArrayManager.java +++ b/src/main/java/io/github/arrayv/main/ArrayManager.java @@ -153,12 +153,14 @@ public Distributions[] getDistributions() { public Distributions getDistribution() { return this.distribution; } - public void setDistribution(Distributions choice) { + public boolean setDistribution(Distributions choice) { if (choice.selectDistribution(arrayVisualizer.getArray(), arrayVisualizer)) { this.distribution = choice; if (!arrayVisualizer.isActive()) this.initializeArray(arrayVisualizer.getArray()); + return true; } + return false; } public boolean containsShuffle(Shuffles shuffle) { diff --git a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java index b285ac6e..42c56485 100644 --- a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java +++ b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java @@ -209,13 +209,23 @@ private void jButton1ActionPerformed() {//GEN-FIRST:event_jList1ValueChanged new ShuffleDialog(arrayManager, this); }//GEN-LAST:event_jList1ValueChanged + private int jList1PrevSelection; + private void jList1ValueChanged() {//GEN-FIRST:event_jList1ValueChanged if (initializing || jList1.getValueIsAdjusting()) return; int selection = jList1.getSelectedIndex(); Distributions[] distributions = arrayManager.getDistributions(); - if (selection >= 0 && selection < distributions.length) - arrayManager.setDistribution(distributions[selection]); + if (selection >= 0 && selection < distributions.length) { + if (arrayManager.setDistribution(distributions[selection])) { + jList1PrevSelection = selection; + } else { + // Selection failed for whatever reason. Need to revert to the previous selection. + initializing = true; + jList1.setSelectedIndex(jList1PrevSelection); + initializing = false; + } + } }//GEN-LAST:event_jList1ValueChanged private void jList2ValueChanged() {//GEN-FIRST:event_jList1ValueChanged From e909a313b8a7db22b16cfdbe556c4fa7445cdda5 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 18 Nov 2023 19:32:35 -0500 Subject: [PATCH 19/27] whoops! Forgot I had been messing around with Reads.java. --- src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java b/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java index d4cf7bed..54697193 100644 --- a/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java +++ b/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java @@ -425,7 +425,7 @@ && tryMergeRuns(a, low, size)) { a[k] = pivot; if (Reads.compareValues(ak, pivot) < 0) { // Move a[k] to the left side - while (Reads.compareIndexValue(a, ++lower, pivot) < 0); + while (Reads.compareIndexValue(a, ++lower, pivot, QUICK_SORT_SLEEP, true) < 0); if (Reads.compareIndexValue(a, lower, pivot, QUICK_SORT_SLEEP, true) > 0) { Writes.write(a, --upper, a[lower], QUICK_SORT_SLEEP, true, false); From 8c68d97d68f470e5ecd9d23570d86e524fe5ea67 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 18 Nov 2023 19:40:41 -0500 Subject: [PATCH 20/27] fix precision error in perlin noise distribution If the starting offset is too high, 1/arrayLength will be less than the minimum float delta at that point. When that happens, we get 9001 copies of the same brown noise output. By using Math.nextUp, we'll get spikier slopes in this case instead of an empty box. --- src/main/java/io/github/arrayv/utils/Distributions.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/github/arrayv/utils/Distributions.java b/src/main/java/io/github/arrayv/utils/Distributions.java index caa5ff20..d79b1a51 100644 --- a/src/main/java/io/github/arrayv/utils/Distributions.java +++ b/src/main/java/io/github/arrayv/utils/Distributions.java @@ -232,6 +232,7 @@ public void initializeArray(int[] array, ArrayVisualizer arrayVisualizer) { int value = (int) (PerlinNoise.returnFracBrownNoise(randomStart, octave) * currentLen); perlinNoise[i] = value; randomStart += step; + randomStart = Math.nextUp(randomStart); // TODO: doubles lol } int minimum = Integer.MAX_VALUE; From 0a0dd4f5396dacee7ca7617f292958807a5ba745 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 18 Nov 2023 19:49:29 -0500 Subject: [PATCH 21/27] fix possibly redundant Math.abs The idea here is to normalize the minimum to 0. Now it works whether that minimum is positive or negative. --- src/main/java/io/github/arrayv/utils/Distributions.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/github/arrayv/utils/Distributions.java b/src/main/java/io/github/arrayv/utils/Distributions.java index d79b1a51..5dfad5c9 100644 --- a/src/main/java/io/github/arrayv/utils/Distributions.java +++ b/src/main/java/io/github/arrayv/utils/Distributions.java @@ -241,9 +241,8 @@ public void initializeArray(int[] array, ArrayVisualizer arrayVisualizer) { minimum = perlinNoise[i]; } } - minimum = Math.abs(minimum); for (int i = 0; i < currentLen; i++) { - perlinNoise[i] += minimum; + perlinNoise[i] -= minimum; } double maximum = Double.MIN_VALUE; From 252c70426d005167ea5e4471014401da3464b4bf Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 18 Nov 2023 21:56:10 -0500 Subject: [PATCH 22/27] docs Please let me know if I'm full of crap on these --- .../io/github/arrayv/sortdata/SortMeta.java | 9 +++-- .../sorts/distribute/AmericanFlagSort.java | 2 +- .../github/arrayv/sorts/templates/Sort.java | 18 ++++++++- .../java/io/github/arrayv/utils/Reads.java | 40 +++++++++++++++++++ .../java/io/github/arrayv/utils/Writes.java | 29 ++++++++++++++ 5 files changed, 92 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/github/arrayv/sortdata/SortMeta.java b/src/main/java/io/github/arrayv/sortdata/SortMeta.java index 1ff6632e..75895214 100644 --- a/src/main/java/io/github/arrayv/sortdata/SortMeta.java +++ b/src/main/java/io/github/arrayv/sortdata/SortMeta.java @@ -79,15 +79,16 @@ boolean bucketSort() default false; /** - * A question to ask the user when they choose this sort. You can perform response validation by creating a method - * that is {@code public static int validateAnswer(int answer)}. + * If specified, a prompt for the runSort {@code param} will pop up when this sort is selected. You can perform + * response validation by creating a method with the following signature in your sort class: + * {@code public static int validateAnswer(int answer)}. It will get called automagically. * @return The question to ask the user, or {@code ""} if there isn't one. */ String question() default ""; /** - * The default response to use for {@link #question()}. This is used when the user pressed "Use default". This - * value is ignored if there is no question. This value is not passed through {@code validatorAnswer}. + * The default response to use for {@link #question()}. This is used when the user presses "Use default". This + * value is ignored if there is no question. This value is not passed through {@code validateAnswer}. * @return The default answer response. */ int defaultAnswer() default 0; diff --git a/src/main/java/io/github/arrayv/sorts/distribute/AmericanFlagSort.java b/src/main/java/io/github/arrayv/sorts/distribute/AmericanFlagSort.java index 4d6c347e..dacd94af 100644 --- a/src/main/java/io/github/arrayv/sorts/distribute/AmericanFlagSort.java +++ b/src/main/java/io/github/arrayv/sorts/distribute/AmericanFlagSort.java @@ -21,7 +21,7 @@ * */ -/* +/** * An American flag sort is an efficient, in-place variant of radix sort that * distributes items into hundreds of buckets. Non-comparative sorting * algorithms such as radix sort and American flag sort are typically used to diff --git a/src/main/java/io/github/arrayv/sorts/templates/Sort.java b/src/main/java/io/github/arrayv/sorts/templates/Sort.java index 33dbbe19..ddfeef82 100644 --- a/src/main/java/io/github/arrayv/sorts/templates/Sort.java +++ b/src/main/java/io/github/arrayv/sorts/templates/Sort.java @@ -7,6 +7,9 @@ import io.github.arrayv.utils.Writes; import io.github.arrayv.sortdata.SortMeta; +/** + * Parent class for sorting algorithms. All classes in the sorts package must extend this. + */ public abstract class Sort { private Object[] deprecatedMetadataTable = null; @@ -279,5 +282,18 @@ public static int validateAnswer(int answer) { return answer; } - public abstract void runSort(int[] array, int sortLength, int bucketCount) throws Exception; //bucketCount will be zero for comparison-based sorts + /** + * Entry point for this algorithm. It should sort the segment from 0 to sortLength - 1. Be sure to call into + * {@link Sort#Reads}, {@link Sort#Writes}, and {@link Sort#Highlights} as much as you can possibly stand. + * + * @param array + * @param sortLength The length of the segment to be sorted. Do not rely on array.length, as we may reserve + * much more space than required. + * @param param A user input parameter to control the algorithm. The archetypal example is the base for radix sort, + * but it could also be something like the insertion sort threshold in introsort. You can safely ignore + * this if it doesn't affect you. Use {@link SortMeta#question()} to set up a prompt. + * + * @throws Exception Because you can't be trusted. + */ + public abstract void runSort(int[] array, int sortLength, int param) throws Exception; } diff --git a/src/main/java/io/github/arrayv/utils/Reads.java b/src/main/java/io/github/arrayv/utils/Reads.java index dc2e1f57..e5262de4 100644 --- a/src/main/java/io/github/arrayv/utils/Reads.java +++ b/src/main/java/io/github/arrayv/utils/Reads.java @@ -83,6 +83,13 @@ public void setComparisons(long value) { this.comparisons.set(value); } + /** + * Doesn't result in any visualizations, but counts for the number of comparisons stat. + * + * @param left + * @param right + * @return Integer.compare(left, right) + */ public int compareValues(int left, int right) { if (arrayVisualizer.sortCanceled()) throw new StopSort(); this.comparisons.incrementAndGet(); @@ -125,6 +132,17 @@ public int compareOriginalValues(int left, int right) { return cmpVal; } + /** + * Compare index to index. + * + * @param array + * @param left The first index to read from the array + * @param right The second index to read from the array + * @param sleep How many milliseconds to sleep (subject to Delays.sleepRatio) + * @param mark Whether to mark and delay for this comparison. + * + * @return Integer.compare(array[left], array[right]) + */ public int compareIndices(int[] array, int left, int right, double sleep, boolean mark) { if (mark) { Highlights.markArray(1, left); @@ -147,6 +165,17 @@ public int compareOriginalIndices(int[] array, int left, int right, double sleep return this.compareOriginalValues(array[left], array[right]); } + /** + * Compare index to value. Useful for comparing, say, the current pointer to the current minimum. + * + * @param array + * @param index The index to read from the array + * @param value A constant value + * @param sleep How many milliseconds to sleep (subject to Delays.sleepRatio) + * @param mark Whether to mark and delay for this comparison. + * + * @return Integer.compare(array[index], value) + */ public int compareIndexValue(int[] array, int index, int value, double sleep, boolean mark) { if (mark) { Highlights.markArray(1, index); @@ -163,6 +192,17 @@ public int compareOriginalIndexValue(int[] array, int index, int value, double s return this.compareOriginalValues(array[index], value); } + /** + * Compare value to index. Useful for comparing, say, the current minimum to the current pointer. + * + * @param array + * @param value A constant value + * @param index The index to read from the array + * @param sleep How many milliseconds to sleep (subject to Delays.sleepRatio) + * @param mark Whether to mark and delay for this comparison. + * + * @return Integer.compare(value, array[index]) + */ public int compareValueIndex(int[] array, int value, int index, double sleep, boolean mark) { if (mark) { Highlights.markArray(1, index); diff --git a/src/main/java/io/github/arrayv/utils/Writes.java b/src/main/java/io/github/arrayv/utils/Writes.java index 7b0aa6c4..c8957c5f 100644 --- a/src/main/java/io/github/arrayv/utils/Writes.java +++ b/src/main/java/io/github/arrayv/utils/Writes.java @@ -156,6 +156,16 @@ private void markSwap(int a, int b) { Highlights.markArray(2, b); } + /** + * Swaps two values in the array. + * + * @param array + * @param a The first index + * @param b The second index + * @param pause How many milliseconds to sleep (subject to Delays.sleepRatio) + * @param mark Whether or not to mark this swap in the visualization + * @param auxwrite Whether the given array reference is an auxiliary array or the main subject being sorted. + */ public void swap(int[] array, int a, int b, double pause, boolean mark, boolean auxwrite) { if (arrayVisualizer.sortCanceled()) throw new StopSort(); if (!auxwrite && a >= arrayVisualizer.getCurrentLength()) { @@ -180,6 +190,18 @@ public void swap(int[] array, int a, int b, double pause, boolean mark, boolean Delays.sleep(pause); } + /** + * Does a rotation on the given range.
+ * If pos < to, it's a left rotate ([1,2,3,4,5] -> [2,3,4,5,1]).
+ * If pos > to, it's a right rotate ([1,2,3,4,5] -> [5,1,2,3,4,5]). + * + * @param array + * @param pos Start pos for the rotation + * @param to End pos for the rotation. Can be less than pos. + * @param sleep How many milliseconds to sleep (subject to Delays.sleepRatio) + * @param mark Whether or not to mark this swap in the visualization + * @param auxwrite Whether the given array reference is an auxiliary array or the main subject being sorted. + */ public void multiSwap(int[] array, int pos, int to, double sleep, boolean mark, boolean auxwrite) { if (to - pos > 0) { for (int i = pos; i < to; i++) { @@ -417,6 +439,10 @@ public ArrayVList createArrayList(int defaultCapacity) { return new ArrayVList(defaultCapacity); } + /** + * Reserves an external array for auxiliary computations (e.g. scratch space for merge sort) and registers it to the + * visualizer. Make sure to call {@link Writes#deleteExternalArray(int[])} when you're done with it. + */ public int[] createExternalArray(int length) { this.allocAmount.addAndGet(length); int[] result = new int[length]; @@ -425,6 +451,9 @@ public int[] createExternalArray(int length) { return result; } + /** + * Deletes a registered auxiliary array from the visualization. + */ public void deleteExternalArray(int[] array) { this.allocAmount.addAndGet(-array.length); arrayVisualizer.getArrays().remove(array); From 28c66dd610e40d9ca4b26afb2c79a4971af96eb8 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 18 Nov 2023 21:57:34 -0500 Subject: [PATCH 23/27] increment aux writes in a couple of places --- src/main/java/io/github/arrayv/utils/Writes.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/github/arrayv/utils/Writes.java b/src/main/java/io/github/arrayv/utils/Writes.java index c8957c5f..09f1d506 100644 --- a/src/main/java/io/github/arrayv/utils/Writes.java +++ b/src/main/java/io/github/arrayv/utils/Writes.java @@ -407,6 +407,7 @@ public void arraycopy(int[] src, int srcPos, int[] dest, int destPos, int length public int[] copyOfArray(int[] original, int newLength) { this.allocAmount.addAndGet(newLength); + changeAuxWrites(newLength); int[] result = Arrays.copyOf(original, newLength); arrayVisualizer.getArrays().add(result); arrayVisualizer.updateNow(); @@ -415,6 +416,7 @@ public int[] copyOfArray(int[] original, int newLength) { public int[] copyOfRangeArray(int[] original, int from, int to) { this.allocAmount.addAndGet(to - from); + changeAuxWrites(to - from); int[] result = Arrays.copyOfRange(original, from, to); arrayVisualizer.getArrays().add(result); arrayVisualizer.updateNow(); From 9cf8bdd86b037d160550a1df804c717cfb403778 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Sat, 18 Nov 2023 22:20:06 -0500 Subject: [PATCH 24/27] don't decrement allocAmount if array wasn't actually removed --- src/main/java/io/github/arrayv/utils/Writes.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/github/arrayv/utils/Writes.java b/src/main/java/io/github/arrayv/utils/Writes.java index 09f1d506..65cece8b 100644 --- a/src/main/java/io/github/arrayv/utils/Writes.java +++ b/src/main/java/io/github/arrayv/utils/Writes.java @@ -457,15 +457,19 @@ public int[] createExternalArray(int length) { * Deletes a registered auxiliary array from the visualization. */ public void deleteExternalArray(int[] array) { - this.allocAmount.addAndGet(-array.length); - arrayVisualizer.getArrays().remove(array); - arrayVisualizer.updateNow(); + if (arrayVisualizer.getArrays().remove(array)) { + this.allocAmount.addAndGet(-array.length); + arrayVisualizer.updateNow(); + } } public void deleteExternalArrays(int[]... arrays) { - this.allocAmount.addAndGet(-Arrays.stream(arrays).reduce(0, (a, b) -> (a + b.length), Integer::sum)); List visArrays = arrayVisualizer.getArrays(); - Arrays.stream(arrays).forEach(visArrays::remove); + this.allocAmount.addAndGet(-Arrays.stream(arrays) + .filter(visArrays::remove) + .mapToInt(array -> array.length) + .sum() + ); arrayVisualizer.updateNow(); } From da42b6819c683c19e863c76ed0e44a6cdb552589 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Tue, 5 Dec 2023 03:42:02 -0500 Subject: [PATCH 25/27] fix javadoc --- .../github/arrayv/sorts/cabin/Java14Sort.java | 16 +++++++------- .../io/github/arrayv/utils/Highlights.java | 4 ++-- .../java/io/github/arrayv/utils/Shuffles.java | 22 +++++++++---------- .../java/io/github/arrayv/utils/Writes.java | 6 ++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java b/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java index 54697193..deab103b 100644 --- a/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java +++ b/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java @@ -14,35 +14,35 @@ * While this parallel merge piece is ostensibly what sets JDK 14's sort apart from its predecessors, there are still a * few little optimizations compared to JDK 11, including pivot selection and a new heapsort fallback (which I haven't * been able to trigger in ArrayV without changing the tuning constants). - *

+ *

* Unfortunately, this janked out copypasta is the only way to observe the standard sort. I thought of adding hooks into * the ArrayV code in a List implementor, but the collections framework actually dumps the List into an array and calls * Arrays::sort when you call List::sort. Plus, it uses a whole different algorithm for Comparables as opposed * to primitives. - *

+ *

* Overview: - *

+ *

* The algorithm is an Introsort variant at heart, but with so many safeguards, it's basically invincible. - *

+ *

* The core algorithm is a dual-pivot Quicksort. It selects the pivots using a weird median of 5 thing which is based * on the golden ratio, because of course it is. Safeguard #1: If any two of them are equal, it switches to a * single-pivot Quicksort for that range. - *

+ *

* Safeguard #2: Before trying Quicksort, it tries to find and merge runs. A run is defined as an ascending, * descending, or constant sequence of values (a constant sequence could technically be considered ascending and * descending, but it is handled slightly differently here). A descending sequence is reversed on the spot. Then, if * all the sequences are long enough, it will attempt to do an N-way merge on them. Otherwise, it will leave them alone * and defer to the core Quicksort loop. - *

+ *

* Safeguard #3: If the recursion delves too greedily and too deep, it will call Heapsort on that range. This is, * of course, a classic Introsort optimization. Interestingly, they set a huge depth on it, likely impossible to * reach without millions of elements. - *

+ *

* Safeguard #4: If called on a small enough range, it will call insertion sort. Another classic Introsort * optimization. But there are two versions of it... one is a regular insertion sort like you're used to. The other is a * so-called "mixed" insertion sort, which uses the pivot to do some sort of double-ended thing that helps cut down on * swaps. I find this fascinating. - *

+ *

* Suggested settings: *

    *
  • Shape = modulo function, Shuffle = no shuffle, N = 32768, Style = Bar Graph
  • diff --git a/src/main/java/io/github/arrayv/utils/Highlights.java b/src/main/java/io/github/arrayv/utils/Highlights.java index 284f352c..8d48f78b 100644 --- a/src/main/java/io/github/arrayv/utils/Highlights.java +++ b/src/main/java/io/github/arrayv/utils/Highlights.java @@ -160,9 +160,9 @@ public boolean containsPosition(int arrayPosition) { } /** - * Point marker at given array index. Markers can be sparse and unordered; that is, marker #3 -> position 5, marker #8 -> position 2 + * Point marker at given array index. Markers can be sparse and unordered; that is, marker #3 -> position 5, marker #8 -> position 2 * is totally fine. - *

    + *

    * Markers #1 and #2 are used by the Reads and Writes methods, so you might want to start at #3 if you're invoking * this method yourself. * diff --git a/src/main/java/io/github/arrayv/utils/Shuffles.java b/src/main/java/io/github/arrayv/utils/Shuffles.java index cbd33fd0..e0ac9167 100644 --- a/src/main/java/io/github/arrayv/utils/Shuffles.java +++ b/src/main/java/io/github/arrayv/utils/Shuffles.java @@ -1456,8 +1456,8 @@ public void shuffle(int[] array, int start, int end, double sleep) { /** * Did you know the card matching minigame in Super Mario Bros. 3 only has 8 possible layouts? It's true! * But did you know that's not an intentional feature, but the result of an - * incredibly sloppy shuffle routine? - *

    + * incredibly sloppy shuffle routine? + *

    * This method emulates that shuffle, with the option to fix or preserve the bugs that resulted in only 8 outcomes. * It was only ever designed to operate on a list of exactly 15 elements, but I've adapted it to work for any length. * @@ -1500,18 +1500,18 @@ protected void smb3Shuffle(int[] array, int length, boolean fixed, double sleep) * rotation can be broken up into one or more independent cycles of swaps. The number of cycles is gcd(length, rotation). * If you don't do it this way, you end up stumbling over yourself and overwriting values that aren't meant to be * written yet. - *

    + *

    * Adapted from Geeksforgeeks. I had to fix it up a * little cause those comments were stanky. - *

    + *

    * Examples: - *

    - * Length = 9, rotation = 2:
    - * 0 -> 2 -> 4 -> 6 -> 8 -> 1 -> 3 -> 5 -> 7 -> 0 - *

    - * Length = 10, rotation = 2:
    - * 0 -> 2 -> 4 -> 6 -> 8 -> 0
    - * 1 -> 3 -> 5 -> 7 -> 9 -> 1 + *

    + * Length = 9, rotation = 2:
    + * 0 -> 2 -> 4 -> 6 -> 8 -> 1 -> 3 -> 5 -> 7 -> 0 + *

    + * Length = 10, rotation = 2:
    + * 0 -> 2 -> 4 -> 6 -> 8 -> 0
    + * 1 -> 3 -> 5 -> 7 -> 9 -> 1 * * @param array * @param length diff --git a/src/main/java/io/github/arrayv/utils/Writes.java b/src/main/java/io/github/arrayv/utils/Writes.java index 65cece8b..1e9e8671 100644 --- a/src/main/java/io/github/arrayv/utils/Writes.java +++ b/src/main/java/io/github/arrayv/utils/Writes.java @@ -191,9 +191,9 @@ public void swap(int[] array, int a, int b, double pause, boolean mark, boolean } /** - * Does a rotation on the given range.
    - * If pos < to, it's a left rotate ([1,2,3,4,5] -> [2,3,4,5,1]).
    - * If pos > to, it's a right rotate ([1,2,3,4,5] -> [5,1,2,3,4,5]). + * Does a rotation on the given range.
    + * If pos < to, it's a left rotate ([1,2,3,4,5] -> [2,3,4,5,1]).
    + * If pos > to, it's a right rotate ([1,2,3,4,5] -> [5,1,2,3,4,5]). * * @param array * @param pos Start pos for the rotation From 5588c7439feac18f70faa58c762257f072ef548c Mon Sep 17 00:00:00 2001 From: warmCabin Date: Tue, 5 Dec 2023 13:50:37 -0500 Subject: [PATCH 26/27] whoops --- src/main/java/io/github/arrayv/utils/Shuffles.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/arrayv/utils/Shuffles.java b/src/main/java/io/github/arrayv/utils/Shuffles.java index e0ac9167..82534e81 100644 --- a/src/main/java/io/github/arrayv/utils/Shuffles.java +++ b/src/main/java/io/github/arrayv/utils/Shuffles.java @@ -1501,7 +1501,7 @@ protected void smb3Shuffle(int[] array, int length, boolean fixed, double sleep) * If you don't do it this way, you end up stumbling over yourself and overwriting values that aren't meant to be * written yet. *

    - * Adapted from Geeksforgeeks. I had to fix it up a + * Adapted from Geeksforgeeks. I had to fix it up a * little cause those comments were stanky. *

    * Examples: From 6511f147e962354821da876259ad2d254b3d7ad1 Mon Sep 17 00:00:00 2001 From: warmCabin Date: Tue, 5 Dec 2023 13:59:06 -0500 Subject: [PATCH 27/27] fix whatever this is I searched the project for href and found this interesting design decision --- src/main/java/io/github/arrayv/sorts/misc/PancakeSort.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/arrayv/sorts/misc/PancakeSort.java b/src/main/java/io/github/arrayv/sorts/misc/PancakeSort.java index 70363910..c53426e9 100644 --- a/src/main/java/io/github/arrayv/sorts/misc/PancakeSort.java +++ b/src/main/java/io/github/arrayv/sorts/misc/PancakeSort.java @@ -4,9 +4,8 @@ import io.github.arrayv.sortdata.SortMeta; import io.github.arrayv.sorts.templates.Sort; -/* - * IDeserve
    - * https://www.youtube.com/c/IDeserve">https://www.youtube.com/c/IDeserve +/** + * IDeserve
    * Given an array, sort the array using Pancake sort. * * @author Saurabh