3
$\begingroup$

I have a minimal working example (MWE) list called listToSort that is a list of string triplets:

listToSort = {{"B", "X", "Y"}, {"C", "Y", "X"}, {"C", "X", "Y"}, {"A", "X", "Y"}};

I wish to sort listToSort by the first element in each triplet so that the triplets starting with "C" come first, the triplets starting with "B" come next, and the triplets starting with "A" come last. I would like the second and third elements to be sorted in canonical order (i.e., "X" comes before "Y").

One way to do this is with SortBy and Which:

SortBy[listToSort, Which[#[[1]] == "C", 1, #[[1]] == "B", 2, #[[1]] == "A", 3] &]

{{"C", "X", "Y"}, {"C", "Y", "X"}, {"B", "X", "Y"}, {"A", "X", "Y"}}

However, it is quite verbose and takes a lot of typing to use Which as above. Switch, on the other hand, uses a more compact syntax and still gives the desired output:

SortBy[listToSort, Switch[#[[1]], "C", 1, "B", 2, "A", 3] &]

{{"C", "X", "Y"}, {"C", "Y", "X"}, {"B", "X", "Y"}, {"A", "X", "Y"}}

But, what if I want to use Switch in tandem with a list of scoring values called atomsScoreList?

atomsScoreList = {{"C", 1}, {"B", 2}, {"A", 3}};

I notice that I can use Apply to obtain a Switch function analogous to the example above. For instance:

Apply[Switch["C", ##] &, Flatten[atomsScoreList]]
Apply[Switch["B", ##] &, Flatten[atomsScoreList]]
Apply[Switch["A", ##] &, Flatten[atomsScoreList]]

1 2 3

How do I incorporate a similar Switch function in a call to SortBy? In particular, could you please help me understand how I can use the slots (# and ##) and & to specify the correct pure function(s)?

For example, this attempt fails to yield the desired output:

SortBy[listToSort, Apply[Switch[#[[1]] &, ##] &, Flatten[atomsScoreList]]]

I probably have the &s in the wrong places. Could you please help me?

(By the way, I'm running a very old version of Mathematica: version 9. I would like potential solutions to work in version 9.)

$\endgroup$
0

3 Answers 3

4
$\begingroup$
Clear["Global`*"]

listToSort = {{"B", "X", "Y"}, {"C", "Y", "X"}, {"C", "X", "Y"}, {"A",
     "X", "Y"}};

atomsScoreList = {{"C", 1}, {"B", 2}, {"A", 3}};

SortBy[listToSort, Position[SortBy[
      atomsScoreList, Last][[All, 1]], #[[1]]][[1, 1]] &]

(* {{"C", "X", "Y"}, {"C", "Y", "X"}, {"B", "X", "Y"}, {"A", "X", "Y"}} *)

SortBy[listToSort, Switch[#[[1]],
   Evaluate[Sequence @@ Flatten[atomsScoreList]]] &]

(* {{"C", "X", "Y"}, {"C", "Y", "X"}, {"B", "X", "Y"}, {"A", "X", "Y"}} *)
$\endgroup$
3
$\begingroup$

Let's specify the order of the first elements in an easy way, just list the possible first elements in the order you want:

OrderingOfFirstElements = {"C", "B", "A"};

This turns out to be just reverse order, but I'm assuming that's coincidence and that you want a general strategy.

Now let's define an ordering function that uses that specification:

MyTripletOrder[a_, b_] := 
  If[
    a[[1]] == b[[1]],
    Order[Rest@a, Rest@b],
    Sign @@
      (FirstPosition[OrderingOfFirstElements, b[[1]]] - 
       FirstPosition[OrderingOfFirstElements, a[[1]]])]

Now we can supply this ordering function to Sort:

listToSort = {{"B", "X", "Y"}, {"C", "Y", "X"}, {"C", "X", "Y"}, {"A", "X", "Y"}};
Sort[listToSort, MyTripletOrder]

{{"C", "X", "Y"}, {"C", "Y", "X"}, {"B", "X", "Y"}, {"A", "X", "Y"}}

If your rule is actually just simply to reverse-sort on the first element (so the coincidence wasn't merely coincidental), you could simplify:

MyTripletOrder[a_, b_] :=
  If[
    a[[1]] == b[[1]],
    Order[Rest@a, Rest@b],
    Order[b[[1]], a[[1]]]]

Now, if you only ever have just these three starting elements (i.e. A, B, and C), then you could be more direct:

MyTripletOrder[{a_, as__}, {a_, bs__}] := Order[{as}, {bs}];
MyTripletOrder[{"C", _, _}, {_, _, _}] := 1;
MyTripletOrder[{"B", _, _}, {"A", _, _}] := 1;
MyTripletOrder[{"B", _, _}, {"C", _, _}] := -1;
MyTripletOrder[{"A", _, _}, {_, _, _}] := -1;

NOTE

It looks like FirstPosition was introduce in version 10. So, here's an alternate:

MyTripletOrder[a_, b_] :=
  If[
    a[[1]] == b[[1]],
    Order[Rest@a, Rest@b],
    Sign @@
      Flatten[
        Position[OrderingOfFirstElements, b[[1]]] - 
        Position[OrderingOfFirstElements, a[[1]]]]]
$\endgroup$
2
$\begingroup$

In each sublist, leave the first letter unchanged but "invert" the remaining letters $(a \leftrightarrow z, b \leftrightarrow y, ...)$. Then use a traditional sort... on the first element, then second element, then third... Finally, re-"invert" all the characters except the first.

invertLetter[x_String] := FromLetterNumber[27 - LetterNumber[x]];

ToUpperCase /@ Flatten[{First[#], invertLetter /@ Rest[#]}] & /@ 
 ReverseSortBy[(Flatten[{First[#], invertLetter /@ Rest[#]}] & /@ 
    listToSort), First]

(*

{{"C", "X", "Y"}, {"C", "Y", "X"}, {"B", "X", "Y"}, {"A", "X", "Y"}}

*)

$\endgroup$

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