4
$\begingroup$

How can I computationally efficiently sort a list so as to return the positions and not the actual values at those positions (which SortBy does)?

$\endgroup$
8
  • 3
    $\begingroup$ Are you talking about Ordering? $\endgroup$
    – Kuba
    Jan 20, 2016 at 9:05
  • $\begingroup$ yes, I didn't notice that function. Confused with R. Thanks! $\endgroup$
    – Al Guy
    Jan 20, 2016 at 9:15
  • 1
    $\begingroup$ @Kuba I guess, rather about OrderingBy (which does not yet exist as built-in). $\endgroup$ Jan 20, 2016 at 9:47
  • 1
    $\begingroup$ @JasonB Speed. The way Mathematica evaluator works, SortBy is often vastly faster. The reason is a bit long to explain, but here is one place to look at, and here is another. It could've been discussed also here on this site, but I can't recall where. $\endgroup$ Jan 20, 2016 at 10:30
  • 1
    $\begingroup$ here. I can see that SortBy is faster, and so a true implementation of OrderingBy probably would be faster than Ordering as written below. $\endgroup$
    – Jason B.
    Jan 20, 2016 at 10:32

2 Answers 2

9
$\begingroup$

There were comments about the lack of an OrderingBy function, and for this I'll quote Szabolcs

I think that OrderingBy is not necessary. It it were to be analogous to SortBy then OrderingBy[list, f] would give exactly the same output as Ordering[f /@ list], which can even be changed to

So we can just write

orderingBy[list_, f_] := Ordering[f /@ list];
orderingBy[f_] := Ordering[f /@ #] &;

and apply it to a list,

orderingBy[{{1, 2, 3}, {2, 3, 1}, {3, 1, 2}, {2, 2}}, Total]
orderingBy[{{1, 2, 3}, {2, 3, 1}, {3, 1, 2}, {2, 2}}, Rest]
orderingBy[{{1, 2, 3}, {2, 3, 1}, {3, 1, 2}, {2, 2}}, Last]
(* {4, 1, 2, 3} *)
(* {4, 3, 1, 2} *)
(* {2, 3, 4, 1} *)

or use it in the operator form

orderingBy[Last]@{{1, 2, 3}, {2, 3, 1}, {3, 1, 2}, {2, 2}}
(* {2, 3, 4, 1} *)
$\endgroup$
1
  • $\begingroup$ To save the next person who tries this solution some time: This function does not sort for tie breakers as would be the expected behavior for such a orderingBy function (Compare to the behavior of SortBy). So please read the answer by Mr. Wizard before applying this solution. Of course a perfect solution is also already given by Leonid Shifrin at the linked question. $\endgroup$
    – Kvothe
    Sep 25, 2018 at 14:11
5
$\begingroup$

I think this should be marked as a duplicate of:

The method Jason posted is not equivalent to SortBy unless one is using the stable form, because no tie-breaking using the original expression is performed. Consider:

a = {{1, 7, 0}, {1, 4}, {1, 2}, {2}};

b = SortBy[a, First]
Ordering[First /@ a]
{{1, 2}, {1, 4}, {1, 7, 0}, {2}}

{1, 2, 3, 4}

Jason's method indicates that the elements are already in order, but SortBy reorders significantly. To match the default behavior of SortBy we could instead use an explicit Identity with my orderingBy function:

orderingBy[a, {First, Identity}]
a[[%]] === b
{3, 2, 1, 4}

True

To handle the default tie-break implicitly we could add a definition:

orderingBy[lst_List, sfn_] := orderingBy[lst, {sfn, Identity}]

And in v10 fashion an operator form:

orderingBy[fns_][lst_List] := orderingBy[lst, fns]

Now:

a // orderingBy[First]
{3, 2, 1, 4}
$\endgroup$
2
  • $\begingroup$ Right you are, I think that makes my answer just plain wrong. Thanks for pointing it out, it isn't letting me delete the answer though $\endgroup$
    – Jason B.
    Jun 6, 2016 at 0:21
  • $\begingroup$ @Jason That is probably because it is the Accepted answer. I don't think your answer is wrong really, just incomplete. Your method is elegant where applicable and I did not intend to denigrate it. Rather I wanted to make the case that this question is a duplicate of the one referenced, and I think I was successful as four people joined me in voting that way. $\endgroup$
    – Mr.Wizard
    Jun 6, 2016 at 23:07

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