Ways to get sum of two lowest positive integers (JS debugging failure) - javascript

I was doing this Codewar challenge of getting sum of two smallest integer.
so I was trying to solve with this:
function sumTwoSmallestNumbers(numbers) {
const m = numbers.sort( (a, b) => a - b )[2];
const a = numbers.filter(v => v < numbers.sort( (a, b) => a - b )[2]);
const b = a.reduce( (acc, n) => acc + n);
return b
};
It works, and later on I feel that const m is redundant, so I commented the whole const m line and then it doesn't work now, by "doesn't work" I mean it's giving incorrect outcome, not typeErrors.
My question is, how is that possible??
I mean on const a I literally rewrote what const m was referring to.
How is deleting something that is repeated affects the outcome?
Thanks in advance, I appreciate your time.

Simply, it is because the function Array.prototype.sort in javascript modifies the actual contents of the same instance of the array as well as returns the reference to the same array, In other words, it does not return a new array with the modified contents, it modifies the same array. That's why it doesn't work when you remove the line :
const m = numbers.sort( (a, b) => a - b )[2];
But, why doesn't it work when you tried to put the same logic with the next line of code?
const a = numbers.filter(v => v < numbers.sort( (a, b) => a - b )[2]);
The issue happens when the smallest value of the array is at an index larger than 2, for example:
[ 15, 28, 4, 2, 43 ]
when we try to execute the code on this array, lets take it a step by step to catch where it gets wrong.
function sumTwoSmallestNumbers(numbers) {
const a = numbers.filter(v => v < numbers.sort( (a, b) => a - b )[2]);
const b = a.reduce( (acc, n) => acc + n);
return b
};
const arr = [ 15, 28, 4, 2, 43 ]
const result = sumTwoSmallestNumbers(arr)
console.log(result)
When executing the previous code, the first thing the function will do is the filtering.
It will iterate through the elements of the array:
Starting from index 0 => arr[0] = 15
Testing the condition: 15 < numbers.sort( (a, b) => a - b )[2] => (15 < 15) => false
Notice! as I explained earlier, that the function sort will change the actual array rather than generating a new one, that means that when attempting to test the condition the first time, the array will be sorted, which means that if it is not already sorted, the order of its elements will defiantly change, which is a bad thing considering that the change happened during an ongoing loop through the arrays elements.
Before continuing the iteration of filter, let's make it clear that the new order of the array after the sorting has been executed when attempting to check the condition is as follows:
[ 2, 4, 15, 28, 43 ]
Index 1 => arr[1] = 4
Testing the condition: 4 < numbers.sort( (a, b) => a - b )[2] => (4 < 15) => true
Index 2 => arr[2] = 15
Testing the condition: 15 < numbers.sort( (a, b) => a - b )[2] => (15 < 15) => false
... The rest of the elements
Conclusion
As you can notice, the value 15 has been checked twice, and the value 2, has not been checked at all, which is because of the sudden change in the order of the array during the currently ongoing iteration of the filter method. Which has lead to placing the value of 2 in an already checked location, and the value of 15 into a location that has not been checked yet.
! Notice This is not an optimal solution to such a problem, it does too much work to get to the solution, (filtering, sorting, reducing), which are expensive operations to execute.
A better solution will be something like this, which involves a single loop through the elements of the array O(n):
function sumTwoSmallestNumbers(numbers) {
let lowest = Math.min(numbers[0], numbers[1]),
secondLowest = Math.max(numbers[0], numbers[1])
for(let i = 2; i < numbers.length; i++){
if(numbers[i] < lowest){
secondLowest = lowest
lowest = numbers[i]
}else if(numbers[i] < secondLowest)
secondLowest = numbers[i]
else continue
}
return lowest + secondLowest
}

Related

Trying to solve using recursion without using other algorithms

I am trying to get better at understanding recursion so that I can get better at implementing dynamic programming principles. I am aware this problem can be solved using Kadane's algorithm; however, I would like to solve it using recursion.
Problem statement:
Given an array of integers, find the subset of non-adjacent elements with the maximum sum. Calculate the sum of that subset.
I have written the following partial solution:
const maxSubsetSum = (arr) => {
let max = -Infinity
const helper = (arr, len) => {
if (len < 0) return max
let pointer = len
let sum = 0
while (pointer >= 0) {
sum += arr[pointer]
pointer -= 2
}
return max = Math.max(sum, helper(arr, len - 1))
}
return helper(arr, arr.length - 1)
}
If I had this data:
console.log(maxSubsetSum([3, 5, -7, 8, 10])) //15
//Our subsets are [3,-7,10], [3,8], [3,10], [5,8], [5,10] and [-7,10].
My algorithm calculates 13. I know it's because when I start my algorithm my (n - 2) values are calculated, but I am not accounting for other subsets that are (n-3) or more that still validate the problem statement's condition. I can't figure out the logic to account for the other values, please guide me on how I can accomplish that.
The code is combining recursion (the call to helper inside helper) with iteration (the while loop inside helper). You should only be using recursion.
For each element of the array, there are two choices:
Skip the current element. In this case, the sum is not changed, and the next element can be used. So the recursive call is
sum1 = helper(arr, len - 1, sum)
Use the current element. In this case, the current element is added to the sum, and the next element must be skipped. So the recursive call is
sum2 = helper(arr, len - 2, sum + arr[len])
So the code looks like something this:
const maxSubsetSum = (arr) => {
const helper = (arr, len, sum) => {
if (len < 0) return sum
let sum1 = helper(arr, len - 1, sum)
let sum2 = helper(arr, len - 2, sum + arr[len])
return Math.max(sum1, sum2)
}
return helper(arr, arr.length - 1, 0)
}
Your thinking is right in that you need to recurse from (n-2) once you start with a current index. But you don't seem to understand that you don't need to run through your array to get sum and then recurse.
So the right way is to
either include the current item and recurse on the remaining n-2 items or
not include the current item and recurse on the remaining n-1 items
Lets look at those two choices:
Choice 1:
You chose to include the item at the current index. Then you recurse on the remaining n-2 items. So your maximum could be item itself without adding to any of remaining n-2 items or add to some items from n-2 items.
So Math.max( arr[idx], arr[idx] + recurse(idx-2)) is the maximum for this choice. If recurse(idx-2) gives you -Infinity, you just consider the item at the current index.
Choice 2:
You didn't choose to include the item at the current index. So just recurse on the remaining n-1 items - recurse(n-1)
The final maximum is maximum from those two choices.
Code is :
const maxSubsetSum = (arr) => {
let min = -Infinity
const helper = (arr, idx) => {
if ( idx < 0 ) return min
let inc = helper(arr, idx-2)
let notInc = helper(arr, idx-1)
inc = inc == min ? arr[idx] : Math.max(arr[idx], arr[idx] + inc)
return Math.max( inc, notInc )
}
return helper(arr, arr.length - 1)
}
console.log(maxSubsetSum([-3, -5, -7, -8, 10]))
console.log(maxSubsetSum([-3, -5, -7, -8, -10]))
console.log(maxSubsetSum([-3, 5, 7, -8, 10]))
console.log(maxSubsetSum([3, 5, 7, 8, 10]))
Output :
10
-3
17
20
For the case where all items are negative:
In this case you can say that there are no items to combine together to get a maximum sum. If that is the requirement the result should be zero. In that case just return 0 by having 0 as the default result. Code in that case is :
const maxSubsetSum = (arr) => {
const helper = (arr, idx) => {
if ( idx < 0 ) return 0
let inc = arr[idx] + helper(arr, idx-2)
let notInc = helper(arr, idx-1)
return Math.max( inc, notInc )
}
return helper(arr, arr.length - 1)
}
With memoization:
You could memoize this solution for the indices you visited during recursion. There is only one state i.e. the index so your memo is one dimensional. Code with memo is :
const maxSubsetSum = (arr) => {
let min = -Infinity
let memo = new Array(arr.length).fill(min)
const helper = (arr, idx) => {
if ( idx < 0 ) return min
if ( memo[idx] !== min) return memo[idx]
let inc = helper(arr, idx-2)
let notInc = helper(arr, idx-1)
inc = inc == min ? arr[idx] : Math.max(arr[idx], arr[idx] + inc)
memo[idx] = Math.max( inc, notInc )
return memo[idx]
}
return helper(arr, arr.length - 1)
}
A basic version is simple enough with the obvious recursion. We either include the current value in our sum or we don't. If we do, we need to skip the next value, and then recur on the remaining values. If we don't then we need to recur on all the values after the current one. We choose the larger of these two results. That translates almost directly to code:
const maxSubsetSum = ([n, ...ns]) =>
n == undefined // empty array
? 0
: Math .max (n + maxSubsetSum (ns .slice (1)), maxSubsetSum (ns))
Update
That was missing a case, where our highest sum is just the number itself. That's fixed here (and in the snippets below)
const maxSubsetSum = ([n, ...ns]) =>
n == undefined // empty array
? 0
: Math .max (n, n + maxSubsetSum (ns .slice (1)), maxSubsetSum (ns))
console.log (maxSubsetSum ([3, 5, -7, 8, 10])) //15
But, as you note in your comments, we really might want to memoize this for performance reasons. There are several ways we could choose to do this. One option would be to turn the array we're testing in one invocation of our function into something we can use as a key in an Object or a Map. It might look like this:
const maxSubsetSum = (ns) => {
const memo = {}
const mss = ([n, ...ns]) => {
const key = `${n},${ns.join(',')}`
return n == undefined
? 0
: key in memo
? memo [key]
: memo [key] = Math .max (n, n + maxSubsetSum (ns .slice (1)), maxSubsetSum (ns))
}
return mss(ns)
}
console.log (maxSubsetSum ([3, 5, -7, 8, 10])) //15
We could also do this with a helper function that acted on the index and memoized using the index for a key. It would be about the same level of complexity.
This is a bit ugly, however, and perhaps we can do better.
There's one issue with this sort of memoization: it only lasts for the current run. It I'm going to memoize a function, I would rather it holds that cache for any calls for the same data. That means memoization in the definition of the function. I usually do this with a reusable external memoize helper, something like this:
const memoize = (keyGen) => (fn) => {
const cache = {}
return (...args) => {
const key = keyGen (...args)
return cache[key] || (cache[key] = fn (...args))
}
}
const maxSubsetSum = memoize (ns => ns .join (',')) (([n, ...ns]) =>
n == undefined
? 0
: Math .max (n, n + maxSubsetSum (ns .slice (1)), maxSubsetSum (ns)))
console.log (maxSubsetSum ([3, 5, -7, 8, 10])) //15
memoize takes a function that uses your arguments to generate a String key, and returns a function that accepts your function and returns a memoized version of it. It runs by calling the key generation on your input, checks whether that key is in the cache. If it is, we simply return it. If not, we call your function, store the result under that key and return it.
For this version, the key generated is simply the string created by joining the array values with ','. There are probably other equally-good options.
Note that we cannot do
const recursiveFunction = (...args) => /* some recursive body */
const memomizedFunction = memoize (someKeyGen) (recursiveFunction)
because the recursive calls in memoizedFunction would then be to the non-memoized recursiveFunction. Instead, we always have to use it like this:
const memomizedFunction = memoize (someKeyGen) ((...args) => /* some recursive body */)
But that's a small price to pay for the convenience of being able to simply wrap up the function definition with a key-generator to memoize a function.
This code was accepted:
function maxSubsetSum(A) {
return A.reduce((_, x, i) =>
A[i] = Math.max(A[i], A[i-1] | 0, A[i] + (A[i-2] | 0)));
}
But trying to recurse that far, (I tried submitting Scott Sauyet's last memoised example), I believe results in run-time errors since we potentially pass the recursion limit.
For fun, here's bottom-up that gets filled top-down :)
function f(A, i=0){
if (i > A.length - 3)
return A[i] = Math.max(A[i] | 0, A[i+1] | 0);
// Fill the table
f(A, i + 1);
return A[i] = Math.max(A[i], A[i] + A[i+2], A[i+1]);
}
var As = [
[3, 7, 4, 6, 5], // 13
[2, 1, 5, 8, 4], // 11
[3, 5, -7, 8, 10] // 15
];
for (let A of As){
console.log('' + A);
console.log(f(A));
}

Whats the best possible solution for getting a maximum difference between sucessive elements in a sorted array

If there is an array
let arr = [7,1,4,3,8], the result should be 3 which is maximum difference between 4 & 7.
Similarly, for let arr2 = [5,5,5,7,4], output is 2 (between 5 & 7) and for let arr3 = [6,3,2,5,9] is 3 (between 6 & 9).
What's the best-optimized solution to find out the maximum difference.
My approach is to sort the array and find out the difference between successive elements and then find the max value using Math.max(...differenceValues)
PS: I am not looking for a difference between max and min values in an array. The above question makes sense when you plot a graph on x-axis.
Here is one using reduce:
let arr = [7,1,4,3,8];
const max = arr.sort((a,b) => a-b)
.reduce((result,currentValue,index) => {
if(result < arr[index + 1] - currentValue) {
return arr[index + 1] - currentValue
}
return result
}, 0);
console.log(max);
Similar, but using map and Math.max
let arr = [7,1,4,3,8];
const max2 = Math.max(...arr.sort((a,b) => a-b)
.map((v, i) => (++i < arr.length) ? Math.abs(v - arr[i]) : 0));
console.log(max2);

javascript steps climbing with recursion

My problem is the same as in this question , but the only difference is that I have to return the number of ways in which the steps can be climbed according to a given array of permitted steps. For example, if steps to be climbed are 4 and the permitted steps are [1,3], the result should be 3. Combinations:[(1,1,1,1),(1,3),(3,1)]
I tried to modify the code as :
function add(x,arr){
let result = 0
if(x<0)
return 0;
if(x===0)
return 1;
else{for(let i=0 ; i<arr.length ; i++){
result += add(x-arr[i]);
}
return result};
}
add(4,[1,3])
but this returns an error saying cannot read property length of "undefined".How do I correct this?
In your else condition,
result += add(x-arr[i])
Should be
result += add(x-arr[i], arr)
I misread the question at first, and was happy to show how to convert a recursive function which counts the results to one which lists them. As I came here to post it, I realized that this was not what was asked, but I still find it a nice example of how one might go about this... so I'm posting it anyway. :-)
A straightforward counting solution, similar to the one in the question (with the fix from Abbas Cyclewala) could be structured like this:
const count = (n, ss) =>
n < 0 ?
0
: n == 0 ?
1
: // else
ss.map(s => count(n - s, ss))
.reduce((a, b) => a + b, 0)
console .log (
count (4, [1, 3]) //~> 3
)
We can switch to a list of results instead of the count by noting that the equivalent of 0 is [], of 1 is [[]] and instead of adding all the recursive results, we need to simply concatenate them. That leaves only an update to the actual recursive results, which is not difficult: if the first step is of length k, recurse on (n - k), and prepend k to each result.
You can see how structurally similar these are:
const paths = (n, ss) =>
n < 0 ?
[]
: n == 0 ?
[[]]
: // else
ss.map(s => paths(n - s, ss).map(ps => [s, ...ps]))
.reduce((a, b) => a.concat(b), [])
console .log (
paths (4, [1, 3]) //~> [[1, 1, 1, 1], [1, 3], [3, 1]]
)
So this doesn't answer the original question, but if someone wants it, here it is!

What is the time complexity of Javascript Array.reduce() and Array.find()?

I am trying to return an array of indexes of values that add up to a given target. I am trying to solve it the fastest way I can!
Examples:
sumOfTwo([1, 2, 4, 4], 8) // => [2, 3]
sumOfTwo([1, 2, 3, 9], 8) // => []
So first I tried a simple brute-force. (Time complexity: O(n^2) )
function sumOfTwo(arr, target) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] + arr[j] === target) {
return [i, j];
}
}
}
return [];
}
Then I tried: (Time complexity: sorting O(n log n) + for loop O(n))
function sumOfTwo(arr, target) {
const sortedArr = arr.sort();
let idxFromBack = arr.length - 1;
for (let [idx, val] of sortedArr.entries()) {
if (val + arr[idxFromBack] > target) {
idxFromBack--;
}
if (val + arr[idxFromBack] === target) {
return [idx, idxFromBack];
}
}
return [];
}
Then I came with this solution that I don't even know the time complexity.
function sumOfTwo(arr, target) {
const complements = [];
for (let [idx, val] of arr.entries()) {
if (complements.reduce((acc, v) => (acc || v.value === val), false)) {
return [complements.find(v => v.value === target - val).index, idx];
}
complements.push({index: idx, value: target - val});
}
return [];
}
I know that I am using a for-loop but I don't know the complexity of the build-in high order functions .reduce() and .find(). I tried a couple of searches but I couldn't find anything.
If anyone can help me would be great! Please include Big-O notation if possible.
Repl.it: https://repl.it/#abranhe/sumOfTwo
Please also include the time complexity of the last solution.
The minimum time complexity of .reduce is O(n), because it must iterate through all elements once (assuming an error isn't thrown), but it can be unbounded (since you can write any code you want inside the callback).
For your
// Loop, O(n), n = length of arr:
for (let [idx, val] of arr.entries()) {
// .reduce, O(n), n = length of complements:
if (complements.reduce((acc, v) => (acc || v.value === val), false)) {
// If test succeeds, .find, O(n), n = length of complements:
return [complements.find(v => v.value === target - val).index, idx];
}
complements.push({index: idx, value: target - val});
}
the time complexity is, worst case, O(n^2). The reduce runs in O(n) time, and you run a reduce for every entry in arr, making it O(n^2).
(The .find is also an O(n) operation, but O(n) + O(n) = O(n))
Your code that sorts the array beforehand has the right idea for decreasing complexity, but it has a couple flaws.
First, you should sort numerically ((a, b) => a - b)); .sort() with no arguments will sort lexiographically (eg, [1, 11, 2] is not desirable).
Second, just decrementing idxFromBack isn't enough: for example, sumOfTwo([1, 3, 8, 9, 9], 9) will not see that 1 and 8 are a match. Perhaps the best strategy here would be to oscillate with while instead: from a idxFromBack, iterate backwards until a match is found or the sum is too small, and also iterate forwards until a match is found or the sum is too large.
You can also improve the performance of this code by sorting not with .sort((a, b) => a - b), which has complexity of O(n log n), but with radix sort or counting sort instead (both of which have complexity of O(n + k), where k is a constant). The optimal algorithm will depend on the general shape and variance of the input.
An even better, altogether different O(n) strategy would be to use a Map or object. When iterating over the array, put the value which would result in a match for the current item into a key of the object (where the value is the current index), and just look to see if the current value being iterated over exists in the object initially:
const sumOfTwo = (arr, target) => {
const obj = {};
for (const [i, num] of arr.entries()) {
if (obj.hasOwnProperty(String(num))) {
return [obj[num], i];
}
const matchForThis = target - num;
obj[matchForThis] = i;
}
return [];
};
console.log(
sumOfTwo([1, 2, 4, 4], 8), // => [2, 3]
sumOfTwo([1, 2, 8, 9], 9), // 1 + 8 = 9; [0, 2]
sumOfTwo([1, 2, 3, 9], 8) // => []
);
As a supplementary answer, here is the algorithm of the find method in the language spec:
When the find method is called, the following steps are taken:
Let O be ? ToObject(this value).
Let len be ? LengthOfArrayLike(O).
If IsCallable(predicate) is false, throw a TypeError exception.
Let k be 0.
Repeat, while k < len,
a. Let Pk be ! ToString(𝔽(k)).
b. Let kValue be ? Get(O, Pk).
c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
d. If testResult is true, return kValue.
e. Set k to k + 1.
Return undefined.
Note the "repeat, while k < len" in step 5. Since time complexity in general measures the worst case scenario (aka the upper bound), we can assume that the searched element is not present in the collection.
The number of iterations made during step 5 then is equal to len which directly depends on the number of elements in the collection. And what time complexity has a direct correlation to the number of elements? Exactly, the linear O(n).
For a visual-ish demonstration, run the following snippet. Apart from some stray dots, the improvized graph should show a linear progression (takes a little while to display in Stack snippets, but you can watch it live in the devtools console):
const iter = 1e7;
const incr = 2e5;
const test = new Array(iter).fill(0);
const steps = Math.ceil(iter / incr);
for (let i = 0; i < steps; i += 1) {
const sub = test.slice(0, i * incr + incr);
const s = Date.now();
const find = sub.find((v) => v === 1);
const e = Date.now();
const d = e - s;
console.log("\u200A".repeat(Math.floor(d/3))+"*");
}

Recursive function issue in JS

So I need to solve this problem STRICTLY using recursion
// 2. Compute the sum of an array of integers.
// sum([1,2,3,4,5,6]); // 21
And then I'm testing this solution in PythonLive
var sum = function(array) {
if(array.length===0){
return array
}
return array.slice(0,array.length)+sum(array.pop())
};
sum([1,2,3,4,5,6]);
Then at step 6 it says "TypeError: array.slice is not a function"
I don't understand why if it already worked taking 6 off the array and returning the remaining array...
Can someone explain me what am I doing wrong please?
thanks! :)
If you look at the return values you will see that you are always returning an array. This can't be right when you want a number as a final result. When array.length === 0 you can safely return 0 because that's the same of an empty array. That's your edge condition. After that you just want the sum of one element plus the rest.
You can also just return the array length when it's zero making for a very succinct solution. && shortcircuits returning the left element if it's false (like 0) otherwise the second:
var sum = (array) => array.length && array.pop() + sum(array)
console.log(sum([1,2,3,4,5,6]));
If you prefer slice you could also this, which is basically the same:
var sum = (array) => array.length && array[0] + sum(array.slice(1))
console.log(sum([1, 2, 3, 4, 5, 6]));
Recursive sum function:
const sum = list => {
if (!list.length) return 0;
return list.pop() + sum(list);
};
Because .pop mutates the array, you don't need to use slice. For a non-destructive version (doesn't alter the original array):
const sum = ([first, ...rest]) => {
if (first === undefined) return 0;
return first + sum(rest);
};
The issue with your code is that you are processing the values the wrong way around, it should be
return sum(array.slice(0,array.length-1)) + array.pop();
In fact since array.pop() removes the element, you can just do it this way around:
return array.pop() + sum(array);
You also need to return 0 when array.length===0, otherwise the sum will fail.
if (array.length===0) return 0;
However it's much simpler just do this with reduce:
let arr = [1,2,3,4,5,6];
console.log(arr.reduce((t, v) => { return t + v; }, 0));
Another encoding using an explicit empty and pure expressions
const empty = x =>
x === empty
const sum = ([ x = empty, ...rest ]) =>
empty (x)
? 0
: x + sum (rest)
console.log
( sum ([ 1, 2, 3, 4, 5 ]) // 15
, sum ([]) // 0
)
Your other question that asks how to sum a nested array was put on-hold for reasons I don't understand. You can adapt the above implementation to support input of nested arrays
const empty = x =>
x === empty
const sum = ([ x = empty, ...rest ]) =>
empty (x)
? 0
: Array.isArray (x)
? sum (x) + sum (rest)
: x + sum (rest)
console.log
( sum ([ 1, [ 2, [ 3, 4 ], 5 ]]) // 15
, sum ([ 1, 2, 3, 4, 5 ]) // 15
, sum ([[[]]]) // 0
, sum ([]) // 0
)

Categories