4

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)?

Al Guy
  • 1,610
  • 1
  • 10
  • 15

2 Answers2

9

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} *)
Jason B.
  • 68,381
  • 3
  • 139
  • 286
  • 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. – Kvothe Sep 25 '18 at 14:11
5

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}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • 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 – Jason B. Jun 06 '16 at 00:21
  • @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. – Mr.Wizard Jun 06 '16 at 23:07