10

I gather from this question that there is no primitive to build unordered tuples. That is, I want to do the equivalent of the following:

Union[Sort /@ Tuples[Range[9], {3}]]

to construct tuples of length 3 from Range[9], but considering (for example) {1,2,3} and {2,3,1} to be the same. The construct above clearly will not work if we replace 3 by, say, 20. Here is another approach:

f[lst_] := Flatten[Map[Table[Append[#, i], {i, Last@#, 9}] &, lst], 1];
Nest[f, Table[{i}, {i, Range[9]}], 2]

This has much better performance. But still, if 2 is replaced by 20, it takes almost two minutes on my computer. Is there a better approach?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
rogerl
  • 4,209
  • 3
  • 27
  • 42

5 Answers5

10

I saw your problem as a partition problem, so I tried the following function:

unorderedTuples[len_] := Flatten[IntegerPartitions[#, {len}, Range[9]] & /@ Range[len, 9*len], 1]

In short, it looks for all the ways you can sum the numbers 1-9 from len to 9*len for a len-tuple.

There's some ordering difference between your method and this function, but

Sort@(Sort /@ unorderedTuples[3]) == Nest[f, Table[{i}, {i, Range[9]}], 2]
(* True *)
Sort@(Sort /@ unorderedTuples[10]) == Nest[f, Table[{i}, {i, Range[9]}], 9]
(* True *)

Lastly, timing:

unorderedTuples[10] // AbsoluteTiming
(* 0.0227264 *)
Nest[f, Table[{i}, {i, Range[9]}], 9] // AbsoluteTiming
(* 0.273851 *)
Dubs
  • 309
  • 3
  • 7
  • 3
    +1 Clever. I was sure I was missing something obvious, but never thought of using partitions! I'll give it a while before accepting. – rogerl Feb 02 '17 at 15:51
6

This solution is not as good and not as fast as the one by @Dubs, but perhaps it is of some interest.

Your example could be written as

Flatten[Table[{i, j, k}, {i, 1, 9}, {j, i, 9}, {k, j, 9}], 2]

We can generalize this to larger tuples of size n as follows:

n = 10;
result = With[
   {list = Table[Unique[it], {n}]},
   {iter = Sequence @@ Table[{list[[i]], If[i == 1, 1, list[[i - 1]]], 9}, {i, n}]},
   Flatten[Table[list, iter], n - 1]
   ];

For n=20, it takes 5.6 s on my machine vs 1.2 s using the method by @Dubs.

Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
6

As ciao astutely remembered the heart of this method has been posted earlier by someone else.

Despite being impressed by that answer at the time I lost all conscious memory of it.

My variation of it below is useful (faster) in the case of this particular question.


I took a look at this a few hours later and I realized the solution was staring me in the face.

This is now competitive with Dubs' IntegerPartitions solution, especially if you consider that unlike his it produces a fully sorted list by default.

f2[n_, m_] := 
  Subsets[Range[m + n - 1], {n}] // 
    Subtract[#, ConstantArray[Range[0, n - 1], Length @ #]] &

Test:

f2[5, 9] === Sort[Sort /@ unorderedTuples[5]]
True

If one includes sorting my code is significantly faster than unorderedTuples; if one does not is it still not too shabby. Closer still after the last update.

Sort[Sort /@ unorderedTuples[20]]         // Length // RepeatedTiming

unorderedTuples[20]                       // Length // RepeatedTiming

f2[20, 9]                                 // Length // RepeatedTiming
{4.643, 3108105}

{1.15, 3108105}

{1.35, 3108105}

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • Now this is clever… :) – J. M.'s missing motivation Feb 03 '17 at 06:21
  • 1
    This very technique is used somewhere else here - searched a bit, could not find it, don't recall whose answer it was (nor the question) ... – ciao Feb 03 '17 at 06:29
  • @J.M. Coming from you that means a lot! :D – Mr.Wizard Feb 03 '17 at 12:16
  • @ciao That wouldn't surprise me. I don't recall seeing it but at the same time if I have and voted for it that wouldn't surprise me either, sadly. I arrived at this solution by methods I would have used for Project Euler in days gone by; I looked up the lengths of the output on http://oeis.org/ which informed me that they were binomial coefficients (though I should have recognized those). I then considered function whose output was also of that length, and then sought a mapping from that output to the target output. Similar methods got me through quite a few PE problems. – Mr.Wizard Feb 03 '17 at 12:20
  • 1
    @ciao Found it, and just as I had feared I saw it, voted for it, commented on it, and completely forgot about it. http://mathematica.stackexchange.com/a/126580/121 – Mr.Wizard Feb 03 '17 at 12:25
  • @J.M. I feel like I came up with this by myself but since I saw it used before that may have been a subconscious guide. Please go vote for Coolwater's answer if you have not done so already. – Mr.Wizard Feb 03 '17 at 13:11
  • +1. I'll accept @Dubs answer since it was first and new. I tried to find the duplicate, but the title wasn't quite informative enough. Can you say more about your Project Euler comment? I don't quite understand what you are saying there. – rogerl Feb 03 '17 at 13:33
  • 1
    @rogerl I take your comment as support of the close-as-duplicate, so I just performed that action. Regarding Project Euler: I am saying that I forced my way through this problem by relying on the "coincidence" that the length of the desired output is part of the series of binomial coefficients, remembering that the length of Subsets is the same with the right input, generating that input for given $n$ and $m$, then attempting (successfully) to "coerce" that output into the target output. Kind of reverse-engineering the problem without actually having to understand anything. ;^) – Mr.Wizard Feb 03 '17 at 13:48
2

only marginally different that @Szabolcs... (independent, really! ) a bit cleaner avoiding that If

unorderedtups[rngmax_, n_] := Module[{a},
  a[0] = 1;
  Flatten[Table[ Array[a, n] ,
    Evaluate[Sequence @@ (Table[{a[i], a[i - 1], rngmax}, {i, n}])]], 
   n-1]]
unorderedtups[9, 20] // AbsoluteTiming

Edit: Just realized using indexed symbols for the iterators makes this considerably slower!!

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
george2079
  • 38,913
  • 1
  • 43
  • 110
2

This is about an order of magnitude slower than Dubs' answer but recursion is quite direct, and I am working to make it faster.

Attributes[f] = Listable;
f[0, c___, _] := {{c}}
f[n_, m_, c___] := Catenate @ f[n - 1, Range @ m, m, c]

f[3, 4]
{{1, 1, 1}, {1, 1, 2}, {1, 2, 2}, {2, 2, 2}, {1, 1, 3},
 {1, 2, 3}, {2, 2, 3}, {1, 3, 3}, {2, 3, 3}, {3, 3, 3},
 {1, 1, 4}, {1, 2, 4}, {2, 2, 4}, {1, 3, 4}, {2, 3, 4},
 {3, 3, 4}, {1, 4, 4}, {2, 4, 4}, {3, 4, 4}, {4, 4, 4}}
f[5, 9]            // Length // RepeatedTiming
unorderedTuples[5] // Length // RepeatedTiming

{0.00279, 1287}

{0.000338, 1287}

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371