2012-09-30 13 views
5

Dada una secuencia de elementos, quiero encontrar los n artículos más frecuentes, en orden descendente de frecuencia. Así, por ejemplo, me gustaría que esta prueba de la unidad pase a:forma idiomática de Clojure para encontrar los artículos más frecuentes en una secuencia

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

Soy bastante nuevo en Clojure y todavía tratando de llegar a la adherencia con la biblioteca estándar. Esto es lo que se me ocurrió:

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

Sin embargo, esto parece una complicada cadena de funciones para hacer una operación bastante común. ¿Hay una manera más elegante o más idiomática (o más eficiente) de hacer esto?

Respuesta

8

Como habrás descubierto, normalmente utilizarías una combinación de ordenar por y frecuencias para obtener una lista ordenada por frecuencia.

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

Luego puede manipular esto con bastante facilidad para obtener los elementos con la frecuencia más baja/más alta. Tal vez algo como:

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

que de nuevo es bastante similar a la solución (aparte de que no necesita las funciones de ayuda con el uso inteligente de la macro ->>).

Así que, en general, creo que su solución es bastante buena. No se preocupe por la cadena de funciones: en realidad es una solución muy corta para lo que lógicamente es un concepto bastante complicado. Intente codificar lo mismo en C#/Java y verá lo que quiero decir ......

+1

Gracias Mikera, su solución es una buena mejora. (1) Veo cómo usar las macros de flecha correctamente para evitar la necesidad de funciones auxiliares. (2) 'sort-by' puede trabajar directamente en el resultado de' frequencies' sin requerir hacer 'seq' primero. (3) Hay una función 'first' en la biblioteca estándar, así que no necesito crear la mía. (4) Hacer el 'take' antes del' map' es probablemente más eficiente. –

+5

'(reverse (sort-by f coll))' es tremendamente costoso sin una razón real, prefiera en su lugar '(sort-by (comp - f) coll)'. Además, sería coherente con respecto a si usa 'first' y' second' o 'key' y' val', ya que son equivalentes para las entradas de mapas. – amalloy

Cuestiones relacionadas