Does my merge sort implementation has divide and conquer? - javascript

I was told in an interview to write a program for implementing merge sort on the concept of divide and conquer.
I wrote the below program,
var myGlobalArray = undefined;
myGlobalArray = [8,4,17,2,1,32];
example01(myGlobalArray);
myGlobalArray = [48,14,17,2,11,132];
example01(myGlobalArray);
myGlobalArray = [45,14,5,2,1,12];
example01(myGlobalArray);
myGlobalArray = [45,-14,-5,2,1,-12];
example01(myGlobalArray);
myGlobalArray = [38,27,43,3,9,82,10];
example01(myGlobalArray);
function example01(myArray){
var mainArray = [];
createSubArray(myArray,0);
mainArray = mergeArrays(mainArray);
console.log(mainArray[0]);
// creates an array which contains n arrays for n numbers present in myarray
// i.e. if array = [ 34, 1, 27, 3 ] that the below method will return
// [ [34], [1], [27], [3] ]
function createSubArray(subArray,index){
var localArray = [];
if(subArray[index] !== undefined){
localArray.push(subArray[index]);
mainArray.push(localArray);
createSubArray(subArray,++index);// performs division recursively
}
}//createSubArray
// merge the arrays present i.e.
// if gblArray = [ [2,5], [1,7] ]
// then the below method will return
// an merged array [ [1, 2, 5, 7] ]
function mergeArrays(gblArray){
var mergedArrays = [],
main_array = gblArray,
arr = [],
counter = 0,
nextCounter = 0;
do{
while(counter < main_array.length){
nextCounter = counter + 1;
if(main_array[nextCounter] !== undefined){
arr = mergeAndSort(main_array[counter],main_array[nextCounter]);
mergedArrays.push(arr);
}else{
mergedArrays.push(main_array[counter]);
}
counter = nextCounter + 1;
}
main_array = mergedArrays;
mergedArrays = [];
counter = 0;
nextCounter = 0;
}while(main_array.length > 1);
return main_array;
}//mergeArrays
// merges two array and sorts i.e.
// if array1 = [23,1] and array2 = [4,12] than
// the below method returns [1,4,12,23]
function mergeAndSort(array1,array2){
var array2Counter = 0,
array1Counter = 0,
mergedArray = [];
while(array2Counter < array2.length && array1Counter < array1.length){
if(array2[array2Counter] < array1[array1Counter]){
mergedArray.push(array2[array2Counter]);
array2Counter++;
}else{
mergedArray.push(array1[array1Counter]);
array1Counter++;
}
}
while(array1Counter < array1.length){
mergedArray.push(array1[array1Counter]);
array1Counter++;
}
while(array2Counter < array2.length){
mergedArray.push(array2[array2Counter]);
array2Counter++;
}
return mergedArray;
} //mergeAndSort
}//example01
If I run the above code,
the output is
[ 1, 2, 4, 8, 17, 32 ]
[ 2, 11, 14, 17, 48, 132 ]
[ 1, 2, 5, 12, 14, 45 ]
[ -14, -12, -5, 1, 2, 45 ]
[ 3, 9, 10, 27, 38, 43, 82 ]
But by looking at my above implemented merge-sort program, the inteviewer said that if doesn't follows divide and conquer concept.
I tried to convince him that method "mergeArrays" and "mergeAndSort"
do the divide and conquer. But he didn't agreed.
Where am I going wrong ?

The definition of divide and conquer on Wikipedia is:
an algorithm design paradigm based on multi-branched recursion. A divide and conquer algorithm works by recursively breaking down a problem into two or more sub-problems of the same or related type, until these become simple enough to be solved directly. The solutions to the sub-problems are then combined to give a solution to the original problem.
Your solution does divide, and does combine the solutions to give the final solution, but it does not perform the divisions recursively, making the sub problems gradually smaller.
Edit: you do use recursion, but not in the way intended here. Your recursion is tail-recursion, chopping off the smallest unit (they are not gradually getting smaller), and repeating that chopping via a chain of recursive calls, which could just as easily be performed in a loop.
Your solution is a bottom-up approach, while divide and conquer is commonly understood as a top-down method. Although both methods are valid implementations of merge sort, strictly speaking, only one of them uses the divide and conquer paradigm.
But in a broader interpretation, divide and conquer can just as well be bottom-up, relaxing some of the requirements in the definition quoted above. So this may in the end be a matter of opinion.

Related

Randomly group elements of array using Javascript

I have JSON file, from which I display the data:
data: [
{
name: "john"
},
{
name: "lora"
},
...
]
In total I have 16 names. What I want to do is to randomly group this array by 4 people, total 4 groups.
What is the best way to do this using react.js?
I don't think a solution to this is React-specific, just vanilla javascript will do. First, randomize your entire array (How to randomize (shuffle) a JavaScript array? has a couple different answers you could use). Then, it should be pretty straightforward to iterate over the array and break each group of 4 successive names out.
Here's a simple example with everything built into one function (comments included to explain what's going on):
const data = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
function randomizeAndSplit(data, chunkSize) {
var arrayOfArrays = [];
var shuffled = [...data]; //make a copy so that we don't mutate the original array
//shuffle the elements
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
//split the shuffled version by the chunk size
for (var i=0; i<shuffled.length; i+=chunkSize) {
arrayOfArrays.push(shuffled.slice(i,i+chunkSize));
}
return arrayOfArrays;
}
console.log(randomizeAndSplit(data, 4))
//Example output: [[13, 7, 2, 14], [9, 12, 8, 15], [1, 16, 10, 3], [11, 6, 5, 4]]

Array returned not equal to the original array

I'm trying to solve this question on LeetCode:
Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array.
Note: The number of elements initialized in nums1 and nums2 are m and n respectively.
You may assume that nums1 has enough space (size that is equal to m + n) to hold additional elements from nums2.
I ended up with coming up with this code:
var merge = function(nums1, m, nums2, n) {
var nums = [];
nums1.length = m;
nums2.length = n;
nums = nums.concat(nums1);
console.log(nums);
nums = nums.concat(nums2);
console.log(nums);
nums = nums.sort();
console.log(nums);
return nums;
}
This is what the 'run code' says:
Your input
[1,2,3,0,0,0]
3
[2,5,6]
3
stdout
[ 1, 2, 3 ]
[ 1, 2, 3, 2, 5, 6 ]
[ 1, 2, 2, 3, 5, 6 ]
Output:
[1,2,3]
Expected
[1,2,2,3,5,6]
(an image version if the quotes weren't clear)
When I'm console.logging the array, the answer is correct but for some reason returning the array gives a completely different output
Can anyone help in this?
I think the hint here is that nums1 is big enough to hold n+m values. So the way to solve this problem is to work backwards through both arrays, filling the empty space in nums1 as you go. So for example, for the first iteration of the loop, you would compare nums1[n-1] and nums2[m-1] and put whichever is larger into nums1[n+m-1]. You then continue this for as long as you have values in either array, copying exclusively from the other if one runs out:
const merge = function(nums1, m, nums2, n) {
n--; m--;
for (let i = m + n + 1; i >= 0; i--) {
nums1[i] = (m < 0 || n >= 0 && nums2[n] > nums1[m]) ? nums2[n--] : nums1[m--];
}
return nums1;
}
console.log(merge([1, 2, 3, 0, 0, 0], 3, [2, 5, 6], 3));
This code is O(n) (as compared to your sort which is O(nlogn)) and requires no additional space.

Assigning every possible combination of a range of numbers to variables

Given a range of numbers (1-25) how can I create a loop so that at each iteration I get a unique set of variables.
To give an example, if I was doing it with 4 numbers my variables would be:
on loop 1:
a = 1, b = 2, c = 3, d = 4,
on loop 2:
a = 1, b = 2, c = 4, d = 3
etc.
What I am trying to do is iterate over every possible number for each position (think sudoku)
so in a 3x3 grid: a = top left position, b = top middle, etc...
so similar to sudoku I would have a condition (each row = 65: a + b + c + d + e= 65)
so what I want to do is have a loop where I can assign all the values to variables:
for (something) {
var topLeft = (determined from loop)
var nextPosition = etc.
My solution is currently like so:
var numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
var a,b,c,d,e,f,g,h,i,j,k,l,n,o,p,q,r,s,t,u,v,w,x,y;
var vars = [a,b,c,d,e,f,g,h,i,j,k,l,n,o,p,q,r,s,t,u,v,w,x,y];
var counter = 0;
var found = false;
while(found == false) {
for (var asdf = numbers, i = asdf.length; i--; ) {
var random = asdf.splice(Math.floor(Math.random() * (i + 1)), 1)[0];
vars[i] = random;
}
if (
{a+b+c+d+e = 65,
f+g+h+i+j = 65,
k+l+1+n+o = 65,
p+q+r+s+t = 65,
u+v+w+x+y = 65,
a+f+k+p+u = 65,
b+g+l+q+v = 65,
c+h+1+r+w = 65,
d+i+n+s+x = 65,
e+j+o+t+y = 65,
u+q+1+i+e = 65,
a+g+1+s+y = 65}
) {
console.log(a,b,c,d,e,f,g,h,i,j,k,l,n,o,p,q,r,s,t,u,v,w,x,y);
found = true;
}
counter++;
}
However the obvious problem with this is that it's just randomly selecting values. So it will take an incredible amount of time. I can't work out how to iterate over every possible combination (without having 25 for loops) so I can check which values will pass the condition.
Given a range of numbers (1-25) how can I create a loop so that at each iteration I get a unique set of variables.
It sounds like you are talking about the permutations of a set. You can find a bunch of different algorithms to do this. Here is a nice one from this StackOverflow answer:
function getArrayMutations(arr, perms = [], len = arr.length) {
if (len === 1) perms.push(arr.slice(0))
for (let i = 0; i < len; i++) {
getArrayMutations(arr, perms, len - 1)
len % 2 // parity dependent adjacent elements swap
? [arr[0], arr[len - 1]] = [arr[len - 1], arr[0]]
: [arr[i], arr[len - 1]] = [arr[len - 1], arr[i]]
}
return perms
}
getArrayMutations([1, 2, 3])
> [ [ 1, 2, 3 ],
[ 2, 1, 3 ],
[ 3, 1, 2 ],
[ 1, 3, 2 ],
[ 2, 3, 1 ],
[ 3, 2, 1 ] ]
Be careful though! Permutations are factorial which means they grow really fast.
P(n, k) =
This means that if you want to permute 25 numbers, you are looking at 1.551121e+25 possible combinations which is getting into the not-computable-in-your-lifetime territory.
What I am trying to do is iterate over every possible number for each position (think sudoku) so in a 3x3 grid: a = top left position, b = top middle, etc...
Two dimensional arrays (really just lists of lists) are a great way to store matrix data like this. It doesn't fundamentally change the math to change the representation from a single array, but it might be easier to think about. I'm not 100% sure if you want a 3x3 grid or a 5x5 grid but I'll assume 5x5 since you have 25 numbers in your example. You can easily reshape them like this:
function reshapeArray(array, n=5) {
let result = []
let row = 0
let col = 0
for (let i = 0; i < array.length; i++) {
if (col == 0) {
result[row] = []
}
result[row][col] = array[i]
col++
if (col == n) {
col = 0
row++
}
}
return result
}
reshapeArray([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25])
> [ [ 1, 2, 3, 4, 5 ],
[ 6, 7, 8, 9, 10 ],
[ 11, 12, 13, 14, 15 ],
[ 16, 17, 18, 19, 20 ],
[ 21, 22, 23, 24, 25 ] ]
so similar to sudoku I would have a condition (each row = 65: a + b + c + d + e= 65)
Now that you have your data in an iteratable array, you can very easily check this or any other constraint. For example:
/**
* Checks if a matrix (a 2-d array like the output from reshapeArray())
* meets our criteria.
*/
function checkMatrix(matrix) {
for (let row = 0; row < matrix.length; row++) {
let rowSum = 0
for (let col = 0; col < matrix[row].length; col++) {
rowSum += matrix[row][col]
}
// The row sum does not equal 65, so this isn't the one!
if (rowSum != 65) {
return false
}
}
// All the row sums equal 65
return true
}
If you want add extra rules (like having the columns sum to 65 as well) just modify the code to check for that. You can get the value at any point in the matrix by indexing it matrix[row][col] so matrix[0][0] is the upper-left, etc.
However the obvious problem with this is that it's just randomly selecting values. So it will take an incredible amount of time. I can't work out how to iterate over every possible combination (without having 25 for loops) so I can check which values will pass the condition.
Yes, it will. Sudoku is an NP-Hard problem. If you haven't seen complexity classes before, that's just a very mathematically formal way of saying that there's no clever solution that's going to be significantly faster than just checking every possible solution. This hypothetical problem is not exactly the same, so it might be possible, but it has a very np-ish feel to it.
Currently, your pseudocode solution would look like this:
let permutations = getPermutations() // You're going to need to change this part
// because getting all the permutations
// ahead of time will take too long.
// Just picking random numbers each time is
// not actually a terrible idea. Or, look at
// generator functions (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators)
for (let permutation of permutations) {
let matrix = reshapeArray(permutation)
if (checkMatrix(matrix)) {
console.log("Found it")
console.log(matrix)
break
}
}
If there is only one possible solution that matches your criteria, you will never find it this way. If there is a relatively high density of solutions, you will probably find some. If you really want to solve this problem I would recommend first looking at it from a mathematical perspective -- can you prove that it is or isn't NP? can you make some prediction about the density of solutions?
Not sure what the question really is. I would store the range in an Array:
function range(start, stop = null, upperCase = false){
let b = start, e = stop, s = 'abcdefghijklmnopqrstuvwxyz', x;
const a = [], z = s.split(''), Z = s.toUpperCase().split('');
if(typeof b === 'string'){
s = z.indexOf(b.toLowerCase());
if(e === null){
x = z.length;
}
else{
x = z.indexOf(e.toLowerCase())+1;
}
if(upperCase){
return Z.slice(s, x);
}
return z.slice(s, x);
}
else if(e === null){
e = b; b = 1;
}
for(let i=b; i<=e; i++){
a.push(i);
}
return a;
}
function permuCount(array){
let c = 1;
for(let i=0,n=1,l=array.length; i<l; i++,n++){
c *= n;
}
return c;
}
function comboCount(array){
let l = array.length;
return Math.pow(l, l);
}
console.log(range(2, 23)); console.log(range(10)); console.log(range('d'));
console.log(range('g', 'p')); console.log(range('c', 'j', true));
// here is where you'll have an issue
const testArray = range(0, 9);
console.log(permuCount(testArray));
console.log(comboCount(testArray));
As you can see there are way too many combinations. Also, you should have already see the following post: Permutations in JavaScript?

Can you sort positive integers in O(N) (linear) time using an object in JavaScript?

I need to sort an array of positive integers.
It's easy enough to do via JavaScript's sort method in O(N * log(N)):
let a = [4, 1, 3, 9, 7, 19, 11];
a.sort((a,b) => a - b);
return a;
// returns [1, 3, 4, 7, 9, 11, 19]
But, it seems like it can be done in O(N) using a JavaScript object?
Looping through the array to add an integer into an object is O(N), then grabbing the values from that object is also O(N). (Alternatively, grab the keys and convert back to numbers).
let o = {};
let a = [4, 1, 3, 9, 7, 19, 11];
a.forEach(integer => { o[integer] = integer });
return Object.values(o);
// returns [1, 3, 4, 7, 9, 11, 19]
Drop the constant and we're looking at sorting positive integers in O(N) (sacrificing additional O(N) space).
From everything I've read, this shouldn't be possible. What am I missing here?
The internal code used for setting and retrieving keys is implementation-dependent. The output (and order) is guaranteed (for all property enumeration methods, as of ES2020), but the mechanism is up to the implementer. You'd have to look at the engine source code for that.
I don't know the code that the different Javascript engines are running under the hood, but if you know of an upper bound on the number in the array, this is possible in O(n) (or, more precisely, O(n + k) where k is a constant - the upper bound) by using counting sort: create a map of the keys (similar to you're doing, but including the number of times each item appears), then iterate from 0 to the upper bound, checking to see if the number being iterated over is included in the keys. If so, push to the array:
let o = {};
let a = [4, 1, 3, 9, 7, 19, 11];
// O(n)
for (const num of a) {
if (!o[num]) {
o[num] = [];
}
o[num].push(num);
}
// O(n). This part isn't strictly necessary, but the alternative makes the code uglier
const max = Math.max(...a);
const result = [];
// O(k)
for (let i = 0; i <= max; i++) {
if (o[i]) {
// total of O(n) items pushed over the whole loop
result.push(...o[i]);
}
}
console.log(result);
If, like in your example, there are no repeated numbers, the code is significantly easier:
let o = {};
let a = [4, 1, 3, 9, 7, 19, 11];
for (const num of a) {
o[num] = true;
}
// O(n)
const max = Math.max(...a);
const result = [];
// O(k)
for (let i = 0; i <= max; i++) {
if (o[i]) {
// total of O(n) items pushed over the whole loop
result.push(i);
}
}
console.log(result);

Comparing arrays of strings for similarity

I have available to me hundreds of JSON strings. Each of these contains an array of 15-20 words sorted by some predetermined weight. This weight, if it's worth noting, is the amount of times these words are found in some chunk of text. What's the best way of finding similarity between arrays of words that are structured like this?
First idea that came to my head was to create a numerical hash of all the words together and basically compare these values to determine similarity. I wasn't very successful with this, since the resulting hash values of very similar strings were not very close. After some research regarding string comparison algorithms, I come to Stackoverflow in hopes of receiving more guidance. Thanks in advance, and please let me know if you need more details of the problem.
Edit 1: Clarifying what I'm trying to do: I want to determine how similar two arrays are according to the words each of these have. I would also like to take into consideration the weight each word carries in each array. For example:
var array1 = [{"word":"hill","count":5},{"word":"head","count":5}];
var array2 = [{"word":"valley","count":7},{"word":"head","count":5}];
var array3 = [{"word":"head", "count": 6}, {"word": "valley", "count": 5}];
var array4 = [{"word": "valley", "count": 7}, {"word":"head", "count": 5}];
In that example, array 4 and array 2 are more similar than array 2 and array 3 because, even though both have the same words, the weight is the same for both of them in array 4 and 2. I hope that makes it a little bit easier to understand. Thanks in advance.
I think that what you want is "cosine similarity", and you might also want to look at vector space models. If you are coding In Java, you can use the open source S-space package.
(added on 31 Oct) Each element of the vector is the count of one particular string. You just need to transform your arrays of strings into such vectors. In your example, you have three words - "hill", "head", "valley". If your vector is in that order, the vectors corresponding to the arrays would be
// array: #hill, #head, #valley
array1: {5, 5, 0}
array2: {0, 5, 7}
array3: {0, 6, 5}
array4: {0, 5, 7}
Given that each array has to be compared to every other array, you are looking at a serious amount of processing along the lines of ∑(n-1) times the average number of "words" in each array. You'll need to store the score for each comparison, then make some sense of it.
e.g.
var array1 = [{"word":"hill","count":5},{"word":"head","count":5}];
var array2 = [{"word":"valley","count":7},{"word":"head","count":5}];
var array3 = [{"word":"head", "count": 6}, {"word": "valley", "count": 5}];
var array4 = [{"word": "valley", "count": 7}, {"word":"head", "count": 5}];
// Comparison score is summed product of matching word counts
function compareThings() {
var a, b, i = arguments.length,
j, m, mLen, n, nLen;
var word, score, result = [];
if (i < 2) return;
// For each array
while (i--) {
a = arguments[i];
j = i;
// Compare with every other array
while (j--) {
b = arguments[j];
score = 0;
// For each word in array
for (m=0, mLen = b.length; m<mLen; m++) {
word = b[m].word
// Compare with each word in other array
for (n=0, nLen=a.length; n<nLen; n++) {
// Add to score
if (a[n].word == word) {
score += a[n].count * b[m].count;
}
}
}
// Put score in result
result.push(i + '-' + j + ':' + score);
}
}
return result;
}
var results = compareThings(array1, array2, array3, array4);
alert('Raw results:\n' + results.join('\n'));
/*
Raw results:
3-2:65
3-1:74
3-0:25
2-1:65
2-0:30
1-0:25
*/
results.sort(function(a, b) {
a = a.split(':')[1];
b = b.split(':')[1];
return b - a;
});
alert('Sorted results:\n' + results.join('\n'));
/*
Sorted results:
3-1:74
3-2:65
2-1:65
2-0:30
3-0:25
1-0:25
*/
So 3-1 (array4 and array2) have the highest score. Fortunately the comparison need only be one way, you don't have to compare a to b and b to a.
Here is an attempt. The algorithm is not very smart (a difference > 20 is the same as not having the same words), but could be a useful start:
var wordArrays = [
[{"word":"hill","count":5},{"word":"head","count":5}]
, [{"word":"valley","count":7},{"word":"head","count":5}]
, [{"word":"head", "count": 6}, {"word": "valley", "count": 5}]
, [{"word": "valley", "count": 7}, {"word":"head", "count": 5}]
]
function getSimilarTo(index){
var src = wordArrays[index]
, values
if (!src) return null;
// compare with other arrays
weighted = wordArrays.map(function(arr, i){
var diff = 0
src.forEach(function(item){
arr.forEach(function(other){
if (other.word === item.word){
// add the absolute distance in count
diff += Math.abs(item.count - other.count)
} else {
// mismatches
diff += 20
}
})
})
return {
arr : JSON.stringify(arr)
, index : i
, diff : diff
}
})
return weighted.sort(function(a,b){
if (a.diff > b.diff) return 1
if (a.diff < b.diff) return -1
return 0
})
}
/*
getSimilarTo(3)
[ { arr: '[{"word":"valley","count":7},{"word":"head","count":5}]',
index: 1,
diff: 100 },
{ arr: '[{"word":"valley","count":7},{"word":"head","count":5}]',
index: 3,
diff: 100 },
{ arr: '[{"word":"head","count":6},{"word":"valley","count":5}]',
index: 2,
diff: 103 },
{ arr: '[{"word":"hill","count":5},{"word":"head","count":5}]',
index: 0,
diff: 150 } ]
*/
Sort the arrays by word before attempting comparison. Once this is complete, comparing two arrays will require exactly 1 pass through each array.
After sorting the arrays, here is a compare algorithm (psuedo-java):
int compare(array1, array2)
{
returnValue = 0;
array1Index = 0
array2Index = 0;
while (array1Index < array1.length)
{
if (array2Index < array2.length)
{
if (array1[array1Index].word == array2[array2Index].word) // words match.
{
returnValue += abs(array1[array1Index].count - array2[array2Index].count);
++array1Index;
++array2Index;
}
else // account for the unmatched array2 word.
{
// 100 is just a number to give xtra weight to unmatched numbers.
returnValue += 100 + array2[array2Index].count;
++array2Index;
}
}
else // array2 empty and array1 is not empty.
{
// 100 is just a number to give xtra weight to unmatched numbers.
returnValue += 100 + array1[array1Index].count;
}
}
// account for any extra unmatched array 2 values.
while (array2Index < array2.length)
{
// 100 is just a number to give xtra weight to unmatched numbers.
returnValue += 100 + array2[array2Index].count;
}
return returnValue;
}

Categories

Resources