How to generate all possible orderless partitions of a list according to another list?
A solution using Repeated
, ReplaceList
, and the Orderless
attribute.
part[a_List, p_List] :=
Module[{f, sym},
Attributes[f] = Orderless;
sym = Unique["x", Temporary] & /@ p;
ReplaceList[
f @@ a,
f @@ MapThread[Pattern[#, Repeated[_, {#2}]] &, {sym, p}] -> List /@ sym
]
]
part[{1, 2, 3, 4, 5}, {2, 2, 1}]
{{{1, 2}, {3, 4}, {5}}, . . ., {{4, 5}, {2, 3}, {1}}}
This proves to be an order of magnitude faster than BoLe's code:
SeedRandom[1]
p = RandomInteger[{1, 3}, 6];
a = Range @ Tr @ p;
Flatten[split[a, p]] /. sol -> List // Length // RepeatedTiming
part[a, p] // Length // RepeatedTiming
{0.7517, 45360} {0.0766, 45360}
Relation to Permutations
Note: I completely overlooked Simon Woods's answer before starting on this section. Nevertheless after reading and digesting his answer I believe I have something unique to offer.
I was reminded of a different approach to this problem using Permutations
thanks to an apparently coincidental vote on an old answer of mine:
- How can I get every distinct size-n combination of a list?
Consider that there is a one-to-one mapping between your target list and this:
Permutations[{1, 1, 2, 2, 3}]
{{1, 1, 2, 2, 3}, {1, 1, 2, 3, 2}, . . ., {3, 2, 1, 2, 1}, {3, 2, 2, 1, 1}}
Permutations
by itself is very efficient. It is nearly two orders of magnitude better than part
defined above, and its output takes a fraction of the memory:
maskFn = Permutations @* Flatten @* MapIndexed[Table[#2, {#}] &];
a = Range @ 12;
p = {2, 3, 1, 3, 2, 1};
part[a, p] // ByteCount // RepeatedTiming
maskFn[p] // ByteCount // RepeatedTiming
{6.49, 2075673680} {0.0984, 319334552}
If you can write whatever operations follow this in terms of the permutation masks rather than the partitions there is clearly the potential for a major optimization.
Now, after reading Simon's answer and being inspired by it, I offer the following solution.
We can use Ordering
as Simon did to convert the permutations, and then split the result using a slight modification of my dynP
from:
- Partitioning with varying partition size
This provides my second proposal:
maskFn = Permutations @* Flatten @* MapIndexed[Table[#2, {#}] &];
dynP2[m_, p_] :=
MapThread[
m[[All, # ;; #2]] &,
{{0} ~Join~ Most@# + 1, #} & @ Accumulate @ p
]\[Transpose]
part2[a_List, p_List] := dynP2[a[[ Ordering @ # ]] & /@ maskFn[p], p]
Comparing (in v10.1) the performance of both of my functions to Simon's parts
:
a = Alphabet[] ~Take~ 11;
p = {2, 1, 3, 1, 2, 2};
RepeatedTiming @ Length @ #[a, p] & /@ {part, part2, parts}
{{1.452, 831600}, {2.052, 831600}, {2.30, 831600}}
And again but with a packable a
list:
a = Range @ 11;
p = {2, 1, 3, 1, 2, 2};
RepeatedTiming @ Length @ #[a, p] & /@ {part, part2, parts}
{{1.45, 831600}, {1.59, 831600}, {1.43, 831600}}
It seems to me that part
is still the best general function, but Simon's code is slightly faster in the case of a packed/packable input list.
Permutations
treats repeated elements as identical, so you can get a flattened version of the desired result with something like
Ordering /@ Permutations[{1, 1, 2, 2, 3}]
(* {{1, 2, 3, 4, 5}, {1, 2, 3, 5, 4}, {1, 2, 4, 5, 3} ... {4, 5, 2, 3, 1} *)
A simple solution based on this idea:
parts[list_, p_] := Module[{q},
q = Flatten@MapThread[ConstantArray, {Range@Length@p, p}];
Internal`PartitionRagged[list[[Ordering[#]]], p] & /@ Permutations[q]]
Unforunately Internal`PartitionRagged
is rather slow; it is faster (though less elegant) to create a function which does the reshaping:
parts[list_, p_] := Module[{q, f, slot},
q = Flatten@MapThread[ConstantArray, {Range@Length@p, p}];
f = Function @@ {Internal`PartitionRagged[Array[slot, Length@list], p]} /. slot -> Slot;
f @@@ (list[[Ordering[#]]] & /@ Permutations[q])]
This is comparable to Mr Wizard's in terms of speed.
It's far from pretty, using pattern matching (OrderlessPatternSequence
):
ReplaceList[#1,
Module[{names = Unique[] & /@ #1, partitions},
partitions = Internal`PartitionRagged[names, #2];
Activate[{OrderlessPatternSequence @@ (Pattern[#, _] & /@ names)} /;
Evaluate[And @@ (Inactive@OrderedQ /@ partitions)] :>
Evaluate@partitions]]] &[Range@5, {2, 2, 1}]
The pattern we construct for the case of these arguments is:
{OrderlessPatternSequence[$3_, $4_, $5_, $6_, $7_]} /;
OrderedQ[{$3, $4}] && OrderedQ[{$5, $6}] && OrderedQ[{$7}] :>
{{$3, $4}, {$5, $6}, {$7}}