Posmax: как argmax, но дает положение (позиции) элемента x, для которого f [x] является максимальным

Mathematica имеет встроенную функцию ArgMax для функций над бесконечными доменами, основанную на стандартное математическое определение.

Аналог для конечных доменов является удобной функцией полезности. Учитывая функцию и список (назовите это доменом функции), верните элемент списка, который максимизирует функцию. Здесь приведен пример конечного argmax в действии: Канонизировать имена команд НФЛ

И здесь моя реализация (наряду с argmin для хорошей меры):

(* argmax[f, domain] returns the element of domain for which f of 
   that element is maximal -- breaks ties in favor of first occurrence. *)
SetAttributes[{argmax, argmin}, HoldFirst];
argmax[f_, dom_List] := Fold[If[f[#1]>=f[#2], #1, #2]&, First[dom], Rest[dom]]
argmin[f_, dom_List] := argmax[-f[#]&, dom]

Во-первых, это самый эффективный способ реализации argmax? Что делать, если вам нужен список всех максимальных элементов, а не только первый?

Во-вторых, как насчет связанной функции posmax, что вместо возврата максимального элемента (ов) возвращается позиция максимальных элементов?

Ответ 1

@dreeves, вы правы в том, что Ordering является ключом к самой быстрой реализации ArgMax над конечным доменом:

ArgMax[f_, dom_List] := dom[[Ordering[f /@ dom, -1]]]

Часть проблемы с вашей исходной реализацией с использованием Fold заключается в том, что вы в конечном итоге оцениваете f вдвое больше, чем необходимо, что неэффективно, особенно при медленном вычислении f. Здесь мы оцениваем только f один раз для каждого члена домена. Когда в домене много дублированных элементов, мы можем дополнительно оптимизировать memoizing значения f:

ArgMax[f_, dom_List] :=
  Module[{g},
    g[e___] := g[e] = f[e]; (* memoize *)
    dom[[Ordering[g /@ dom, -1]]]
  ]

Это было примерно на 30% быстрее в некоторых базовых тестах для списка из 100 000 случайных чисел от 0 до 100.

Для функции posmax этот несколько не элегантный подход - это самая быстрая вещь, которую я могу придумать:

PosMax[f_, dom_List] :=
  Module[{y = f/@dom},
    [email protected][y, Max[y]]
  ]

Конечно, мы можем снова применить memoization:

PosMax[f_, dom_List] := 
  Module[{g, y},
    g[e___] := g[e] = f[e];
    y = g /@ dom;
    [email protected][y, Max[y]]
  ]

Чтобы получить все максимальные элементы, теперь вы можете просто реализовать ArgMax в терминах posmax:

ArgMax[f_, dom_List] := dom[[PosMax[f, dom]]]

Ответ 2

Для posmax вы можете сначала сопоставить функцию над списком, а затем просто запросить позицию максимального элемента (ов). То есть:

posmax[f_, dom_List] := posmax[f /@ dom]

где posmax[list] определяется полиморфно, чтобы просто вернуть положение максимального элемента (ов). Оказывается, есть встроенная функция Ordering, которая по существу делает это. Таким образом, мы можем определить однопользовательскую версию posmax следующим образом:

posmax[dom_List] := Ordering[dom, -1][[1]]

Я только что протестировал это по сравнению с версией на основе цикла и рекурсивной версией, а Ordering - во много раз быстрее. Рекурсивная версия довольно, поэтому я покажу ее здесь, но никогда не пытаюсь запускать ее на больших входах.

(* posmax0 is a helper function for posmax that returns a pair with the position 
   and value of the max element. n is an accumulator variable, in lisp-speak. *)
posmax0[{h_}, n_:0] := {n+1, h}
posmax0[{h_, t___}, n_:0] := With[{best = posmax0[{t}, n+1]},
  If[h >= best[[2]], {n+1, h}, best]]

posmax[dom_List] := [email protected][dom, 0]
posmax[f_, dom_List] := [email protected][f /@ dom, 0]
posmax[_, {}] := 0

Ничто из этого не касается вопроса о том, как найти все максимальные элементы (или их позиции). Это обычно не подходит для меня на практике, хотя я думаю, что было бы хорошо иметь.