28
$\begingroup$

Is there an easy way to sort a list on multiple levels of criteria?

By this I mean, first the list should be sorted according by criteria A (the same as using the usual sort function, Sort[list, A[#1] < A[#2] & ]. I am a big fan of using the pure ordering function in Sort.). However, then I want to have elements with the same value for criterion A to be sorted within their class by criterion B. In general, I would like to do this to an arbitrary depth of criteria.

An example would be, given a set of colored blocks, first sort the blocks alphabetically by shape, then (maintaining that all the squares come before all the triangles) sort each cluster of shapes alphabetically by their colors. (Blue square, Purple square, Red square, black triangle, orange triangle, yellow triangle, etc.)

$\endgroup$
0

2 Answers 2

30
$\begingroup$

This is implemented in SortBy:

Mathematica graphics

Because this function does not perform a pairwise compare, you would need to be able to recast your sort function to produce a canonical ordering. On the upside, if you are able to do so it will be far more efficient than Sort.

f1 = Mod[#, 4] &;
f2 = Mod[#, 7] &;

SortBy[Range@10, {f1, f2}]

{#, f1@#, f2@#} & /@ % // Grid
{8, 4, 1, 9, 5, 2, 10, 6, 7, 3}

$\begin{array}{r} 8 & 0 & 1 \\ 4 & 0 & 4 \\ 1 & 1 & 1 \\ 9 & 1 & 2 \\ 5 & 1 & 5 \\ 2 & 2 & 2 \\ 10 & 2 & 3 \\ 6 & 2 & 6 \\ 7 & 3 & 0 \\ 3 & 3 & 3 \end{array}$

Also see:

$\endgroup$
1
  • $\begingroup$ Excellent! Thank you. I knew there had to be some simple way of doing this. $\endgroup$ Commented Aug 25, 2012 at 3:16
8
$\begingroup$

Coming from a long experience in Perl, one of my great disappointments in Mathematica was the tricky business of sorting multidimensional lists and strings.

Sort orders strings as in a dictionary, with uppercase versions of letters coming after lowercase ones. Your example calls for ASCII sorting. Unfortionately there is no "ASCII" option in Sort nor SortBy.

Standard sorting strings of different lengths gives:

string = {"cat", "fish", "catfish", "Cat"};
Sort[string]
out: {"cat", "Cat", "catfish", "fish"}

In order to get ASCII sorting one has to dissect the strings into their ASCII codes:

ToCharacterCode[string]
out: {{99, 97, 116}, {102, 105, 115, 104}, {99, 97, 116, 102, 105, 115,104}, {67, 97, 116}}

This can be sorted by SortBy[...,First], where ties are now solved by the next value in each of the sublists:

SortBy[ToCharacterCode[string], First]
out: {{67, 97, 116}, {99, 97, 116}, {99, 97, 116, 102, 105, 115, 104}, {102, 105, 115, 104}}

The ASCII-betically sorted list:

FromCharacterCode[
    SortBy[ToCharacterCode[string], First]
]
out: {"Cat", "cat", "catfish", "fish"}
$\endgroup$
4
  • 7
    $\begingroup$ "ASCII-betical" sorting is more compactly done as SortBy[{"cat", "fish", "catfish", "Cat"}, Composition[First, ToCharacterCode]]. $\endgroup$ Commented Aug 25, 2012 at 11:13
  • $\begingroup$ @J.M. Here, I discussed the generalization of your idea. $\endgroup$ Commented Aug 25, 2012 at 17:41
  • 1
    $\begingroup$ If you really want an ASCII sort of a list of strings I recommend: #[[Ordering @ PadRight @ ToCharacterCode @ #]] & $\endgroup$
    – Mr.Wizard
    Commented Aug 25, 2012 at 20:20
  • $\begingroup$ @Mr.Wizard, very direct solution. Thanks too. $\endgroup$ Commented Aug 27, 2012 at 14:27

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.