我正在寻找有条件避免在初始化/定义时向地图添加元素的最佳方法。在这种情况下,如果键的值为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])
}])
)
如果get-in的结果为nil(数据中的性别字段不存在),我不想在地图上添加性别。有没有办法在我创建地图时这样做?在创建地图后,我可以删除所有值为nil的键,但在某些情况下,我希望某些键具有nil值,而其他键如果它们的值为nil则根本不在地图中。
答案 0 :(得分:19)
我会将merge
和when-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))
根据您需要执行此操作的频率,我建议围绕when-let编写一个简短的宏或函数,以使代码更简洁。
答案 1 :(得分: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
从输入映射中提取值的阶段,因为当作为映射查看时,记录似乎总是包含与其字段对应的键。 E.g。
(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}
答案 2 :(得分: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]))))
答案 3 :(得分: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) %)))))
答案 4 :(得分: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}
答案 5 :(得分: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}
根据需要修改:)
答案 6 :(得分:0)
(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])))