Here's an exercise from the "easy" section of Coderbyte.
Have the function ArrayAdditionI(arr) take the array of numbers and return "true" if any combination of numbers in the array can be added up to equal the largest number in the array, otherwise return "false".
For example: if arr contains [4, 6, 23, 10, 1, 3] the output should return true because 4 + 6 + 10 + 3 = 23.
I can imagine an interative solution to this, but the complexity fills me with horror.
What do I need to go study to solve this problem?
I'm reading up on the Combination function C(n,k). Is that the right path?
I think it's a 1d bin-packing or a knappsack problem. The problem is also a decision problem so it's a np problem. It can be a weak polynominal problem.
There is perhaps a very naive solution:
arrAddition = function(values) {
// sort from largest
values.sort(function(a, b) {
return b-a;
});
var sum = 0;
// starts from second, and add until reaching the limit
for (var i = 1; i < values.length; i++) {
sum += values[i];
if (sum == values[0]) {
return true;
} else if (sum > values[0]) {
// don't go further
return false;
}
}
// or fail.
return false
}
It's even shorter with Underscore handy methods (like reduce).
But I might have totally misunderstood the problem...
Related
I am returning null with this code, however I cannot see why/where this is going wrong. (It may be due to the sort function since I got this from a well upvoted snippet on how to sort an array). I care more about the understanding than the correct answer.
Task I am trying to achieve :
Ratiorg got statues of different sizes as a present from CodeMaster for his birthday, each statue having an non-negative integer size. Since he likes to make things perfect, he wants to arrange them from smallest to largest so that each statue will be bigger than the previous one exactly by 1. He may need some additional statues to be able to accomplish that. Help him figure out the minimum number of additional statues needed.
Example
For statues = [6, 2, 3, 8], the output should be
solution(statues) = 3.
Ratiorg needs statues of sizes 4, 5 and 7.
My code & thinking:
function solution(statues) {
let total = 0;
statues.sort(function(a, b) { //From what I understand this should sort the array numerically
return a - b;
});
for (let i = 1; i < statues.length; i++) { //iterate through the array comparing the index to the one before it
if (statues[i + 1] != (statues[i] + 1)) { //if there is a diff between index of more than 1 it will add the diff
total += statues[i + 1] - statues[i]; //to the total variable
}
}
return total;
}
const result = solution([6, 2, 3, 8]);
console.log(result);
Thanks to people who commented who helped me see that there were some errors in my logic - I have now changed the if statement to compare an index starting at 1 to the behind it i.e index 1 compared to index 0. On top of that, there was an issue with the following line total += statues[i] - statues[i-1];
I replaced that with a for loop so that it counts up to the number rather than a simple subtraction of it.
function solution(statues) {
//[6, 2, 3, 8] --- 2,3,6,8 --- 3,6 7-2,
let total = 0;
statues.sort(function(a, b) {
return a - b;
});
for(let i =1; i<statues.length; i++){
if(statues[i] != (statues[i-1]+1) ){
//total += statues[i] - statues[i-1];
for(let j = statues[i-1]; j<statues[i]-1; j++){
total += 1;
}
}
}
return total;
}
Looking to optimise my code to get even more speed. Currently the code detects any poker hand and takes ~350ms to do 32000 iterations. The function to detect straights, however seems to be taking the biggest individual chunk of time at about 160ms so looking if any way to optimise it further.
The whole code was originally written in php since that is what I'm most familiar with but despite php 7's speed boost it still seems to be slower than javascript. What I found when translating to javascript though is that many of php's built in functions are not present in javascript which caused unforeseen slowdowns. It is still faster overall than the original php code though but I'm looking to see if it can be optimised more. Perhaps the answer is no, but I thought I'd check anyway.
I have written the functions range and arrays_equal since these are either missing from javascript or don't quite work properly.
function straight(handval) {
if (arrays_equal(handval.slice(0, 4),[2, 3, 4, 5]) && handval[handval.length-1] == 14) {//if is Ace 2345
return [4,14];
}
else {//if normal straight
for (let i = handval.length - 5; i >= 0; i--) {
let subhand = handval.slice(i, i + 5);
if (arrays_equal(subhand, range(subhand[0], subhand[subhand.length-1]))) {
return [4,subhand[4]];
}
} return [0]
}
}
function arrays_equal(a,b) { return !!a && !!b && !(a<b || b<a); }
function range(start, end) {
let arr = [];
for (let i = start; i <= end; i++) {
arr.push(i);
}
return arr;
}
Handval comes in as a simple array of 5-7 elements of numbers from 2-14 representing the cards So for example it could be [6,8,4,11,13,2] or [8,4,13,8,10].
EDIT: The function is called and sorted at the same time with this code:
straight(handval.slice(0).sort(sortNumber));
function sortNumber(a,b) { return a - b; }
You could just go from right to left and count the number of sequential numbers:
function straight(handval) {
if([2, 3, 4, 5].every((el, i) => handval[i] === el) && handval[handval.length-1] === 14)
return [4, 14];
let count = 1;
for(let i = handval.length - 1; i >= 1; i -= 1) {
if(handval[i] === handval[i - 1] + 1) {
count += 1;
if(count === 5) return [ 4, handval[i + 3] ];
} else {
count = 1;
}
}
return [0];
}
That is way faster as it:
1) does not create intermediate arrays on every iteration, which you did with range and slice
2) does not compare arrays as strings, which requires a typecast and a string comparison, which is way slower than comparing two numbers against each other
3) It does not check all 3 ranges on its own (1 - 5, 2 - 6, 3 - 7), but does all that in one run, so it only iterates 5 positions instead of 3 x 5.
Given I have an array of numbers for example [14,6,10] - How can I find possible combinations/pairs that can add upto a given target value.
for example I have [14,6,10], im looking for a target value of 40
my expected output will be
10 + 10 + 6 + 14
14 + 14 + 6 + 6
10 + 10 + 10 + 10
*Order is not important
With that being said, this is what I tried so far:
function Sum(numbers, target, partial) {
var s, n, remaining;
partial = partial || [];
s = partial.reduce(function (a, b) {
return a + b;
}, 0);
if (s === target) {
console.log("%s", partial.join("+"))
}
for (var i = 0; i < numbers.length; i++) {
n = numbers[i];
remaining = numbers.slice(i + 1);
Sum(remaining, target, partial.concat([n]));
}
}
>>> Sum([14,6,10],40);
// returns nothing
>>> Sum([14,6,10],24);
// return 14+10
It is actually useless since it will only return if the number can be used only once to sum.
So how to do it?
You could add the value of the actual index as long as the sum is smaller than the wanted sum or proceed with the next index.
function getSum(array, sum) {
function iter(index, temp) {
var s = temp.reduce((a, b) => a + b, 0);
if (s === sum) result.push(temp);
if (s >= sum || index >= array.length) return;
iter(index, temp.concat(array[index]));
iter(index + 1, temp);
}
var result = [];
iter(0, []);
return result;
}
console.log(getSum([14, 6, 10], 40));
.as-console-wrapper { max-height: 100% !important; top: 0; }
For getting a limited result set, you could specify the length and check it in the exit condition.
function getSum(array, sum, limit) {
function iter(index, temp) {
var s = temp.reduce((a, b) => a + b, 0);
if (s === sum) result.push(temp);
if (s >= sum || index >= array.length || temp.length >= limit) return;
iter(index, temp.concat(array[index]));
iter(index + 1, temp);
}
var result = [];
iter(0, []);
return result;
}
console.log(getSum([14, 6, 10], 40, 5));
.as-console-wrapper { max-height: 100% !important; top: 0; }
TL&DR : Skip to Part II for the real thing
Part I
#Nina Scholz answer to this fundamental problem just shows us a beautiful manifestation of an algorithm. Honestly it confused me a lot for two reasons
When i try [14,6,10,7,3] with a target 500 it makes 36,783,575 calls to the iter function without blowing the call stack. Yet memory shows no significant usage at all.
My dynamical programming solution goes a little faster (or may be not) but there is no way it can do above case without exhousting the 16GB memory.
So i shelved my solution and instead started investigating her code a little further on dev tools and discoverd both it's beauty and also a little bit of it's shortcomings.
First i believe this algorithmic approach, which includes a very clever use of recursion, might possibly deserve a name of it's own. It's very memory efficient and only uses up memory for the constructed result set. The stack dynamically grows and shrinks continuoously up to nowhere close to it's limit.
The problem is, while being very efficient it still makes huge amounts of redundant calls. So looking into that, with a slight modification the 36,783,575 calls to iter can be cut down to 20,254,744... like 45% which yields a much faster code. The thing is the input array must be sorted ascending.
So here comes a modified version of Nina's algorithm. (Be patient.. it will take like 25 secs to finalize)
function getSum(array, sum) {
function iter(index, temp) {cnt++ // counting iter calls -- remove in production code
var s = temp.reduce((a, b) => a + b, 0);
sum - s >= array[index] && iter(index, temp.concat(array[index]));
sum - s >= array[index+1] && iter(index + 1, temp);
s === sum && result.push(temp);
return;
}
var result = [];
array.sort((x,y) => x-y); // this is a very cheap operation considering the size of the inpout array should be small for reasonable output.
iter(0, []);
return result;
}
var cnt = 0,
arr = [14,6,10,7,3],
tgt = 500,
res;
console.time("combos");
res = getSum(arr,tgt);
console.timeEnd("combos");
console.log(`source numbers are ${arr}
found ${res.length} unique ways to sum up to ${tgt}
iter function has been called ${cnt} times`);
Part II
Eventhough i was impressed with the performance, I wasn't comfortable with above solution for no solid reason that i can name. The way it works on side effects and the very hard to undestand double recursion and such disturbed me.
So here comes my approach to this question. This is many times more efficient compared to the accepted solution despite i am going functional in JS. We have still have room to make it a little faster with ugly imperative ways.
The difference is;
Given numbers: [14,6,10,7,3]
Target Sum: 500
Accepted Answer:
Number of possible ansers: 172686
Resolves in: 26357ms
Recursive calls count: 36783575
Answer Below
Number of possible ansers: 172686
Resolves in: 1000ms
Recursive calls count: 542657
function items2T([n,...ns],t){cnt++ //remove cnt in production code
var c = ~~(t/n);
return ns.length ? Array(c+1).fill()
.reduce((r,_,i) => r.concat(items2T(ns, t-n*i).map(s => Array(i).fill(n).concat(s))),[])
: t % n ? []
: [Array(c).fill(n)];
};
var cnt = 0, result;
console.time("combos");
result = items2T([14, 6, 10, 7, 3], 500)
console.timeEnd("combos");
console.log(`${result.length} many unique ways to sum up to 500
and ${cnt} recursive calls are performed`);
Another important point is, if the given array is sorted descending then the amount of recursive iterations will be reduced (sometimes greatly), allowing us to squeeze out more juice out of this lemon. Compare above with the one below when the input array is sorted descending.
function items2T([n,...ns],t){cnt++ //remove cnt in production code
var c = ~~(t/n);
return ns.length ? Array(c+1).fill()
.reduce((r,_,i) => r.concat(items2T(ns, t-n*i).map(s => Array(i).fill(n).concat(s))),[])
: t % n ? []
: [Array(c).fill(n)];
};
var cnt = 0, result;
console.time("combos");
result = items2T([14, 10, 7, 6, 3], 500)
console.timeEnd("combos");
console.log(`${result.length} many unique ways to sum up to 500
and ${cnt} recursive calls are performed`);
I'm working on a challenge from codefights.com.
Given an array of integer (possibly negative) I need to return the biggest sum I can achieve without adding two consecutive integer (I can't change the order of the array).
Not easy to explain so here's a few examples:
input: [1, 2, 3, 4]: you're gonna pass the '1', take 2, can't take 3, take 4 and you get 6.
input: [1, 3, 1]: pass the '1', take 3 and you can't take 1 so you have 3.
I though I had it with this code :
function solve(vals) {
var even=0; var odd=0;
for(var i=0; i<vals.length; i++){
if(i%2==0){
even+=vals[i];
} else {
odd+=vals[i];
}
}
return Math.max(even, odd);
}
But then I got this testcase: [1,0,0,3] where it should return 4, skipping the two '0' which made me realize I've been looking at it all wrong.
And now I'm stuck, don't really know how to do it.
Any ideas ?
edit:
Using MrGreen's answer I got this:
function target_game(a) {
var dp=[], l=a.length-1;
dp[0]=a[0];
dp[1]=Math.max(a[0],a[1]);
for(var i=2; i<=a.length-1; i++){
dp[i]=Math.max(dp[i - 1], dp[i - 2] + a[i]);
}
return dp[l];
}
Which works fine unless the array contains negative value.
This input: [-1,0,1,-1] returns 0.
I'm still working on a fix but I'm editing the question to have a bullet proof solution :p
This is a classical dynamic programming problem.
Define dp[i] to be the maximum sum we can get if we consider the elements from 0 to i.
Then dp[i] = max(dp[i - 1], dp[i - 2] + a[i])
The intuition behind this, if you takea[i] in the sum then you cannot take a[i - 1]
Base cases: dp[0] = max(0, a[0]) and dp[1] = max(0, a[0], a[1])
You can check this lesson:
part-1 part-2 part-3 part-4
Here is the "best" answer from the challenge (shortest actually):
function solve(a) {
b = t = 0
for (i in a) {
c = b + a[i]
b = t
t = c > t ? c : t
}
return t
}
Here is a version where I renamed the variables to make it more understandable:
function solve(vals) {
prevTotal = total = 0
for (i in vals) {
alt = prevTotal + vals[i]
prevTotal = total
total = alt > total ? alt : total
}
return total
}
I have an array of numbers
var number = [7,7,6,9,9,9,3,3]
and we want to sort it by number of occurrences.
In this example the result should be:
[6,3,3,7,7,9,9,9]
We tried to use sort, but couldn't figure it out.
If we only can use sort, how could we solve this problem in one sort?
var number = [7,7,6,9,9,9,3,3];
number.sort(function(a,b) {
//code here
});
It's very hard and inefficient to do it by sort alone. You want your frequency counts first.
var freq = {};
number.forEach(function(i) {
freq[i] = (freq[i] || 0) + 1;
})
number.sort(function(a,b) {
return (freq[a] - freq[b]) || (a - b);
});
// => [6, 7, 7, 3, 3, 9, 9, 9]
If you really, really, really need it in one function call,
number.sort(function(a, b) {
var afreq = number.filter(function(i) { return i == a; }).length;
var bfreq = number.filter(function(i) { return i == b; }).length;
return (afreq - bfreq) || (a - b);
});
but as I said above, very, very inefficient.
EDIT: Thanks to Derek 朕會功夫 for golfing it!
EDIT2: Fixed the bug that mixed numbers of same frequency. Thanks again, Derek 朕會功夫!