11

I need a function to find max element of a list according to some custom ordering function, assuming that the function implements a total order (elements being compared might not even be numbers). Unlike Sort, the predefined Max function does not accept a custom ordering function. A naïve solution would be to use Sort[list, p]〚1〛, but this would unnecessarily sort the whole list, while I only need to find the first element.

I ended up defining

max[list_, p_] := list[[Ordering[list, 1, p][[1]] ]];

and it works fine from performance point of view. But I wonder if there is a more simple or natural way to do this task. Is there a predefined function solving this problem that I missed?

Dr. belisarius
  • 115,881
  • 13
  • 203
  • 453
Vladimir Reshetnikov
  • 7,213
  • 27
  • 75

1 Answers1

8

If your operation can be converted to a canonical ranking rather than a pairwise comparison then you can use MaximalBy introduced in version 10.

If not a good approach to a single pass through a list is Fold. Here is a function using that:

foldMax[list_, p_] := Fold[If[p[##], ##] &, list]

This proves to be faster in some cases than using Ordering (in your function):

x = RandomReal[9, 5000000];

max[x, Less]     // AbsoluteTiming
foldMax[x, Less] // AbsoluteTiming
{1.209069, 4.32482*10^-6}

{0.430025, 4.32482*10^-6}

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • 2
    Search for the max element requires 1 pass thru the list, that is $O(n)$, while sorting is $O(n\ln n)$. – Vladimir Reshetnikov Jan 02 '15 at 01:30
  • @Vladimir Okay, I think I understand what you are saying. – Mr.Wizard Jan 02 '15 at 01:49
  • 2
    I think the first sentence also isn't quite right: just because you have a pairwise ordering function doesn't mean that there is a significant $O(n^2)$ or $O(n\log n)$ performance penalty, as that would only be the case when you want to construct the entire total ordering of the list. In the case of a max element search, constructing the entire total ordering is not necessary, and instead the max search is $O(n)$, so there is no performance penalty. Still, I don't know of a built-in way to find the max... maybe you could make your own custom $Max function, if performance really was crucial? – DumpsterDoofus Jan 02 '15 at 01:55
  • @Dumpster Thanks. I'll abolish that nonsense entirely now. – Mr.Wizard Jan 02 '15 at 01:58
  • 3
    That's a cool use of Fold!(+1) – DumpsterDoofus Jan 02 '15 at 02:01
  • I like the ## as the second argument of If. It took a few seconds before it dawned on me that this usage made them actually the second and third arguments of If. – Sjoerd C. de Vries Jan 02 '15 at 14:42
  • Ordering with second argument 1 proves to be O(n) and I'm finding is consistently faster with user constructed pairwise functions. ( i.e. the best answer is the one in your comment.. ). – george2079 Jan 02 '15 at 20:38
  • @george2079 I haven't tested this but I imagine that if Fold is to have a chance of being faster the p function must be compilable, and input list must be packed or packable. I would have stated this in the question except that I'd like to actually attempt to confirm this first. – Mr.Wizard Jan 03 '15 at 02:14
  • @Sjoerd Sorry if you found ## confusing; I use it so much it feels quite natural now. By the way I would not write p[##] ~If~ ## & because that goes against my (visual) interpretation of infix as a binary function. – Mr.Wizard Jan 03 '15 at 02:17