Clojure highest 3 values of a map

Viewed 166

Hi l'm trying to do a function that returns the 3 most commom strings

  (take 3 (sort-by val > (frequencies s))))

(freq ["hi" "hi" "hi" "ola" "hello" "hello" "string" "str" "ola" "hello" "hello" "str"])

l've got this so far but a noticed that if there are more than 1 string with the same frenquency it won't return. Is there a way to filter the values of the frequencies funcition by their highest (eventually the top 3 highest)?

Thanks in advance.

2 Answers

I would propose slightly different solution which involves inverting frequencies map with group-by value (which is the items' count):

(->> data
     frequencies
     (group-by val))

;;{3 [["hi" 3]],
;; 2 [["ola" 2] ["str" 2]],
;; 4 [["hello" 4]],
;; 1 [["string" 1]]}

so the only thing you need is to just sort and process it:

(->> data
     frequencies
     (group-by val)
     (sort-by key >)
     (take 3)
     (mapv (fn [[k vs]] {:count k :items (mapv first vs)})))

;;[{:count 4, :items ["hello"]}
;; {:count 3, :items ["hi"]}
;; {:count 2, :items ["ola" "str"]}]

frequencies gives you a map, where the keys are the original values to investigate and the values in that map are the frequency of those values. For your result you are interested for all original values, that have the most occurrences including those original values with the same occurrences.

One way would be to "invert" the frequencies result, to get a map from occurrences to all original values with that occurrence. Then you can get the highest N keys and from this map and select them (by using a "sorted map" for inverting the map, we get the sorting by keys without further steps).

(defn invert-map
  ([source]
   (invert-map source {}))
  ([source target]
   (reduce (fn [m [k v]]
             (update m v (fnil conj []) k))
           target
           source)))

(assert (=
         {1 ["do" "re"]}
         (invert-map {"do" 1 "re" 1})))

(defn freq
  [n s]
  (let [fs (invert-map (frequencies s) (sorted-map-by >))
        top-keys (take n (keys fs))]
    (select-keys fs top-keys)))

(assert (=
         {4 ["hello"], 3 ["hi"], 2 ["ola" "str"]}
         (freq 3 ["hi" "hi" "hi" "ola" "hello" "hello" "string" "str" "ola" "hello" "hello" "str"])))
Related