2

EDIT:

var Combos = [1, 2, 4],
    Target = 4;

    for(var i = 0; i < Combos.length; i++){
        var Available = [];

        for(var j = -1; j < i; j++) Available.push(Combos[j+1]);
        for(var k = 0; k < Available.length; k++){
            var C = 0;
            var Att = 0;

            while(C < Target){ C += Available[k]; Att++; }
            if(C === Target) console.log(new Int8Array(Att).fill(Available[k]));
        }
    }

This outputs:

[1, 1, 1, 1]
[1, 1, 1, 1]
[2, 2]
[1, 1, 1, 1]
[2, 2]
[4]

I don't know why I'm getting the repetition of 1, 1, 1, 1 and 2, 2 but I'm investigating!


My provided numbers array will be [1, 2, 4], I'm trying to get to a target that will always fit; let's assume this is 4.

How can I return an array of combinations where the process reuses the provided numbers, for example: all of the questions I've looked at on here only use the numbers once, and thus these programs would return [[4]], whereas I'd like the result to be [[1, 1, 1, 1], [1, 1, 2], [2, 2], [4]].

At first, I thought about using modulus to say: is the target an integer, if so we can instantly return target / 1. But I honestly can't figure out the logic!

An IRC chat I've had, perhaps this might help you understand:

13:20   Robinlemon  So I have the numbers [1, 2] and I'm trying to make 4 so I want a function that will return [[1, 1, 1, 1], [1, 1 ,2], [2, 2], [4]] -> all the possible combinations to make the target, by reusing the numbers supplied
13:21   kakashiAL   how does your function api looks like?
13:21   Robinlemon  What do you mean?
13:21   Robinlemon  I've scrapped everything
13:21   kakashiAL   foo(myArray, combination=
13:21   Robinlemon  Because I can't ever work out the logic to do this
13:21   Robinlemon  Well I suppose i'd have
13:22   Robinlemon  Permutate(Price) => SubPermutate()
13:22   Robinlemon  Then return
13:22   Robinlemon  and tierate through each sub function
13:22   kakashiAL   so you have [1, 2] and you want to make 4, which means you want to have this:
13:22   kakashiAL   1, 1, 1, 1
13:22   kakashiAL   2, 2, 2, 2
13:23   kakashiAL   1, 2, 2, 2
13:23   kakashiAL   1, 1, 2, 2
13:23   kakashiAL   and so on
13:23   kakashiAL   right?
13:26   Robinlemon  yep
13:26   Robinlemon  well no
13:26   Robinlemon  The numbers need to add to 4
13:26   Robinlemon  so it wouldnt be 1, 1, 2, 2
13:26   Robinlemon  it would be 1 1 2
13:26   Robinlemon  get it
13:26   kakashiAL   you have the numbers 1, 2 and 4, correct?
13:28   Robinlemon  yes
13:28   Robinlemon  For example sake
13:29   Robinlemon  It should work with anything tho
13:29   Robinlemon  The target will always be reachable with the numbers
13:29   kakashiAL   okay, now you want to get this, with 1, 2 and 4:
13:29   kakashiAL   1, 1, 1
13:29   kakashiAL   2, 2, 2
13:29   kakashiAL   4, 4, 4
13:29   kakashiAL   1, 2, 4
13:29   kakashiAL   1, 1, 2
13:29   Robinlemon  No
13:29   Robinlemon  1, 1, 1, 1
13:29   Robinlemon  2, 2
13:29   Robinlemon  4
13:30   Robinlemon  The numbers need to add to 4
13:30   Robinlemon  I wnat the combinations that add to 4
13:30   kakashiAL   ahh if you have 6 you would have this:
13:30   kakashiAL   1, 1, 1, 1, 1, 1
13:30   kakashiAL   2, 2, 2
13:30   Robinlemon  Yes

Thanks for reading, I look forward to looking at your replies!

  • 1
    nice, have you tried anything? – Nina Scholz Apr 03 '17 at 12:36
  • Stackoverflow is not a free code writing or tutorial service. Please try something and when you run into real problems with your code come back and ask questions then and show what you have tried. See [ask] – charlietfl Apr 03 '17 at 12:54
  • I have tried stuff, I even said I've tried messing around with modulus, however, I'd need it dynamically generate if's via a function then return so it's just stupid at that point and I think there's an easier way to do it! –  Apr 03 '17 at 12:57
  • 1
    Post some code. We aren't here to read your chat logs – charlietfl Apr 03 '17 at 13:14
  • I've added some code and I'm still trying to figure this out, help is appreciated! –  Apr 03 '17 at 13:38

2 Answers2

0

You could use a recursive approach with some exit conditions with checking the sum or the index.

Otherwise take the actual element and concat it to the temporary object and call the function again, without incrementing the index.

The other direction is to call the function again with an incremented index.

function getCombinations(array, sum) {

    function fork(i, t) {
        var s = t.reduce(function (a, b) { return a + b; }, 0);
        if (sum === s) {
            result.push(t);
            return;
        }
        if (s > sum || i === array.length) {
            return;
        }
        fork(i, t.concat([array[i]]));
        fork(i + 1, t);
    }

    var result = [];
    fork(0, []);
    return result;
}

console.log(getCombinations([1, 2, 4], 4));
console.log(getCombinations([0.11, 0.33, 1], 26.66));
console.log(getCombinations([0.11, 0.33, 1], 23.33));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Single use of a number

function getCombinations(array, sum) {

    function fork(i, t) {
        var s = t.reduce(function (a, b) { return a + b; }, 0);
        if (sum === s) {
            result.push(t);
            return;
        }
        if (s > sum || i === array.length) {
            return;
        }
        fork(i + 1, t.concat([array[i]]));
        fork(i + 1, t);
    }

    var result = [];
    fork(0, []);
    return result;
}

console.log(getCombinations([1, 2, 4], 4));
console.log(getCombinations([0.11, 0.33, 1], 26.66));
console.log(getCombinations([0.11, 0.33, 1], 23.33));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Wow, thanks! The issue here is the numbers 1, 2 and 4 were examples and my actual numbers are [1, 0.33, 0.11]and the target will be 26.66, 23.33 etc, so is it possible to adapt it and have it still work? –  Apr 03 '17 at 13:54
  • you could order the items ascending. but with floats you might get some unwanted behaviour, like [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) – Nina Scholz Apr 03 '17 at 15:15
  • So you've just added +1 in the internal fork? > fork(i + 1, t.concat([array[i]])); –  Apr 06 '17 at 11:18
  • yes, not more to change, it could be implemented as flag. – Nina Scholz Apr 06 '17 at 11:19
  • This might sound a bit stupid of a request, but currently I get the lower combinations, eg [1, 1, 1, 1] first then [4] last, I usually just .reverse() and sue the first index, but because my combinations are so big, I'm running out of memory, how can I adapt it to work backwards so I don't need to reverse, then return on the first combination, obviously without working backwards you'll just get the first combination and return - which would be pointless. I assume instead of fork(0, []), we do fork(array.length, []) amd then i - 1? –  Apr 06 '17 at 11:33
  • Like 170, although it could be a thousand –  Apr 06 '17 at 11:37
  • Running this on a 512 MB RAM server xD –  Apr 06 '17 at 11:37
  • I've changed | i === array.length ---> i === 0 | fork(i + 1, t.concat([array[i]])); ---> fork(i - 1, t.concat([array[i]])); | fork(i + 1, t); ---> fork(i - 1, t); | fork(0, []); ---> fork(array.length, []); –  Apr 06 '17 at 11:40
  • you could use a different approach, like the `combine` of this http://stackoverflow.com/questions/40255189/a-counter-keeping-track-of-multiple-values-inside-an-array/40256159#40256159 answer, but i think you should better ask a new question, regarding huge data sets for combining to a given sum. – Nina Scholz Apr 06 '17 at 11:44
  • http://stackoverflow.com/questions/43254801/creating-permutations-to-reach-a-target-number –  Apr 06 '17 at 12:10
  • @Nina Scholz, Hmmm how about `getCombinations([1, 4, -3, -4, 6, -7, 8, -5], 0)`. – Redu Apr 07 '17 at 09:24
  • the use of negative numbers is not part of specification. – Nina Scholz Apr 07 '17 at 09:27
0

This is a typical dynamical programming case question which most likely turns out to be a more efficient way to solve compared to other methods. Here is a non recursive dynamical programming solution.

function getCombos(a,t){
  var h = {},
    len = a.length,
      n = 0;

  for (var i = 0; i < len; i++){
    n = a[i];
    h[n] ? h[n].push([n]) : h[n] = [[n]];
    for(var j = a[0]; j <= t; j++){
      h[j] && (h[j+n] = h[j+n] ? h[j+n].concat(h[j].map(s => s.concat(n)))
                               : h[j].map(s => s.concat(n)));
    }
  }
  return h[t] || [];
}

var result = [];
console.time("dp for [1,2,4] to sum 4");
result = getCombos([1,2,4],4);
console.timeEnd("dp for [1,2,4] to sum 4");
console.log(JSON.stringify(result));

console.time("dp for [1,5,9] to sum 500");
result = getCombos([1,5,9],500);
console.timeEnd("dp for [1,5,9] to sum 500");
console.log("There are", result.length, "solutions");

Well as per your second request. To reach the target without repeating the items can also be done by above code however it would be terribly inefficient.

Such as giving [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20] and asking for the numbers to give the sum 210 (sum of all numbers) would take time until it crashes your browser.

The proper way of doing the reaching target sum by non-repetitive use of the array items is as follows;

function getSummingItems(a,t){
  return a.reduce((h,n) => Object.keys(h)
                                 .reduceRight((m,k) => +k+n <= t ? (m[+k+n] = m[+k+n] ? m[+k+n].concat(m[k].map(sa => sa.concat(n)))
                                                                                      : m[k].map(sa => sa.concat(n)),m)
                                                                 :  m, h), {0:[[]]})[t];
}
var arr = Array(20).fill().map((_,i) => i+1),
    tgt = 210,
    res = [];

console.time("test");
res = getSummingItems(arr,tgt);
console.timeEnd("test");
console.log(res);

It will finalize the above calculation in less than 1 second. In order to boost the performance a little more one can replace the .reduce() and .reduceRight() with their imperative equivalents. Other than that, this is as fast as it gets from me.

Redu
  • 25,060
  • 6
  • 56
  • 76
  • Jesus dude, your really went to town! Thanks, I'll definitely be using this! –  Apr 06 '17 at 10:57
  • Is there any easy way to make this single layered, ie so it doesn't reuse the numbers in the array? –  Apr 06 '17 at 11:07
  • @Robinlemon, please add an example. – Nina Scholz Apr 06 '17 at 11:11
  • @Robinlemon So you mean no reuse of an item but a combination of all items. That's generating all subarrays problem. [Please have a look at my combination permutation answers](http://stackoverflow.com/search?q=user%3A4543207+combinations). One should do it. – Redu Apr 06 '17 at 11:11
  • As in, the current system to reach 4 with the values [1, 2, 4] would output [[1, 1, 1, 1], [1, 1, 2], [2, 2], [4]]; whereas if you can only use each number once, it would just be [[4]]. –  Apr 06 '17 at 11:13
  • in my solution, you could use the index and increment it for every new recusion call. – Nina Scholz Apr 06 '17 at 11:15
  • One way of doing this is obviously using the above code and picking the last element of the results array. – Redu Apr 06 '17 at 11:16
  • @Robinlemon I have just added the code to do the "Reach the target sum by non-repetitive use of array items" at the bottom of my answer. Please have a look. – Redu Apr 06 '17 at 16:07
  • Okay, so this returns all of the combinations, if we set the target to 21, the final array of numbers is [20, 1], which is what I want, so is it possible to work backwards so that the first 'answer' then just return it immediately rather than keep gong? –  Apr 06 '17 at 20:52
  • @Robinlemon Well the algorithm has to assume you need all possible values among the available items, those summing up to your target. For further logic it needs you to provide more information. You may say "No more than 3 items summing up to `n`" or you may say "I give you 5 and what other items summing up to `n`". Now how would you expect it to distinguish `[20,1]` from `[19,2]` or `[10,11]`. You have to tell what exactly you want. :) – Redu Apr 06 '17 at 21:01
  • Well currently the system provides an array, then I just .reverse() it and take the last, so if working forwards means the useful result is last, if we work backward, ie iterating backward, it should return the first result, no? To differentiate `[20, 1]` from `[19, 2]`, we can just do `array[n] > array[n+1]` –  Apr 07 '17 at 10:14
  • Also, Redu, I see you've answered my previous JavaScript questions, so thanks for everything you've done, it really means a lot! <3 –  Apr 07 '17 at 10:42
  • @Redu do you understand what I mean by working backwards, can we just replace the reduce with reduceRight, and the reduceRight to reduce? –  Apr 07 '17 at 10:47
  • @Robinlemon The use of `reduceRight()` is quite tricky there. It prevents multiple evaluation of the same item when constructing the results array. Yet, if you would like to get the order in reverse then you may try exchanging the `m[+k+n].concat(m[k].map(sa => sa.concat(n)))` with `m[k].map(sa => sa.concat(n)).concat(m[+k+n])`. – Redu Apr 07 '17 at 10:57
  • @Redu, I did that and it actually works faster! it works as expected, but now how can I return the first result? The changed code: https://pastebin.com/b2izS3nd, returns the whole array so how can I change it to just return the first method? –  Apr 07 '17 at 18:05