diff --git a/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java b/src/main/java/io/github/arrayv/dialogs/ShuffleDialog.java index e0da3ac8..397fbaed 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])) { @@ -81,13 +82,16 @@ 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(); + 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,17 +380,29 @@ 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) + 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 private void jList1ValueChanged() {//GEN-FIRST:event_jList1ValueChanged - if (bypassEvents) + if (bypassEvents || jList1.getValueIsAdjusting()) return; String selection = (String)jList1.getSelectedValue(); distributions.stream() @@ -398,8 +414,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 +428,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(); diff --git a/src/main/java/io/github/arrayv/main/ArrayManager.java b/src/main/java/io/github/arrayv/main/ArrayManager.java index 83415c1c..3cdb2d45 100644 --- a/src/main/java/io/github/arrayv/main/ArrayManager.java +++ b/src/main/java/io/github/arrayv/main/ArrayManager.java @@ -153,11 +153,14 @@ public Distributions[] getDistributions() { 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()); + 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/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; } } diff --git a/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java b/src/main/java/io/github/arrayv/prompts/ShufflePrompt.java index bd4cbb52..42c56485 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; @@ -62,6 +63,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,10 +72,12 @@ 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); + // 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++) { @@ -83,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); } } @@ -205,20 +209,31 @@ 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) + 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 - if (initializing) + 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--; 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/cabin/Java14Sort.java b/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java new file mode 100644 index 00000000..deab103b --- /dev/null +++ b/src/main/java/io/github/arrayv/sorts/cabin/Java14Sort.java @@ -0,0 +1,893 @@ +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 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. 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: + *

+ */ +@SuppressWarnings("StatementWithEmptyBody") +@SortMeta( + name = "Java 14", + runName = "Java 14", + category = "Custom" +) +public class Java14Sort 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; + + 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. ========================= \\ + + /** + * 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) { + 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) { + 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, 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); + } + 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) { + + Highlights.markArray(3, clamp(low)); + Highlights.markArray(4, clamp(end - 1)); + Highlights.markArray(5, clamp(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; + } +} 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/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 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/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(); + } +} diff --git a/src/main/java/io/github/arrayv/utils/Distributions.java b/src/main/java/io/github/arrayv/utils/Distributions.java index c7c44d65..5dfad5c9 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; @@ -240,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; @@ -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,27 +526,39 @@ 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) { + 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); } diff --git a/src/main/java/io/github/arrayv/utils/Highlights.java b/src/main/java/io/github/arrayv/utils/Highlights.java index bca1537e..8d48f78b 100644 --- a/src/main/java/io/github/arrayv/utils/Highlights.java +++ b/src/main/java/io/github/arrayv/utils/Highlights.java @@ -158,29 +158,36 @@ 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 == -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 { - 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(); } arrayVisualizer.updateNow(); Delays.enableStepping(); 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/ShuffleInfo.java b/src/main/java/io/github/arrayv/utils/ShuffleInfo.java index 7451f7a0..42bcc2bd 100644 --- a/src/main/java/io/github/arrayv/utils/ShuffleInfo.java +++ b/src/main/java/io/github/arrayv/utils/ShuffleInfo.java @@ -115,10 +115,8 @@ 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); + 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 0b87beb8..82534e81 100644 --- a/src/main/java/io/github/arrayv/utils/Shuffles.java +++ b/src/main/java/io/github/arrayv/utils/Shuffles.java @@ -48,10 +48,9 @@ public String getName() { return "Randomly"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); - shuffle(array, 0, currentLen, delay ? 1 : 0, Writes); + shuffle(array, 0, currentLen, delay ? 1 : 0); } }, REVERSE { @@ -59,8 +58,7 @@ public String getName() { return "Backwards"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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); } @@ -70,10 +68,7 @@ public String getName() { return "Slight Shuffle"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); - Random random = new Random(); - + 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); @@ -86,8 +81,7 @@ public String getName() { return "No Shuffle"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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); @@ -99,10 +93,9 @@ public String getName() { return "Sorted"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); - this.sort(array, 0, currentLen, delay ? 1 : 0, Writes); + this.sort(array, 0, currentLen, delay ? 1 : 0); } }, NAIVE { @@ -110,10 +103,8 @@ public String getName() { return "Naive Randomly"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +115,9 @@ public String getName() { return "Scrambled Tail"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +128,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 +136,9 @@ public String getName() { return "Scrambled Head"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +149,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 +157,8 @@ public String getName() { return "Shifted Element"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); - Random random = new Random(); int start = random.nextInt(currentLen); int dest = random.nextInt(currentLen); @@ -189,15 +174,13 @@ public String getName() { return "Noisy"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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,10 +188,7 @@ public String getName() { return "Scrambled Odds"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); - Random random = new Random(); - + 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); @@ -222,8 +202,7 @@ public String getName() { return "Final Merge Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int count = 2; @@ -243,14 +222,13 @@ public String getName() { return "Shuffled Final Merge"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +236,12 @@ public String getName() { return "Shuffled Half"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +249,13 @@ public String getName() { return "Partitioned"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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,8 +263,7 @@ public String getName() { return "Sawtooth"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int count = 4; @@ -308,8 +283,7 @@ public String getName() { return "Pipe Organ"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -329,8 +303,7 @@ public String getName() { return "Final Bitonic Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -352,8 +325,7 @@ public String getName() { return "Interlaced"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] referenceArray = new int[currentLen]; @@ -376,9 +348,7 @@ public String getName() { return "Double Layered"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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); @@ -390,8 +360,7 @@ public String getName() { return "Final Radix"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); currentLen -= currentLen % 2; @@ -412,9 +381,7 @@ public String getName() { return "Real Final Radix"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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; @@ -440,14 +407,13 @@ public String getName() { return "Recursive Final Radix"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +429,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,8 +438,7 @@ public String getName() { return "Half Rotation"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int a = 0, m = (currentLen + 1) / 2; @@ -496,8 +461,7 @@ public String getName() { return "Half Reversed"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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); @@ -509,8 +473,7 @@ public String getName() { return "BST Traversal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { int[] temp = Arrays.copyOf(array, currentLen); // credit to sam walko/anon @@ -546,8 +509,7 @@ public String getName() { return "Inverted BST"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -588,8 +550,7 @@ public String getName() { return "Logarithmic Slopes"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] temp = new int[currentLen]; @@ -610,8 +571,7 @@ public String getName() { return "Heapified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); MaxHeapSort heapSort = new MaxHeapSort(arrayVisualizer); @@ -623,9 +583,7 @@ public String getName() { return "Smoothified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); - + public void shuffleArray(int[] array, int currentLen) { SmoothSort smoothSort = new SmoothSort(arrayVisualizer); smoothSort.smoothHeapify(array, currentLen); } @@ -635,9 +593,7 @@ public String getName() { return "Poplarified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); - + public void shuffleArray(int[] array, int currentLen) { PoplarHeapSort poplarHeapSort = new PoplarHeapSort(arrayVisualizer); poplarHeapSort.poplarHeapify(array, 0, currentLen); } @@ -647,8 +603,7 @@ public String getName() { return "Triangular Heapified"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); if (delay) Delays.setSleepRatio(Delays.getSleepRatio()*10); @@ -666,21 +621,19 @@ public String getName() { return "First Circle Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +648,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 +657,11 @@ public String getName() { return "Final Pairwise Pass"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +695,20 @@ public String getName() { return "Recursive Reversal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +716,13 @@ public String getName() { return "Gray Code Fractal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { 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 +730,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,8 +739,7 @@ public String getName() { return "Sierpinski Triangle"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { int[] triangle = new int[currentLen]; triangleRec(triangle, 0, currentLen); @@ -820,8 +769,7 @@ public String getName() { return "Triangular"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] triangle = new int[currentLen]; @@ -857,8 +805,7 @@ public String getName() { return "Quicksort Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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--) @@ -866,9 +813,6 @@ public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays De } }, PDQ_BAD { - Reads Reads; - Writes Writes; - Highlights Highlights; boolean delay; double sleep; @@ -898,13 +842,9 @@ public String getName() { return "PDQ Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int currentLen) { delay = arrayVisualizer.shuffleEnabled(); sleep = delay ? 1 : 0; - this.Reads = arrayVisualizer.getReads(); - this.Writes = Writes; - this.Highlights = Highlights; int[] copy = new int[currentLen]; @@ -918,7 +858,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 +895,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 +970,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 +990,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,8 +1155,7 @@ public String getName() { return "Grailsort Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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); @@ -1227,23 +1166,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 +1191,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,8 +1209,7 @@ public String getName() { return "Shuffle Merge Adversary"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int n = arrayVisualizer.getCurrentLength(); + public void shuffleArray(int[] array, int n) { boolean delay = arrayVisualizer.shuffleEnabled(); int[] tmp = new int[n]; @@ -1294,23 +1232,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,9 +1272,8 @@ public String getName() { return "Bit Reversal"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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; @@ -1387,21 +1324,19 @@ public String getName() { return "Randomly w/ Blocks"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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(); 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,8 +1355,7 @@ public String getName() { return "Block Reverse"; } @Override - public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - 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(); @@ -1429,13 +1363,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); } @@ -1447,9 +1381,50 @@ 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, int currentLen) { + // Multiply length by 5/6 to simulate that bottom right corner easter egg + smb3Shuffle(array, (int) (currentLen * 5.0 / 6.0), false, + arrayVisualizer.shuffleEnabled() ? 0.5 : 0); + } + }, + SMB3_FIXED { + @Override + public String getName() { + return "SMB3 Matching Game (Fixed)"; + } + @Override + public void shuffleArray(int[] array, int currentLen) { + // Multiply length by 5/6 to simulate that bottom right corner easter egg + smb3Shuffle(array, (int) (currentLen * 5.0 / 6.0), true, + arrayVisualizer.shuffleEnabled() ? 0.5 : 0); + } }; - public void sort(int[] array, int start, int end, double sleep, Writes Writes) { + protected ArrayVisualizer arrayVisualizer; + protected Delays Delays; + protected Highlights Highlights; + protected Writes Writes; + protected Reads Reads; + + protected Random random = new Random(); + + public final void init(ArrayVisualizer arrayVisualizer) { + this.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) { int min = array[start], max = min; for (int i = start+1; i < end; i++) { if (array[i] < min) min = array[i]; @@ -1471,14 +1446,104 @@ 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); } } + /** + * 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 + */ + 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 + ? 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); + + if (length <= 10) return; + + int x = fixed + ? 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); + Writes.swap(array, x + 5, x + 10, sleep, true, false); + } + } + + } + + // TODO: HALF_ROTATE may be interested in this function. + /** + * 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 + */ + 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); + for (cycle = 0; cycle < gcd; cycle++) { + // Rotate cycle + temp = array[cycle]; + j = cycle; + while (true) { + k = (j + rotation) % length; + if (k == cycle) + break; + Writes.write(array, j, array[k], sleep, true, false); + j = k; + } + Writes.write(array, j, temp, sleep, true, false); + } + } + + protected int gcd(int a, int b) { + return b == 0 + ? a + : 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); + public abstract void shuffleArray(int[] array, int currentLen); } diff --git a/src/main/java/io/github/arrayv/utils/Writes.java b/src/main/java/io/github/arrayv/utils/Writes.java index 7b0aa6c4..1e9e8671 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++) { @@ -385,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(); @@ -393,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(); @@ -417,6 +441,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,16 +453,23 @@ 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); - 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(); }