Javascript Counting Sort implementation - javascript
Is this a good way or the best way to implement Counting Sort in Javascript?
Can't find a standard JS Counting Sort example.
function countingSort(arr){
var helper = []; // This helper will note how many times each number appeared in the arr
// Since JS arrary is an object and elements are not continuously stored, helper's Space Complexity minor that n
for(var i = 0; i<arr.length; i++){
if(!helper[arr[i]]){
helper[arr[i]] = 1;
}else{
helper[arr[i]] += 1;
}
}
var newArr = [];
for(i in helper){
while(helper[i]>0){
newArr.push(parseInt(i));
helper[i]--;
}
}
return newArr;
}
var arr = [5,4,3,2,1,0];
console.log(countingSort(arr)); // [0, 1, 2, 3, 4, 5]
The code is correct, with some comments:
In general, the use of for..in on arrays is discouraged, but unless you define enumerable properties on the Array prototype (which is a bad idea anyway), your use of it is fine to me
You could improve the part where you loop to push the same value several times. This can be done in "one" go by concatenating Array(helper[i]).fill(i) to the results.
You could also use reduce to make the function more functional programming style. In the extreme, it could look like this:
function countingSort(arr){
return arr.reduce( (acc, v) => (acc[v] = (acc[v] || 0) + 1, acc), [] )
.reduce( (acc, n, i) => acc.concat(Array(n).fill(i)), [] );
}
// Sample run:
var arr = [5,4,3,2,1,0];
console.log(countingSort(arr)); // [0, 1, 2, 3, 4, 5]
counting sort is to start by initializing an auxiliary array of length k, that will hold the count of each number. Each index has an initial value of 0. After that, you loop through the input array and increase the “count” for each value by 1 every time you encounter that number in the array. Now, the auxiliary array holds the number of times each element is in the input array. The last step is to loop from the minimum value to the maximum value. In this loop, you’ll loop through each corresponding value in the count array, and add the elements who’s count is greater than 0 to the array in sequential order. You add each item by using a secondary incrementing variable (e.g. if we’re using “i” to loop from the min to max values, then we’ll use “j” to loop through the array), then increasing that second incrementing variable so the next item is placed in the next highest array index, and finally you decrease the value of the current item in the count array so that you don’t add too many of elements that value.
const countingSort = (arr, min, max) => {
const count = {};
// First populate the count object
for (let i = min; i <= max; i++) {
count[i] = 0;
}
for (let i = 0; i < arr.length; i++) {
count[arr[i]] += 1;
}
/* Now, count is indexed by numbers, with values corresponding to occurrences, eg:
* {
* 3: 1,
* 4: 0,
* 5: 2,
* 6: 1,
* 7: 0,
* 8: 0,
* 9: 1
* }
*/
// Then, iterate over count's properties from min to max:
const sortedArr = [];
for (let i = min; i <= max; i++) {
while (count[i] > 0) {
sortedArr.push(i);
count[i]--;
}
}
return sortedArr;
};
console.log(countingSort([3, 6, 5, 5, 9], 3, 9));
const countingSort = (arr, min, max) => {
let counters = [...Array(max+1)].map(e => 0);
let result = []
for(let i = min; i < max; i++){
counters[arr[i]] += 1
}
for(let j = min; j <= max; j++){
while( counters[j] > 0){
result.push(j)
counters[j]--
}
}
return result
}
const countingSort = (arr, min, max) => {
const count = {};
// First populate the count object
for (let i = min; i <= max; i++) {
count[i] = 0;
}
for (let i = 0; i < arr.length; i++) {
count[arr[i]] += 1;
}
/* Now, count is indexed by numbers, with values corresponding to occurrences, eg:
* {
* 3: 1,
* 4: 0,
* 5: 2,
* 6: 1,
* 7: 0,
* 8: 0,
* 9: 1
* }
*/
// Then, iterate over count's properties from min to max:
const sortedArr = [];
for (let i = min; i <= max; i++) {
while (count[i] > 0) {
sortedArr.push(i);
count[i]--;
}
}
return sortedArr;
};
console.log(countingSort([3, 6, 5, 5, 9], 3, 9));
const countingSort = (arr, min, max) => {
const count = {};
// First populate the count object
for (let i = min; i <= max; i++) {
count[i] = 0;
}
for (let i = 0; i < arr.length; i++) {
count[arr[i]] += 1;
}
/* Now, count is indexed by numbers, with values corresponding to occurrences, eg:
* {
* 3: 1,
* 4: 0,
* 5: 2,
* 6: 1,
* 7: 0,
* 8: 0,
* 9: 1
* }
*/
// Then, iterate over count's properties from min to max:
const sortedArr = [];
for (let i = min; i <= max; i++) {
while (count[i] > 0) {
sortedArr.push(i);
count[i]--;
}
}
return sortedArr;
};
console.log(countingSort([3, 6, 5, 5, 9], 3, 9));
let a = [2, 1, 1, 0, 2, 5, 4, 0, 2, 8, 7, 7, 9, 2, 0, 1, 9];
let max = Math.max(...a);
let min = Math.min(...a);
function countingSort(arr) {
const count = [];
for (let i = min; i <= max; i++) {
count[i] = 0;
}
for (let i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
const sortedArr = [];
for (let i = min; i <= max; i++) {
while (count[i] > 0) {
sortedArr.push(i);
count[i]--;
}
}
return sortedArr;
}
console.log(countingSort(a));
The simplest way to solve this problem you write like this:
const range = (start, stop, step) => {
if (typeof stop == "undefined") {
stop = start;
start = 0;
}
if (typeof step == "undefined") step = 1;
if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) return [];
let result = [];
for (let i = start; step > 0 ? i < stop : i > stop; i += step)
result.push(i);
return result;
};
const numbers = [1, 2, 2, 2, 1, 3, 3, 1, 2, 4, 5];
const max = Math.max.apply(Math, numbers);
let count = Array.apply(null, Array(max + 1)).map(() => 0);
for (x of numbers)
count[x] += 1;
let arr = [];
for (x in range(max + 1))
for (i in range(count[x]))
arr.push(parseInt([x]));
console.log(arr);
Related
JavaScript algorithm: is there a way to sort an already sorted array with the absolute values of its elements
I got this question during an interview. I have an array that contains both negative and positive integers that are already sorted e.g. const array = [-5, -3, 0, 2,7] I am trying to write a function to sort the array using the absolute values of its elements. So the sorted array would be [ 0, 2, 3, 5, 7 ] Here is my attempt function sortArrayWithAbsoluteValue(array) { const result = array.map(num => Math.abs(num)).sort((a,b) => a - b) return result } Apparently, this works but it doesn't take advantage of the fact that the array is already sorted . Is there a better or more clever/efficient way to sort this?
the easiest solution is to introduce a new array and simply unshift it with elements from the first array const array = [-9, -8, -5, -3, -2, 0, 2,7]; const newArray = []; let i = 0, j = array.length - 1; while(i <= j) { const first = Math.abs(array[i]); const last = Math.abs(array[j]); if(first > last){ newArray.unshift(first) i++; } else { newArray.unshift(last) j--; } } console.log(newArray) But this solution could be challenged by interviewers as unshift operator is slow o(n) so we can create newArray with the same size as array and then simply fill it in a similar way const array = [-9, -8, -5, -3, -2, 0, 2,7]; const newArray = new Array(array.length); let i = 0, j = array.length - 1, l = array.length - 1; while(i <= j) { const first = Math.abs(array[i]); const last = Math.abs(array[j]); if(first > last){ newArray[l] = first; i++; } else { newArray[l] = last; j--; } l--; } console.log(newArray) hope it helps!
You can let two indexes move towards eachother from either ends of the input array and based on how they compare, you copy the absolute value to the target array, filling it from end to front: function absSorted(array) { let result = Array(array.length); for (let k = array.length - 1, i = 0, j = k; k >= 0; k--) { result[k] = Math.abs(array[-array[i] < array[j] ? j-- : i++]); } return result; } const array = [-5, -3, 0, 2, 7]; console.log(absSorted(array));
You can use two iterators. One iterator starts from left and the other from right. Since the array is sorted one iterator points to the max absolute value. Store this value in a new array and iterate that iterator const array = [-5, -3, 0, 2,7] function f(array) { let i = 0; let j = array.length - 1; const newArray = []; while (i <= j) { if (Math.abs(array[i]) < Math.abs(array[j])) { newArray.push(Math.abs(array[j])); --j; } else { newArray.push(Math.abs(array[i])); ++i; } } return newArray; } console.log(f(array)); You can start at the min values with the inverted logic to get an increasing sort: const array = [-5, -3, 0, 2, 7] function g(array) { let j = 0; while (j < array.length && array[j] < 0) { ++j; } let i = j - 1; const newArray = []; while (i >= 0 && j < array.length) { if (Math.abs(array[i]) < Math.abs(array[j])) { newArray.push(Math.abs(array[i])); --i; } else { newArray.push(Math.abs(array[j])); ++j; } } if (i >= 0) { newArray.push(...array.slice(0, i + 1).reverse().map(el => -el)); } if (j < array.length) { newArray.push(...array.slice(j)); } return newArray; } console.log(g(array));
I converted all the numbers to the absolute value first using map Then using a while loop, I used the indexOf and Math.min functions and the spread operator (...) to find the index of the minimum number of the array Then I removed that from the array using splice const array = [-5, -3, 0, 2,7]; function resort(array) { const newArray = []; array = array.map(i => Math.abs(i)); while (array.length) { const minIndex = array.indexOf(Math.min(...array)); newArray.push(array.splice(minIndex, 1)[0]); } return newArray; } console.log(resort(array));
JS: restart loop with a updated index number
Is there a possibility to restart this loop with a new index number: let ar = [1, 1, 2, 1, 2, 1, 3, 2, 3, 1]; let sortedArray = ar.sort(); let sameNumbersArray = []; let numberOfSameNumbers = 0; let lastIndexNumber = 0; for (i = lastIndexNumber; i < sortedArray.length; i++) { if (sortedArray[i] == sortedArray[i + 1]) { const sameNumber = sortedArray[i]; sameNumbersArray.push(sameNumber); } else { break; } let lastIndexFromNumberArray = []; lastIndexFromNumberArray.push(sameNumbersArray.length); lastIndexFromNumberArray.push(3); lastIndexFromNumberArray.push(2); lastIndexNumber = lastIndexFromNumberArray.reduce(function (a, b) { return a + b; }, 0); So basically that the loop (lastIndexNumber) starts with index[0], but then restarts with index[5] and index[7]. How would one add this extra loop?
I'm not 100% clear on the aim here. Are you able to elaborate on the desired result of the above? It looks like you want to get an array of the unique numbers and perhaps the number of unique numbers from the source array? If so, here's another way which might be cleaner: let ar = [1, 1, 2, 1, 2, 1, 3, 2, 3, 1]; let sortedArray = ar.sort(); let newSameNumbersArray = unique(sortedArray); //array of unique numbers: console.log(newSameNumbersArray); //count of unique numbers: console.log(newSameNumbersArray.length); function unique(array) { return Array.from(new Set(array)); } This is based on this answer: https://stackoverflow.com/a/44405494/4801692 That said, you can directly set the value of i and use continue to move to the 'next' iteration. i = 5; continue; This is bad though as you are in danger of feeding i a lower number and getting stuck in an infinite loop. If you can explain the requirement a little more I might be able to suggest something better.
you can do such thing to find the pairs let ar = [10, 10, 10, 20, 20, 10 ,30, 20 ,30] function findPair(ar) { let counts = {}; let count = []; let sum = 0; for(let i = 0; i < ar.length; i++){ let item = ar[i] counts[item] = counts[item] >= 1 ? counts[item] + 1 : 1; } count = Object.values(counts); for(let i = 0; i < count.length; i++){ if(count[i] >= 2){ sum += Math.floor(count[i]/2) } } console.log(sum) } findPair(ar)
Mini-Max Sum HACKERHANK JS, why isn't working?
I don't know what's wrong, my function miniMaxSum isn't summing 1+3+4+5. At the end, the result array turns into this [ 14, 12, 11, 10 ], when it should looks like this [ 14, 13, 12, 11, 10 ] function miniMaxSum(arr) { let results = []; let actualValue = 0; let skipIndex = 0; for (let i = 0; i < arr.length; i++) { //skip actual index if (i == skipIndex) continue; actualValue += arr[i]; //restart the loop if (i == arr.length - 1) { skipIndex++; results.push(actualValue); actualValue = 0; i = 0; } } console.log(results); console.log(Math.min(...results), Math.max(...results)); } console.log(miniMaxSum([1, 2, 3, 4, 5]));
You're over-complicating your algorithm by trying to check whether you should add the current number to the overall sum or not. Instead, all you need to do is run a loop over your array, to sum up all your elements in your array. This will give you the total sum of all your elements. Then, again, iterate through your array. For each element in your array subtract it from the sum you just calculated and push it into a new array. This will give you the sum if you were to not use the number in the ith position. You can then find the min/max of this using JavaScript's Math.min and Math.max functions. Here is an example using .reduce() and .map() to calculate the final result: const miniMaxSum = arr => { const sum = arr.reduce((s, n) => n+s, 0) const results = arr.map(n => sum - n); return [Math.min(...results), Math.max(...results)]; } const [min, max] = miniMaxSum([1, 2, 3, 4, 5]); console.log(min, max); If you prefer standard for loops, here is an implementation of the above in a more imperative style: const miniMaxSum = arr => { let sum = 0; for(let i = 0; i < arr.length; i++) { // sum all elements sum += arr[i]; } let results = []; for(let i = 0; i < arr.length; i++) { results[i] = sum - arr[i]; // sum minus the current number } return [Math.min(...results), Math.max(...results)]; } const [min, max] = miniMaxSum([1, 2, 3, 4, 5]); console.log(min, max);
Assuming you're talking about this question. Whenever you want to restart the loop, you're setting i=0 but observe that you also have increment statement i++ in for loop so, effectively i starts from 1, not 0. You need to set i=-1 so that i=-1+1 = 0 in subsequent iteration. After doing this, you need to handle a corner case. When skipIndex==arr.length-1, check if i == arr.length-1. If yes, do results.push(actualValue); for the last value and then for loop terminates because i < arr.length is false in next iteration. Code: function miniMaxSum(arr) { let results = []; let actualValue = 0; let skipIndex = 0; for (let i = 0; i < arr.length; i++) { //skip actual index if (i == skipIndex){ if(i == arr.length - 1) results.push(actualValue); continue; } actualValue += arr[i]; //restart the loop if (i == arr.length - 1) { skipIndex++; results.push(actualValue); actualValue = 0; i = -1; } } console.log(results); console.log(Math.min(...results), Math.max(...results)); } miniMaxSum([1, 2, 3, 4, 5]); Output [ 14, 13, 12, 11, 10 ] 10 14
Change given amount of money to bills
Change the given amount of money into minimum number of bills. Inputs: Amount: positive integer; Bills: a sorted list of distinct positive integers (e.g. [1, 5, 10]). Assumptions: Amount does not exceed 100. At most 4 bill values. Must return 0 if the amount cannot be changed. Examples: Amount: 17, bills: [1, 5, 10], answer: 4 -> 10+5+1+1 Amount: 17, bills: [2, 4], answer: 0 Here's the code I have so far function sort(array) { for (var i = array.length - 1; i >= 0; i--) { for (var j = 0; j < i; j++) { if (array[j + 1] > array[j]) { var z = array[j]; array[j] = array[j + 1]; array[j + 1] = z; } } } return array; } function change(amount, bills) { sort(bills); var result = []; while (amount > 0) { for (var i = 0; i < bills.length; i++) { if (amount >= bills[i]) { amount -= bills[i]; result.push(bills[i]); i--; } } } return result.length; } console.log(change(17, [1, 5, 10])); // Expect: 4 console.log(change(17, [2, 4])); // Expect: 0 console.log(change(18, [2, 4])); // Expect: 5 //console.log(change(17, [3, 5])); // Expect: 5 There are 2 problems One is that if the amount cannot be divided it doesn't return 0 but just lags out because it's an infinite loop. Second is that in the last example, 17,[3,5] my code takes the 5 3 times and then realises that it can't do the remaining 2 and lags out, instead of doing 3 4 times and adding a 5. Would really appreciate suggestions, or fixed code. Please keep it fairly simple I am just a starter.
If fixed your change function and added comments to explain my changes, let me know if you have any doubts function change (amount, bills) { //Asign sorted array of bills to possibleBills var possibleBills = sort(bills); var result = []; //Asign amount to currentAmount var currentAmount = amount; //Sort through possibleBills for (var i = 0; i < possibleBills.length; i++) { //Perform action inside while loop if the current bill value can be substracted from currentAmount while (currentAmount - possibleBills[i] >= 0) { currentAmount -= possibleBills[i]; result.push(possibleBills[i]); //End loop and return the length of result if currentAmount reaches 0 if (currentAmount === 0) { return result.length; } } } //Return 0 if the amount couldn't be changed with the given bills if (currentAmount > 0) { return 0; } return result.length; };
function change(amount, bills) { const billsDesc = bills.sort((a, b) => b - a); const give = {} let remaining = amount; for (const bill of billsDesc) { const qty = Math.floor(remaining/bill); give[bill] = qty; remaining -= qty*bill; } give.totalQty = Object.values(give).reduce((curr, prev) => curr + prev, 0); return remaining === 0? give.totalQty : 0; } console.log(`${change(17, [1, 5, 10])} should equal 4`); console.log(`${change(17, [2, 4])} should equal 0`); console.log(`${change(18, [2, 4])} should equal 5`);
Return all possible combinations of numbers in an array whose sum is less than or equal to n
var a = [1,3,6,10,-1]; function combinations(array, n) { } combinations(a, 9) // should return... [[1], [3], [6], [-1], [1,3], [1,6], [1,-1], [3,6], [3,-1], [6, -1], [10, -1], [1,3,-1], [3,6,-1], [1,6,-1], [1,3,6,-1]] maybe i'm missing some correct answers but you get the idea. Really dying to know how to solve this!
I would say the problem here is to take the power set of an array, and filter it down to only the elements whose sum is greater than a certain number. The power set of a set is the set of all subsets of that set. (Say that five times fast and you'll be a mathematician) For example, the power set of [1] is [[], [1]] and the power set of [1, 2] is [[], [1], [2], [1, 2]]. First I would define a powerSet function like this: var powerSet = function (arr) { // the power set of [] is [[]] if(arr.length === 0) { return [[]]; } // remove and remember the last element of the array var lastElement = arr.pop(); // take the powerset of the rest of the array var restPowerset = powerSet(arr); // for each set in the power set of arr minus its last element, // include that set in the powerset of arr both with and without // the last element of arr var powerset = []; for(var i = 0; i < restPowerset.length; i++) { var set = restPowerset[i]; // without last element powerset.push(set); // with last element set = set.slice(); // create a new array that's a copy of set set.push(lastElement); powerset.push(set); } return powerset; }; Then I would define a function that takes the power set of the array and only includes elements whose sum is less than or equal to some amount: var subsetsLessThan = function (arr, number) { // all subsets of arr var powerset = powerSet(arr); // subsets summing less than or equal to number var subsets = []; for(var i = 0; i < powerset.length; i++) { var subset = powerset[i]; var sum = 0; for(var j = 0; j < subset.length; j++) { sum += subset[j]; } if(sum <= number) { subsets.push(subset); } } return subsets; }; This might not be fast on large arrays, but it works well for small ones. It looks like it gives the right answer for console.log(subsetsLessThan([1,3,6,10,-1], 9)); edit: a little more about the power set function as implemented here The only subset of [] is [], so the power set of [] is a set containing only []. That would be [[]]. The initial if statement in the powerSet function immediately returns [[]] if you pass in []. var powerSet = function (arr) { if(arr.length === 0) { return [[]]; } If you pass in a set with at least one element, the powerSet function begins by removing the last element. For example, if you call powerSet on [1, 2], the variable lastElement will be set to 2 and arr will be set to [1]. var lastElement = arr.pop(); Then the powerSet function recursively calls itself to get the power set of the "rest" of the list. If you had passed in [1, 2], then restPowerset is assigned to powerSet([1]) which is [[], [1]]. var restPowerset = powerSet(arr); We define a variable that's going to hold the power set of what was passed in, here [1, 2] var powerset = []; We loop through every set in restPowerset. for(var i = 0; i < restPowerset.length; i++) { var set = restPowerset[i]; Any subset of [1] is also a subset of [1, 2] so we add it to the list. That is, [] and [1] are both subsets of [1, 2]. powerset.push(set); If you add the element 2 to any subset of [1], that is also a subset of [1, 2], so we add it to the list. Both [2] and [1, 2] are subsets of [1, 2]. set = set.slice(); // copy the array set.push(lastElement); // add the element powerset.push(set); That's all. At this point, the variable powerset is [[], [2], [1], [1, 2]]. Return it! } return powerset; };
Brute force O(N*2N) solution, where N = a.length < 31. This uses the index i as a bit field to filter the elements of a in each iteration into a sublist. var a = [1,3,6,10,-1]; function combinations(array, n) { var lists = [], M = 1<<array.length; for( var i = 1 ; i < M ; ++i ) { var sublist = array.filter(function(c,k){return i>>k & 1}); if( sublist.reduce(function(p,c){return p+c},0) <= n ) lists.push(sublist); } return lists; } console.log(JSON.stringify(combinations(a,9))); [[1],[3],[1,3],[6],[1,6],[3,6],[-1],[1,-1],[3,-1],[1,3,-1],[6,-1],[1,6,-1],[3,6,-1],[1,3,6,-1],[10,-1]]
Similar to Matt's answer, but uses Array.filter() and Array.reduce() to pack a punch. The variable, mask is incremented from 1 to 32-1 in this example (because array length is 5 and count = 1 << 5, which is 32). The array is filtered for each mask increment, producing a new array or permutation where only certain values are included. A value is included in the permutation if the mask shifted right by the value's index is odd. Think binary here, because either a value is supposed to be in the permutation or it isn't (0 or 1) and since the mask will go through all possible numbers, all of the possible permutations are covered directly in the number when expressed as binary: index: 4,3,2,1,0 mask: 0 0 0 0 1 (grab index 0, [1]) mask: 0 0 0 1 0 (grab index 1, [3]) mask: 0 0 0 1 1 (grab index 0 and 1, [1,3]) mask: 1 1 0 0 0 (grab index 3 and 4, [10,-1]) var a = [1,3,6,10,-1]; function combinations(array, n) { var mask, len = array.length, count = 1 << len, permutations = []; var indexVisible = function(v, i) { return ((mask >> i) & 1) == 1 } var sum = function(a, b) { return a + b } for (mask = 1; mask < count; ++mask) { permutations.push(array.filter(indexVisible)) } return permutations.filter(function(p) { return p.reduce(sum) <= n }) } console.log(JSON.stringify(combinations(a, 9))); The function, indexVisible() is used to filter the original array and return a permutation that matches the mask. The function, sum() is used to reduce each permutation to the sum of its values, and if that sum is less than or equal to n then it is included in the final result and returned from combinations() Here are the permutations: [[1],[3],[1,3],[6],[1,6],[3,6],[1,3,6],[10],[1,10],[3,10],[1,3,10],[6,10],[1,6,10],[3,6,10],[1,3,6,10],[-1],[1,-1],[3,-1],[1,3,-1],[6,-1],[1,6,-1],[3,6,-1],[1,3,6,-1],[10,-1],[1,10,-1],[3,10,-1],[1,3,10,-1],[6,10,-1],[1,6,10,-1],[3,6,10,-1],[1,3,6,10,-1]] Here are the results: [[1],[3],[1,3],[6],[1,6],[3,6],[-1],[1,-1],[3,-1],[1,3,-1],[6,-1],[1,6,-1],[3,6,-1],[1,3,6,-1],[10,-1]] You can see how all of this works and play with different combinations in this JSFiddle.
The following code will give you all sub-arrays summing up to 9 or less.. function getSubArrays(arr,n){ var len = arr.length, subs = Array(Math.pow(2,len)).fill(); return subs.map((_,i) => { var j = -1, k = i, res = []; while (++j < len ) { k & 1 && res.push(arr[j]); k = k >> 1; } return res; }).slice(1) .filter(a => a.reduce((p,c) => p+c) <= n); } var arr = [1,3,6,10,-1], result = getSubArrays(arr,9); console.log(JSON.stringify(result));
edit: giving credit where due.. borrowed the bulk of this logic from this answer var combinations = function(a,m) { var gc = function(a) { var fn = function(n, src, got, all) { if (n == 0) { if (got.length > 0) { all[all.length] = got; } return; } for (var j = 0; j < src.length; j++) { fn(n - 1, src.slice(j + 1), got.concat([src[j]]), all); } return; } var all = []; for (var i = 0; i < a.length; i++) { fn(i, a, [], all); } all.push(a); return all; } var c = gc(a); return c.filter(function(e) { var n = e.length; var sum = 0; while(n--) sum += parseFloat(e[n]) || 0; return sum<=m; },m); } var a = [1,3,6,10,-1]; combinations(a,9); output [[1], [3], [6], [-1], [1, 3], [1, 6], [1, -1], [3, 6], [3, -1], [6, -1], [10, -1], [1, 3, -1], [1, 6, -1], [3, 6, -1], [1, 3, 6, -1]]
It looked like to much fun not to play, here's what I have. Javascript function kCombs(set, k) { var setLength = set.length, combs = [], i = 0, tailLength, head, tail, j, t, u; if (k > 0 && k <= setLength) { if (k === setLength) { combs.push(set); } else if (k === 1) { while (i < setLength) { combs.push([set[i]]); i += 1; } } else { u = k - 1; setLength = setLength - k + 1; while (i < setLength) { t = i + 1; head = set.slice(i, t); tail = kCombs(set.slice(t), u); j = 0; tailLength = tail.length; while (j < tailLength) { combs.push(head.concat(tail[j])); j += 1; } i = t; } } } return combs; } function combinations(array, n) { var arrayLength = array.length, combs = [], combsLength, results = [], temp = 0, current, currentLength, i, j, k = 1; while (k <= arrayLength) { i = 0; current = kCombs(array, k); currentLength = current.length; while (i < currentLength) { combs.push(current[i]); i += 1; } k += 1; } i = 0; combsLength = combs.length; while (i < combsLength) { j = 0; current = combs[i]; currentLength = current.length; while (j < currentLength) { temp += current[j]; j += 1; } if (temp <= n) { results.push(current); } temp = 0; i += 1; } return results; } var a = [1, 3, 6, 10, -1]; console.log(JSON.stringify(combinations(a, 9))); Output [[1],[3],[6],[-1],[1,3],[1,6],[1,-1],[3,6],[3,-1],[6,-1],[10,-1],[1,3,-1],[1,6,-1],[3,6,-1],[1,3,6,-1]] On jsFiddle And a jsPerf of all these, although #jcarpenter solutions gives an ambiguity. On a modern browser you could squeeze more out of this solution using for intead of while as they are highly optimised for for. And assign by index rather than push would also give you a performance boost. It would be nice to extend the performance tests to include some more test sets, maybe if I get bored.
Brevity is very cryptic here. How about some descriptive functions? The approach uses binary to create maps of all the possible combinations. Then the map is used to pluck items from the array. The plucked items are summed, and that's about it. The result of combinations([1, 3, 6, 10, -1], 9) produced is: [[-1],[10,-1],[6],[6,-1],[3],[3,-1],[3,6],[3,6,-1],[1],[1,-1],[1,6],[1,6,-1],[1,3],[1,3,-1],[1,3,6,-1]]. Here is a Fiddle. /** * Get an array of all the possible combinations * of x items. Combinations are represented as binary. * #param {Number} x - example 2 * #return {String[]} - example ['00', '01', '10', '11'] */ function getCombinationsOfXItems(x) { var allOn = '', numCombos = 0, i = 0, combos = []; // find upper limit while (allOn.length < x) { allOn += 1; } // number of possible combinations numCombos = parseInt(allOn, 2) + 1; // generate the combos while(i < numCombos) { combos.push(pad(toBase2(i++), allOn.length)); } return combos; } /** * Pad a string with leading zeros. * #param {String} x - example '100' * #param {Number} length - example 6 * #return {String} - example '000100' */ function pad(x, length) { while (x.length < length) { x = 0 + x; } return x; } /** * Get a number as a binary string. * #param {Number} x - example 3 * #return {String} - example '11' */ function toBase2(x) { return x.toString(2); } /** * Given an array and a map of its items as a binary string, * return the items identified by 1. * #param {Array} arr - example [1,2,3] * #param {String} binary - example '101' * #return {Array} - example [1,3] */ function pluckFromArrayByBinary(arr, binary) { var plucked = [], i = 0, max = binary.length; for (; i < max; i++) { if (binary[i] === '1') { plucked.push(arr[i]); } } return plucked; } /** * Given an array, return a multi-dimensional * array of all the combinations of its items. * #param {Array} - example [1, 2]; * #return {Array[]} - [ [1], [1, 2], [2] ] */ function getCombosOfArrayItems(arr) { var comboMaps = getCombinationsOfXItems(arr.length), combos = []; // remove the "all off" combo (ex: '00000') comboMaps.shift(); for (var i = 0; i < comboMaps.length; i++) { combos.push(pluckFromArrayByBinary(arr, comboMaps[i])); } return combos; } /** * Return all possible combinations of numbers in an * array whose sum is less than or equal to n * #param {Number[]} arr * #param {Number} x * return {Number[]} - stringified for readability */ function combinations(arr, x) { var combos = getCombosOfArrayItems(arr), i = 0, max = combos.length, combo; for (; i < max; i++) { if (sumArray(combos[i]) > x) { combos.splice(i, 1); i--; max--; } } return JSON.stringify(combos); } /** * Return the sum of an array of numbers. * #param {Number[]} arr * #return {Number} */ function sumArray(arr) { var sum = 0, i = 0, max = arr.length; for (; i < max; i++) { sum += arr[i]; } return sum; } console.log(combinations([1, 3, 6, 10, -1], 9));
#jcarpenter solution was so nice I just had to rework it for those that love ECMA5. This will not be as fast as the raw power of for, the modern methods have not had the length of time to be so highly optimised (and they do quite a bit more work). But the performance results do show just how good the powerSet algorithm is (and it is a reusable function). I've also filtered out the ambiguity, which slows things slightly. Javascript function powerSet(arr) { var lastElement, val; if (!arr.length) { val = [[]]; } else { lastElement = arr.pop(); val = powerSet(arr).reduce(function (previous, element) { previous.push(element); element = element.slice(); element.push(lastElement); previous.push(element); return previous; }, []); } return val; } function combinations(array, n) { return powerSet(array).filter(function (set) { return set.length && set.reduce(function (previous, element) { return previous + element; }, 0) <= n; }); } var a = [1, 3, 6, 10, -1]; console.log(JSON.stringify(combinations(a, 9))); Output [[-1],[10,-1],[6],[6,-1],[3],[3,-1],[3,6],[3,6,-1],[1],[1,-1],[1,6],[1,6,-1],[1,3],[1,3,-1],[1,3,6,-1]] On jsFiddle And added to the jsPerf
Try this: var a = [1,3,6,10,-1]; function combinations(array, n) { var arrayCopy = [], results = []; // duplicate the array for (var i in array) arrayCopy[i] = array[i]; for (var i in array) for (var j in arrayCopy) if ((array[i] + arrayCopy[j]) <= n) results.push([array[i], arrayCopy[j]]); return results; } console.log(combinations(a, 9)); This logged: [1, 1], [1, 3], [1, 6], [1, -1], [3, 1], [3, 3], [3, 6], [3, -1], [6, 1], [6, 3], [6, -1], [10, -1], [-1, 1], [-1, 3], [-1, 6], [-1, 10], [-1, -1]