2012-01-24 17 views
20

Estoy buscando la mejor manera de evitar condicionalmente agregar un elemento a un mapa cuando se inicializa/define. En este caso, quiero evitar agregar un elemento a un mapa si el valor de la clave es nulo.Inicializando elementos de un mapa de forma condicional en Clojure

(defn create-record [data] 
    (let [res { 
    :username (data :username) 
    :first-name (get-in data [:user-info :name :first]) 
    :last-name (get-in data [:user-info :name :last]) 
    :gender (get-in data [:user-info :sex]) 
    }]) 
) 

no quiero añadir el género al mapa si los resultados del encuentro en son nulas (el campo del sexo en los datos no existe). ¿Hay alguna manera de hacer esto cuando creo el mapa? Podría eliminar todas las claves cuyo valor sea nulo después de crear el mapa, pero, en algunos casos, deseo que algunas claves tengan valores nulos y otras que no estén en el mapa si tuvieran valores nulos.

+0

aside: "gender"! = "Sex" – tom

Respuesta

15

me gustaría utilizar una combinación de merge y when-let para estos parámetros opcionales.

La idea principal es combinar en un mapa de elemento único o cero para cada uno de los parámetros opcionales. Fusionarse en nil no hará nada, por lo que no verá el nil en el mapa.

(defn create-record [data] 
    (let [res (merge {:username (data :username) 
        :first-name (get-in data [:user-info :name :first]) 
        :last-name (get-in data [:user-info :name :last])} 
        (when-let [gender (get-in data [:user-info :sex])] 
        {:gender gender}))] 
    res)) 

Dependiendo de la frecuencia con que tiene que hacer esto, yo recomendaría no escribe una breve macro o función cuando todo el let-mantener el código más conciso.

+1

Gracias, esto es genial, pero ¿por qué el envoltorio '(let [res ... res)' superfluo? –

+0

@Alex Estaba manteniendo el código lo más parecido posible al suyo. Supongo que puede haber estado haciendo otra cosa con el resultado después de la tarea, así que lo dejé allí. Tienes razón en que no es necesario sin embargo. – deterb

+0

Ah, gracias, tiene sentido @deterb. Me preocupaba que me estaba perdiendo algo. –

0

Aquí es un intento:

(defn exclude-nils-for [m kw-set] 
    (apply hash-map (apply concat (remove (fn [[k v]] (and (kw-set k) (nil? v))) m)))) 

prueba:

user> (exclude-nils-for {:gender "m" :name "Thomas" :age "24"} #{}) 
{:age "21", :gender "m", :name "Thomas"} 
user> (exclude-nils-for {:gender "m" :name "Thomas" :age "24"} #{:name}) 
{:age "21", :gender "m", :name "Thomas"} 
user> (exclude-nils-for {:gender "m" :name nil :age "24"} #{:name}) 
{:age "21", :gender "m"} 
user> (exclude-nils-for {:gender "m" :name nil :age nil} #{:age}) 
{:gender "m", :name nil} 
+1

Es posible que desee reemplazar 'flatten' por' apply concat', de lo contrario, valores más complejos causan problemas (intente agregar ': foo [1 2]' al mapa en el primer ejemplo). (O quizás desenvuelva un nivel de parens y haga que 'apply (comp hash-map concat)'.) Además, agregaría una prueba para el caso donde un valor 'nil' es * no * para ser excluido (aunque esto funciona). –

+0

Hecho. ¡Gracias por el comentario! – MHOOO

1

La construcción de un mapa y dissoc ing las teclas que desea imponer condiciones sobre la base de un predicado (aquí - nil?) podría ser el enfoque más simple (Nota: esta función solo prueba las claves explícitamente mencionadas como argumentos, las que no se mencionan nunca se eliminan, si los valores adjuntos satisfacen el predicado o no):

(defn dissoc-when 
    "Dissoc those keys from m which are mentioned among ks and whose 
    values in m satisfy pred." 
    [pred m & ks] 
    (apply dissoc m (filter #(pred (m %)) ks))) 

Al REPL:

user> (dissoc-when nil? {:foo nil :bar true :quux nil} :foo :bar) 
{:quux nil, :bar true} 

Aunque en términos generales, si va a trabajar con una gran cantidad de mapas que representan entidades del mundo real de algún tipo particular, es posible que quiera ir con registros - y a continuación, puede omitir todos los nil s en la etapa donde extrae los valores de su mapa de entrada, porque los registros, cuando se ven como mapas, siempre parecen incluir las claves correspondientes a sus campos. P.ej.

(defrecord Person [username first-name last-name]) 

A continuación, puede factorizar la lógica de "conversiones de esquema" entre mapas:

(defn translate-map 
    "Transforms the data map in accordance with the spec in table. 
    Skips nil-valued entries." 
    [data table] 
    (->> table 
     (keep (fn [[to from]] 
       (when-let [from-val (get-in data from)] 
       [to from-val]))) 
     (into {}))) 

Ahora su función create-record se convierte en una composición de translate-map y map->Person:

(defn create-person [data] 
    (map->Person 
    (translate-map data {:username [:username] 
         :first-name [:user-info :name :first] 
         :last-name [:user-info :name :last] 
         :gender [:user-info :sex]}))) 

Si Si prefiere trabajar con mapas regulares, puede usar algo como lo siguiente en lugar de una salida equivalente:

(defn create-person [data] 
    (merge (zipmap [:username :first-name :last-name] (repeat nil)) 
     (translate-map data {:username [:username] 
           :first-name [:user-info :name :first] 
           :last-name [:user-info :name :last] 
           :gender [:user-info :sex]}))) 

En el REPL (versión de registro en Clojure 1.3):

user> (create-person {:username "jsmith" 
         :user-info {:name {:first "John" :last "Smith"}}}) 
#user.Person{:username "jsmith", :first-name "John", :last-name "Smith"} 
user> (create-person {:username "jsmith" 
         :user-info {:name {:first "John" :last "Smith"} 
            :sex :male}}) 
#user.Person{:username "jsmith", :first-name "John", :last-name "Smith", :gender :male} 
0

puede definir sus campos y cuáles son opcionales:

(def fields 
[[:username [:username]] 
[:first-name [:user-info :name :first]] 
[:sex  [:user-info :sex]   true]]) 

y luego escribir una función para utilizar esa información:

(defn create-record [data keys] 
    (->> 
    (for [[n k ignore-nil?] keys 
      :let [v (get-in data k)] 
      :when (or (not ignore-nil?) v)] 
     [n v]) 
    (into {}))) 

y funcionará como esto:

; If :sex is missing don't create a field 
user=> (create-record {:username "dr" :user-info { :name {:first "Dave"} }} fields) 
{:username "dr", :first-name "Dave"} 

user=> (create-record {:username "dr" :user-info { :name {:first "Dave"} :sex :m }} fields) 
{:username "dr", :first-name "Dave", :sex :m} 

; If :first is missing, create a nil field 
user=> (create-record {:username "dr" :user-info { :name {} :sex :m }} fields) 
{:username "dr", :first-name nil, :sex :m} 

Modificar como n ecesarios :)

1

Se podría hacer algo como

(let [not-nils #{:gender}] 
    (defn create-record [data] 
    (into {} (for [[k v] {:username (data :username) 
          :first-name (get-in data [:user-info :name :first]) 
          :last-name (get-in data [:user-info :name :last]) 
          :gender (get-in data [:user-info :sex])} 
        :when (not (and (nil? v) (not-nils k)))] 
       [k v])))) 
Cuestiones relacionadas