2012-01-24 56 views
20

我正在尋找有條件地避免在初始化/定義時向地圖添加元素的最佳方法。在這種情況下,如果密鑰的值爲零,我想避免向地圖添加元素。在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]) 
    }]) 
) 

如果插入的結果爲零(數據中的性別字段不存在),我不想在地圖上添加性別。創建地圖時有沒有辦法做到這一點?我可以在創建地圖後刪除所有值爲零的鍵,但在某些情況下,我希望某些鍵具有零值,而其他鍵在地圖中根本不在地圖中,如果它們具有零值。

+0

撇開:「gender」!=「sex」 – tom

回答

15

我會使用的mergewhen-let組合這些可選參數。

其核心思想是合併在一個單一的元素映射或零爲每個可選參數。在nil中合併將不會執行任何操作,因此您不會在地圖中看到零。

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

取決於你需要如何頻繁地做到這一點,我會建議寫周圍的時候,讓短宏或功能,使代碼更簡潔。

+1

謝謝,這很好 - 但爲什麼多餘的'(讓[res ... res]'包裝? –

+0

@Alex我儘量保持代碼與他的相似。我假設他可能在作業後做了其他的結果,所以我把它留在那裏。你說得對,雖然沒有必要。 – deterb

+0

啊謝謝,有道理@deterb。我擔心我錯過了一些東西。 –

0

下面是一個嘗試:

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

測試:

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

您可能想用'apply concat'替換'flatten',否則更復雜的值會導致問題(嘗試在地圖中添加':foo [1 2]'第一個例子)。 (或者可能打開一個層次的parens並使其適用(comp hash-map concat)')。另外,我會添加一個測試用於'nil'值不*被排除的情況(儘管這確實有效)。 –

+0

完成。感謝您的評論! – MHOOO

1

構建地圖和dissoc荷蘭國際集團要強加條件的鑰匙在基於謂詞(在這裏 - nil?)可能是最簡單的方法(注意,這個函數只測試明確提到的參數;未提及的參數不會被刪除,不管它們的值是否滿足謂詞):

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

在REPL:

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

雖然一般來說,如果你希望有很多代表某些特定類型的真實世界實體映射的工作,你可能會想要去的記錄 - 和那麼你可以跳過所有的nil s在你從輸入地圖中提取數值的階段,因爲當被視爲地圖時,記錄總是包含與其字段對應的鍵。例如。

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

然後你就可以分解出的邏輯圖之間的 「模式轉換」:

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

現在你create-record功能成爲translate-mapmap->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]}))) 

如果您更喜歡使用常規地圖,您可以使用類似下面的內容代替等效輸出:

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

在REPL(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

您可以定義字段,哪些是可選的:

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

,然後寫一個函數來使用信息:

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

,它會像這個:

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

修改爲n eeded :)

1

你可以做類似

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