Ok, here is my stab at it. As I mentioned in comments, I can see two ways to solve this.
Ordering - based solution (and a by-product: compilable SortBy
)
One way is to use Ordering
, but the problem here is that SortBy
when used with a list of functions performs in a way where each function serves as a tie-breaker for ties resulting from application of a previous function to a pair of list elements. Should we have a built-in OrderingBy
function, and that would be easy. But Ordering
requires a single sorting function, just like Sort
.
Generating a comparison function
The solution I will use here is to generate a possibly complex single comparison function from a list of functions used in SortBy
. Here is a possible code for it:
Clear[generateSortingFunction];
generateSortingFunction[funcs__] :=
Module[{sortFGenerator},
sortFGenerator[funcs] //. {
sortFGenerator[f_, funs___] :>
(With[{fst = f[#1], sec = f[#2]},
If[OrderedQ[{fst, sec}],
If[OrderedQ[{sec, fst}],
sortFGenerator[funs][##],
True
],
False
]] &),
sortFGenerator[][__] :> True}
];
Assuming that a list of functions in your example is {#[[1]]&,#[[2]]&}
, we will have then:
sf = generateSortingFunction[#[[1]]&,#[[2]]&]
With[{fst$=(#1[[1]]&)[#1],sec$=(#1[[1]]&) [#2]},
If[OrderedQ[{fst$,sec$}],
If[OrderedQ[{sec$,fst$}],
(With[{fst$=(#1[[2]]&)[#1],sec$=(#1[[2]]&)[#2]},
If[OrderedQ[{fst$,sec$}],
If[OrderedQ[{sec$,fst$}],True,True],
False]
]&)[##1],
True],
False]]&
This is admittedly a mess, but the reason I decided to go with the code generation is that such generated pure functions can be compiled, which in this case can be checked by
executing e.g.
cf =
Compile[{{fst, _Integer, 1}, {sec, _Integer, 1}},
sf[fst, sec],
CompilationOptions -> {"InlineExternalDefinitions" -> True}
]
To some extent, this works around SortBy
not being compilable, since Sort
with this comparison function can be compiled and effectively would work as SortBy
.
Implementing orderingBy
With the above construct, we can now implement our own version of orderingBy
, as follows:
ClearAll[orderingBy];
orderingBy[lst_, funs_List] :=
Ordering[lst, All, generateSortingFunction[funs]];
Now, for your example, we have:
orderingBy[data,{#[[1]]&,#[[2]]&}]
(* {6,1,2,3,4,5} *)
Using SortBy
The second option is to use SortBy
itself, but on a more complex list where positions will be added to elements. I will use here the code from this answer, which allows to elegantly compose functions passed to SortBy
. I will reproduce this here for completeness:
ClearAll[sortFun];
sortFun /: SortBy[expr_, sortFun[funs_List, partFun_]] :=
SortBy[expr, Map[Composition[#, partFun] &, funs]];
with this, the implementation ot orderingBy
function is straight-forward: "dress" original list with element positions, reorder it by SortBy
, and extract positions from reordered list:
ClearAll[orderingByAlt]
orderingByAlt[lst_, funs_List] :=
SortBy[
Transpose[{#, Range[Length[#]]}] &@lst,
sortFun[funs, First]
][[All, 2]]
which of course gives the same result:
orderingByAlt[data, {#[[1]] &, #[[2]] &}]
(* {6, 1, 2, 3, 4, 5} *)
Remarks
Which of the two approaches to pick is largely a matter of taste. The second one seems more economical, since we reused higher-level abstraction (SortBy
). However, if for example one would want to compile the code, then the first approach seems preferable, since it generates compilable code.
Transpose[{lst,Range[Length[lst]]}]
,wherelst
is the original list, and useSortBy
on this one, changing all your sorting functions asf
->First@f[#]&
. Then, after a list is sorted, extract positions of elements of the original list in a sorted list assorted[[All,2]]
. $\endgroup$