web-dev-qa-db-ja.com

Clojureで永続マップをフィルタリングする方法は?

フィルタリングしたい永続的なマップがあります。このようなもの:

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

上記は([:a 1] [:b 1])(マップエントリの怠惰なシーケンス)として出てきます。しかし、私は{:a 1 :b 1}を取得したいと思っています。

一連のマップエントリからマップを再構築せずにマップを維持するようにマップをフィルタリングするにはどうすればよいですか?

36
Alex B

そしてもう1つ:

(let [m {:a 1 :b 2 :c 1}]
  (select-keys m (for [[k v] m :when (= v 1)] k)))
49
kotarak
(into {} (filter #(-> % val (= 1)) {:a 1 :b 1 :c 2}))

もちろん、これ一連のマップエントリからマップを再構築しますが、それを回避する方法はありません。エントリを値でフィルタリングする場合は、エントリを1つずつ調べて、どの値が述語に一致し、どの値が一致しないかを確認する必要があります。

更新(以下のコメントを参照):

新しく導入されたkeep関数を使用すると、そのソースを確認できます ここ (バックポートしたい場合は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}
20
Michał Marczyk

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łのバージョンで多くの利益が得られるとは思いません。

3
Brian Carper

すべてのエントリをトラバースする必要がありますが、Clojures永続マップを活用できます。

(apply dissoc my-map (for [[k v] my-map :when (not= v 1)] k))
3
Jürgen Hötzel

コタラックのバージョンを元に、マクロで試してみました。それは私の最初のマクロが何か役に立つことをしているので、私に耐えてください、そしてコメントを歓迎します。

(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}}
1
Benjamin Peter