4

I need to find all possible combinations of numbers 1:8 such that sum of all elements is equal to 8 The combinations need to be arranged in an ascending order.

Eg

1 7

2 2 4

1 3 5

1 2 2 3

1 1 1 1 1 1 1 1

A number can repeat itself. But a combination must not.. i.e 1 2 2 3 and 2 1 2 3 I need the the solution in ascending order So there will be only one possibility of every combination

I tried a few codes online suggested on Find vector elements that sum up to specific number in MATLAB

   VEC = [1:8];
      NUM = 8;
      n = length(VEC);
      finans = zeros(2^n-1,NUM);
      for i = 1:(2^n - 1)
        ndx = dec2bin(i,n) == '1';
        if sum(VEC(ndx)) == NUM
        l = length(VEC(ndx));
        VEC(ndx)
        end
      end

but they dont include the possibilities where the numbers repeat.

Community
  • 1
  • 1
newbie2015
  • 581
  • 5
  • 29
  • As a starting point, I'd try to make this into a vector problem of sorts. You mention that you may have repeat values, so what about treating a combination of numbers as a 8 dimensional vector. The first component is the number of 1s, the second component is the number of 2s and so forth. So the set 1, 1, 1, 2, 3 would be <3, 1, 1, 0, 0, 0, 0, 0> because it has three 1's, one 2, and one three. That way of approaching the problem will help because there will be some geometric ways of thinking about the problem to avoid having to calculate all of the possibilities. – Cort Ammon Apr 16 '15 at 06:04
  • 2
    Of course, one question that is important: are you just trying to find values that add up to 8? Or are you trying to write a program so that, no matter what number they ask you to use, you can run your program and generate the right results. – Cort Ammon Apr 16 '15 at 06:05
  • Also, if the answer to the question by @CortAmmon is the latter, what sort of constraints on the list of numbers are there? Will they always be positive? Will they always include all integers in a given range? – TheBlackCat Apr 16 '15 at 14:51
  • Can OP accept the [second answer](http://stackoverflow.com/a/29688046/802378) if it's correct? If not, please let me know how it can be fixed! – krisdestruction Apr 18 '15 at 23:57

3 Answers3

2

EDIT: Look at my second more efficient answer!

The Naive approach! Where the cartprod.m function can be found here.

% Create all permutations
p(1:8) = {0:8};
M = fliplr( cartprod( p{:} ) );

% Check sums
r = sum( M, 2 ) == 8;
M = M(sum( M, 2 ) == 8,:);   % Solution here

There are definitely more efficient solutions than this but if you just need a quick and dirty solution for small permutations, this will work. Please note that this made Matlab take 3.5 GB of RAM to temporarily store the permutations.


Community
  • 1
  • 1
krisdestruction
  • 1,950
  • 1
  • 10
  • 20
2

I found a better approach through recursion and it's more elegant (I like elegant) and faster than my previous attempt (0.00399705213 seconds on my computer).

EDIT: You will need my custom function stretchmat.m that stretches a vector to fit the size of another matrix. Kinda like repmat but stretching the first parameter (see help for details). Very useful!

script.m

% Define funciton to prepend a cell x with a variable i
cellprepend = @(x,i) {[i x]};

% Execute and time function
tic;
a = allcomb(cellprepend,1,8);   % Solution in a
toc;

allcomb.m

function a = allcomb( cellprepend, m, n )
    % Add entire block as a combination
    a{1} = n;

    % Exit recursion if block size 1
    if n == 1
        return;
    end

    % Recurse cutting blocks at different segments
    for i = m:n/2
        b = allcomb(cellprepend,i,n-i);
        a = [a cellfun( cellprepend, b, num2cell( stretchmat( i, b ) ) )];
    end
end

So the idea is simple, for solutions that add to 8 is exhaustive. If you look for only valid answers, you can do a depth first search by breaking up the problem into 2 blocks. This can be written recursively as I did above and is kinda similar to Merge Sort. The allcomb call takes the block size (n) and finds all the ways of breaking it up into smaller pieces.

We want non-zero pieces so we loop it from 1:n-1. It then prepends the first block to all the combinations of the second block. By only doing all comb on one of the blocks, we can ensure that all solutions are unique.

As for the sorting, I'm not quite sure what you mean by ascending. From what I see, you appear to be sorting from the last number in ascending order. Can you confirm? Any sort can be appended to the end of script.m.

EDIT 2/3 Notes

  • For the permutatively unique case, the code can be found here
  • Thanks to @Simon for helping me QA the code multiple times
krisdestruction
  • 1,950
  • 1
  • 10
  • 20
  • Undefined function or variable `stretchmat`. – Simon Apr 17 '15 at 07:24
  • @Simon Sorry, I just added a link to my custom function stretchmat. It's so useful that I forget that not everyone has it ;) – krisdestruction Apr 17 '15 at 15:07
  • Many thanks, it seems useful :) However your code also considers the order of the numbers, so `1 1 8` and `8 1 1` are considered as different solutions. I think this is also the reason why your code is a bit slower than mine (setting the desired sum to 10, on my computer it takes `0.092109 seconds` vs my `0.016947`). – Simon Apr 17 '15 at 21:47
  • @Simon My code is permutatively unique. If you're looking for combinitorically unique, you could stick on a `unique` call after my code for a quick fix. However, I don't think the OP specified. I apologize, I haven't tested your code on my machine. – krisdestruction Apr 17 '15 at 22:14
  • @Simon Also if we're assuming different definitions of unique, it wouldn't be fair to compare speeds in the first place :) – krisdestruction Apr 17 '15 at 22:17
  • 1
    Actually OP specified it: "a number can repeat itself but a combination must not, i.e. 1 2 2 3 and 2 1 2 3". I was comparing the time just because I was going out of memory with your solution in the other answer and I was curious about computational times :D – Simon Apr 17 '15 at 22:23
  • @Simon Thanks for pointing it out! I have updated my code, can you verify the speed compared to your answer? I'm going to run it myself and update my answer's performance – krisdestruction Apr 17 '15 at 22:41
  • `0.047383s` but it still find duplicates, like `1 1 2 2 2` and `1 2 2 2 1`. – Simon Apr 17 '15 at 22:52
  • @Simon I'll look at it when I return. It's getting there :) I feel this is the most optimal way of going about it – krisdestruction Apr 17 '15 at 22:53
  • Yes, your are on the right track. Actually I noticed that you should add a parameter to decide the number of numbers you can choose from (for instance, choose between numbers `1 : 5` that sum up to `7`. With your code I can only choose from numbers between `1 : X` that sum up to `X`. I tried the same on my code (numbers in `1 : 10` that sum up to `10`) for a fair comparison and it took `4s` (pretty bad :D). – Simon Apr 17 '15 at 23:09
  • @Simon Aha! I found the error in allcomb.m. So to avoid going past the half way point, I set the for loop to loop through `m:n/2`. Can you verify now? – krisdestruction Apr 17 '15 at 23:52
  • 1
    @Simon I see what you mean and it's pretty easy to do. I'm not going to change it for this answer though since it's OT and a bit messier. However to do so should be as simple as changing the loop line to `for i = max( m, p ):min( n / 2, q )` where p and q are the lower and upper bound of your desired range. Then feeding it through the recursive function as parameters. – krisdestruction Apr 17 '15 at 23:56
  • 1
    Thanks for working with me on this! I wish I could share rep in some way if it gets upvoted. Also I will update the performance on my computer when I get home. – krisdestruction Apr 18 '15 at 00:01
  • Thanks, it is always nice to help an enthusiast programmer like you :) – Simon Apr 18 '15 at 00:13
  • likewise to you as well :) – krisdestruction Apr 18 '15 at 00:16
0

First save all combinations with repetitions in a cell array. In order to do that, just use nmultichoosek.

v = 1 : 8;
combs = cell(length(v),0);
for i = v
    combs{i} = nmultichoosek(v,i);
end

In this way, each element of combs contains a matrix where each row is a combination. For instance, the i-th row of combs{4} is a combination of four numbers.

Now you need to check the sum. In order to do that to all the combinations, use cellfun

sums = cellfun(@(x)sum(x,2),combs,'UniformOutput',false);

sums contains the vectors with the sum of all combinations. For instance, sums{4} has the sum of the number in combination combs{4}.

The next step is check for the fixed sum.

fixed_sum = 10;
indices = cellfun(@(x)x==fixed_sum,sums,'UniformOutput',false);

indices contains arrays of logical values, telling if the combination satisfies the fixed sum. For instance, indices{4}(1) tells you if the first combination with 4 numbers sums to fixed_sum.

Finally, retrieve all valid combinations in a new cell array, sorting them at the same time.

valid_combs = cell(length(v),0);
for i = v
    idx = indices{i};
    c = combs{i};
    valid_combs{i} = sortrows(c(idx,:));
end

valid_combs is a cell similar to combs, but with only combinations that sum up to your desired value, and sorted by the number of numbers used: valid_combs{1} has all valid combinations with 1 number, valid_combs{2} with 2 numbers, and so on. Also, thanks to sortrows, combinations with the same amount of numbers are also sorted. For instance, if fixed_sum = 10 then valid_combs{8} is

 1     1     1     1     1     1     1     3
 1     1     1     1     1     1     2     2

This code is quite efficient, on my very old laptop I am able to run it in 0.016947 seconds.

Community
  • 1
  • 1
Simon
  • 5,070
  • 5
  • 33
  • 59