22
$\begingroup$

I am interested in a certain subset of all tuples. Generating all with Tuples and then filtering is wasteful, and will "blow up" very quickly.

For a concrete example, let's say I want to find all 8-tuples of {1,2,3,4,5} such that: the ordering does not matter, i.e. {1,2,3,4,5} is the same as {5,4,3,2,1}; the sum of all elements is constrained, say 24; the sum of squares is constrained, say 86. I can do it like this.

candidates = Tuples[Range[5], 8];

candidates = Sort /@ candidates;

candidates = DeleteDuplicates@candidates;

f[x_] := If[Total[x] == 24, x, 0 ];

candidates = DeleteCases[f /@ candidates, 0];

g[x_] := If[Total[x^2] == 86, x, 0 ];

candidates = DeleteCases[g /@ candidates, 0];

Starting from 5^8 elements generated by Tuples, the final step leaves me with just 5 elements. Even if I could just directly generate the different combinations without reorderings that would already be a huge improvement (495 elements in this particular case). I should emphasize that repetition of the same number is allowed.

EDIT

Thanks for all who have answered so far. I was able to find a fast way for larger lists by modifying the answer by Kuba. The basic idea is to still use IntegerPartitions but enforce the sum of squares rather than the sum of elements and then filtering with Select. The code that can do this is below.

rN = 8;
rD = 24;
rA = 86;
dmin = 1;
dmax = 5;

candidates = 
Round@Sqrt[
 Select[
  Developer`ToPackedArray[
   IntegerPartitions[rA,{rN},Range[dmin,dmax]^2],
  Complex],
 Total[Sqrt[#]] == rD&]
];

In the code above, packed array coercion is used to speed up the Sqrt and Round is used on the result to get rid of the auxiliary imaginary part. While this appears to be the fastest approach at least for now, the answer by qwr is more memory efficient, based on values given by MaxMemoryUsed[].

For a power test, I used rN=40, rD=188, rA=1020, dmin=1 and dmax=9, with the code above doing the job in 3 seconds while the answer by qwr took 4.3 seconds. On the other hand, MaxMemoryUsed[] gave a result almost 5 times bigger with the fast code than with the answer by qwr.

$\endgroup$
0

3 Answers 3

17
$\begingroup$
Select[
 IntegerPartitions[24, {8}, Range[5]],
 #.# == 86 &
]
{{5, 5, 4, 2, 2, 2, 2, 2}, {5, 5, 3, 3, 3, 2, 2, 1}, 
{5, 4, 4, 4, 2, 2, 2, 1}, {5, 4, 4, 3, 3, 3, 1, 1}, {4, 4, 4, 4, 4, 2, 1, 1}}

Slightly more general approach (in case where IntegerPartitions is not what we need):

ClearAll[ar, a];
ar = Array[a, 8]

ar /. Solve[Flatten@{
     Tr[ar] == 24,
     ar.ar == 86,
     Thread[1 <= ar <= 5],
     LessEqual @@ ar (* this part takes care about
                        droping equivalent permutations*)
     },
     ar,
     Integers
] 
{{1, 1, 2, 4, 4, 4, 4, 4}, {1, 1, 3, 3, 3, 4, 4, 5}, 
 {1, 2, 2, 2, 4, 4, 4, 5}, {1, 2, 2, 3, 3, 3, 5, 5}, {2, 2, 2, 2, 2, 4, 5, 5}}
$\endgroup$
1
  • 1
    $\begingroup$ I modified your answer using IntegerPartitions by enforcing sum of squares first and then selecting those that satisfy the sum of elements. Together with packed array coercion to speed up the otherwise expensive Sqrt, I found that this can be the fastest way at least for the parameters I have been using. Apparently, IntegerPartitions can be faster when in the input the list of elements are spread a bit wider and not bunched together. $\endgroup$
    – Kiro
    Commented Feb 1, 2016 at 12:57
10
$\begingroup$

A slightly different approach with Reduce (or Solve)

We define:

matC = Range[5]; (* the list of the integers from which we build a list *)

Let us use a vector to indicate the multiplicities of any integer:

matX = Array[ x, 5 ]; (* @Kuba: that's more concise, indeed *)

So x[1] will tell us how many times $1$ appears a possible solution.

We can then get all possible solutions by

sol= Reduce[
   {

      matC.matX == 24, (* the numbers add up to 24 *)

      Total[ matC^2 matX ] == 86, (* the sum of their squares is 86 *)

      Total[ matX ] == 8, (* there are exactly 8 numbers in the solution *)

      And @@ Thread[ matX >= 0 ]  (* non-negativity of course *)

   }
   ,
   matX
   ,
   Integers
]
      (x[1] == 0 && x[2] == 5 && x[3] == 0 && x[4] == 1 && x[5] == 2) 
   || (x[1] == 1 && x[2] == 2 && x[3] == 3 && x[4] == 0 && x[5] == 2) 
   || (x[1] == 1 && x[2] == 3 && x[3] == 0 && x[4] == 3 && x[5] == 1) 
   || (x[1] == 2 && x[2] == 0 && x[3] == 3 && x[4] == 2 && x[5] == 1) 
   || (x[1] == 2 && x[2] == 1 && x[3] == 0 && x[4] == 5 && x[5] == 0)

With this we can get back to the octlets as follows:

rules = List @* ToRules @ sol;

Function[
    rule,
    Flatten[
       Table[ #1, {#2} ]& @@@ Transpose[ { matC, matX /. rule } ]
    ]
] /@ rules

{{2, 2, 2, 2, 2, 4, 5, 5}, {1, 2, 2, 3, 3, 3, 5, 5}, {1, 2, 2, 2, 4, 4, 4, 5}, {1, 1, 3, 3, 3, 4, 4, 5}, {1, 1, 2, 4, 4, 4, 4, 4}}

$\endgroup$
1
  • $\begingroup$ This is of course a very general approach and probably a bit overdone here. For better readability I have used the symbol x but I would recommend to use \[FormalX] instead. $\endgroup$
    – gwr
    Commented Feb 1, 2016 at 11:19
7
$\begingroup$

This is not really very efficient but here goes. We can create a rational function that is effectively a generating function in three variables, one to force 8 factors, one to force a sum e1ual to 24, one to force a sum of squares equal to 86. The other parameters just keep track of what factors get used in a coefficient.

vals = Range[5];
aa = Array[a, 5];
ratfunc = Apply[Times, 1/(1 - t*aa*x^vals*y^(vals^2))]

(* Out[253]= 1/((1 - t x y a[1]) (1 - t x^2 y^4 a[2]) (1 - 
     t x^3 y^9 a[3]) (1 - t x^4 y^16 a[4]) (1 - t x^5 y^25 a[5])) *)

listOfLists = 
 Apply[List, 
  Apply[List, 
   SeriesCoefficient[ratfunc, {t, 0, 8}, {x, 0, 24}, {y, 0, 86}]], 1]

(* Out[263]= {{a[1]^2, a[2], a[4]^5}, {a[1]^2, a[3]^3, a[4]^2, 
  a[5]}, {a[1], a[2]^3, a[4]^3, a[5]}, {a[1], a[2]^2, a[3]^3, 
  a[5]^2}, {a[2]^5, a[4], a[5]^2}} *)

listOfLists /. 
 a[j_]^e_. :> Apply[Sequence, ConstantArray[j, e]]

(* Out[264]= {{1, 1, 2, 4, 4, 4, 4, 4}, {1, 1, 3, 3, 3, 4, 4, 5}, {1, 2, 
  2, 2, 4, 4, 4, 5}, {1, 2, 2, 3, 3, 3, 5, 5}, {2, 2, 2, 2, 2, 4, 5, 
  5}} *)
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.