I believe this is a job for SplitBy
with some post-processing:
separate[lst:{{_, _Integer} ..}] :=
Flatten[Partition[#, #[[1, 2]]] & /@ SplitBy[lst, Last], 1][[All, All, 1]]
Alternatively (and a touch faster),
separate[lst : {{_, _Integer} ..}] :=
(Sequence @@ Partition[#, #[[1, 2]]] & /@ SplitBy[lst, Last])[[All, All, 1]]
Here is an explanation of the process:
Take as a sample list
lst = {{a, 3}, {b, 3}, {c, 3}, {d, 2}, {e, 2}, {f, 2}, {g, 2}, {h, 4}, {i, 4}, {j, 4}, {k, 4}};
Then
lst2 = SplitBy[lst, Last]
(* {{{a, 3}, {b, 3}, {c, 3}}, {{d, 2}, {e, 2}, {f, 2}, {g, 2}}, {{h, 4}, {i, 4}, {j, 4}, {k, 4}}} *)
We can see that the elements labeled d
, e
, f
, and g
have been improperly grouped. So we do some post-processing of these lists by Partition
ing the sets by the number:
lst3 = Partition[#, #[[1, 2]]] & /@ lst2
(* { {{{a, 3}, {b, 3}, {c, 3}}},
{{{d, 2}, {e, 2}}, {{f, 2}, {g, 2}}},
{{{h, 4}, {i, 4}, {j, 4}, {k, 4}}}} *)
It's still not quite right, so we Flatten
once:
lst4 = Flatten[lst3, 1]
(* {{{a, 3}, {b, 3}, {c, 3}}, {{d, 2}, {e, 2}}, {{f, 2}, {g, 2}}, {{h, 4}, {i, 4}, {j, 4}, {k, 4}}} *)
Finally, take only the first elements:
lst4[[All, All, 1]]
(* {{a, b, c}, {d, e}, {f, g}, {h, i, j, k}} *)
Update 1
Here is another version that is about 8 times slower than my other one, but it's cute because it does the Split
ting in one go:
separate2[lst : {{_, _Integer} ..}] :=
Module[{i = 1}, SplitBy[lst, If[2 #[[2]] - 1 > i++, 1, i = 0; 0] &]][[All, All, 1]]
Some basic timings. We generate a sample list:
SeedRandom[1]
lst = Flatten[Table[{RandomInteger[{1, #}], #}, {#}] & /@ RandomInteger[{1, 7}, 3000], 1];
lst // Length
(* 11883 *)
and then
lst // separate; // AbsoluteTiming // First
lst // separate2; // AbsoluteTiming // First
(* 0.025516 *)
(* 0.083057 *)
Update 2
Here is a replacement-based approach using Sow
and Reap
that I feel is pretty clever, but it is really slow:
separate3[lst : {{_, _Integer} ..}] :=
Last@Reap[
lst //. {pat : Longest[PatternSequence[{_, n_Integer} ..]], b___}
:> (Sow[Partition[{pat}[[All, 1]], n]]; {b})
]~Flatten~2
So don't try it on big lists! It scales terribly.
Update 3
Here's another solution, using recursion. It's much better than the replacement version, but still a lot significantly slower than the first two versions at large list sizes. Still, I like it:
Clear@func
func[{}] = {};
func[lst_List] := Module[{},
Sow[Partition[lst[[;; #, 1]], #] &@lst[[1, 2]]];
func[Drop[lst, lst[[1, 2]]]];
]
separate4[lst : {{_, _Integer} ..}] :=
Block[{$RecursionLimit = 10000}, Last@Reap@func[lst]~Flatten~2]
Values@GroupBy[lst, Last -> First]
work? Or maybeSplitBy[lst, Last][[All, All, 1]]
instead? Are the things elements to be grouped together always in order? $\endgroup$