Why is this highly iterative code faster than less iteration using a dictionary (js obbject really...) - javascript

This question is specific to the hacker rank problem Largest Permutation
I wrote a solution in javascript that works for most cases (A), but there are some select test cases where it times out. Ok... so then it must not be very efficient. I found someone else's solution to the problem (B) which is highly iterative. This loops through the given array at least once total, and then through the rest of the array at least K times. This seems very inefficient to me but this approach did not cause a timeout and I can't figure out why that is... one of these seems to be A: O(n) vs B: O(n^2) in my analysis but I guess im wrong... so I am asking here to see if anyone can help me figure out why this is or what the correct algorithm speed analysis is
These code snippets are the function body of a function that takes arr as an argument
A) My solution
const indices = {}
arr.forEach((n,i) => {
indices[n] = i
})
let count = 0
while(count < k){
// swap to earliest index (count)
const targetValue = arr.length - count
const targetIndex = indices[targetValue]
let tmp = arr[count]
arr[count] = arr[targetIndex]
arr[targetIndex] = tmp
//update indices
indices[targetValue] = count
indices[tmp] = targetIndex
count++
}
return arr
B) an alternative that to me should be slower but seems to be faster...
let n = arr.length
for(var i = 0; i < n - 1; i++) {
if( k > 0) {
var max = i;
for(var j = i + 1; j < n; j++){
if(arr[j] > arr[max])
max = j;
}
if(max != i) {
var temp = arr[max];
arr[max] = arr[i];
arr[i] = temp;
k--;
}
}
}
return arr

There are these issues with your implementation:
When k is larger than arr.length, then the arr.length - count will become 0 or less and thus targetValue will be a value that is not in the array. And so targetIndex will be undefined.
Your loop represents O(𝑘), and as 𝑘 can be much larger than 𝑛, this can make a huge difference in running time.
When the swap is a none-operation because the value happens to already be in its optimal position, then count is still incremented, reducing the number of actual swaps that are done.
Not an issue, but a for loop seems more appropriate here than a while.
Here is a correction of your main loop:
for (let count = 0; count < k && count < arr.length; count++) {
const targetValue = arr.length - count
if (arr[count] != targetValue) { // target not at optimal index?
// swap to earliest index (count)
const targetIndex = indices[targetValue]
let tmp = arr[count]
arr[count] = arr[targetIndex]
arr[targetIndex] = tmp
//update indices
indices[targetValue] = count
indices[tmp] = targetIndex
}
else k++; // Allow one more swap, as this was a no-op
}

Related

Why does my specific JavaScript algorithm, which uses 1 array of size n, always use up more memory than another that uses 3 arrays of size n?

I am looking at the LeetCode problem Product of Array Except Self. I have one JS solution that uses a single array of size n (n being the length of the input array), and another that is very similar, but uses 3 arrays of size n. No matter how many times I submit the solutions, the 1-array solution seems to use way more space than the 3-array solution. (I say it uses "way more space" because while the actual difference is only a few MB, the percentile difference is huge - the 1-array solution uses more space than about 90-95% of submissions, while the 3-array solution uses more space than about 50-60% of submissions.)
I also have a 2-array solution that consistently falls in the middle of my other two solutions in terms of space usage. This makes sense, but the order is reversed - I would expect the 1-array solution to always use less space than the 3-array solution. To make things stranger, I have tested somebody else's 1-array solution, and theirs uses much less space than my 1-array solution, but I can't understand why. Perhaps it's a quirk of JS that I'm unaware of, or perhaps I'm missing something obvious. Any input would be much appreciated!
Here are the solutions. The actual algorithmic logic is essentially the same; the main difference is how many arrays they use.
3-array solution (the 3 arrays of size n are left, right, and res):
const productExceptSelf = function(nums) {
const left = [];
const right = new Array(nums.length);
for (let i = 0; i < nums.length; i++) {
if (i === 0)
left.push(1);
else
left.push( left[i - 1] * nums[i - 1] );
}
for (let i = nums.length - 1; i >= 0; i--) {
if (i === nums.length - 1)
right[i] = 1;
else
right[i] = right[i + 1] * nums[i + 1];
}
const res = [];
for (let i = 0; i < nums.length; i++)
res.push( left[i] * right[i] );
return res;
};
1-array solution (it uses the same logic as the above, but uses a single res array of size n):
const productExceptSelf = function(nums) {
const res = [ 1 ];
for (let i = 1; i < nums.length; i++)
res.push( res[i - 1] * nums[i - 1] );
let right = 1;
for (let i = nums.length - 1; i >= 0; i--) {
res[i] *= right;
right *= nums[i];
}
return res;
};
Someone else's 1-array solution (I understand why it would be faster than mine since it uses just 1 loop, but I don't see why it would use so much less space since the res array is also size n):
const productExceptSelf = function(nums) {
const res = [];
let leftMult = 1;
let rightMult = 1;
for (let i = nums.length - 1, j = 0; i >= 0; i--, j++) {
res[i] = (res[i] ?? 1) * rightMult;
res[j] = (res[j] ?? 1) * leftMult;
rightMult *= nums[i];
leftMult *= nums[j];
}
return res;
}

Javascript: Best way to sort array of length N and get top m

I have a huge array of length over 200,000. I need to get top 10 values. I know sort is not the best solution. I have tried the following solution:
const sortBy = 'key';
const results = [];
const m = 10;
const N = array.length;
while (array.length && results.length < m) {
let currentMax = 0;
let currentMaxIndex = 0;
array.forEach((record, i) => {
if (record[sortBy] >= currentMax) {
currentMax = record[sortBy];
currentMaxIndex = i;
}
});
results.push(...array.splice(currentMaxIndex, 1));
}
Here array is an Array of length 200,000.
Problem is, I think if m equals N, then this
is going to take more time than sort itself. I want to know if there
is a better solution, that can handle both the cases.
Thank you for the help, but the actual question is m ∈ (0, N). m can take any value up to N. So, at which point would it be advisable to switch to in-built sort?
As per my understanding as m reaches N the complexity increases and sort is the best solution when m === N
I have tested with the example provided by #t-j-crowder here. A test to get top 10 from 100 entries.
When testing with different values in m, the faster algorithm is changing to sort at m === 85. So, I want to find out if there is any way to determine when we should switch back to sort, to have optimal performance in all cases.
You don't need to sort the whole array, you just need to insert into your top 10 array in numeric order and drop any additional entries:
var a = Array.from({length:100}, () => Math.floor(Math.random() * 1000000));
var check = a.slice().sort((left, right) => right - left).slice(0, 10);
console.log("check", check);
function checkResult(top10) {
var n;
for (n = 0; n < 10; ++n) {
if (top10[n] !== check[n]) {
console.log("Error in test at #" + n + ", expected " + check[n] + ", got " + top10[n]);
return false;
}
}
return true;
}
var top10 = [];
var cutoff = -Infinity;
var full = false;
var n, len, value;
for (n = 0, len = a.length; n < len; ++n) {
value = a[n];
// If the value may be in the top 10...
if (!full || value >= cutoff) {
// Insert into top10
let found = false;
for (let n = 0; n < top10.length; ++n) {
if (top10[n] <= value) {
top10.splice(n, 0, value);
found = true;
break;
}
}
if (!found) {
top10.push(value);
}
// Trim it
if (top10.length > 10) {
full = true;
top10.length = 10;
}
// Remember the lowest of the top 10 candidates we have now so we can not bother with checking lower ones
cutoff = top10[top10.length - 1];
}
}
console.log("top10", top10);
console.log("Good? " + checkResult(top10));
.as-console-wrapper {
max-height: 100% !important;
}
You can probably tidy that up a bit, optimize it further, but you get the idea. Maintaining a list of just the top 10 highest you've seen, dropping ones off the bottom if others join it.
Benchmark here, easily beats sorting and then grabbing the top 10 on Chrome and Firefox; the converse is true on Edge.
function limitSort(array, sortBy, limit) {
const result = [];
// Iterate over the array *once*
for(const el of array) {
// Exclude all elements that are definetly not in the resulting array
if(result.length < limit || el[sortBy] > result[0]) {
// Determine the position were the element needs to be inserted (Insertion sort)
let insertAt = 0;
while(insertAt < result.length && result[insertAt][sortBy] < el[sortBy]) insertAt++;
// Finally insert it
result.splice(insertAt, 0, el);
// If the results are getting to long, remove the lowest element
if(result.length > limit)
result.splice(0, 1);
}
}
return result;
}
This implements the algorithm, Niet the Dark Absol proposed above. Try it!

JavaScript neglecting the else statement

I created a function which takes in two values..
Both are numbers represented by n & p. What the function does is that it gets the number n and splits it up then squares it to the value of p and sums them in an increasing order like this: n^p + n^(p+1) + n^(p+2) + ...
Here is the function
function digPow(n, p) {
// ...
let num = n.toString();
let pow = p;
let arrn = [];
let arrp = [];
for (let i = 0; i < num.length; i++) {
arrn.push(JSON.parse(num[i]));
}
let index = arrn.join('');
let sindex = index.split('');
for (let j = 0; j < sindex.length; j++) {
let power = p + j;
let indexs = sindex[j];
let Mathpow = Math.pow(indexs, power);
arrp.push(Mathpow);
}
let total = 0;
for (let m in arrp) {
total += arrp[m]
}
let secondVal = total / n;
let totals = total / secondVal;
let mx = [-1]
if (totals.length == n.length) {
return secondVal
} else {
return -1
}
}
Now i created variables and arrays to store up the values and then the if part is my problem.. The if/else statement is meant to let the program check if a particular variable totals is equal to n which is the input.. if true it should return a variable secondVal and if not it should return -1..
So far its only returning secondVal and i'snt returning -1 in cases where it should like:
digPow(92, 1) instead it returns 0.14130434782608695
What do i do?
totals and n are both numbers. They don't have a .length property, so both totals.length and n.length evaluate to undefined. Thus, they are equal to each other.
There are plenty of other weird things going on in your code, too. I'd recommend finding a good JavaScript tutorial and working through it to get a better feel for how the language (and programming in general) works.
Let's start by stripping out the redundant variables and circular-logic code from your function:
function digPow(n, p) {
let num = n.toString();
// let pow = p; // this is never used again
// let arrn = []; // not needed, see below
// let arrp = []; // was only used to contain values that are later summed; can instead just sum them in the first place
// this does the same thing as num.split(''), and isn't needed anyway:
//for (let i = 0; i < num.length; i++) {
// arrn.push(JSON.parse(num[i]));
//}
// this is the same as the original 'num' variable
// let index = arrn.join('');
// This could have been num.split(), but isn't needed anyway
// let sindex = index.split('');
let total = 0; // moved this line here from after the loop below:
for (let j = 0; j < num.length; j++) { // use num.length instead of the redundant sindex
let power = p + j;
// The only reason for the sindex array was to get individual characters from the string, which we can do with .charAt().
//let indexs = sindex[j];
let indexs = num.charAt(j);
let Mathpow = Math.pow(indexs, power);
//arrp.push(Mathpow); // No need to collect these in an array
total += Mathpow; // do this instead
}
// No need for this loop, since we can sum the total during the previous loop
// let total = 0;
//for (let m in arrp) {
// total += arrp[m]
//}
let secondVal = total / n;
// let totals = total / secondVal;
// The above is the same thing as total / total / n, which is:
let totals = 1/n;
// This is never used
//let mx = [-1]
// This was totals.length and n.length, which for numbers would always be undefined, so would always return true
if (totals == n) {
return secondVal
} else {
return -1
}
}
So the above reduces to this functionally identical code:
function digPow(n, p) {
let num = n.toString();
let total = 0;
for (let j = 0; j < num.length; j++) {
let power = p + j;
let indexs = num.charAt(j);
let Mathpow = Math.pow(indexs, power);
total += Mathpow;
}
let secondVal = total / n;
let totals = 1 / n;
if (totals == n) {
return secondVal
} else {
return -1
}
}
Now let's talk about the logic. The actual output will always be -1, unless the input is 1, due to what's clearly a logic error in the totals variable: the only case where 1/n == n is true is when n==1.
Setting that aside, and looking only at the secondVal variable, some examples of what it's calculating for a given input would be
digPow(123,1) --> (1^1 + 2^2 + 3^3) / 123 --> 14/123
digPow(321,2) --> (3^2 + 2^3 + 1^4) / 321 --> 21/321
digPow(92, 1) --> (9^1 + 2^2) / 92 --> 13/92
I'm pretty sure from your description that that's not what you intended. I'm not at all sure from your description what you did intend, so can't be much help in correcting the function beyond what I've done here.
What I'd suggest is to sit down and think through your algorithm first; make sure you know what you're trying to build before you start building it. There were some syntax problems with your code, but the real issues are with the logic itself. Your original function shows clear signs of "just keep throwing more lines of code at it until something happens" rather than any planned thinking -- that's how you wind up with stuff like "split a string into an array, then join it back into a string, then split that string into another array". Write pseudocode first: break the problem down into steps, think through those steps for some example inputs and make sure it'll produce the output you're looking for. Only then should you bust out the IDE and start writing javascript.

quick sort in javascript

I have this working insertion sort javascript code.
Array.prototype.insertionSort = function() {
var A = this;
var time = 0;
var n = A.length;
for (var i = 1; i < n; i++) {
var v = A[i];
var j = i-1;
while (j >= 0 && A[j] > v) {
A[j+1] = A[j];
j--;
var temp = new Array(this);
setTimeout(callback(JSON.stringify(temp)), 500*(time++));
}
A[j+1] = v;
var temp = new Array(this);
setTimeout(callback(JSON.stringify(temp)), 500*(time++));
}
return time;
}
I need something similar for quciksort, but I don't know how to do it. Can someone help me, please?
This might be the late posting, but I hope this may help somebody. QuickSort is a Divide and Conquer algorithm. It picks an element as pivot and partitions the given array around the picked pivot. You can select pivot in the following ways.
a)pick the first element as the pivot(Implemented below) b)pick the last element as pivot c)Pick a random element as pivot.d)pick median as a pivot.
if your array is already sorted then time complexity would be O(n^2) but for the average and best case it is O(N log N). It is in place algorithm and nonstable(as partitioning does one of the long-range exchanges). In some cases quicksort might take quadratic time. Quicksort has more overhead even for tiny arrays for sizes less than 10-20. If you use insertion sort for the small size of arrays then you might reduce the processing time by 15-20 %. Sharing my code below, also if you want to reduce more processing time then try to estimate partitioning element to be middle of an array.
class QuickSort{
constructor(){
this.cutoff = 10;
}
partition(arr, lo, hi){
let i = lo, j = hi+1;
debugger;
while(true){
while(arr[++i] < arr[lo]) if(i == hi) break;
while(arr[lo] < arr[--j]) if(j == lo) break;
if(i >= j) break;
this.swap(arr, i, j);
}
this.swap(arr, lo, j);
return j;
}
swap(arr, i, j){
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
sort(arr, lo, hi){
//if(hi <= lo) return;
if((this.cutoff + lo) -1 >= hi){
let insertionSort = new InsertionSort();
insertionSort.sort(arr); return;
}
console.log('Inside Quick Sort');
let j = this.partition(arr, lo, hi);
this.sort(arr, lo, j-1);
this.sort(arr, j+1, hi);
}
shuffle(arr) {
let n = arr.length;
for (let i = 0; i < n; i++) {
// choose index uniformly in [0, i]
let r = Math.floor(Math.random() * (i + 1));
this.swap(arr, i, r);
}
console.log('After uniform random Shuffle');
console.log(arr);
this.sort(arr, 0, n-1);
}
}
class InsertionSort{
constructor(){
this.quickSort = new QuickSort();
}
sort(arr){
console.log("Inside Insertion sort");
for(let i = 1; i < arr.length; i++){
for(let j = i; j > 0; j--){
if(arr[j-1] > arr[j]){
this.quickSort.swap(arr, j, j-1);
}
}
}
}
}
let arr = [101, 22,4,22,2,7,9,10,49,101,-1,4,1,6,99];
console.log('Before sorting an array');
console.log(arr);
let quickSort = new QuickSort();
quickSort.shuffle(arr);
console.log('After Sorting an array');
console.log(arr);
you can get to know the time complexity of from this post time complexity
The Wikipedia article on Quick Sort is pretty thorough and even includes a few pseudocode implementations.
Wikipedia summarizes the problem like so:
Quicksort is a divide and conquer algorithm. Quicksort first divides a
large array into two smaller sub-arrays: the low elements and the high
elements. Quicksort can then recursively sort the sub-arrays.
The steps are:
Pick an element, called a pivot, from the array.
Partitioning: reorder the array so that all elements with values less than the pivot come before the pivot, while all elements with values greater than the pivot come after it (equal values can go either way). After this partitioning, the pivot is in its final position. This is called the partition operation.
Recursively apply the above steps to the sub-array of elements with smaller values and separately to the sub-array of elements with greater values.
The base case of the recursion is arrays of size zero or one, which are in order by definition, so they never need to be sorted.
The pivot selection and partitioning steps can be done in several
different ways; the choice of specific implementation schemes greatly
affects the algorithm's performance.
And just a heads-up, I remember off-by-one errors being the biggest headache with this problem when I did it in school, so if you don't get the right answer right away, double-check your pivot selection and partitioning logic.
You should check google before posting a question: Quicksort is a very common algorithm and you have plenty of resources describing it.
function swap(items, firstIndex, secondIndex){
var temp = items[firstIndex];
items[firstIndex] = items[secondIndex];
items[secondIndex] = temp;
}
function partition(items, left, right) {
var pivot = items[Math.floor((right + left) / 2)],
i = left,
j = right;
while (i <= j) {
while (items[i] < pivot) {
i++;
}
while (items[j] > pivot) {
j--;
}
if (i <= j) {
swap(items, i, j);
i++;
j--;
}
}
return i;
}
function quickSort(items, left, right) {
var index;
if (items.length > 1) {
left = typeof left != "number" ? 0 : left;
right = typeof right != "number" ? items.length - 1 : right;
index = partition(items, left, right);
if (left < index - 1) {
quickSort(items, left, index - 1);
}
if (index < right) {
quickSort(items, index, right);
}
}
return items;
}
// first call
var result = quickSort(items);
Code credits: N.C.Zakas
Furthermore, note that Quicksort is used by V8 when calling Array.prototype.sort() on arrays > 10 items (V8 source). On smaller arrays, InsertionShort is actually faster.
heres a short version of quick sort written in pure JS!
as seen in Intro To Algorithms
var partition = function (arr, low, high) {
var x = arr[high]
var i = low - 1
for (var j = low; j <= high - 1; j++) {
if (arr[j] <= x) {
i++
var temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
}
var temp = arr[i + 1]
arr[i + 1] = arr[high]
arr[high] = temp
return i + 1
}
var quickSort = function (arr, low, high) {
if (low < high) {
index = partition(arr,low,high)
if (low < index-1) quickSort(arr,low,index-1)
if (index+1 < high) quickSort(arr,index+1,high)
}
}
var list2 = [1000,13,12,1001,82,1,2,4,3,0]
console.log(quickSort(list2,0,list2.length))
also on my GitHub

string addition I coderbyte completely stumped

Can anyone explain the solution to me like a 6 year old? I haven't been able to make sense of the solutions. Maybe some code comments?
Thank you.
I've spent the last 2 hours on coderbyte trying to solve this one. Here's the question:
Have the function ArrayAdditionI(arr) take the array of numbers stored
in arr and return the string true if any combination of numbers in the
array can be added up to equal the largest number in the array,
otherwise return the string false. For example: if arr contains [4, 6,
23, 10, 1, 3] the output should return true because 4 + 6 + 10 + 3 =
23. The array will not be empty, will not contain all the same elements, and may contain negative numbers.
I've scoured the internet, read through a bunch of people's solutions from the people's answers on coderbyte itself, but without any comments I'm really struggling to figure out how this is done. I've started over countless times, so I'm not even sure if this last attempt is any better or not.
I understand that I'll need to loop through somehow and test every combination index 5, index 4, index 3, index 2, index 1, AND every combination of less than all of those (ie just index 5 and index 3). I just can't figure out how to do that. If I knew the list would always be an array of 5 numbers, and it required all 5, I would write 5 loops all equally nested inside of one big one, right? However, with the added complexity of not requiring all numbers and not knowing the length of the array for all cases, I am completely stumped. I tried using Math.floor(Math.random() * array.length); to generate a list of numbers... but that didnt work either.
function ArrayAdditionI(arr) {
var longest = arr.sort( function(a,b) { return a-b });
var longest = longest[longest.length - 1];
var sumArr = function (arrb) {
var sum = 0;
for (var z = 0; z < arrb.length; z++){
sum += arrb[z];
}
return sum;
};
for (var i = 0; i > arr.length; i++) {
for (var y = 0; y > arr.length; i++) {
testArr.push(arr[i]);
if (sumArr(testArr) === longest) {
return true;
}
testArr.push(... its 4am and I'm stumped!...)
}}
// code goes here
return false;
}
// keep this function call here
// to see how to enter arguments in JavaScript scroll down
ArrayAdditionI(readline());
A fairly simple to understand and common solution to the problem is as follows. It basically starts by looping forward through the array (loop i) by adding each subsequent number (loop j). If loop j finishes without a solution, loop k begins and removes each subsequent number. Then i increments and the loops start over.
function ArrayAdditionI(arr) {
arr.sort(function(a,b){return a - b})
var largest = arr.pop(); // Set largest to last (largest) array value
var sum = 0;
for (var i = 0; i < arr.length; i++){ // Start outer loop
sum += arr[i];
for (var j = 0; j < arr.length; j++){ // Start inner to begin sum
if (i != j) { // Ensure we don't add the same array index to itself
sum += arr[j];
console.log(sum);
if (sum == largest) {
return true;
}
}
}
for (var k = 0; k < arr.length; k++) { // If no match, start 2nd loop to re-iterate removing index values
if (i != k) {
sum -= arr[k];
console.log(sum);
if (sum == largest) {
return true;
}
}
}
sum = 0; // Reset sum for outer loop
}
return false;
}
The comment by thefourtheye basically told you the name of the problem and what to search for on google.
Solutions in java code would be the following...
1) find all subsets that sum to a particular value
This is the theory and pseudocode.
2) How to implement the Sum of Subsets problem in Java
Change the code to return what you wish if number of sets > 0 .
3) Modify GetAllSubsetByStack in below code to stop when a set is found:
https://codereview.stackexchange.com/questions/36214/find-all-subsets-of-an-int-array-whose-sums-equal-a-given-target
We did a similar recursion for a "MakeChange" algorithm at a meetup I went to last night. This morning I took a look at this problem with fresh eyes after not looking at it for a few weeks. Here's what I came up with.
Essentially: sort the array to biggest to smallest, shift that one off and declare it "goal", then recursively reduce an array by slicing it smaller and smaller each recurse, when the array reaches 0 length, randomize the original array and begin reducing again.
return true if goal = total (ie the reduce)
return false if ive randomized the array more than 1000 times.
function ArrayAdditionI(arr) {
var originalArr = arr.sort(function(a,b) {return b-a});
var goal = arr.shift();
var counter = 0;
function randomArray(array) {
for (var i = array.length - 1; i > 0; i -= 1){
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function recurse(arr) {
if (arr.length == 0){
counter++
var newArr = randomArray(originalArr);
return recurse(newArr);
} else {
var total = arr.reduce(function(a,b) {return a+b});
if (goal == total){
return true
} else if (counter == 1000) {
return false
} else {
newArr = arr.slice(1);
return recurse(newArr);
}
}
}
// code goes here
return recurse(originalArr);
}

Categories