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


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


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!


6 Answers 6

sector2 = {k, r};

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

enter image description here

Count[Alternatives @@ sector2] /@ list
{0, 1, 2}


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

enter image description here


enter image description here

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

enter image description here

Selecting sublists

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

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

enter image description here

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

enter image description here

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

enter image description here

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

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

enter image description here

lstW0S2Elements = gb @ 0

enter image description here

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}; 

   Count[# /. { Power -> Table, Times -> Flatten@*List}, Alternatives @@ sector2] &]
 <|0 -> {a^2 b}, 1 -> {a b k}, 2 -> {a k r}|>
sector1 = {a, b};
   Count[# /. { Power -> Table, Times -> Flatten@*List}, Alternatives @@ sector1] &]
 <|3 -> {a^2 b}, 2 -> {a b k}, 1 -> {a k r}|>
  • $\begingroup$ The sorting is okay, but I also want to extract that new information in new lists, as explained in my question $\endgroup$ Commented Dec 31, 2020 at 11:54
  • 1
    $\begingroup$ @NathanaelNoir, please see the updated version. $\endgroup$
    – kglr
    Commented Dec 31, 2020 at 11:58
  • $\begingroup$ It works, but has troubles if my list contains expressions like a^2 b^2. Why is that the case? $\endgroup$ Commented Dec 31, 2020 at 13:59
  • $\begingroup$ @NathanaelNoir, please see the latest update. $\endgroup$
    – kglr
    Commented Dec 31, 2020 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}*)
  • $\begingroup$ 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 $\endgroup$ Commented Dec 31, 2020 at 11:12
  • 1
    $\begingroup$ 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. $\endgroup$ Commented Dec 31, 2020 at 11:18
  • $\begingroup$ 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 ? $\endgroup$ Commented Dec 31, 2020 at 11:22
  • $\begingroup$ 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. $\endgroup$ Commented Dec 31, 2020 at 11:46
  • $\begingroup$ 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 $\endgroup$ Commented Dec 31, 2020 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:

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};

Then define the test:


Then select the elements:

(* {A^2*B} *)

(* {A*B*K} *)

(* {A*K*R} *)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.