【问题标题】:How to filter a persistent map in Clojure?如何在 Clojure 中过滤持久映射?
【发布时间】:2010-05-02 15:08:51
【问题描述】:

我有一个要过滤的持久性地图。像这样的:

(filter #(-> % val (= 1)) {:a 1 :b 1 :c 2})

上面的结果是([:a 1] [:b 1])(映射条目的惰性序列)。但是我想得到{:a 1 :b 1}

如何过滤地图,使其保持地图不变,而无需从一系列地图条目中重建它?

【问题讨论】:

    标签: clojure


    【解决方案1】:

    还有一个:

    (let [m {:a 1 :b 2 :c 1}]
      (select-keys m (for [[k v] m :when (= v 1)] k)))
    

    【讨论】:

      【解决方案2】:
      (into {} (filter #(-> % val (= 1)) {:a 1 :b 1 :c 2}))
      

      当然,这确实从一系列地图条目重建地图,但没有办法绕过它。如果您要按值过滤条目,则必须逐个检查它们以查看哪些值与您的谓词匹配,哪些不匹配。

      已更新(参见下面的 cmets):

      使用新引入的keep 函数,您可以看到其源代码here(如果您想向后移植,在 Clojure 1.1 中应该可以正常工作),这似乎是一个不错的方法如果你不使用nil 作为键

      (let [m {:a 1 :b 1 :c 2}]
        (apply dissoc m (keep #(-> % val (= 1) (if nil (key %))) m)))
      ; => {:a 1, :b 1}
      

      此外,如果您确实看到与重建地图相关的减速,您可以在重建步骤中使用临时地图:

      (persistent! (loop [m (transient {})
                          to-go (seq [[:a 1] [:b 2]])]
                     (if to-go
                       (recur (apply assoc! m (first to-go))
                              (next to-go))
                       m)))
      ; => {:a 1, :b 2}
      

      【讨论】:

      • 嗯,理论上您可以通过返回一个包含与非匹配值对应的解散键的映射,使其按值过滤而无需重建。我希望有一种语言支持的方式来做到这一点。
      • 好的,我明白你的意思了。我将在一秒钟内添加两种方法,但请注意,你不太可能在性能部门看到很大的收益(除非你有一张非常大的地图,而且你只会分解一个小键数)。
      • 嗯,实际上与其说是“两种方法”,不如说是“一种方法,一种不用担心重建的方法”。并不是说你很可能需要担心。 :-)
      • into 使用瞬态 conj!,所以我不确定循环递归是否有意义
      【解决方案3】:

      根据您对 Michał Marczyk 的评论:

      (defn filter* [f map]
        (reduce (fn [m [k v :as x]]
                  (if-not (f x)
                    (dissoc m k)
                    m))
                map map))
      
      user> (filter* #(-> % val (= 1)) {:a 1 :b 1 :c 2})
      {:a 1, :b 1}
      

      我认为与 Michał 的版本相比,您不会获得太多收益。

      【讨论】:

        【解决方案4】:

        需要遍历所有条目,但可以利用 Clojures 持久映射:

        (apply dissoc my-map (for [[k v] my-map :when (not= v 1)] k))
        

        【讨论】:

          【解决方案5】:

          这是另一个使用reduce-kv

          (defn filter-kv [pred map]
            (reduce-kv (fn [accumulator key value]
                         (if (pred key value)
                           (assoc accumulator key value)
                           accumulator)) {} map))
          

          用法

          (filter-kv (fn [key _]
                       (not (= key "a"))) {"a" {:some "a"}
                                           "b" {:some "b"}
                                           "c" {:some "c"}})
          
          >> {"b" {:some "b"}
              "c" {:some "c"}}
          

          【讨论】:

          • 您可以在末尾更改{}(empty map) 以使其更通用并适用于任何支持kv-reduce 协议的集合。
          • 此外,当大多数键将被删除时,这种方法更有效,即在以空{} 开头的结果映射中会有一些assoc。如果期望只删除几个键,那么使用原始的map 开始accumulator 并将if 反转为dissoc 过滤掉的键会更有效。
          【解决方案6】:

          我根据 kotarak 的版本尝试了自己的宏。这是我的第一个宏做一些有用的事情,所以请多多包涵,欢迎 cmets。

          (defmacro filter-map [bindings pred m]
            `(select-keys ~m
              (for [~bindings ~m
                :when ~pred]
                ~(first bindings)
              )
            )
          )
          

          例子

          user=> (filter-map [key val] (even? (:attr val)) {:a {:attr 2} :b {:attr 3} :c {:attr 4}})
          {:c {:attr 4}, :a {:attr 2}}
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-11-04
            • 2012-03-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-09-25
            • 2012-09-07
            相关资源
            最近更新 更多