在 Clojure 中有条件地初始化映射元素

问题描述 投票:0回答:9

我正在寻找最好的方法来有条件地避免在初始化/定义时向地图添加元素。在这种情况下,如果键的值为 nil,我想避免将元素添加到映射中。

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

如果获取结果为零(数据中的性别字段不存在),我不想将性别添加到地图中。当我创建地图时有办法做到这一点吗?我可以在创建映射后删除所有值为 nil 的键,但在某些情况下,我希望某些键具有 nil 值,而其他键则根本不在映射中(如果它们具有 nil 值)。

clojure
9个回答
28
投票

对于这些可选参数,我将使用

merge
when-let
的组合。

核心思想是合并单个元素映射或每个可选参数为零。合并到 nil 不会执行任何操作,因此您不会在地图中看到 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))

根据您需要执行此操作的频率,我建议在 when-let 周围编写一个简短的宏或函数,以使代码更加简洁。


3
投票
(cond-> {:username   (data :username)
         :first-name (get-in data [:user-info :name :first])
         :last-name  (get-in data [:user-info :name :last])}
    (get-in data [:user-info :sex]) (assoc :gender (get-in data [:user-info :sex])))

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

只需为每个要检查的元素添加

when
,如果它不是
nil
,然后它将返回要合并的映射或nil,这不会影响返回映射值。

您可以将多个地图合并在一起,这样合并的地图就没有限制,并且您可以为每个不是

nil
的元素返回一个单独的地图。


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

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
输入映射中的值,因为当将记录视为映射时,记录似乎总是包含与其字段相对应的键。例如

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

如果您确实更喜欢使用常规地图,则可以使用类似以下内容来代替等效输出:

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

1
投票
(defn create-record [data]
  (let [gender (get-in data [:user-info :sex])]
    (->> {:username (data :username)
          :first-name (get-in data [:user-info :name :first])
          :last-name (get-in data [:user-info :name :last])}
         (#(if gender (assoc % :gender gender) %)))))

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}

根据需要修改:)


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}

0
投票

如果您不喜欢创建新名称(用于when-let)或重复获取并一次性完成,这里还有另一种方法:

(letfn [(cond-assoc [m k v] (if (nil? v) (assoc m k v) m))]
  (-> {}
      (cond-assoc :a 1)
      (cond-assoc :b nil)))
=> {:a 1}
© www.soinside.com 2019 - 2024. All rights reserved.