# GatherBy output with given Shape

I would like to group items in a binary list according to some property like in the following simple example.

l = {0,0,1,1,0,0}
{a, b} = GatherBy[l, 0 == # &]


This example will return a shape error when l only contains one type of element though. Is there a way to force the output of a certain shape.

I could use

a = Select[l, 0 == # &];
b = Select[l, 1 == # &];


but its much slower.

• The order of elements is also not guaranteed. Why don't you use GroupBy instead? asc = GroupBy[l, # == 0 &]; Lookup[asc, True, {}]. {} is the default, in case True is not in the association. Alternatively, Join[<|True -> {}, False -> {}|>, asc], packaged into a function. – Szabolcs Jul 9 '15 at 15:12
• @Szabolcs Actually, your solution (with True) is the one which answers best the OP. It is clear that he expects a binary list, and that he tests a "property" that can be very general. You should just post it. – SquareOne Jul 9 '15 at 17:12

A very readable version is to make a simple Switch after you called GatherBy that will create the correct output form:

myFunc[list_] := With[{res = Gather[list]},
Switch[res,
{{0 ..}, {1 ..}}, res,
{{1 ..}, {0 ..}}, Reverse[res],
{{0 ..}}, Append[res, {}],
{{1 ..}}, Prepend[res, {}],
_,
\$Failed]
]

Column[myFunc/@{{0,0}, {1}, {0,1,0}, {1,0,1}}]
(* {{0,0},{}}
{{},{1}}
{{0,0},{1}}
{{0},{1,1}}
*)


Note, that I used only Gather for simplicity and that this approach assumes that your list is really binary. Otherwise, the patterns in the Switch will not work.

The error is not caused by GatherBy, but by your assumption that a list of two elements will be returned. You should assign the result to a single variable, and then check its length.

• I'm aware of the cause of the error. I just thought there might be a more elegant solution then using one variable, checking the length, if the length was 1 I would have to check which of the two elements i got etc. – paw Jul 9 '15 at 11:46
group[shape_][list_] :=
GroupBy[list, # &] // (Lookup[#, shape] /. Missing[__] -> {}) &


or simpler (as proposed by @Szabolcs in the comments) :

group[shape_][list_] := GroupBy[list, # &] // Lookup[#, shape, {}] &


Examples

list1 = {0, 0, 0, 0};
list2 = {1, 1, 1};
list3 = {0, 0, 1, 0};


then

group[{0, 1}][list1]


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

group[{0, 1}][list2]


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

group[{0, 1}][list3]


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

group[{0, 1, 2, 3}][list2]


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

• Sorry, I didn't see this when I wrote the comment. Lookup takes a third argument for the default. – Szabolcs Jul 9 '15 at 15:13
• @Szabolcs ;) I somehow did not pay attention to this 3rd argument ! I will add this to the post. – SquareOne Jul 9 '15 at 15:35

This might be faster:

reformat[list_] := With[{tot = Total@list}, {
ConstantArray[1, tot],
ConstantArray[0, Length@list - tot]
}]