13

Suppose we have a list of values and a function f. I want to find which of the elements maximizes the return value of the function in Mathematica. Let call the function ListMaxArg. On the following example,

ListMaxArg[Total, {{1, 2, 3}, {10}}]

it should return {10}. Is there any such function in Mathematica's library? If not, what is the simplest way to write it? I can for sure write the function using a loop, but I am looking for something more functional style.

rm -rf
  • 88,781
  • 21
  • 293
  • 472
Helium
  • 4,059
  • 24
  • 41

2 Answers2

17

Update 2017

Mathematica 10 introduced MaximalBy, becoming the canonical method.

MaximalBy[{{1, 2, 3}, {10}, {6, 2}, {3, 7}}, Total]
{{10}, {3, 7}}

It is fast as shown in the updated timings below.


If I understand the question this should be fastest:

listMaxArg[f_, L_List] := L ~Extract~ Ordering[f /@ L, -1]

listMaxArg[Total, {{1, 2, 3}, {10}}]
{10}

You could also find the top n values by using -n as the second argument to Ordering.
This could be included in the function, e.g. listMaxArg[f_, L_List, n_Integer] := . . .


This method should be fast for finding multiple maximum values:

listMaxArg[f_, L_List] := L ~Extract~ Position[#, Max@#] &[f /@ L]

listMaxArg[Total, {{1, 2, 3}, {10}, {6, 2}, {3, 7}}]
{{10}, {3, 7}}

I argue the superiority of the Extract-Position method (from Arnoud Buzing) over Select on the basis of timings. I will use Tr in the place of Total as it is faster on Packed Arrays, and therefore better shows the overhead of each method.

listMaxArgRM[f_, list_] :=
  With[{max = f /@ list // Max}, Select[list, f@# == max &]]

listMaxArgMrW[f_, L_List] :=
  L ~Extract~ Position[#, Max@#] &[f /@ L]

SeedRandom[1]
list = RandomInteger[7, #] & /@ RandomInteger[{1, 5}, 1000000];

r1 = listMaxArgRM[Tr, list];  // RepeatedTiming
r2 = listMaxArgMrW[Tr, list]; // RepeatedTiming
r3 = MaximalBy[list, Tr];     // RepeatedTiming
r1 === r2 === r3
{0.77, Null}

{0.219, Null}

{0.191, Null}

True

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
15

While using Ordering as in Mr.Wizard's answer will most likely be the fastest non-compiled solution, it will not return all possible arguments that maximize the return value. Here's a simple way of writing a function that does this:

listMaxArg[f_, list_] := With[{max = f /@ list // Max}, Select[list, f@# == max &]]

Here's an example that compares the two solutions:

a = {{1, 2, 3}, {10}, {6, 4}};
listMaxArg[Total, a] 
(* Out[1]= {{10}, {6, 4}} *)

listMaxArgMrWiz[Total, a]
(*Out[2]= {{6, 4}} *)
rm -rf
  • 88,781
  • 21
  • 293
  • 472
  • +1. Note that Pick will be faster than Select here, about twice faster for really long lists. – Leonid Shifrin Feb 23 '12 at 11:43
  • @LeonidShifrin Thanks. I did remember that Pick should be faster than Select, and tried listMaxArgPick[f_, list_] := Pick[list, Unitize[##/Max[##] &@(f /@ list), 1], 1]. Unfortunately, this unpacks, which slows it down, so I put it aside to poke at later. Can you point me to what I'm doing incorrectly (or how that can be written better)? I generated a random list with l = Table[RandomInteger[5, RandomInteger[{1, 5}]], {100000}]; – rm -rf Feb 23 '12 at 12:48
  • 3
    Pick has an optional third parameter, which allows to specify which element you want to be picked. The code I have is thus: listMaxArgAlt[f_, lst_] := Pick[lst, #, Max[#]] &[f /@ lst], and it is about twice faster, since I don't have to play games with Unitize. – Leonid Shifrin Feb 23 '12 at 12:57
  • It should be noted that Pick unpacks in version 7. – Mr.Wizard Feb 23 '12 at 13:29
  • @Mr.Wizard Yes, Pick was not optimized for packed arrays for v7 and earlier. Andy mentions this in a comment under his answer – rm -rf Feb 23 '12 at 13:33
  • @LeonidShifrin I'm afraid it still unpacks for me and is in fact slower than using Unitize (tested in a fresh kernel). Please see this screenshot. Could it possibly have anything to do with it being a list of integers? Compare the first screenshot with this one (the only difference being RandomReal instead of RandomInteger). Here Pick with Unitize does not unpack and you see the speedup of 2x, whereas with simple Pick, it still unpacks. – rm -rf Feb 23 '12 at 13:55
  • Ok, here are the main points: 1. Your total list is not packed, since it contains sublists of different lengths. Sublists are packed however. It is unpacking those sublists which causes messages. 2. When you turn messages on, they are so many (one per each sublist), that they dominate the running time. My version was running 4 times slower with On["Packing"] than without - so we are in a quantum-mechanical situation here :). 3. Unpacking is needed in this approach, for Pick to test elements, and there is no direct way to disable it. But, the code below is a possible workaround... – Leonid Shifrin Feb 23 '12 at 14:13
  • This code does not unpack sublists: listMaxArgAltAlt[f_, lst_] := lst[[Pick[Range[Length[lst]], #, Max[#]] &[f /@ lst]]]. It is however only marginally faster than my previous version, because sublists are so small. So, my conclusions here are that in this particular situation, I would not care so much about unpacking. Also, turning on the messages may be not so innocent for the running time, and in particular this completely changed the relative speed of our solutions. – Leonid Shifrin Feb 23 '12 at 14:15
  • 1
    Interestingly, Select does not unpack, because for each element, Total applied to it, does not unpack. Still, it is slower than Pick here, because of the overhead of extra invocation of Function[f@#==max] (symbolic overhead). Pick has to unpack because it performs structural comparison (based on internal equivalent of SameQ actually, not Equal). This situation is similar to that in this question. This is an interesting and didactic example IMO, this present one. – Leonid Shifrin Feb 23 '12 at 14:30
  • @LeonidShifrin Very interesting. Thank you for the insight; these are very useful points and if you have the time, please write an answer (or an edit). If you don't have the time, I can incorporate them into the answer later in the day. One last (hopefully!) question though: Why does Pick with Unitize not unpack? – rm -rf Feb 23 '12 at 14:53
  • It does unpack all right:). In this sense, it is the same as my first version with Pick. As for you sugestion - I think it is more appropriate to place this information into your answer, since then it is more natural to compare with Select-based solution etc. I might have time to do this later, but if you could incorporate that earlier, even better. – Leonid Shifrin Feb 23 '12 at 15:16
  • Congrats with 4000 rep by the way. It is exactly 4000 at the time I am typing this. – Leonid Shifrin Feb 23 '12 at 15:32
  • @LeonidShifrin Thanks! Feels nice to join the 4k club :) – rm -rf Feb 24 '12 at 04:37