2012-09-30 5 views
5

Data una sequenza di elementi, desidero trovare gli elementi più frequenti, in ordine decrescente di frequenza. Così, per esempio vorrei questo test unità di passare:Idiomatic Clojure per trovare gli oggetti più frequenti in un seq

(fact "can find 2 most common items in a sequence" 
     (most-frequent-n 2 ["a" "bb" "a" "x" "bb" "ccc" "dddd" "dddd" "bb" "dddd" "bb"]) 
     => 
     '("bb" "dddd")) 

Sono abbastanza nuovo per Clojure e ancora cercando di arrivare alla presa con la libreria standard. Ecco quello che mi si avvicinò con:

(defn- sort-by-val [s]  (sort-by val s)) 
(defn- first-elements [pairs] (map #(get % 0) pairs)) 

(defn most-frequent-n [n items] 
    "return the most common n items, e.g. 
    (most-frequent-n 2 [:a :b :a :d :x :b :c :d :d :b :d :b]) => 
     => (:d :b)" 
    (take n (-> 
      items    ; [:a :b :a :d :x :b :c :d :d :b :d :b] 
      frequencies   ; {:a 2, :b 4, :d 4, :x 1, :c 1} 
      seq     ; ([:a 2] [:b 4] [:d 4] [:x 1] [:c 1]) 
      sort-by-val   ; ([:x 1] [:c 1] [:a 2] [:b 4] [:d 4]) 
      reverse    ; ([:d 4] [:b 4] [:a 2] [:c 1] [:x 1]) 
      first-elements))) ; (:d :b :a :c :x) 

Tuttavia questa sembra una catena complessa di funzioni di eseguire un'operazione abbastanza comune. C'è un modo più elegante o più idiomatico (o più efficiente) per farlo?

risposta

8

Come hai scoperto, in genere si utilizza una combinazione di ordinamento e frequenze per ottenere un elenco ordinato in base alla frequenza.

(sort-by val (frequencies ["a" "bb" "a" "x" "bb" "ccc" "dddd" "dddd" "bb" "dddd" "bb"])) 
=> (["x" 1] ["ccc" 1] ["a" 2] ["dddd" 3] ["bb" 4]) 

Poi si può manipolare questo abbastanza facilmente per ottenere le più alte voci più bassi/frequenza. Forse qualcosa di simile:

(defn most-frequent-n [n items] 
    (->> items 
    frequencies 
    (sort-by val) 
    reverse 
    (take n) 
    (map first))) 

che è ancora abbastanza simile alla soluzione (a parte che non è necessario le funzioni di supporto con l'utilizzo intelligente del ->> macro).

Quindi, nel complesso, penso che la vostra soluzione sia piuttosto buona. Non preoccuparti della catena di funzioni: è in realtà una soluzione molto breve per quello che è logicamente un concetto piuttosto complicato. Prova a scrivere la stessa cosa in C#/Java e vedrai cosa intendo ...

+1

Grazie Mikera, la tua soluzione è un bel miglioramento. (1) Vedo come utilizzare correttamente i macro delle frecce per evitare la necessità di funzioni di supporto. (2) 'sort-by' può lavorare direttamente sul risultato di' frequenze 'senza richiedere prima un 'seq'. (3) Esiste una funzione 'first' nella libreria standard, quindi non ho bisogno di crearne una mia. (4) Fare il 'take' prima della' map' è probabilmente più efficiente. –

+5

'(reverse (sort-by f coll))' è terribilmente costoso senza una vera ragione - preferisco invece '(sort-by (comp -f) coll)'. Inoltre, sarei coerente se usi 'first' e' second' o 'key' e' val', poiché sono equivalenti per le voci della mappa. – amalloy

Problemi correlati