14

I have a list {x1,...,xN} where N is even, and I need to find all the possible ways to split it into pairs of elements, e.g. the output I would like is something like (say N = 4):

{{{x1,x2},{x3,x4}},{{x1,x3}{x2,x4}},...}

How can this be achieved?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
sdnnds
  • 383
  • 1
  • 7
  • I meant that it should be (and it is, in the example I gave) a list of lists of pairs, not a list of pairs as you suggested: it is a List of all the possible List of pairs in which one can partition the original list. – sdnnds Jul 13 '15 at 09:01
  • Ok, thanks. Does order matter on any level? – Kuba Jul 13 '15 at 09:01
  • @Kuba The order does not matter, but it is important that repeated identical elements are treated as different, i.e. for {x1,x1,x3,x4} I would like to get {{{x1,x1},{x3,x4}},{{x1,x3}{x1,x4}}, {x1,x4},{x1.x3}}} – sdnnds Jul 13 '15 at 09:04
  • 1
    I see, does this answer your question? http://mathematica.stackexchange.com/q/3044/5478 – Kuba Jul 13 '15 at 09:09
  • I've been looking at that, the top answer works except it does not handle identical elements properly, ie. it counts them as one. I couldn't figure out how to change that. I've tried some of the other answers but they are doing slightly different things – sdnnds Jul 13 '15 at 09:14
  • @ciao I will try to reformulate the question. I have a list of N elements, some of which may be identical, and I need to find all the possible ways to partition this list in pairs. The output I would like is thus a list of all these partitions, each of which is a list of pairs. Ie, the output would be a list of sublists of pairs. Your code works well for N=4, but for N=6 it produces all possible partitions in triplets rather than pairs. Moreover, I would like to get to retain all identical partitions, e.g. for {x1, x1, x3, x4,x5,x6} I want two copies of say {{x1,x3},{x1,x4},{x5,x6}}. – sdnnds Jul 13 '15 at 10:04
  • @user2596320 the code in the (now deleted ) comment can handle that with a minor tweak, so if you happen to have copied it I'm sure you can figure that out. It already handled duplicate elements properly from your prior comment example. If you didn't copy it, and no satisfactory answer appears but I get back to lounging, I'll gladly mod it. – ciao Jul 13 '15 at 10:15
  • I have edited your code; now it almost does what I need, but it retains more pairings than I want, e.g. the order matters: Module[{l = #, b = Permutations@ Join[Flatten[ Table[ConstantArray[ii - 1, Length@#/3], {ii, 1, Length@#/2}]]]}, Table[Pick[l, #, jj - 1], {jj, 1, Length@#/2}] & /@ b[[;; Length@b/3]]] &[{x1, x1, x3, x4, x6, x7}] – sdnnds Jul 13 '15 at 10:54
  • @user2596320: Better ways to do it - look at LLlAMnYP's latest - if that's fast enough, accept it - it's pretty elegant. If it's not. I've cobbled up a solution that's ~15X faster than that on a length 14 list, I can polish and post... – ciao Jul 13 '15 at 22:32

5 Answers5

6

My take:

genIdx[n_?EvenQ] := 
  Flatten@With[{r = Range@n}, Fold[With[{l1 = #, l2 = #2},
       Flatten[Map[With[{la = #, c = Complement[r, #]}, 
           Join[la, c[[#]]] & /@ l2] &, l1], 1]] &, 
     Subsets[Range@#, {2}, # - 1] & /@ Range[#, 2, -2] &@n]];

xformLst[lst_, idx_] :=
  If[(Length@lst*(Length@lst - 1)!!) == Length@idx, 
   With[{l = Length@lst}, ArrayReshape[lst[[idx]], {(l - 1)!!, l/2, 2}]], Abort[]];

Use examples:

source = {x1, x1, x3, x4} 
xformLst[source, genIdx@Length@source]

(* {{{x1, x1}, {x3, x4}}, {{x1, x3}, {x1, x4}}, {{x1, x4}, {x1, x3}}} *)

On its own, including genIdx time, this was ~15X faster than fastest answer so far on a list of length 14 (caveats as usual for loungebook performance).

However, the prime benefit is amortization of time over multiple lists to be transformed (I assume you're doing this for more than some one-shot). One simply uses genIdx to generate and save the index set(s) for the size(s) of lists to be transformed, once, and supply those to the transformation function, e.g.

source = {x1, x1, x3, x4};
idx4= genIdx@4;
result=xformLst[source,idx4];

Using this for tests against multiple randomly generated lists of length 14 showed it to be ~500X faster than the fastest answer so far posted...

Only rudimentary error checking is done (e.g., even length argument, will abort if called with list length not matching required index list) - season as desired.

Update: An explanation of what's going on...

I viewed this as a problem of getting the most efficient way of picking items from the source list in the needed order. I chose to do this as a flattened index, since nearly always grabbing with something like list[[{n1,n2,...nm}]] is faster than alternatives like say Map[list[[#]]&,{n1,n2,...}] and the like.

Let's use an example case of lists of length 6. For the output the OP is after, that means the first two positions picking for the first pair look like this:

{{1, 1, 0, 0, 0, 0}, {1, 0, 1, 0, 0, 0}, {1, 0, 0, 1, 0, 0}, 
 {1, 0, 0,0, 1, 0}, {1, 0, 0, 0, 0, 1}}

You'll note that is precisely described by

Subsets[Range@6, {2}, 5]
(* {{1, 2}, {1, 3}, {1, 4}, {1, 5}, {1, 6}} *)

For the next two positions to pick, we can only fill places not already occupied. So, we treat those as lists of length 4, meaning the picks look like

{{1, 1, 0, 0}, {1, 0, 1, 0}, {1, 0, 0, 1}}

and are precisely described by

Subsets[Range@4, {2}, 3]
(* {{1, 2}, {1, 3}, {1, 4}} *)

This continues until there are only two empty slots left, whatever the length of the source list.

This cascade of subset results forms the basis for our work, and is built via the part of the code

Subsets[Range@#, {2}, # - 1] & /@ Range[#, 2, -2] &@n

So for the length 6 example, we'd have a basis of

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

N.b.: the #-1 term that reduces the length of the subsets returned hides a pretty relationship: we can extend this method to arbitrary sizes (triplets, etc.) by using the appropriate values for cut-offs there, and these are just the appropriate order figurate numbers.

Once we have the basis, we Fold over that list. For each sub-result, we derive what actual position(s) are available for that round (by Complementing the positions already taken with the span of possible positions), and then select from those using the appropriate subset(s) of those positions.

The end result is then just Flattened out to increase efficiency at picking the items from the list in the needed order, the result of which is then reshaped into the final form.

ciao
  • 25,774
  • 2
  • 58
  • 139
  • Wow, much faster than what I posted. Sometimes it's really not a good idea to force an old solution to fit a new problem. :-/ – Mr.Wizard Jul 14 '15 at 05:01
  • By the way how is posting something like this as a community wiki helping to improve the admittedly problematic "reputation" system? If you feel your generous bounties improve justice do you not want to build the points necessary to continue them? – Mr.Wizard Jul 14 '15 at 05:03
  • @Mr.Wizard: Glad you find it interesting - there's performance left in there, and if I get the hankering, I might tweak it, pretty it up, and extend it to arbitrary set sizes (there's a pretty relationship with figurate numbers and the cut-offs for taking the subsets buried here). As for wiki? Part of not giving a poop about "rep". – ciao Jul 14 '15 at 07:49
  • I like the use of ArrayReshape (an alternative would be nested Partitions). +1. Similarly, e.g. Fold[Partition, genIdx[l], {2, l/2}] – LLlAMnYP Jul 14 '15 at 11:48
  • However, the prime benefit is amortization of time over multiple lists to be transformed IMO, this can be done with any of the functions on offer here, by supplying them with a list idxN = function[Range[length]] to partition, then doing Map[list[[#]]&,idxN,{2}] – LLlAMnYP Jul 14 '15 at 12:49
  • @LLlAMnYP - that much should be self-evident, just as it should be that the other methods are vastly less efficient at producing said indices, and using Map is a terribly inefficient way to do the second piece.... I'd invite you to take your fastest, use it to work on a list of say length 20, followed by your mapping comment, and time it. Then time the above.... – ciao Jul 14 '15 at 22:35
  • @ciao as far as Map goes, one can then do idxN = Flatten@function@Range@length and subsequently apply xformLst. The way I see it, it is the efficiency of genIdx in itself that is the breakthrough. I'm still staring at it, trying to figure out how it works and why it is so much faster (and also why my approach is so much slower). – LLlAMnYP Jul 14 '15 at 23:23
  • @LLlAMnYP: If you'd like, I can add explanation for genIdx - though I'll have to re-read it and figure it out - it was cowboy-coded and spewed out as I thought of it while lounging... I'd venture the pattern matching and prepending is the main difference. – ciao Jul 14 '15 at 23:32
  • @ciao If it's not too much trouble, I'd love to see that. Perhaps the most confusing is keeping track of the Slots scoped by With. I'm also curious as to why use Subsets rather than some {1,#}&/@... I suppose it's just you thinking on generalizing this code to other partitions. – LLlAMnYP Jul 14 '15 at 23:56
  • @LLlAMnYP - done – ciao Jul 15 '15 at 00:28
  • @ciao Do you have time to chat? – Mr.Wizard Jul 15 '15 at 02:05
  • @Mr.Wizard mobile, so no, but will be at cigar lounge in hour or so, happy to then if you're around... – ciao Jul 15 '15 at 02:14
  • @ciao I'll try to be. In case not I just wanted your help understanding if Cycles and PermutationGroup could be applied to this problem, and if not if there is an easy to understand proof. – Mr.Wizard Jul 15 '15 at 02:20
  • @Mr.Wizard - interesting thought - I'd not considered that. Easy to show the "not" is false - by definition, each member of the results (that is each set, each flattened) is a permutation of the list, or a product of permutations. The question becomes "is the some pattern of cycles we can generate and apply quickly...". I must ponder... – ciao Jul 15 '15 at 04:20
  • @Mr.Wizard: Try, e.g., FindPermutation[#] & /@ Flatten /@ xformLst[Range@6, genIdx@6]. Nothing jumps out at me as an "a-ha! I can generate that efficiently...", perhaps you'll spy something that eludes me. – ciao Jul 15 '15 at 04:27
  • @ciao Thanks for your effort with explaining, however it was the work of what's inside Fold I had the most trouble understanding from a programmer's viewpoint, algorithmically, your approach is not too different from mine. Maybe we can find time to chat around European evening(?) – LLlAMnYP Jul 15 '15 at 11:23
  • @LLlAMnYP: Sure... start one, I'll look for it when I get to lounge. – ciao Jul 15 '15 at 23:51
  • @ciao after much staring at your construct I have managed to recreate it using Function[{...}... rather than using With with symbols replaced by slots (I find the named-argument approach somewhat easier to follow). I've still created chat room and will be around for the next several hours if you find the time to discuss performance issues. – LLlAMnYP Jul 16 '15 at 12:43
5

Not efficient:

<< Combinatorica`
list = {a, a, c, d};
idx[n_] := Select[SetPartitions[n], Union[Length /@ #] == {2} &];
confs[set_] := Map[set [[#]] &, idx[Length@set], {2}]
confs@list
(* {{{a, a}, {c, d}}, {{a, d}, {a, c}}, {{a, c}, {a, d}}} *)
Dr. belisarius
  • 115,881
  • 13
  • 203
  • 453
  • This seems to work! I'll test it later and get back here for feedback. – sdnnds Jul 13 '15 at 12:13
  • Perhaps more efficient is KSetPartitions[Length[list],Length[list]/2] – LLlAMnYP Jul 13 '15 at 17:30
  • @LLlAMnYP this does something different, it finds all partitions in a given number of sets but they can all be of different length. – sdnnds Jul 13 '15 at 18:13
  • belisarius's answer does the job as far as I can tell. – sdnnds Jul 13 '15 at 18:15
  • @belisarius That's true, however all partitions that we seek are split into Length[list]/2 chunks, so this slightly narrows down the search space, no? @user2596320 I think I'm onto a non-bruteforce algorithm that may be much more efficient. – LLlAMnYP Jul 13 '15 at 18:17
5

Here's a completely different and non-bruteforcing approach, so I'm adding it as a separate answer.

Helper function:

help[list_] := Join[{First@list}, #] & /@ Rest@list

Main function:

iterate[list_List /; Length@list < 3] := {{list}}
iterate[list_List /; Length@list > 3] := Module[
   {sublists = 
     iterate /@ (Delete[list, {{1}, {#}}] & /@ 
        Range[2, Length[list]]), inter},

   inter = 
    MapThread[
     Prepend, {sublists, ({First@list, list[[#]]} & /@ 
        Range[2, Length[list]])}];
   Flatten[help /@ inter, 1]
   ];

Timing:

iterate[Range[14]] // AbsoluteTiming // First
6.89226

As compared to belisarius' approach, where a list of 12 elements takes about 38 seconds on my machine.

The algorithm is quite simple. Take the first element and pair it with the second, find all partitions of the remaining elements (3 to n). Append the 1st and 2nd element to these partitions. Then add to that the result of doing the same with the 1st and 3rd, 1st and 4th... 1st and nth.

Edit:

Here's a somewhat cleaned up attempt with recursive functions, but I guess there's no chance of it (recursion) beating ciao's approach and as you'll see, the first step towards improvement is already a step in the direction of his solution:

idx[{a_, b_}] := {{a, b}}
idx[list_List] := 
 Flatten[Function[{row}, Join[First@row, #] & /@ Last@row] /@
   (({#, idx[Complement[list, #]]} &) /@ (list[[{1, #}]] & /@ 
       Range[2, Length@list])), 1]
idx[n_?EvenQ] := idx[Range@n]
part[list_List /; EvenQ[Length@list]] := 
 Fold[Partition, 
  list[[Flatten[idx[Length@list]]]], {2, Length@list/2}]
part[Range@14] // AbsoluteTiming // First
4.08278
LLlAMnYP
  • 11,486
  • 26
  • 65
2

My first attempt was to port Leonid's unsortedComplement from Removing elements from a list which appear in another list into Rojo's partitions code from Partition a set into subsets of size $k$ (to allow repeated elments) but as LLlAMnYP commented that was a wasteful choice.

Starting again from scratch, though based on Rojo's function:

foo[a_List] :=
 Join @@ Table[{x, ##} & @@@ foo[a~Complement~x], {x, Tuples[{{First@a}, Rest@a}]}]

foo[a : {_, _}] := {{a}}

bar[a_List] := Partition[#, 2] & /@ Partition[a[[ Flatten@foo@Range@Length@a ]], Length@a]

Test:

bar[{x1, x1, x3, x4}]
{{{x1, x1}, {x3, x4}}, {{x1, x3}, {x1, x4}}, {{x1, x4}, {x1, x3}}}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • Actually, partitions[list,2] on its own appears to do the job just fine and about two times as fast as my solution. Simply use Map[list[[#]]&,partitions[Range... as I mentioned under ciao's answer. – LLlAMnYP Jul 14 '15 at 14:14
  • @LLlAMnYP That sounds like a much better idea. I'll write it up later and replace this mess. Thank you! – Mr.Wizard Jul 15 '15 at 00:12
  • ciao also warns that Map is quite slow to replace indices with elements, but see the comment thread under his answer for details :-) – LLlAMnYP Jul 15 '15 at 00:30
1

Possibly similar to the solution in the comments.

list = {x1, x1, x3, x4};
Map[list[[#]] &, 
 DeleteDuplicates[
  Map[Sort, Partition[#, 2] & /@ Permutations[Range[Length@list]], 
   2]], {2}]
{{{x1, x1}, {x3, x4}}, {{x1, x3}, {x1, x4}}, {{x1, x4}, {x1, x3}}}

Efficiency decays fast (as length!). Treating repeated identical elements as distinct seems to basically be the same, as, well, only having distinct elements.

LLlAMnYP
  • 11,486
  • 26
  • 65