Fork me on GitHub
#datascript
<
2022-03-18
>
Kevin Lynagh17:03:39

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.

Kevin Lynagh17:03:58

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?"

Niki19:03:38

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

Niki19:03:29

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]])

Kevin Lynagh21:03:54

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"]      )])