# What does IndexBy do and how exactly does it work?

I just saw the function IndexBy in the autocomplete suggestions and I was curious to know exactly how it works. Unfortunately, there's no trace of it in the documentation. It returns Association objects, so it must be new (or recently refurbished) in version 10.0.2

What does this function do are what syntax does it require? Does it have further perks that are not immediately obvious?

Edit: As mentioned in the comments, as an undocumented function this was liable to be removed. And, indeed, it has been removed from version 10.1.0. The good thing is, if anyone needs a function like this, there's now kguler and Mr. Wizard's implementations to choose from.

• if it is not documented, then it is not meant to be used at user level. It might be a place holder for future version. Feb 20, 2015 at 12:40
• Related answer Feb 20, 2015 at 13:16
• @Nasser If it is indeed not meant to be used at user level, I'm curious about why it shows up in the autocomplete suggestions. Feb 20, 2015 at 15:35
• @episanty, I think all system symbols will show up in the autocomplete suggestions, though many are undocumented. For a complete list of such symbols: Select[Names["*"],MatchQ[ToExpression[#<>"::usage"],_MessageName]&] (it takes a minute to run). Feb 20, 2015 at 22:01
• Note that IndexBy will be removed in a future version of Mathematica. It was something that was considered for 10.0.0 but didn't make the cut. Feb 26, 2015 at 17:37

?? **IndexBy*


Following the usual spelunking steps

ClearAttributes[IndexBy, {Protected, ReadProtected}]
?? IndexBy


reveals the code that defines IndexBy. Simplifying (and ignoring argument type-checks) it is something like:

indexBy[f_][expr_]:=Association[(f[#]->#)&/@If[AssociationQ[expr],Values[expr],expr]]

indexBy[foo][Range[5]]
(* <|foo[1]->1,foo[2]->2,foo[3]->3,foo[4]->4,foo[5]->5|> *)

indexBy[foo][<|a -> x, b -> y, c -> z|>]
(* <|foo[x]->x,foo[y]->y,foo[z]->z|> *)


Observing the code reported in kguler's answer I note that it could be written more efficiently, if such an operation is desired. Specifically (f[#]->#)& cannot be compiled because the result is a Rule. It would be better to map f directly to the values, then use AssociationThread to construct the association.

## Proposal

indexBy[f_][expr_] := expr ~indexBy~ f

indexBy[asc_Association, f_] := Values[asc] ~indexBy~ f

p : indexBy[expr_, f_] /;
! AtomQ[expr] || Message[indexBy::normal, 1, HoldForm @ p] :=

p : indexBy[_, _, __] /;
Message[indexBy::argt, indexBy, Length @ Unevaluated @ p, 1, 2] := Null


## Timings

expr = Range[1*^6];
f = #^2 &;

IndexBy[expr, f] // AbsoluteTiming // First
indexBy[expr, f] // AbsoluteTiming // First

1.353077

0.404023

expr = AssociationThread @@ RandomReal[9, {2, 1*^6}];
f = Round;

IndexBy[expr, f] // AbsoluteTiming // First
indexBy[expr, f] // AbsoluteTiming // First

1.122064

0.372021

• I just ran into this and had a question. On 10.1, the AtomQ version is placed above indexBy[expr_, f_] in the DownValues. So, that index[5, f] returns AssociationThread[5, 5]. This can be fixed by using indexBy[expr : Except[_?AtomQ, _], f_], but I'm not sure if that's the right thing to do. Any thoughts. Apr 20, 2015 at 19:45
• @rcollyer I read your message before and thought "okay, when I get v10.1 I'll fix this" but now that I am running 10.1 I realize this has nothing to do with version and everything to do with a poorly conceived definition. Feel free to be more blunt about my stupid mistakes next time! May 12, 2015 at 6:01
• I didn't realize it was your mistake. I was just playing with it, broke it, and tried to fix it. Next time, do better. :D May 12, 2015 at 13:48
• @rcollyer By the way what do you think of the ||` form in the answer now? It is my attempt at using Simon's flow control from (7820). Do you make use of such things yourself now? (You expressed interest below that post.) May 12, 2015 at 15:07