2011-09-02 17 views
8

Soy muy nuevo en Clojure y tengo un problema interesante para ustedes, los gurús de Clojure. Estoy trabajando en el libro "Programación de la inteligencia colectiva" e intentando codificar los ejemplos en Clojure (el libro los tiene todos en Python). En el primer capítulo tenemos una configuración de mapas hash de los críticos de cine y los rankings que han dado a diferentes películas. Se ve así:Convierte un mapa hash al revés en Clojure

{"Lisa Rose" {"Lady in the Water" 2.5, "Snakes on a Plane" 3.5 }, 
"Gene Seymour" {"Lady in the Water" 3.0, "Snakes on a Plane" 3.5}} 

El problema es esto. Cómo convertir esa adentro hacia afuera de modo que consiga un mapa hash que se parece a esto:

{"Lady in the Water" {"Lisa Rose" 2.5, "Gene Seymour" 3.0}, 
"Snakes on a Plane" {"Lisa Rose" 3.5, "Gene Seymour" 3.5}} 

¿cuál sería su función para lograr esto?

+0

Gracias a todos por las excelentes respuestas. Aprendí algo de todos ellos. –

Respuesta

17
(let [m {"Lisa Rose" {"Lady in the Water" 2.5, "Snakes on a Plane" 3.5 }, 
     "Gene Seymour" {"Lady in the Water" 3.0, "Snakes on a Plane" 3.5}}] 
    (apply merge-with merge 
     (for [[ok ov] m 
       [ik iv] ov] 
      {ik {ok iv}}))) 

{"Snakes on a Plane" {"Gene Seymour" 3.5, "Lisa Rose" 3.5}, 
"Lady in the Water" {"Gene Seymour" 3.0, "Lisa Rose" 2.5}} 
+0

Gracias por marcar mi [pregunta] (http://stackoverflow.com/q/23651045/813665) como duplicado. ¿Crees que es mejor usar 'conj' o' merge' para 'merge-with'? – viebel

+0

@viebel Prefiero 'fusionar'.No me gusta mucho confiar en el extraño hecho de que para los mapas, 'conj' es equivalente a 'into' - Prefiero fingir que 'conj' solo acepta entradas de mapas. – amalloy

3

Puedo proponer lo siguiente: Tenemos mapa - colección de entradas. Convertir cada entrada de adentro hacia afuera y ellos se unen ellas: inicial:

{:Lisa {:Lady 2.5, :Snakes 3.5}, 
:Gene {:Lady 3.0, :Snakes 3.5}} 

inverso cada entrada:

([:Lady {:Lisa 2.5}], [:Snakes {:Lisa 3.5}]) 
([:Lady {:Gene 3.0}], [:Snakes {:Gene 3.5}]) 

de concatenación ellos:

([:Lady {:Lisa 2.5}], [:Snakes {:Lisa 3.5}], [:Lady {:Gene 3.0}], [:Snakes {:Gene 3.5}]) 

Y ellos les unen a un mapa:

{:Lady {:Lisa 2.5, :Gene 3.0}, 
:Snakes {:Lisa 3.5, :Gene 3.5}} 

Código:

(defn inverse-map [m] 
     (let [inverse-entry (fn [[name movies]] 
           (map (fn [[movie rating]] 
             [movie {name rating}]) 
            movies))] 
      (->> (map inverse-entry m) 
       (reduce concat) 
       (reduce (fn [res [movie entry]] 
          (update-in res [movie] merge entry)) 
         {})))) 

lo tanto, obtener un mapa, (que es una colección de vectores [valor de clave]), cada entrada inversa (vector [valor de clave]). Ahora tenemos colección de colecciones de vectores, concaténalas a una colección. Y finalmente, usando reducir, agregamos todos los vectores al mapa.

Supongo que hay una solución más elegante, pero la mía también funciona.

6
(defn inverse-map [m] 
    (let [inner-keys (-> m first val keys) 
     outer-keys (keys m)] 
    (apply merge-with merge 
      (for [ik inner-keys 
       ok outer-keys] 
      {ik {ok (get-in input [ok ik])}})))) 

Esto supone que todas las claves de interés en los mapas interiores están presentes en el primer mapa interior. Si esto es incorrecto, (-> m first val keys) debería reemplazarse por algo que devuelva una colección de todas las claves de interés, p. (->> m (map values) (mapcat keys)).

La idea es crear un mapa del formulario {inner-key {outer-key the-value-at-inner-key-in-the-map-at-outer-key}}, luego combinarlos adecuadamente.

El valor de retorno en el mapa en el texto de la pregunta es el especificado.

Ahora, lo anterior crea una gran cantidad de estructuras intermedias, lo que puede ser un problema de rendimiento. Si la velocidad es de la esencia, se puede cambiar a loop y transitorios:

(defn transient-inverse-map [m] 
    (let [inner-keys (-> m first val keys) 
     outer-keys (keys m) 
     t (transient {})] 
    (loop [inner-keys inner-keys 
      t t] 
     (if (seq inner-keys) 
     (recur (next inner-keys) 
       (assoc! t ik 
         (let [ik (first inner-keys) 
          t (transient {})] 
         (loop [outer-keys outer-keys 
           t t] 
          (if (seq outer-keys) 
          (let [ok (first outer-keys)] 
           (recur (next outer-keys) 
             (assoc! t ok (get-in m [ok ik])))) 
          (persistent! t)))))) 
     (persistent! t))))) 

La idea es la misma, si es más difícil de discernir a partir del código.

Cuestiones relacionadas