Can one implement argmin/argmax-style functions as a datascript aggregation? I first tried:
(d/q '{:find [(argmax ?salary)]
:with [?player]
:where [[?player :salary ?salary]]}
[["A" :salary 10]
["B" :salary 7]
["C" :salary 7]])
;;=> (["A"]). argmin would return (["B"] ["C"]).
but the :with value doesn't get passed to custom aggregates, so it's not actually possible to write it this way.I can't do (argmax ?player ?salary) either, since :with and :find clauses can't share vars (and I need the ?player in :with clause so I can do other aggregations in the same query. E.g., query might be "what is the average salary across all players and who has the largest?"
I guess the problem lies in the fact that custom aggregate functions can only take 1 variable, not tuples: > The one and only aggregated variable must be the last argument to the function. This was copied from Datomic, not sure why the limitation
I guess you can do this
(require '[datascript.core :as d])
(defn argmin [xs]
(let [min (reduce min (map second xs))]
(filter (fn [[player salary]] (= min salary)) xs)))
(defn argmax [xs]
(let [max (reduce max (map second xs))]
(filter (fn [[player salary]] (= max salary)) xs)))
(d/q '[:find (user/argmin ?tuple) (user/argmax ?tuple)
:where [?player :salary ?salary]
[(vector ?player ?salary) ?tuple]]
[["A" :salary 10]
["B" :salary 7]
["C" :salary 7]])
Thanks! Constructing the tuple manually makes sense. I expanded on the idea and show an example with additional grouping:
(defn arg-f
[f]
(fn [tuples]
(let [v (reduce f (map last tuples))]
(keep (fn [tuple]
(when (= v (last tuple))
(vec (butlast tuple))))
tuples))))
(def argmin (arg-f min))
(def argmax (arg-f max))
(d/q '{:find [?even (user/argmin ?tuple) (user/argmax ?tuple)]
:where [[?player :salary ?salary]
[(even? ?salary) ?even]
[(vector ?player ?salary) ?tuple]]}
[["A" :salary 10]
["B" :salary 7]
["C" :salary 5]
["D" :salary 12]
["E" :salary 12]])
;; => ([true (["A"]) (["D"] ["E"])]
[false (["C"]) (["B"] )])