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)?
2 Answers
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 asOrdering[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} *)
-
$\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$– KvotheSep 25, 2018 at 14:11
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}
-
$\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$ Jun 6, 2016 at 23:07
Ordering
? $\endgroup$OrderingBy
(which does not yet exist as built-in). $\endgroup$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$SortBy
is faster, and so a true implementation ofOrderingBy
probably would be faster thanOrdering
as written below. $\endgroup$