mergeSort algorithm and memory issue in JavaScript - javascript

Here's the code I wrote:
function mergeSort(array){
if(array.length < 2) return array;
var mid = Math.floor(array.length / 2);
var left = array.slice(0, mid);
var right = array.slice(mid, array.length);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right){
var result = [];
while (left.length && right.length){
if(left[0]>right[0]){
result.push(right[0]);
} else {
result.push(left[0]);
}
}
while(left.length){
result.push(left[0]);
}
while(right.length){
result.push(right[0]);
}
return result;
}
array = [1000, -94, -115, 300, 22]
mergeSort(array);
and below is another solution i found online
function mergeSort (arr) {
if (arr.length < 2) return arr;
var mid = Math.floor(arr.length /2);
return merge(mergeSort(arr.slice(0,mid)), mergeSort(arr.slice(mid)));
}
function merge (a,b) {
var result = [];
while (a.length >0 && b.length >0)
result.push(a[0] < b[0]? a.shift() : b.shift());
return result.concat(a.length? a : b);
}
var test = [-100,3,53,21,4,0];
console.log(mergeSort(test));
in comparison I can't find any significant difference besides some syntax. But for some reason mine code won't run in both chrome dev console and node.js environment. In chrome, it won't return any result and in node.js it gives me
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory Abort trap: 6
Can someone help me understand what is the difference between the two snippet that actually made the difference?
Thanks in advance!

Think about it, you have an array left and you do
while(left.length){
result.push(left[0]);
}
left[0] doesn't change the array, it just gets the first item.
The length of left will never change, what you have there is a while loop that goes on as long as the array has a length over zero, and it always will, as the length never changes.
This is a perfect example of an infinite loop, that eventually fills the callstack and errors out, or in older browsers just crashes.
However if you do
while(left.length){
result.push(left.shift());
}
Array.shift() removes the first item from the array, so at some point the arrays length will be zero, and the loop stops

You never move from first element when you are doing merge. Try this code:
function mergeSort(array){
if(array.length < 2) return array;
var mid = Math.floor(array.length / 2);
var a = array.slice(0, mid);
var b = array.slice(mid);
return merge(mergeSort(a), mergeSort(b));
}
function merge(a, b){
var result = [];
var i = 0;
var j = 0;
while (i < a.length && j < b.length){
if(a[i] < b[j]){
result.push(a[i]);
i++;
} else {
result.push(b[j]);
j++;
}
}
while(i < a.length){
result.push(a[i]);
i++;
}
while(j < b.length){
result.push(b[j]);
j++
}
return result;
}
array = [1000, -94, -115, 300, 22];
array = mergeSort(array);
console.log(array);

Related

Generate non-duplicate random number array JS 2nd [duplicate]

I need help with writing some code that will create a random number from an array of 12 numbers and print it 9 times without dupes. This has been tough for me to accomplish. Any ideas?
var nums = [1,2,3,4,5,6,7,8,9,10,11,12];
var gen_nums = [];
function in_array(array, el) {
for(var i = 0 ; i < array.length; i++)
if(array[i] == el) return true;
return false;
}
function get_rand(array) {
var rand = array[Math.floor(Math.random()*array.length)];
if(!in_array(gen_nums, rand)) {
gen_nums.push(rand);
return rand;
}
return get_rand(array);
}
for(var i = 0; i < 9; i++) {
console.log(get_rand(nums));
}
The most effective and efficient way to do this is to shuffle your numbers then print the first nine of them. Use a good shuffle algorithm.What Thilo suggested will give you poor results. See here.
Edit
Here's a brief Knuth Shuffle algorithm example:
void shuffle(vector<int> nums)
{
for (int i = nums.size()-1; i >= 0; i--)
{
// this line is really shorthand, but gets the point across, I hope.
swap(nums[i],nums[rand()%i]);
}
}
Try this once:
//Here o is the array;
var testArr = [6, 7, 12, 15, 17, 20, 21];
shuffle = function(o){ //v1.0
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
};
shuffle(testArr);
This is relatively simple to do, the theory behind it is creating another array which keeps track of which elements of the array you have used.
var tempArray = new Array(12),i,r;
for (i=0;i<9;i++)
{
r = Math.floor(Math.random()*12); // Get a random index
if (tempArray[r] === undefined) // If the index hasn't been used yet
{
document.write(numberArray[r]); // Display it
tempArray[r] = true; // Flag it as have been used
}
else // Otherwise
{
i--; // Try again
}
}
Other methods include shuffling the array, removing used elements from the array, or moving used elements to the end of the array.
If I understand you correctly, you want to shuffle your array.
Loop a couple of times (length of array should do), and in every iteration, get two random array indexes and swap the two elements there. (Update: if you are really serious about this, this may not be the best algorithm).
You can then print the first nine array elements, which will be in random order and not repeat.
Here is a generic way of getting random numbers between min and max without duplicates:
function inArray(arr, el) {
for(var i = 0 ; i < arr.length; i++)
if(arr[i] == el) return true;
return false;
}
function getRandomIntNoDuplicates(min, max, DuplicateArr) {
var RandomInt = Math.floor(Math.random() * (max - min + 1)) + min;
if (DuplicateArr.length > (max-min) ) return false; // break endless recursion
if(!inArray(DuplicateArr, RandomInt)) {
DuplicateArr.push(RandomInt);
return RandomInt;
}
return getRandomIntNoDuplicates(min, max, DuplicateArr); //recurse
}
call with:
var duplicates =[];
for (var i = 1; i <= 6 ; i++) {
console.log(getRandomIntNoDuplicates(1,10,duplicates));
}
const nums = [1,2,3,4,5,6,7,8,9,10,11,12];
for(var i = 1 ; i < 10; i++){
result = nums[Math.floor(Math.random()*nums.length)];
const index = nums.indexOf(result);
nums.splice(index, 1);
console.log(i+' - '+result);
}

What would be the better algorithm for generating following result?

We have a input of array having n numbers (non-zero) i.e.
input--> [a1, a2, a3, a4, a5, ...., aN] (not in order)
Now we want the output as
output--> Ai <= Aj >= Ak <= Al >= Am <= ....An.
i have written a code for this problem and it is quite fine but not optimized if i talk about time and space complexity then it must not be good solution.
function test(array){
if(array && array.length){
let newArray = [];
array = array.sort();
let m;
if(array.length % 2){
m = (array.length +1 )/2;
}else{
m = (array.length)/2;
}
newArray.push(array[m-1]);
for(let i=1;i<=m;i++){
if(array[m-1+i])
newArray.push(array[m-1+i]);
if(array[m-1-i])
newArray.push(array[m-1-i]);
}
console.log(newArray);
return newArray;
}else{
throw 'invalid argument';
}
}
test([1,2,3,4]);
Please help me if you any idea to optimise like using no other variables(it will reduce space complexity). Thanks
You don't need to sort the array.
Just do one pass over the array, and in each step fix only a[i], a[i+1].
Suppose a[1] <= a[2] >= a[3]...<= a[i-1] >= a[i]
Now, if a[i] <= a[i+1], continue by increasing i.
if a[i] > a[i+1], swap them.
Symmetrically, when i is even, if a[i] < a[i+1] swap a[i],a[i+1].
Your current algorithm runs on O(n log n) time. The .sort() at the beginning takes O(n log n) time, while the for loop below it runs in O(n) time. So, one possible improvement would be to change the sort function to use counting sort instead (which has time complexity O(n + k), where k is the range from the lowest number to the highest number). This will reduce the overall time complexity from O(n log n) to O(n + k), which will be a significant improvement with larger sets:
function countingSort(array) {
const counts = array.reduce((a, num) => {
a[num] = (a[num] || 0) + 1;
return a;
}, {});
/*
ok, maybe this is cheating
return Object.entries(counts).reduce((arr, [num, occurrences]) => {
for (let i = 0; i < occurrences; i++) {
arr.push(Number(num));
}
return arr;
}, []);
*/
const sorted = [];
const min = Math.min(...array);
const max = Math.max(...array);
for (let i = min; i <= max; i++) {
for (let j = 0; j < counts[i]; j++) {
sorted.push(i);
}
}
return sorted;
}
function test(array){
if(array && array.length){
let newArray = [];
array = countingSort(array);
let m;
if(array.length % 2){
m = (array.length +1 )/2;
}else{
m = (array.length)/2;
}
newArray.push(array[m-1]);
for(let i=1;i<=m;i++){
if(array[m-1+i])
newArray.push(array[m-1+i]);
if(array[m-1-i])
newArray.push(array[m-1-i]);
}
console.log(newArray);
return newArray;
}else{
throw 'invalid argument';
}
}
test([1,2,3,4]);
Counting sort performs best when there aren't large gaps between the numbers. If there are large gaps between the numbers, you could use radix sort instead, which also runs in linear time (but is a bit more complicated to code).
(if you do use the built-in sort function, though, make sure to sort numerically, with .sort((a, b) => a - b), otherwise your resulting array will be sorted lexiographically, eg [1, 11, 2], which is not desirable)
This could be a better logic finally :-
function optimizedSort(array) {
if(array && array.length){
let temp;
for(let i=0;i<array.length;i++){
if((i+1)%2){
// i is odd
if(array[i]>=array[i+1]){
temp = array[i];
array[i] = array[i+1];
array[i+1] = temp;
}
}else{
// i is even
if(array[i]<=array[i+1]){
temp = array[i];
array[i] = array[i+1];
array[i+1] = temp;
}
}
}
console.log('********',array);
return array;
}else{
throw 'invalid argument';
}
}
optimizedSort([1,2,3,4]);

array function that generates array with subset range of numbers

I am trying to create a function that builds an array up to a number set by the function parameter, with an if condition on being included based on whether the remainder is zero. The last number in the array should be no higher than the parameter. Here's what I came up with so far --
function justThreesUpTo(num) {
var array = [];
array.length = num;
for (i = 1; i < array.length; i++) {
if(i % 3 === 0){
array.push(i);
}
else i;
}
array.splice(0, num);
return array;
}
When I console log this, with justThreesUpTo(20), I get --
// [ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42 ]
I see the issue being setting the limiter at array.length, which maxes out the number of items that can be in the array, but I can't figure out what else to call to make sure the last number in the array goes no higher than the "num" parameter specified by the function call. Any ideas?
Setting an array's length to something before the array is populated isn't a great idea - better to just iterate over the num itself. For example
for (var i = 1; i < num; i++) {
// push to array if i % 3 === 0
Your else i won't do anything - you can just leave it off completely.
You could make your code a whole lot shorter and cleaner if you wanted:
function justThreesUpTo(num) {
const length = Math.floor(num / 3);
return Array.from({ length }, (_, i) => (i + 1) * 3);
}
console.log(justThreesUpTo(20));
Modifying an array while looping over it (or its indices, which is what you’re doing with i < array.length) is a recipe for confusion. Start with an empty array and compare with num instead:
function justThreesUpTo(num) {
var array = [];
for (var i = 1; i < num; i++) {
if (i % 3 === 0) {
array.push(i);
}
}
return array;
}
Now you can optimize the check out of that entirely by moving up the appropriate amount each time.
function justThreesUpTo(num) {
var array = [];
for (var i = 3; i < num; i += 3) {
array.push(i);
}
return array;
}
(In your original code, the entire first num holes created by array.length = num; are unused and get spliced off, and else i does nothing.)
You can try with a simple while loop
function justThreesUpTo(num) {
var array = [];
var i = 0;
while (i < num) {
if(i % 3 === 0){
array.push(i);
}
i++;
}
return array;
}
console.log(justThreesUpTo(20));
You can use map method and spread syntax in order to write a clean solution.
function justThreesUpTo(num) {
return [ ...Array(Math.floor(num/3)).keys() ].map((_,i)=> (i+1) * 3);
}
console.log(justThreesUpTo(20));
Hmm. Looks like it was a pretty simple solution. Changed the limiter from "array.length" to "num", and it worked fine.
function justThreesUpTo(num) {
var array = [];
array.length = num;
for (i = 1; i < num; i++) {
if(i % 3 === 0){
array.push(i);
}
else i;
}
array.splice(0, num);
return array;
}
Never mind!
Use while with i+=3; inside the while loop:
function justThreesUpTo(num) {
var array = [];
var i = 0;
while(i<num){
array.push(i);
i+=3;
}
return array;
}
console.log(justThreesUpTo(20));

Implementing merge sort in Javascript

So I'm working on Khan Academy's Algorithms course, and am trying to implement a recursive merge sort in Javascript. Here is my code so far:
var mergeSort = function(array, p, r) {
if(r>p) {
var q = floor(r/2);
mergeSort(array, p, q);
mergeSort(array, q+1, r);
merge(array, p, q, r);
}
};
merge is a function provided by Khan Academy to merge the subarrays back together. It is giving me the error: 'Uncaught RangeError: Maximum call stack size exceeded'.
EDIT: More details: I am fairly sure the error is in my code, there code is purposefully obfuscated and unreadable because the user needs to implement it themselves in a later challenge.
Here is the code that actually calls the mergeSort function initially and declares the array:
var array = [14, 7, 3, 12, 9, 11, 6, 2];
mergeSort(array, 0, array.length-1);
println("Array after sorting: " + array);
Program.assertEqual(array, [2, 3, 6, 7, 9, 11, 12, 14]);
And here is the code for the merge function, although it is obfuscared as I mentioned above:
var merge = function(array, p, q, r) {
var a = [],
b = [],
c = p,
d, e;
for (d = 0; c <= q; d++, c++) {
a[d] = array[c];
}
for (e = 0; c <= r; e++, c++) {
b[e] = array[c];
}
c = p;
for (e = d = 0; d < a.length && e < b.length;) {
if (a[d] < b[e]) {
array[c] = a[d];
d++;
} else {
array[c] = b[e];
e++;
}
c++;
}
for (; d < a.length;) {
array[c] = a[d];
d++;
c++;
}
for (; e < b.length;) {
array[c] = b[e];
e++;
c++;
}
};
They also require my code inside of the mergeSort function be of the form:
if (____) {
var ____ = ____;
mergeSort(____,____,____);
mergeSort(____,____,____);
merge(____,____,____,____);
}
Mergesort is a divide and conquer algorithm which splits the range of indices to sort in two, sorts them separately, and then merges the results.
Therefore, middle variable should be the arithmetic mean of from and to, not the half of to.
I have renamed the variables to make it more understandable:
var mergeSort = function(array, from, to) {
if(to > from) {
var middle = Math.floor( (from+to)/2 ); // Arithmetic mean
mergeSort(array, from, middle);
mergeSort(array, middle+1, to);
merge(array, from, middle, to);
}
};
q is supposed to be the half way point between p and r, but you've failed to take into account that the starting point (i.e. p) might not be 0 when you do this:
var q = floor(r/2);
You need to do something like:
var q = floor((r-p)/2) + p;
Although as #Oriol points out the middle point is actually exactly the same as the arithmetic mean and so the calculation can be simplified.
Just for the sake of implementation of the merge sort in JS
function merge(arr){
if(arr.length <= 1) return arr;
let mid = Math.floor(arr.length/2);
let left = merge( arr.slice(0, mid));
let right = merge(arr.slice(mid))
function mergeSort(arr1, arr2) {
let result = [];
let i=0;
let j=0;
while(i< arr1.length && j < arr2.length) {
if(arr1[i] < arr2[j]){
result.push(arr1[i])
i++;
} else {
result.push(arr2[j])
j++;
}
}
while(i < arr1.length) {
result.push(arr1[i])
i++;
}
while(j < arr2.length){
result.push(arr2[j])
j++;
}
return result
}
return mergeSort(left,right)
}
console.log(merge([1,4,3,6,2,11,100,44]))
High level strategy
the merge sort works on the principle that it's better to sort two numbers than to sort a large list of numbers. So what it does is that it breaks down two lists into their individual numbers then compares them one to the other then building the list back up. Given a list of say 1,3,2, it'll split the list into 1 and 3,2 then compare 3 to 2 to get 2,3 and then compare the list of 2,3 to 1. If 1 is less than the first element in list of 2,3 it simply places 1 in front of the list. Just like that, the list of 1,3,2 is sorted into 1,2,3.
pseudocode-steps
1.take first member of first array
2.compare to first member of second array
3.if first member of first array is less than first member
of second array
4.put first member into sorted array
5.now merge first array minus first element
with second array
6.else take first member of second array
merge first array with remaining portion of second array
7.return sorted array
function mergesorted(list1, list2) {
let sorted = [];
if (list1.length === 0 || list2.length === 0) {
return list1.length === 0 ? list2.length === 0 ? [] : list2 : list1;
}
if (list1[0] < list2[0]) {
sorted.push(list1[0])
return sorted.concat(mergesorted(list1.slice(1), list2))
} else {
sorted.push(list2[0])
return sorted.concat(mergesorted(list1, list2.slice(1)))
}
}
console.log(mergesorted([1, 2], [3, 4])) //should: [1,2,3,4]
console.log(mergesorted([1,2], [3])) //should: [1,2,3]
Merge sort implementation, stable and in place
function sort(arr, start, end) {
if (start >= end-1) return;
var mid = start + ~~((end-start)/2);
// after calling this
// left half and right half are both sorted
sort(arr, start, mid);
sort(arr, mid, end);
/**
* Now we can do the merging
*/
// holding merged array
// size = end-start
// everything here will be copied back to original array
var cache = Array(end-start).fill(0);
var k=mid;
// this is O(n) to arr[start:end]
for (var i=start, r=0;i<mid;r++,i++) {
while (k<end && arr[k] < arr[i]) cache[r++] = arr[k++];
cache[r] = arr[i];
}
// k marks the position of the element in the right half that is bigger than all elements in the left
// effectively it tells that we should only copy start~start+k element from cache to nums
// because the rests are the same
for (var i=0;i<k-start;i++) arr[i+start]=cache[i];
}
A nice solution:
const merge = (left, right) => {
const resArr = [];
let leftIdx = 0;
let rightIdx = 0;
while (leftIdx < left.length && rightIdx < right.length) {
left[leftIdx] < right[rightIdx]
? resArr.push(left[leftIdx++])
: resArr.push(right[rightIdx++]);
}
return [...resArr, ...left.slice(leftIdx), ...right.slice(rightIdx)];
};
const mergeSort = arr =>
arr.length <= 1
? arr
: merge(
mergeSort(arr.slice(0, Math.floor(arr.length / 2))),
mergeSort(arr.slice(Math.floor(arr.length / 2)))
);
try this
const mergeTwoSortedArr = (arr1 = [], arr2 = []) => {
let i = 0,
j = 0,
sortedArr = [];
while(i < arr1.length || j < arr2.length) {
if(arr1[i] <= arr2[j] || (i < arr1.length && j >= arr2.length)) {
sortedArr.push(arr1[i]);
i++;
}
if(arr2[j] <= arr1[i] || (j < arr2.length && i >= arr1.length)) {
sortedArr.push(arr2[j]);
j++;
}
}
return sortedArr;
}
const mergeSort = (arr) => {
if(arr.length === 0 || arr.length === 1) {
return arr;
}
var mid = Math.floor(arr.length / 2);
var left = mergeSort(arr.slice(0, mid));
var right = mergeSort(arr.slice(mid));
return mergeTwoSortedArr(left, right);
}
when trying this
mergeSort([5, 2, 1, 4]) //[1, 2, 4, 5]
Time Complexity O(nlogn)
logn --> decomposition
n comaprison
Time complexity O(nlogn)

Random number generator without dupes in Javascript?

I need help with writing some code that will create a random number from an array of 12 numbers and print it 9 times without dupes. This has been tough for me to accomplish. Any ideas?
var nums = [1,2,3,4,5,6,7,8,9,10,11,12];
var gen_nums = [];
function in_array(array, el) {
for(var i = 0 ; i < array.length; i++)
if(array[i] == el) return true;
return false;
}
function get_rand(array) {
var rand = array[Math.floor(Math.random()*array.length)];
if(!in_array(gen_nums, rand)) {
gen_nums.push(rand);
return rand;
}
return get_rand(array);
}
for(var i = 0; i < 9; i++) {
console.log(get_rand(nums));
}
The most effective and efficient way to do this is to shuffle your numbers then print the first nine of them. Use a good shuffle algorithm.What Thilo suggested will give you poor results. See here.
Edit
Here's a brief Knuth Shuffle algorithm example:
void shuffle(vector<int> nums)
{
for (int i = nums.size()-1; i >= 0; i--)
{
// this line is really shorthand, but gets the point across, I hope.
swap(nums[i],nums[rand()%i]);
}
}
Try this once:
//Here o is the array;
var testArr = [6, 7, 12, 15, 17, 20, 21];
shuffle = function(o){ //v1.0
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
};
shuffle(testArr);
This is relatively simple to do, the theory behind it is creating another array which keeps track of which elements of the array you have used.
var tempArray = new Array(12),i,r;
for (i=0;i<9;i++)
{
r = Math.floor(Math.random()*12); // Get a random index
if (tempArray[r] === undefined) // If the index hasn't been used yet
{
document.write(numberArray[r]); // Display it
tempArray[r] = true; // Flag it as have been used
}
else // Otherwise
{
i--; // Try again
}
}
Other methods include shuffling the array, removing used elements from the array, or moving used elements to the end of the array.
If I understand you correctly, you want to shuffle your array.
Loop a couple of times (length of array should do), and in every iteration, get two random array indexes and swap the two elements there. (Update: if you are really serious about this, this may not be the best algorithm).
You can then print the first nine array elements, which will be in random order and not repeat.
Here is a generic way of getting random numbers between min and max without duplicates:
function inArray(arr, el) {
for(var i = 0 ; i < arr.length; i++)
if(arr[i] == el) return true;
return false;
}
function getRandomIntNoDuplicates(min, max, DuplicateArr) {
var RandomInt = Math.floor(Math.random() * (max - min + 1)) + min;
if (DuplicateArr.length > (max-min) ) return false; // break endless recursion
if(!inArray(DuplicateArr, RandomInt)) {
DuplicateArr.push(RandomInt);
return RandomInt;
}
return getRandomIntNoDuplicates(min, max, DuplicateArr); //recurse
}
call with:
var duplicates =[];
for (var i = 1; i <= 6 ; i++) {
console.log(getRandomIntNoDuplicates(1,10,duplicates));
}
const nums = [1,2,3,4,5,6,7,8,9,10,11,12];
for(var i = 1 ; i < 10; i++){
result = nums[Math.floor(Math.random()*nums.length)];
const index = nums.indexOf(result);
nums.splice(index, 1);
console.log(i+' - '+result);
}

Categories

Resources