Merge sort visualisation using recursion and Promises - javascript

I've written a merge sort visualisation in p5.js which shows the steps of merge sort. This works fine as a sequential visualisation, but I'd quite like to show this as a true representation, where you can see each part of the array being sorted at the same time (with multiple sections being visualised sorting at the same time, to truly reflect the recursion). The code itself is relatively simple:
// Split the array recursively
let mid = Math.floor((right + left) / 2);
if (right - left < 1) {
return;
}
// My attempt to visualise this properly
await Promise.all([mergeSortSlice(array, left, mid), mergeSortSlice(array, mid + 1, right)]);
// THIS WORKS, but only for sequential sorting
// await mergeSortSlice(array, left, mid);
// await mergeSortSlice(array, mid + 1, right)
// Putting sleep(200) here also works, but doesn't show the steps of the sort as they are happening, just the result of each stage of the sort.
leftCounter = 0;
rightCounter = 0;
l = left;
r = mid + 1;
valuesStartIndex = l;
let leftArray = array.slice(left, r);
let rightArray = array.slice(r, right + 1);
while (rightCounter < rightArray.length && leftCounter < leftArray.length) {
if (leftArray[leftCounter] < rightArray[rightCounter]) {
array.splice(l + rightCounter, 1);
array.splice(valuesStartIndex, 0, leftArray[leftCounter]);
l++;
leftCounter++;
valuesStartIndex++;
await sleep(200);
} else {
array.splice(r, 1);
array.splice(valuesStartIndex, 0, rightArray[rightCounter]);
r++;
rightCounter++;
valuesStartIndex++;
await sleep(200);
}
}
The problem with using Promise.all is that the split parts of the array are getting mixed up, I believe due to the recursion? This is resulting in the array not getting sorted properly.
My timeout function:
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
The setup function and draw loop:
let values = [50, 10, 80, 56, 30, 25, 15]
function setup() {
createCanvas(600, 190);
frameRate(60);
mergeSort(values)
}
function draw() {
rectWidth = 10;
background(23);
stroke(0);
fill(255);
for (let i = 0; i < values.length; i++) {
rect(i * rectWidth, height - values[i], rectWidth, values[i]);
}
}
The combination of async functions and recursion makes it difficult for me to come up with a solution for this. Any help/advice would be much appreciated.

You were actually very close to having a working solution. Your issue is that you are creating a bunch of global variables inside your mergeSortSlice function:
// These were all missing the let keyword
// And were therefore either assigning or implicitly declaring
// globally scoped variables.
let leftCounter = 0;
let rightCounter = 0;
let l = left;
let r = mid + 1;
let valuesStartIndex = l;
let leftArray = array.slice(left, r);
let rightArray = array.slice(r, right + 1);
When two instances of a function invocation being run as part of a Promise each of which await on timeouts, their execution is going to be interleaved (which you want so you can graphically represent the theoretical parallelism). However, when those functions alter global variables this is a classic shared memory multi-threading bug.
Here's an adaptation of your code with the bug fixed, highlighting added, and a slightly different delay strategy:
function merge_sort(p) {
const Mode = {
Shuffling: 0,
Sorting: 1
};
const spacing = 5;
let array = [...Array(40)].map((_, i) => i);
let highlights = [];
let itemWidth;
let itemHeight;
let currentMode = Mode.Shuffling;
let iterator;
let frameRate = 8;
let redrawPromise;
let signalRedraw;
p.setup = function() {
p.createCanvas(p.windowWidth, p.windowHeight);
p.frameRate(frameRate * 5);
itemWidth = (p.width - (spacing * (array.length + 1))) / array.length;
itemHeight = p.height - spacing * 2;
iterator = shuffle();
initRedrawPromise();
};
function initRedrawPromise() {
redrawPromise =
new Promise(resolve => {
signalRedraw = resolve;
});
redrawPromise.then(() => initRedrawPromise());
}
p.draw = function() {
p.background('white');
// draw
for (let i = 0; i < array.length; i++) {
if (highlights[i]) {
p.fill(highlights[i]);
} else {
p.fill('blue');
}
let fractionalHeight = (array[i] + 1) / array.length;
let pixelHeight = fractionalHeight * itemHeight;
p.rect(
(i + 1) * spacing + i * itemWidth,
spacing + (itemHeight - pixelHeight),
itemWidth,
pixelHeight
);
}
signalRedraw();
if (currentMode === Mode.Shuffling) {
// update
let next = iterator.next();
if (next.value) {
// Done suffle, switch to sort
currentMode = Mode.Sorting;
p.frameRate(frameRate);
sort().then(() => {
// switch back to shuffling
currentMode = Mode.Shuffling;
p.frameRate(frameRate * 5);
iterator = shuffle();
});
}
}
};
p.keyPressed = function(e) {
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
frameRate++;
p.frameRate(frameRate);
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
frameRate = Math.max(0, frameRate - 1);
p.frameRate(frameRate);
}
}
// shuffle the array. yield false for each step where the array is not yet shuffled. yield true once the array is shuffled.
function* shuffle() {
// for each position in the array (except the last position),
// if the chosen item is not the current item, swap the two items.
for (let i = 0; i < array.length - 1; i++) {
highlight(i);
yield false;
let j = randomInt(i, array.length);
if (j !== i) {
highlight(i, j);
yield false;
swap(i, j);
highlight(j, i);
yield false;
} else {
highlight(i);
yield false;
}
}
yield true;
}
function sort() {
highlights = [];
return sortSlice(0, array.length - 1);
}
async function sortSlice(left, right) {
if (right - left < 1) {
return;
}
// Split the array recursively
let mid = Math.floor((right + left) / 2);
await Promise.all([sortSlice(left, mid), sortSlice(mid + 1, right)]);
for (let ix = left; ix <= right; ix++) {
highlights[ix] = undefined;
}
let leftCounter = 0;
let rightCounter = 0;
let l = left;
let r = mid + 1;
let valuesStartIndex = l;
let leftArray = array.slice(left, r);
let rightArray = array.slice(r, right + 1);
while (rightCounter < rightArray.length && leftCounter < leftArray.length) {
if (leftArray[leftCounter] < rightArray[rightCounter]) {
array.splice(l + rightCounter, 1);
array.splice(valuesStartIndex, 0, leftArray[leftCounter]);
highlights[valuesStartIndex] = 'green';
highlights[r] = 'red';
l++;
leftCounter++;
valuesStartIndex++;
} else {
array.splice(r, 1);
array.splice(valuesStartIndex, 0, rightArray[rightCounter]);
highlights[valuesStartIndex] = 'green';
r++;
rightCounter++;
valuesStartIndex++;
highlights[l + rightCounter] = 'red';
}
// at each merge step wait for a redraw that shows this step
await redrawPromise;
highlights[valuesStartIndex - 1] = 'gray';
for (let ix = valuesStartIndex; ix <= right; ix++) {
highlights[ix] = undefined;
}
}
}
function swap(i, j) {
const tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
function randomInt(lowerBound, upperBound) {
return lowerBound + Math.floor(Math.random() * (upperBound - lowerBound));
}
function highlight(i, j) {
highlights = [];
if (i !== undefined) {
highlights[i] = 'green';
}
if (j !== undefined) {
highlights[j] = 'red';
}
}
}
sketch = new p5(merge_sort);
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

Related

async await javascript for merge sort visualization

function sleep(ms) { // new code: checkout a video about promises
return new Promise(resolve => setTimeout(resolve, ms));
}
async function mergeDraw(anArray,startPoint){
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
j = startPoint;
for(num = 0; num < anArray.length; num++){
anArray[num][1] = j;
ctx.lineWidth = 3.2 ;
ctx.strokeStyle = 'seagreen'
ctx.beginPath();
ctx.moveTo(j, 0);
ctx.lineTo(j, 800);
ctx.stroke();
ctx.lineWidth = 3;
ctx.strokeStyle = 'black'
ctx.beginPath();
ctx.moveTo(j, 0);
ctx.lineTo(j, anArray[num][0]);
ctx.stroke();
j+=8;
}
await sleep(10);
}
function merger(arr1, arr2) {
let i = 0,j = 0,mergedArr = [];
while (i < arr1.length && j < arr2.length) {
if (arr1[i][0] > arr2[j][0]){
mergedArr.push(arr2[j]);
j++;
} else{
mergedArr.push(arr1[i]);
i++;
}
}
while (i < arr1.length) {
mergedArr.push(arr1[i]);
i++;
}
while (j < arr2.length) {
mergedArr.push(arr2[j]);
j++;
}
var lowest = mergedArr[0][1];
for( num = 1; num < mergedArr.length; num++){
if(mergedArr[num][1] < lowest){
lowest = mergedArr[num][1];
}
}
mergeDraw(mergedArr, lowest);
return mergedArr;
}
function mergeSort(array) {
//Array of length 1 is sorted so we return the same array back
if (array.length == 1) return array;
//Break down the array to half from middle into left and right
let middle = Math.floor(array.length / 2);
let left = mergeSort(array.slice(0, middle));
let right = mergeSort(array.slice(middle));
//Return the merged sorted array
return merger(left, right);
}
I am trying to make a visualization for mergeSort. My mergeSort function divides my bigger array into smaller arrays. These smaller arrays are then sorted into the mergeArr array by the merger function. The mergeDraw then draws the changes made. I want there to be a small pause after the drawing has been made before the a new mergedArr is drawn. I tried to do this using async await but this doesn't work. Is there a better way to do this instead of using async await?

JavaScript create two dimensional array

I'm new to JavaScript, I'm trying to solve leetcode question 37. I need to a create a blank two dimensional array, I initially used the method in the comments; however, it doesn't work correctly, it will change all the value. Then, I used the for loop method to create array and currently it worked correctly. But I still cannot figured out why this will happen, could anyone explain the reason why this will happen, is this because of shallow copy?
var solveSudoku = function (board) {
// let rows = new Array(9).fill(new Array(10).fill(0)),
let rows = new Array(9);
for (let i = 0; i < 9; i++) {
rows[i] = new Array(10).fill(0);
}
let cols = new Array(9);
for (let i = 0; i < 9; i++) {
cols[i] = new Array(10).fill(0);
}
let boxes = new Array(9);
for (let i = 0; i < 9; i++) {
boxes[i] = new Array(10).fill(0);
}
// let cols = new Array(9).fill(new Array(10).fill(0)),
// boxes = new Array(9).fill(new Array(10).fill(0));
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
let c = board[i][j];
if (c !== '.') {
let n = parseInt(c),
bx = Math.floor(j / 3),
by = Math.floor(i / 3);
// 0代表为使用,1为使用过
rows[i][n] = 1;
console.log(i, n)
cols[j][n] = 1;
// box索引
boxes[by * 3 + bx][n] = 1;
}
}
}
fill(board, 0, 0)
function fill(board, x, y) {
// 完成填充条件
if (y === 9) return true;
// 下一个点的坐标
let nx = (x + 1) % 9,
// 判断进入是否下一行
ny = (nx === 0) ? y + 1 : y;
// 如果已经填充,则进入下一个点
if (board[y][x] !== '.') return fill(board, nx, ny);
// 没有被填充过
for (let i = 1; i <= 9; i++) {
let bx = Math.floor(x / 3),
by = Math.floor(y / 3),
box_key = by * 3 + bx;
if (!rows[y][i] && !cols[x][i] && !boxes[box_key][i]) {
rows[y][i] = 1;
cols[x][i] = 1;
boxes[box_key][i] = 1;
board[y][x] = i.toString();
console.log(board[y][x])
// 递归向下一个点求解
if (fill(board, nx, ny)) return true;
// 恢复初始状态
board[y][x] = '.';
boxes[box_key][i] = 0;
rows[y][i] = 0;
cols[x][i] = 0;
}
}
return false;
}
console.log(board);
};
The problem with fill(), at least with object, is that it passes the same object, by reference, to all element of the array. So if you mutate this object, then it will mutate every object of every arrays.
Note that in your case, you are creating a new Array object using it's constructor ( new Array() ) which makes them objects.
const matrix = new Array(5).fill(new Array(5).fill(0));
console.log(matrix);
In the previous snippet, you can see that the values of the other rows, from the second one to the end, are reference to the initial row.
To get around that, you can fill you array with empty values and then use the map() to create unique object for each position in the array.
const matrix = new Array(5).fill().map(function() { return new Array(5).fill(0); });
console.log(matrix);
As you can see in the previous snippet, all the rows are now their unique reference.
This is the reason all of your values were changed.
I've applied this solution to your code. I wasn't able to test it, because I wasn't sure of the initial parameters to pass.
I've also used anonymous function here ( function() { return; } ), but I would success using arrow function ( () => {} ) instead, if you are comfortable with them. It's cleaner.
var solveSudoku = function (board) {
let rows = new Array(9).fill().map(function() { return new Array(10).fill(0); }),
cols = new Array(9).fill().map(function() { return new Array(10).fill(0); }),
boxes = new Array(9).fill().map(function() { return new Array(10).fill(0); });
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
let c = board[i][j];
if (c !== '.') {
let n = parseInt(c),
bx = Math.floor(j / 3),
by = Math.floor(i / 3);
// 0代表为使用,1为使用过
rows[i][n] = 1;
console.log(i, n)
cols[j][n] = 1;
// box索引
boxes[by * 3 + bx][n] = 1;
}
}
}
fill(board, 0, 0)
function fill(board, x, y) {
// 完成填充条件
if (y === 9) return true;
// 下一个点的坐标
let nx = (x + 1) % 9,
// 判断进入是否下一行
ny = (nx === 0) ? y + 1 : y;
// 如果已经填充,则进入下一个点
if (board[y][x] !== '.') return fill(board, nx, ny);
// 没有被填充过
for (let i = 1; i <= 9; i++) {
let bx = Math.floor(x / 3),
by = Math.floor(y / 3),
box_key = by * 3 + bx;
if (!rows[y][i] && !cols[x][i] && !boxes[box_key][i]) {
rows[y][i] = 1;
cols[x][i] = 1;
boxes[box_key][i] = 1;
board[y][x] = i.toString();
console.log(board[y][x])
// 递归向下一个点求解
if (fill(board, nx, ny)) return true;
// 恢复初始状态
board[y][x] = '.';
boxes[box_key][i] = 0;
rows[y][i] = 0;
cols[x][i] = 0;
}
}
return false;
}
console.log(board);
};

Is this Quick Sort Logic Correct?

I have tried to implement Quick-Sort in Javascript without any reference to psuedo Code. Is this correct implementation? if not how can i improve on it.
const quickSort = (arr = []) => {
const length = arr.length;
if (length === 0 || length === 1) {
return arr;
}
const pivot = arr[0];
const leftArray = [];
const rightArray = [];
for (let i = 1; i < length; i++) {
if (arr[i] < pivot) {
leftArray.push(arr[i]);
} else {
rightArray.push(arr[i]);
}
}
return [...quickSort(leftArray), pivot, ...quickSort(rightArray)];
};
console.log(quickSort([2, 45, 6, 7, 8, 1]));
I have added code of test case and executed it over 250000 times.
// Function to generate random array.
const generateRandomArray = n =>
Array.from({
length: n
}, () => Math.floor(Math.random() * n));
// Function to check whether array is sorted.
const checkSorted = (arr = []) => {
let sorted = true;
for (let i = 1; i < arr.length; i++) {
if (arr[i] < arr[i - 1]) {
sorted = false;
break;
}
}
return sorted;
};
// Testing Quick-Sort
const testQuickSort = () => {
for (let i = 1; true; i++) {
const sortedArray = quickSort(generateRandomArray(Date.now() % 100000));
const isSorted = checkSorted(sortedArray);
if (!isSorted) {
console.log("error");
break;
}
console.log("pass", i);
}
};
testQuickSort();
You can try the following Solution
function pivot(arr, start = 0, end = arr.length - 1) {
const swap = (arr, idx1, idx2) => {
[arr[idx1], arr[idx2]] = [arr[idx2], arr[idx1]];
};
// We are assuming the pivot is always the first element
let pivot = arr[start];
let swapIdx = start;
for (let i = start + 1; i <= end; i++) {
if (pivot > arr[i]) {
swapIdx++;
swap(arr, swapIdx, i);
}
}
// Swap the pivot from the start the swapPoint
swap(arr, start, swapIdx);
return swapIdx;
}
function quickSort(arr, left = 0, right = arr.length -1){
if(left < right){
let pivotIndex = pivot(arr, left, right) //3
//left
quickSort(arr,left,pivotIndex-1);
//right
quickSort(arr,pivotIndex+1,right);
}
return arr;
}
console.log(quickSort([100,-3,2,4,6,9,1,2,5,3,23]));

JavaScript start iterating Map() from specific element/index

I know that elements in a Map can be iterated over, in the order of insertion.
Let's assume we have this map:
const a = new Map();
a.set('x', 5);
a.set('y', 10);
a.set('z', 5);
And we want to find first element in a with the value 5 and then the next element with same value of 5.
// el will be 5, 10, 5...
for(const el of a) {
if(el === 0) {
// How can I iteratate over `a` starting from index(el) + 1
for (??) {}
}
}
If I was using an Array instead we could do something like (ignoring the keys):
const a = new Array(5, 10, 5);
for(let i = 0; i < a.length; ++i) {
if(a[i] === 5) {
// Here I can start iterating from i + 1
for(let j = i + 1; j < a.length; ++j) {
a[j] === 5 && console.log('FOUND!');
}
}
}
I am not very familiar with iterators, but I think it should be somehow possible to start iterating from a specific element in the map.
const x = a.get('x');
// iterate over Map `a` starting from the element that comes after x
One solution, that I am not particularly happy about, is to get a copy of the keys or entries each time we perform the operation const elements = a.entries(), so then we could quickly iterate over it, but it uses a lot of extra memory.
You could use a generators to do this,..
One advantage is that you can short circuit generators with a break..
Example below..
const a = new Map();
a.set('x', 5);
a.set('y', 10);
a.set('z', 5);
a.set('a', 10);
a.set('b', 5);
function* findFirstThenNext(m, v) {
let ix = 0;
for (const mm of m) {
if (v === mm[1]) {
yield {ix, key:mm[0]};
}
ix += 1;
}
}
let count = 0;
for (const ret of findFirstThenNext(a, 5)) {
console.log(`Found #${ret.ix} with key ${ret.key}`);
count ++;
if (count >= 2) break;
}
Using a mix of the for loop and iterators, you could create simple list, then do your double for loops using iterators.
The nice thing here, is if you use this sort of for looping in lots of places, then the makeOuterInnerIter function can be re-used for any iterable.
const a = new Map();
a.set('x', 5);
a.set('y', 10);
a.set('z', 5);
function* makeOuterInnerIter(iter) {
const stack = Array.from(iter);
for (let ol = 0; ol < stack.length; ol += 1) {
yield {
value: stack[ol],
inner: (function *inner() {
for (let il = ol + 1; il < stack.length; il += 1) yield stack[il];
})()
};
}
}
for (const {value: [okey, ovalue], inner} of makeOuterInnerIter(a)) {
console.log(`outer: ${okey}: ${ovalue}`);
for (const [ikey, ivalue] of inner) {
console.log(` inner: ${ikey}: ${ivalue}`);
}
}
Another approach with generators:
function* itWrapper(iterator, value) {
let found = false;
for(let item of iterator) {
if(item[1] == value) {
if(found) {
yield 'FOUND!'; // or: yield item[1];
} else {
found = true;
}
}
}
}
And then use like this:
for(let item of itWrapper(a[Symbol.iterator](), 5)) {
console.log(item);
}

Is there a way to improve this code in order to avoid a timeout with large arrays?

I am working with this problem: https://www.hackerrank.com/challenges/fraudulent-activity-notifications/
My code works almost fine, but for some test cases it fails, because of the large array (over 200000 items). I am spending hours trying to understand what I can do to improve the speed, but I cannot come out with a working solution, so 2 of my tests always fail for timeout and I am frustrated trying to pass this test.
I think I cannot avoid the first loop and also the loop in sort, but cannot think of a faster way.
The problem described in the website is is this:
HackerLand National Bank has a simple policy for warning clients about possible fraudulent account activity. If the amount spent by a client on a particular day is greater than or equal to the client's median spending for a trailing number of days, they send the client a notification about potential fraud. The bank doesn't send the client any notifications until they have at least that trailing number of prior days' transaction data.
I solved it with this code
function getMedianNumber(arr) {
arr.sort((a, b) => a - b);
let medianNumber = 0;
const middle = Math.floor(arr.length / 2);
if (arr.length % 2 === 0) {
// Is even we get the median number
medianNumber = (arr[middle] + arr[middle - 1]) / 2;
} else {
const index = Math.floor(middle);
medianNumber = arr[index];
}
return medianNumber;
}
function activityNotifications(expenditure, d) {
let notifications = 0;
let len = expenditure.length - 1;
for (let i = len; i > d - 1; i--) {
let trailingDays = expenditure.slice(i - d, i);
let dayExpense = expenditure[i];
let median = getMedianNumber(trailingDays);
if (expenditure[i] >= median * 2) {
notifications++;
}
}
return notifications;
}
It only fails in 2 test cases because the passed array is huge and I get a timeout error.
expenditure.slice(i - d, i); is too expensive, you are making it O(n^2) by copying the array elements over each iteration. Use indexes over the original array to calculate median: getMedianNumber(arr, startIndex, endIndex).
function copy(a, ind) {
b = [];
for(var i = ind; i < a.length; ++i) {
b.push(a[i]);
}
return b;
}
function processData(input) {
//Enter your code here
var inputArr = input.split("\n");
var d = parseInt(inputArr[0].split(" ")[1]);
var arr = inputArr[1].split(" ").map(Number);
var countArray = [], sortedArray = [], tempArray = [], notifications = 0, median, count, middle;
for(var i = 0; i <= 200; ++i) {
countArray.push(0);
}
for(i = 0; i < d; ++i) {
countArray[arr[i]]++;
}
for(var j = 0; i < arr.length; ++i, ++j) {
tempArray = [], count = 0;
for(var k = 0; k <= 200; ++k) {
if(countArray[k] > 0) {
count += countArray[k];
tempArray.push({
no: k,
count: count
});
}
}
middle = {};
if((d&1) === 0) {
middle.index = count / 2;
} else {
middle.index = Math.ceil(count / 2);
}
var tempCount = 0;
for(k = 0; k < tempArray.length; ++k) {
if(tempArray[k].count === middle.index) {
if((d&1) === 0) {
median = (tempArray[k].no + tempArray[k + 1].no) / 2;
break;
} else {
median = tempArray[k].no;
break;
}
} else if(tempArray[k].count > middle.index) {
median = tempArray[k].no;
break;
}
}
//console.log(tempArray, median, arr[i]);
if(arr[i] >= (2 * median)) {
notifications++;
}
countArray[arr[i]]++;
countArray[arr[j]]--;
}
console.log(notifications);
}
process.stdin.resume();
process.stdin.setEncoding("ascii");
_input = "";
process.stdin.on("data", function (input) {
_input += input;
});
process.stdin.on("end", function () {
processData(_input);
});

Categories

Resources