如何解决使用clojure,是否有更好的方法从序列中删除项目,即地图中的值?
有一个包含序列的地图。序列包含项目。 我想从包含它的任何序列中删除给定的项目。
我找到的解决方案做了它应该做的,但我想知道是否有更好的 或更优雅的方式来实现相同的目标。
我目前的解决方案:
(defn remove-item-from-map-value [my-map item]
(apply merge (for [[k v] my-map] {k (remove #(= item %) v)})))
测试描述了预期的行为:
(require '[clojure.test :as t])
(def my-map {:keyOne ["itemOne"]
:keyTwo ["itemTwo" "itemThree"]
:keyThree ["itemFour" "itemFive" "itemSix"]})
(defn remove-item-from-map-value [my-map item]
(apply merge (for [[k v] my-map] {k (remove #(= item %) v)})))
(t/is (= (remove-item-from-map-value my-map "unkNown-item") my-map))
(t/is (= (remove-item-from-map-value my-map "itemFive") {:keyOne ["itemOne"]
:keyTwo ["itemTwo" "itemThree"]
:keyThree ["itemFour" "itemSix"]}))
(t/is (= (remove-item-from-map-value my-map "itemThree") {:keyOne ["itemOne"]
:keyTwo ["itemTwo"]
:keyThree ["itemFour" "itemFive" "itemSix"]}))
(t/is (= (remove-item-from-map-value my-map "itemOne") {:keyOne []
:keyTwo ["itemTwo" "itemThree"]
:keyThree ["itemFour" "itemFive" "itemSix"]}))
我对 clojure 还很陌生,对不同的解决方案很感兴趣。 所以欢迎提出任何意见。
解决方法
我会选择这样的:
user> (defn remove-item [my-map item]
(into {}
(map (fn [[k v]] [k (remove #{item} v)]))
my-map))
#'user/remove-item
user> (remove-item my-map "itemFour")
;;=> {:keyOne ("itemOne"),;; :keyTwo ("itemTwo" "itemThree"),;; :keyThree ("itemFive" "itemSix")}
您还可以编写一个方便的函数 map-val
对地图值执行映射:
(defn map-val [f data]
(reduce-kv
(fn [acc k v] (assoc acc k (f v)))
{} data))
或者像这样:
(defn map-val [f data]
(reduce #(update % %2 f) data (keys data)))
user> (map-val inc {:a 1 :b 2})
;;=> {:a 2,:b 3}
(defn remove-item [my-map item]
(map-val (partial remove #{item}) my-map))
user> (remove-item my-map "itemFour")
;;=> {:keyOne ("itemOne"),;; :keyThree ("itemFive" "itemSix")}
,
我投入了 specter 版本很好。它将向量保留在地图内 而且它真的很紧凑。
(setval [MAP-VALS ALL #{"itemFive"}] NONE my-map)
示例
user=> (use 'com.rpl.specter)
nil
user=> (def my-map {:keyOne ["itemOne"]
#_=> :keyTwo ["itemTwo" "itemThree"]
#_=> :keyThree ["itemFour" "itemFive" "itemSix"]})
#_=>
#'user/my-map
user=> (setval [MAP-VALS ALL #{"itemFive"}] NONE my-map)
{:keyOne ["itemOne"],:keyThree ["itemFour" "itemSix"],:keyTwo ["itemTwo" "itemThree"]}
user=> (setval [MAP-VALS ALL #{"unknown"}] NONE my-map)
{:keyOne ["itemOne"],:keyThree ["itemFour" "itemFive" "itemSix"],:keyTwo ["itemTwo" "itemThree"]}
,
我认为您的解决方案基本没问题,但我会尽量避免使用 apply merge
部分,因为您可以使用 into
从序列轻松重新创建地图。此外,您还可以使用 map
而不是 for
,我认为在这种情况下它更惯用一点,因为您不使用 for
的任何列表理解功能。
(defn remove-item-from-map-value [m item]
(->> m
(map (fn [[k vs]]
{k (remove #(= item %) vs)}))
(into {})))
,
另一个很像@leetwinski 的解决方案:
(defn remove-item [m i]
(zipmap (keys m)
(map (fn [v] (remove #(= % i) v))
(vals m))))
,
这是一个以优雅的方式做到这一点的单线。我在这个场景中使用的完美函数是 clojure.walk/prewalk
。这个 fn 的作用是遍历你传递给它的表单的所有子表单,并用提供的 fn 转换它们:
(defn remove-item-from-map-value [data item]
(clojure.walk/prewalk #(if (map-entry? %) [(first %) (remove #{item} (second %))] %) data))
remove-item-from-map-value
fn 将检查当前表单是否是地图条目,如果是,它将从其值中删除指定的键(地图条目的第二个元素,它是一个包含分别是键和值)。
这种方法的最佳之处在于它是完全可扩展的:您可以决定为不同类型的表单做不同的事情,您还可以处理嵌套表单等。
我花了一些时间来掌握这个 fn,但一旦我掌握了它,我发现它非常有用!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。