14
$\begingroup$

I was playing with Sort. All examples I have come across so far, i.e

Sort[{1, -1, 3, -3, 2, 5}, Abs[#1]<Abs[#2]&] 

can expressed in a shorter fashion using SortBy

SortBy[{1, -1, 3, -3, 2, 5}, Abs] 

Surely, they wouldn't put in a completely redundant function, so what are the preferred use cases for Sort?

$\endgroup$

4 Answers 4

26
$\begingroup$

In general SortBy can do pretty much anything that Sort does; in some cases, possibly better or faster. You can find many comparisons on this site if you just search for both function names.

I also disagree with @user21382 that his task could not be expressed elegantly in SortBy form: not only can it be done, I would actually argue that it could be done even more readably with SortBy than with Sort.

User21382 set the task to sort the following data, presented as an association, first by ascending age, then by descending alphabetical order. This can be accomplished using the fact that SortBy can take a list of functions that are applied in sequence to break ties:

data = {
  <|"Name"->"Jill", "Age" -> 23|>,
  <|"Name"->"Jack", "Age" -> 55|>,
  <|"Name"->"Jen", "Age" -> 55|>,
  <|"Name"->"Joe", "Age" -> 23|>
};

SortBy[data, 
  {
  (#["Age"]&), (* by age, ascending *)
  (Total@ ToCharacterCode@ ToUpperCase@ #["Name"]&) (* by alpha order, descending *)
  }
]

(* Out:
{
<|"Name" -> "Joe", "Age" -> 23|>,
<|"Name" -> "Jill", "Age" -> 23|>,
<|"Name" -> "Jen", "Age" -> 55|>,
<|"Name" -> "Jack", "Age" -> 55|>
}
*)

Here I take advantage of the fact that higher character codes correspond to letters further down in alphabetical order, so ordering by increasing character code effectively gives reverse alphabetical order.

SortBy also has an operator syntax, i.e. the following two usages are equivalent:

SortBy[data, sortingfunction] == SortBy[sortingfunction] [data]

I find the latter form very readable and highly expressive.

Another interesting property of SortBy is the fact that it gives ready access to a stable sort function, i.e. a sorting algorithm that maintains the relative order of records with equal values, by using the following syntax:

SortBy[ data, {sortingfunction} ]

In this case, no tie-breaking function is provided, so tied values will be left in the original order. This has been showcased multiple times on the site already, so I will just link to this older answer from StackOverflow that explains the point very nicely.

$\endgroup$
2
  • 8
    $\begingroup$ I wish I could +2 for mentioning stable sort and even linking to the best origin post for it. $\endgroup$
    – Mr.Wizard
    May 9, 2015 at 6:49
  • 1
    $\begingroup$ @Mr.Wizard Thank you! That's very kind. $\endgroup$
    – MarcoB
    May 9, 2015 at 14:24
11
$\begingroup$

No one has mentioned the difference in performance:

SeedRandom[0];
data = RandomInteger[{-10000, 10000}, 10^5];

sdata = Sort[data, Abs[#1] < Abs[#2] &]; // AccurateTiming
sbdata = SortBy[data, Abs]; // AccurateTiming
sbstable = SortBy[data, {Abs}]; // AccurateTiming
(*
  1.86124
  0.0222057
  0.00999333
*)

As mentioned by MarcoB, the results are not always equivalent, since the second SortBy does a stable sort and apparently Sort and SortBy do different tie-breaks:

sdata[[10 ;; 20]]
sbdata[[10 ;; 20]]
sbstable[[10 ;; 20]]
(*
  {-1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1}
  {-1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1}
  {-1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1}
*)
$\endgroup$
3
  • $\begingroup$ I would include a BenchMarkPlot but it's not working this morning -- emits SymbolName::sym errors. Is this a known problem in V10.1 (Mac OS, if that's important)? $\endgroup$
    – Michael E2
    May 9, 2015 at 12:17
  • 1
    $\begingroup$ FYI I did mention the difference in performance, and I also linked to an answer of mine with a graph. I forgot to include the stable performance however though I have written about it before. +1 for the order differences however. $\endgroup$
    – Mr.Wizard
    May 9, 2015 at 23:45
  • $\begingroup$ @Mr.Wizard I misunderstood your reference. Perhaps because it was late. Sorry about that. Thanks for the upvote, in any case. $\endgroup$
    – Michael E2
    May 10, 2015 at 0:13
10
$\begingroup$

I commented on this a bit here: What are the most common pitfalls awaiting new users?

Since SortBy was introduced in Mathematica 6 it is preferred where applicable over Sort with a custom comparator. This is because SortBy uses vector application of its second argument over the sort expression then the fast internal sort/ordering function, whereas Sort results in high-level application to pairs of values. Not all Sort operations can be practically converted into SortBy ones however. (As far as I know.)

To highlight the behavior of Sort consider:

Sort[Range[7], (Print[{#, #2}]; #2 - # >= 3) &]

{1,2}

{1,3}

{2,3}

{4,5}

{6,7}

{4,7}

{1,5}

{3, 2, 1, 5, 4, 7, 6}

Individual pairs of elements are compared and blocks of elements are reordered if a test returns False. This is simply not the way that SortBy uses its second parameter. Admittedly I struggle to think of examples where this behavior of Sort is needed.

$\endgroup$
2
  • $\begingroup$ I have a matrix of numbers and want to sort it using 4th or 5th column, not First or Last column. I cannot find an easy way to just sort the matrix by 4th column. For example, something like SortBy[matrix, Fourth]. $\endgroup$ Jan 22, 2020 at 23:55
  • 1
    $\begingroup$ @TugrulTemel Use SortBy[m, #[[4]] &] or SortBy[m, Extract[4]]. $\endgroup$
    – Mr.Wizard
    Jan 23, 2020 at 1:34
5
$\begingroup$

SortBy is usually a more concise form of Sort, yes, but at times, you may have data that cannot be expressed, or cannot be expressed elegantly, using SortBy. For example, let's suppose that we want to sort the following by age, ascending, then by name, descending:

data = {
  <|"Name"->"Jill", "Age" -> 23|>,
  <|"Name"->"Jack", "Age" -> 55|>,
  <|"Name"->"Jen", "Age" -> 55|>,
  <|"Name"->"Joe", "Age" -> 23|>};
Sort[
  data,
  Function@Or[
    #1["Age"] < #2["Age"],
    #1["Age"] == #2["Age"] && OrderedQ[{#2["Name"], #1["Name"]}]]]

{<|"Name" -> "Joe", "Age" -> 23|>, <|"Name" -> "Jill", "Age" -> 23|>, <|"Name" -> "Jen", "Age" -> 55|>, <|"Name" -> "Jack", "Age" -> 55|>}

Admittedly, I almost never use Sort unless I can just call Sort[data], but it has its uses on occasion.

$\endgroup$
1
  • 4
    $\begingroup$ It should be noted that SortBy was new in v6 and has an operator form, while Sort doesn't, and is older. $\endgroup$
    – evanb
    May 8, 2015 at 21:51

Your Answer

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

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