From fae2ee7717fdec397811f7ead5f147eb9dc4374e Mon Sep 17 00:00:00 2001 From: lee-naish Date: Tue, 4 Feb 2025 11:42:47 +1100 Subject: [PATCH] Straight radix sort not too bad now --- src/algorithms/controllers/MSDRadixSort.js | 208 ++++++++++++------ .../controllers/straightRadixSort.js | 4 +- src/algorithms/pseudocode/MSDRadixSort.js | 33 +-- .../Array1DRenderer.module.scss | 16 +- .../Array/Array1DRenderer/index.js | 5 +- 5 files changed, 170 insertions(+), 96 deletions(-) diff --git a/src/algorithms/controllers/MSDRadixSort.js b/src/algorithms/controllers/MSDRadixSort.js index 07eb20f6..941e8f97 100644 --- a/src/algorithms/controllers/MSDRadixSort.js +++ b/src/algorithms/controllers/MSDRadixSort.js @@ -4,21 +4,28 @@ import { areExpanded, } from './collapseChunkPlugin'; -// see stackFrameColour in index.js to find corresponding function mapping to css +// see stackFrameColour in Array1DRenderer/index.js to find corresponding function mapping to css const STACK_FRAME_COLOR = { No_color: 0, In_progress_stackFrame: 1, Current_stackFrame: 2, Finished_stackFrame: 3, - I_color: 4, - J_color: 5, - P_color: 6, // pivot + I_color: 4, // not currently used + J_color: 5, // not currently used + P_color: 6, // pivot, left-over from quicksort + // Because MSD radix sort doesn't have a pivot splitting the two + // halves of a partition, it was hard to reconsruct recursion after + // the fact. The solution here is to have a separate colour for the + // right part of each partition (when known) + In_progress_stackFrameR: 7, + Current_stackFrameR: 8, + Finished_stackFrameR: 9, }; const VIS_VARIABLE_STRINGS = { i_left_index: 'i', j_right_index: 'j', - i_eq_0: 'i==0', + i_gt_n: 'i==n+1', j_eq_0: 'j==0', left: 'left', right: 'right' @@ -27,10 +34,11 @@ const VIS_VARIABLE_STRINGS = { const MSD_BOOKMARKS = { start: 1, get_mask: 100, - first_pass: 200, + rec_function: 200, + top_call: 201, base_case: 300, set_i: 301, - set_j: 302, + // set_j: 302, // deleted partition_while: 303, partition_left: 304, partition_right: 305, @@ -44,15 +52,20 @@ const MSD_BOOKMARKS = { }; -const update_vis_with_stack_frame = (a, stack_frame, stateVal) => { - let left, right, depth; - [left, right, depth] = stack_frame; +// update stack frame left..right with new value(s) +// If mid is defined and index >= mid use stateValR, otherwise stateVal +// Stack frames are all [left, right, mid, depth], +const FRAME_MID = 2; +const FRAME_DEPTH = 3; +const update_vis_with_stack_frame = (a, stack_frame, stateVal, stateValR) => { + let left, right, mid, depth; + [left, right, mid, depth] = stack_frame; - for (let i = left; i <= right; i += 1) { + for (let k = left; k <= right; k += 1) { // each element in the vis stack is a tuple: // 0th index is for base color, - // 1th index is for pivot, i, j colors - a[depth][i] = { base: stateVal, extra: [] }; + // 1th index is for pivot, i, j colors (not used here) + a[depth][k] = { base: (k >= mid? stateValR: stateVal), extra: [] }; } return a; } @@ -88,7 +101,10 @@ const unhighlight = (vis, index, isPrimaryColor = true) => { }; const updateMask = (vis, value) => { - vis.mask.setMask(2 ** value, value) + if (value < 0) + vis.mask.setMask(0, value) + else + vis.mask.setMask(2 ** value, value) } const updateBinary = (vis, value) => { @@ -112,11 +128,11 @@ export default { initVisualisers() { return { mask: { - instance: new MaskTracer('mask', null, 'Mask'), + instance: new MaskTracer('mask', null, 'Key + Mask'), order: 0, }, array: { - instance: new ArrayTracer('array', null, 'Array view', { arrayItemMagnitudes: true }), // Label the input array as array view + instance: new ArrayTracer('array', null, 'Array', { arrayItemMagnitudes: true }), // Label the input array as array view order: 1, }, } @@ -137,8 +153,10 @@ export default { const entire_num_array = nodes; let max_depth_index = -1; // indexes into 2D array, starts at zero - const finished_stack_frames = []; // [ [left, right, depth], ...] (although depth could be implicit this is easier) - const real_stack = []; // [ [left, right, depth], ...] + // stack frames are all [left, right, mid, depth], + // where mid is undefined until after partition + const finished_stack_frames = []; + const real_stack = []; let leftCheck = false // ---------------------------------------------------------------------------------------------------------------------------- @@ -147,23 +165,22 @@ export default { // The main helper function that acts as an interface into refreshStack // This function is the only way information is cached and incremented properly in the while loop - const partitionChunker = (bookmark, i, j, left, right, depth, arr) => { + const partitionChunker = (bookmark, i, j, prev_i, prev_j, left, right, depth, arr, mask) => { assert(bookmark !== undefined); // helps catch bugs early, and trace them in stack - const args_array = [real_stack, finished_stack_frames, i, j, left, right, depth, leftCheck, arr] + const args_array = [real_stack, finished_stack_frames, i, j, prev_i, prev_j, left, right, depth, leftCheck, maxIndex, arr, mask] chunker.add(bookmark, refreshStack, args_array, depth) } - const refreshStack = (vis, cur_real_stack, cur_finished_stack_frames, cur_i, cur_j, left, right, cur_depth, checkingLeft, arr) => { - // XXX - // We can't render the -1 index in the array - // For now we display i==0/j==0 at left of array if appropriate - let cur_i_too_low; + const refreshStack = (vis, cur_real_stack, cur_finished_stack_frames, cur_i, cur_j, prev_i, prev_j, left, right, cur_depth, checkingLeft, maxIndex, arr, mask) => { + // If we fall off the start/end of the array we just use the + // first/last element and give the actual value of j/i + let cur_i_too_high; let cur_j_too_low; - if (cur_i === -1) { + if (cur_i === A.length) { cur_i = undefined; - cur_i_too_low = 0; + cur_i_too_high = A.length - 1; } else { - cur_i_too_low = undefined; + cur_i_too_high = undefined; } if (cur_j === -1) { cur_j = undefined; @@ -184,32 +201,53 @@ export default { if (!isPartitionExpanded() && !isRecursionExpanded()) { // i should not show up in vis if partition + recursion is collapsed cur_i = undefined; - cur_i_too_low = undefined; + cur_i_too_high = undefined; } vis.array.setStackDepth(cur_real_stack.length); vis.array.setStack( deriveStack(cur_real_stack, cur_finished_stack_frames, cur_i, cur_j, cur_depth) ); +console.log(cur_real_stack, cur_finished_stack_frames); + // XXX This is getting very messy - (un)highlighting depends a + // lot on where we are and we have a bunch of tricky testing of + // various vars to determine that. Could pass in bookmark to + // simplify some things at least?? // Show the binary representation for the current index - if (checkingLeft) { - if (cur_i > 0 && cur_i < n) - unhighlight(vis, cur_i - 1) + // plus (un)highlight appropriate element(s) + updateMask(vis, mask) // only needed for start of recursive function + if (maxIndex !== undefined) { // top level call to recursive fn + unhighlight(vis, maxIndex) + updateBinary(vis, 0) + } + if (cur_i == left && cur_j === right && prev_i === undefined) { + // init, before partition + highlight(vis, cur_i) + highlight(vis, cur_j) + } else if (cur_i !== undefined && cur_j === undefined && prev_i !== undefined) { + // just before first recursive call + unhighlight(vis, prev_i) + unhighlight(vis, prev_j) + } else if (checkingLeft) { + if (prev_i !== undefined && prev_i !== cur_j) + unhighlight(vis, prev_i) highlight(vis, cur_i) - if (arr && cur_i) + if (arr && cur_i !== undefined) updateBinary(vis, arr[cur_i]) } else { - if (cur_j > 0 && cur_j < n) - unhighlight(vis, cur_j - 1) + if (prev_j !== undefined && prev_j !== cur_i) + unhighlight(vis, prev_j) highlight(vis, cur_j) if (arr && cur_j) updateBinary(vis, arr[cur_j]) } - assignVariable(vis, VIS_VARIABLE_STRINGS.left, left); - assignVariable(vis, VIS_VARIABLE_STRINGS.right, right); + if (left < A.length) + assignVariable(vis, VIS_VARIABLE_STRINGS.left, left); + if (right >= 0) + assignVariable(vis, VIS_VARIABLE_STRINGS.right, right); assignVariable(vis, VIS_VARIABLE_STRINGS.i_left_index, cur_i); - assignVariable(vis, VIS_VARIABLE_STRINGS.i_eq_0, cur_i_too_low); + assignVariable(vis, VIS_VARIABLE_STRINGS.i_gt_n, cur_i_too_high); assignVariable(vis, VIS_VARIABLE_STRINGS.j_right_index, cur_j); assignVariable(vis, VIS_VARIABLE_STRINGS.j_eq_0, cur_j_too_low); }; @@ -221,7 +259,7 @@ export default { let stack_vis = []; - for (let i = 0; i < max_depth_index + 1; i++) { + for (let k = 0; k < max_depth_index + 1; k++) { // for whatever reason fill() does not work here... JavaScript stack_vis.push( [...Array.from({ length: entire_num_array.length })].map(() => ({ @@ -236,6 +274,7 @@ export default { stack_vis, stack_frame, STACK_FRAME_COLOR.Finished_stackFrame, + STACK_FRAME_COLOR.Finished_stackFrameR, ); }); @@ -244,6 +283,7 @@ export default { stack_vis, stack_frame, STACK_FRAME_COLOR.In_progress_stackFrame, + STACK_FRAME_COLOR.In_progress_stackFrameR, ); }); @@ -252,6 +292,7 @@ export default { stack_vis, cur_real_stack[cur_real_stack.length - 1], STACK_FRAME_COLOR.Current_stackFrame, + STACK_FRAME_COLOR.Current_stackFrameR, ); } @@ -280,12 +321,21 @@ export default { // ---------------------------------------------------------------------------------------------------------------------------- // Real code goes here // ---------------------------------------------------------------------------------------------------------------------------- + // index of max number for determinine number of mask bits, + // defined here so we can highlight it outside + // msdRadixSortRecursive then unhighlight it at in the first call + let maxIndex; + let i; // moved outside partition because they are needed in + let j; // the recursive calls; XX best rename - "i" too generic + let prev_i; // for unhighlighting + let prev_j; // for unhighlighting const partition = (arr, left, right, mask, depth) => { - let i = left - let j = right + i = left + j = right const partitionChunkerWrapper = (bookmark) => { - partitionChunker(bookmark, i, j, left, right, depth, arr) + partitionChunker(bookmark, i, j, prev_i, prev_j, left, right, +depth, arr, mask) } function swapAction(bookmark, n1, n2) { @@ -299,48 +349,59 @@ export default { (vis, _n1, _n2, cur_real_stack, cur_finished_stack_frames, cur_i, cur_j, cur_depth) => { vis.array.swapElements(_n1, _n2); - refreshStack(vis, cur_real_stack, cur_finished_stack_frames, cur_i, cur_j, cur_depth) - // After swap, unhighlight the swapped elements - unhighlight(vis, n1) - unhighlight(vis, n2) + refreshStack(vis, cur_real_stack, cur_finished_stack_frames, cur_i, cur_j, prev_i, prev_j, left, right, cur_depth, false, undefined, arr, mask) }, [n1, n2, real_stack, finished_stack_frames, i, j, depth], depth); } partitionChunkerWrapper(MSD_BOOKMARKS.set_i) - partitionChunkerWrapper(MSD_BOOKMARKS.set_j) + // partitionChunkerWrapper(MSD_BOOKMARKS.set_j) while (i <= j) { + prev_i = i; // save prev value for unhighlighting + prev_j = j; // Build the left group until it reaches the mask (find the big element) - // chunker.add(MSD_BOOKMARKS.partition_left) leftCheck = true while (i <= right && ((arr[i] >> mask & 1)) === 0) { - partitionChunkerWrapper(MSD_BOOKMARKS.partition_right) + // partitionChunkerWrapper(MSD_BOOKMARKS.partition_left) i++ } - // chunker.add(MSD_BOOKMARKS.partition_right) + partitionChunkerWrapper(MSD_BOOKMARKS.partition_left) // Build the right group until it fails the mask (find the small element) leftCheck = false while (j >= left && ((arr[j] >> mask & 1)) === 1) { - partitionChunkerWrapper(MSD_BOOKMARKS.partition_right) + // partitionChunkerWrapper(MSD_BOOKMARKS.partition_right) j-- } + partitionChunkerWrapper(MSD_BOOKMARKS.partition_right) // Swap if the bigger element is not in the right place - if (j > i) { + if (i < j) { + partitionChunkerWrapper(MSD_BOOKMARKS.swap_condition) swapAction(MSD_BOOKMARKS.swap, i, j) + } else { + // about to return i from partition. We update the "mid" of + // the partition on the stack here so it is displayed at the + // last chunk of partition + real_stack[real_stack.length - 1][FRAME_MID] = i; + partitionChunkerWrapper(MSD_BOOKMARKS.swap_condition) } } + prev_i = i; + prev_j = j; return i } const msdRadixSortRecursive = (arr, left, right, mask, depth) => { - real_stack.push([left, right, depth]); + real_stack.push([left, right, undefined, depth]); max_depth_index = Math.max(max_depth_index, depth); // Base case: If the array has 1 or fewer elements or mask is less than 0, stop - chunker.add(MSD_BOOKMARKS.base_case, (vis) => { + partitionChunker(MSD_BOOKMARKS.rec_function, undefined, undefined, undefined, undefined, left, right, depth, arr, mask) + maxIndex = undefined; // defined only for top level call +/* + chunker.add(MSD_BOOKMARKS.rec_function, (vis, left, right) => { if (left < n) assignVariable(vis, VIS_VARIABLE_STRINGS.left, left); if (right >= 0) @@ -351,56 +412,67 @@ export default { unhighlight(vis, i); } }, [left, right], depth) +*/ + chunker.add(MSD_BOOKMARKS.base_case, (vis) => {}, [], depth) if (left < right && mask >= 0) { + // partition leaves final i and j highlighted, sets prev_i, + // prev_j const mid = partition(arr, left, right, mask, depth) // Need a dummy chunk before the recursion call, and an actual call after the recursion call. - // Each of them should hide the variables, hence the undefined calls. + // j is no longer needed but i is - partitionChunker(MSD_BOOKMARKS.pre_sort_left, undefined, undefined, undefined, undefined, depth) + partitionChunker(MSD_BOOKMARKS.pre_sort_left, i, undefined, prev_i, prev_j, left, right, depth, arr, mask) + prev_i = undefined; // i, j now unhighlighted + prev_j = undefined; + let saved_i = i; // needed for second recursive call msdRadixSortRecursive(arr, left, mid - 1, mask - 1, depth + 1) - partitionChunker(MSD_BOOKMARKS.sort_left, left, right, undefined, undefined, depth) + i = saved_i; + partitionChunker(MSD_BOOKMARKS.sort_left, i, undefined, undefined, undefined,left, right, depth, arr, mask) - partitionChunker(MSD_BOOKMARKS.pre_sort_right, undefined, undefined, undefined, undefined, depth) + partitionChunker(MSD_BOOKMARKS.pre_sort_right, i, undefined, undefined, undefined, left, right, depth, arr, mask) msdRadixSortRecursive(arr, mid, right, mask - 1, depth + 1) - partitionChunker(MSD_BOOKMARKS.sort_right, undefined, undefined, undefined, undefined, depth) + partitionChunker(MSD_BOOKMARKS.sort_right, undefined, undefined, undefined, undefined, left, right, depth, arr, mask) - // After the recursive call, we need to pop from the real stack to go back up one - finished_stack_frames.push(real_stack.pop()) - } else { - finished_stack_frames.push(real_stack.pop()) } + // After the recursive call, we need to pop from the real stack to go back up one + finished_stack_frames.push(real_stack.pop()); } // Initialise the array on start chunker.add(MSD_BOOKMARKS.start, (vis, array) => { vis.array.set(array, 'MSDRadixSort') + vis.array.setSize(5); // more space for array + vis.array.setZoom(0.90); }, [nodes], 0 ) - const maxIndex = A.indexOf(Math.max(...A)) + maxIndex = A.indexOf(Math.max(...A)) const mask = getMaximumBit(A); // Highlight the index chunker.add(MSD_BOOKMARKS.get_mask, - (vis) => { + (vis, maxIndex, mask, A) => { highlight(vis, maxIndex) vis.mask.setMaxBits(mask + 1) updateMask(vis, mask) + updateBinary(vis, A[maxIndex]) }, - [maxIndex, mask], + [maxIndex, mask, A], 0 ) - chunker.add(MSD_BOOKMARKS.first_pass, - (vis) => { +/* + chunker.add(MSD_BOOKMARKS.rec_function, + (vis, maxIndex) => { unhighlight(vis, maxIndex) }, [maxIndex], 0 ) +*/ msdRadixSortRecursive(A, 0, n-1, mask, 0); diff --git a/src/algorithms/controllers/straightRadixSort.js b/src/algorithms/controllers/straightRadixSort.js index 8a60bd74..b56bd7f6 100644 --- a/src/algorithms/controllers/straightRadixSort.js +++ b/src/algorithms/controllers/straightRadixSort.js @@ -319,7 +319,7 @@ export function initVisualisers() { if (isCountExpanded()) { return { mask: { - instance: new MaskTracer('mask', null, 'Mask'), + instance: new MaskTracer('mask', null, 'Key + Mask'), order: 0, }, array: { @@ -338,7 +338,7 @@ export function initVisualisers() { } else { return { mask: { - instance: new MaskTracer('mask', null, 'Mask'), + instance: new MaskTracer('mask', null, 'Key + Mask'), order: 0, }, array: { diff --git a/src/algorithms/pseudocode/MSDRadixSort.js b/src/algorithms/pseudocode/MSDRadixSort.js index 526d6a7b..7e6cb126 100644 --- a/src/algorithms/pseudocode/MSDRadixSort.js +++ b/src/algorithms/pseudocode/MSDRadixSort.js @@ -19,7 +19,7 @@ Rexsort(A, n) // Sort array A[1]..A[n] in ascending order. \\B 1 \\Expl} \\Note{ implementation should scan data \\Note} - RexsortRecursive(A, 1, n, mask) \\B 200 + RexsortRecursive(A, 1, n, mask) \\B 201 \\Expl{ We need left and right indices because the code is recursive and both may be different for recursive calls. For each call, all elements in the array segment must have the same pattern @@ -27,9 +27,10 @@ Rexsort(A, n) // Sort array A[1]..A[n] in ascending order. \\B 1 \\Expl} \\In} //====================================================================== -RexsortRecursive(A, left, right, mask) // Sort array A[left]..A[right] using bits up to mask +RexsortRecursive(A, left, right, mask) // Sort A[left]..A[right] \\B 200 \\Expl{ -Only the mask bit and smaller bits are used for sorting; higher bits +Sort A[left]..A[right]; +only the mask bit and less significant bits are used for sorting. Higher bits should be the same for all data in the array segment. \\Expl} if (left < right and mask > 0) \\B 300 @@ -57,19 +58,19 @@ should be the same for all data in the array segment. \\Code{ MSDRadixSortLeft -// *Recursively* sort first part: \\B 400 -RexsortRecursive(A, left, i-1, mask-1) \\B 401 +// *Recursively* sort smaller elements: \\B 400 +RexsortRecursive(A, left, i-1, mask/2) \\B 401 \\Code} \\Code{ MSDRadixSortRight -// *Recursively* sort first part: \\B 500 -RexsortRecursive(A, i, right, mask-1) \\B 501 +// *Recursively* sort larger elements: \\B 500 +RexsortRecursive(A, i, right, mask/2) \\B 501 \\Code} \\Code{ Partition -Set index i at left the of array segment and j at the right \\Ref InitCounters +i,j <- left,right \\B 301 \\Expl{ i scans from left to right stopping at "large" elements (with "1" as the mask bit) and j scans from right to left stopping at "small" elements (with "0" as the mask bit). @@ -83,15 +84,15 @@ while i < j \\B 303 \\In{ Repeatedly increment i until i >= j or A[i] has 1 as the mask bit \\B 304 \\Expl{ Scan right looking for a "large" element that is out of - place. Bitwise "and" between A[i] and mask can be used to + place (mask bit is one). Bitwise "and" between A[i] and mask can be used to extract the desired bit. \\Expl} Repeatedly decrement j until j <= i or A[j] has 0 as the mask bit \\B 305 \\Expl{ Scan left looking for a "small" element that is out of - place. Bitwise "and" between A[i] and mask can be used to + place (mask bit is zero). Bitwise "and" between A[i] and mask can be used to extract the desired bit. \\Expl} - if j > i \\B 309 + if i < j \\B 309 \\Expl{ If the indices cross, we exit the loop. \\Expl} \\In{ @@ -103,14 +104,4 @@ while i < j \\B 303 \\In} \\Code} -\\Code{ -InitCounters -i <- left \\B 301 -\\Expl{ The i pointer scans left to right, so it is set to left -\\Expl} -j <- right \\B 302 -\\Expl{ The j pointer scans right to left, so it is set to right -\\Expl} -\\Code} - `); diff --git a/src/components/DataStructures/Array/Array1DRenderer/Array1DRenderer.module.scss b/src/components/DataStructures/Array/Array1DRenderer/Array1DRenderer.module.scss index c23b244b..a2c42293 100644 --- a/src/components/DataStructures/Array/Array1DRenderer/Array1DRenderer.module.scss +++ b/src/components/DataStructures/Array/Array1DRenderer/Array1DRenderer.module.scss @@ -170,10 +170,18 @@ transition-duration: 0.1s; margin-bottom: m#{i}n(1vh, 1em); + // XXX should really have names for these (that may vary with + // different global color options) --not-started-section: rgba(0, 0, 0, 0); // transparent - --in-progress-section: rgb(169, 169, 169); - --current-section: var(--peach); - --finished-section: rgb(0, 0, 0); + --in-progress-section: rgb(20, 20, 190); + --current-section: rgb(210, 30, 30); + --finished-section: rgb(100, 100, 100); + --in-progress-sectionR: rgb(0, 0, 255); + --current-sectionR: rgb(255, 0, 0); + --finished-sectionR: rgb(140, 140, 140); + --in-progress-sectionOLD: rgb(169, 169, 169); + --current-sectionOLD: var(--peach); + --finished-sectionOLD: rgb(0, 0, 0); .stackSubElement { transition-duration: 0.1s; @@ -188,4 +196,4 @@ font-size: 0.8em; } } -} \ No newline at end of file +} diff --git a/src/components/DataStructures/Array/Array1DRenderer/index.js b/src/components/DataStructures/Array/Array1DRenderer/index.js index 495b1769..a20a18a6 100644 --- a/src/components/DataStructures/Array/Array1DRenderer/index.js +++ b/src/components/DataStructures/Array/Array1DRenderer/index.js @@ -205,7 +205,7 @@ class Array1DRenderer extends Array2DRenderer { )}
- {// Quicksort stuff + {// Quicksort + MSD radix sort stuff stack && stack.length > 0 ? ( this.maxStackDepth = Math.max(this.maxStackDepth, stackDepth), stackRenderer(stack, data[0].length, stackDepth, this.maxStackDepth) @@ -258,6 +258,9 @@ function stackFrameColour(color_index) { 'var(--i-section)', // 4 'var(--j-section)', // 5 'var(--p-section)', // 6 + 'var(--in-progress-sectionR)', // 7 + 'var(--current-sectionR)', // 8 + 'var(--finished-sectionR)', // 9 ][color_index] }