# How to sort and extract a list containing products

I've some difficulties with lists containing products as elements, e.g. list = {A B A, A K B, A K R}, where A and B are from the same 'family', let's call it sector 1, while K and R are from sector 2.

I would like to find a way to sort the list according to the occurrence of elements from sector 2.

The desired output should be something like

listWith0Sector2elements = {A K B}
listWith1Sector2elements = {A K B}
listWith2Sector2elements = {A K R}


my naive attempt (Count, MemberQ...) unfortunately failed, as I have never had anything to do with products

EDIT:

In the end, I would like to have the sorting, but also these different new lists, as explained in my example.

EDIT 2:

It's really hard for me to choose, which answer is the right/best one. I think all of them are great. I will go with the one I finally implemented in my own code. Thank you all!

sector2 = {k, r};

list = Inactivate[{a b a, a k b, a k r}, Times]


Count[Alternatives @@ sector2] /@ list

{0, 1, 2}


### Sorting

SortBy[Count[Alternatives @@ sector2]] @ list


Reverse[list]


SortBy[Count[Alternatives @@ sector2]] @ %


### Selecting sublists

lstW0S2Elements = Select[Count[Alternatives @@ sector2]@# == 0 &]@list  (* or *)

lstW0S2Elements = Pick[list, Count[Alternatives @@ sector2] /@ list, 0]


lstW1S2Elements = Select[Count[Alternatives @@ sector2]@# == 1 &]@list  (* or *)
lstW1S2Elements = Pick[list, Count[Alternatives @@ sector2] /@ list, 1]


lstW2S2Elements = Select[Count[Alternatives @@ sector2]@# == 2 &]@list  (* or *)
lstW2S2Elements = Pick[list, Count[Alternatives @@ sector2] /@ list, 2]


And using variant of J.M.'s answer with an alternative specification of keys in GroupBy:

gb = GroupBy[list, Count[Alternatives @@ sector2]]


lstW0S2Elements = gb @ 0


Update: If you wish to work with an input list with multiplication already carried out:

lst0 = {a^2 b, a b k, a k r};

GroupBy[lst0,
Count[# /. { Power -> Table, Times -> Flatten@*List}, Alternatives @@ sector2] &]

 <|0 -> {a^2 b}, 1 -> {a b k}, 2 -> {a k r}|>

sector1 = {a, b};
GroupBy[lst0,
Count[# /. { Power -> Table, Times -> Flatten@*List}, Alternatives @@ sector1] &]

 <|3 -> {a^2 b}, 2 -> {a b k}, 1 -> {a k r}|>

• The sorting is okay, but I also want to extract that new information in new lists, as explained in my question Dec 31 '20 at 11:54
• @NathanaelNoir, please see the updated version.
– kglr
Dec 31 '20 at 11:58
• It works, but has troubles if my list contains expressions like a^2 b^2. Why is that the case? Dec 31 '20 at 13:59
• @NathanaelNoir, please see the latest update.
– kglr
Dec 31 '20 at 14:15

First, you must write products with a space, otherwise it is considered a name. Note that A A will be written as A square.

I assume that elements of different sectors or not already ordered alphabetically as in your example (Sector1: A,B, Sector 2: K,R). In this case sorting would be trivial.

To sort lists according to elements of sector 2 we first extract these elements. Then we use Order to determine if the products are in order or not.

Now you can sort ascending or descending. For the first case you would have:

list = {A K R, A K B, A B A};
Sort[list, Order[Cases[#1, K | R], Cases[#2, K | R]] &]
(*{A^2 B, A B K, A K R}*)


For descending sort you would have to change the sign of Order:

list = {A B A, A K R, A K B};
Sort[list, -Order[Cases[#1, K | R], Cases[#2, K | R]] &]
(*{A K R, A B K, A^2 B}*)

• That's a really easy and nice way! Thank you! How could I extract the different cases in that way? I would like to split the list in different ones according to the found ordered structure Dec 31 '20 at 11:12
• For this you need to give a criterion where to split. E.g. you could put all lists containing no elements of sector 2 in one list. Those with 1 element of sector 2 in a second list e.t.c. Dec 31 '20 at 11:18
• I've tried to do it with 'Select' and your ordering but it's not working. Do you have an idea to do that fast ? Dec 31 '20 at 11:22
• What criterion do you need. Counting is easy if there are no repeated elements. But if there are repeated elements, say A A, MMA rewrites this to Power[A,2] what makes counting difficult. Therefore, restrict your criterion to what you really need. Dec 31 '20 at 11:46
• My criterion is exactly the one that you implemented in your code, but I would like to split it in different new lists. Let's say: a new list with -Order[Cases[#1, K | R]], a new one with Order[Cases[#2, K | R], as in the example in my original question Dec 31 '20 at 11:48

Things are a little easier if you use NonCommutativeMultiply[] instead to represent your products (unless you know your products are commutative). Then, it is just a matter of using GroupBy[] with an appropriate criterion:

sector2 = {k, r};
prods = {a ** b ** a, a ** k ** b, a ** k ** r};

GroupBy[prods, Count[#, x_ /; MemberQ[sector2, x]] &] // KeySort
<|0 -> {a ** b ** a}, 1 -> {a ** k ** b}, 2 -> {a ** k ** r}|>


SortBy[] is also usable:

SortBy[prods, Count[#, x_ /; MemberQ[sector2, x]] &]
{a ** b ** a, a ** k ** b, a ** k ** r}


If we want to split the result list according to the factors from sector 2, we need to count those factors. When we have repeated factors, MMA represent them as Power[factor,n] and we must take care of this.

Toward this aim, we first change the product into a list. Then we replace every power by a list of repeated factors. And flatten out the double lists. Here is the code for this:

ClearAll[count];
count[p0_, base_] := Module[{p},
p = List @@ p0;
p = p /. Power[x_, n_] :> Table[x, n] // Flatten;
Count[p, Alternatives @@ base]
]


baseis a list of all factors we wish to count, e.g all factors in scope 2.

Having now a counting function we may split the list by:

list = {A^2 B, A B K, A B R, A K R, A K K}
base = {R, K};
Split[list, count[#1, base] == count[#2, base] &]
(*{{A^2 B}, {A B K, A B R}, {A K R, A K^2}*)


Using Exponent to count powers:

list = {A B A, A K B, A K R};

GroupBy[list, Exponent[# /. {K | R -> $$x},$$x] &]
(* <|0 -> {A^2 B}, 1 -> {A B K}, 2 -> {A K R}|> *)

list = {A B A, A K B, A K R};
sector1={"A","B"};
sector2={"K","R"};


Then define the test:

numOfSector2Element[myEl_]:=StringCount[StringReplace[ToString[InputForm[myEl]],{Shortest[l_~~"^"~~n_]:>StringJoin[Table[l,{ToExpression[n]}]],"*"->""}],sector2]


Then select the elements:

listWith0Sector2elements=Select[list,numOfSector2Element[#]==0&]
(* {A^2*B} *)

listWith1Sector2elements=Select[list,numOfSector2Element[#]==1&]
(* {A*B*K} *)

listWith1Sector2elements=Select[list,numOfSector2Element[#]==2&]
(* {A*K*R} *)