2012-01-24 7 views
20

sto cercando il modo migliore per evitare condizionale aggiunta di un elemento di una mappa quando si è in fase di inizializzazione/definito. In questo caso, voglio evitare di aggiungere un elemento a una mappa se il valore della chiave è nullo.Inizializzazione elementi di una mappa in modo condizionale in 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]) 
    }]) 
) 

Non voglio aggiungere genere alla mappa se i risultati del get-in sono pari a zero (il campo del sesso nei dati non esiste). C'è un modo per farlo quando creo la mappa? Potrei rimuovere tutte le chiavi il cui valore è nullo dopo aver creato la mappa ma, in alcuni casi, voglio che alcune chiavi abbiano valori nulli e altre che non siano affatto nella mappa se avessero valori nulli.

+0

a parte: "gender"! = "Sex" – tom

risposta

15

vorrei usare una combinazione di merge e when-let per questi parametri opzionali.

Il nucleo idea è di unire sia in una cartina elemento singolo o nullo per ciascuno dei parametri facoltativi. La fusione in nil non farà nulla, quindi non vedrai il nulla nella mappa.

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

A seconda di come spesso si deve fare questo, consiglio a scrivere una breve macro o una funzione in tutto il quando-lasciare per mantenere il codice più conciso.

+1

Grazie, è grandioso, ma perché il superfluo '(let [res ... res)' wrapper? –

+0

@Alex Stavo mantenendo il codice il più simile possibile al suo. Suppongo che potrebbe aver fatto qualcos'altro con il risultato dopo l'assegnazione, quindi l'ho lasciato lì. Hai ragione nel senso che non è necessario però. – deterb

+0

Ah grazie, ha senso @deterb. Ero preoccupato che mi mancasse qualcosa. –

0

Qui è un tentativo:

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

prova:

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

Si potrebbe voler sostituire 'flatten' con' apply concat', altrimenti i valori più complessi causano problemi (prova ad aggiungere ': foo [1 2]' alla mappa nel primo esempio). (O forse scartare un livello di parentesi e fare che 'si applicano (comp hash-map concat)'.) Inoltre, mi piacerebbe aggiungere un test per il caso in cui un valore 'quelle negative * non * è da escludere (anche se questo funziona). –

+0

Fatto. Grazie per il commento! – MHOOO

1

Costruire una mappa e dissoc ing le chiavi che si vuole imporre condizioni in base in base a un predicato (qui - nil?) potrebbe essere l'approccio più semplice (NB questa funzione verifica solo le chiavi esplicitamente menzionate come argomenti, quelle non menzionate non vengono mai rimosse, se i valori ad esse associati soddisfano o meno il predicato):

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

Anche se in generale, se si prevede di lavorare con un sacco di mappe che rappresentano entità del mondo reale di un certo tipo particolare, si potrebbe desiderare di andare con i record - e quindi puoi semplicemente saltare tutti gli nil s nella fase in cui estrai i valori dalla tua mappa di input, perché i record, se visualizzati come mappe, sembrano sempre includere le chiavi corrispondenti ai loro campi. Per esempio.

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

Poi si può scomporre la logica per "conversioni schema" tra mappe:

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

Ora la funzione create-record diventa una composizione di translate-map e 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]}))) 

Se preferisci lavorare con le mappe normali, potresti invece usare qualcosa del tipo seguente per un output 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]}))) 

Al REPL (versione record in 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

È possibile definire i campi e quali sono facoltativi:

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

e poi scrivere una funzione per utilizzare queste informazioni:

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

e funzionerà come this:

; 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} 

Modifica come n eeded :)

1

Si potrebbe fare qualcosa di simile

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