Clojure:ハッシュマップのエントリのサブセットに関数を適用するにはどうすればよいですか?
質問
私はClojureではなく、これを行う方法を見つけようとはしていません。
ハッシュマップ内のキーのサブセットに対して関数を要素に適用する新しいハッシュマップを作成したい。これを行う最良の方法は何ですか?
(let
[my-map {:hello "World" :try "This" :foo "bar"}]
(println (doToMap my-map [:hello :foo] (fn [k] (.toUpperCase k)))
これにより、次のようなマップが作成されます
{:hello "WORLD" :try "This" :foo "BAR"}
解決
(defn do-to-map [amap keyseq f] (reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq))
内訳:
裏返しに見ると役立ちます。 Clojureでは、ハッシュマップは関数のように機能します。引数としてキーを持つ関数のようにそれらを呼び出すと、そのキーに関連付けられた値が返されます。したがって、単一のキーが与えられた場合、そのキーの現在の値は次の方法で取得できます。
(some-map some-key)
古い値を取得し、それらに対していくつかの関数 f
を呼び出して新しい値に変更します。そのため、単一のキーを指定すると、新しい値は次のようになります。
(f (some-map some-key))
この新しい値をハッシュマップのこのキーに関連付け、「置換」します。古い値。これが assoc
の機能です:
(assoc some-map some-key (f (some-map some-key)))
(" Replace"は、単一のハッシュマップオブジェクトを変更していないため、怖い引用符で囲まれています。 assoc
。Clojureでは、ハッシュマップは永続的であり、 assoc
で構造を共有するため、これは依然として高速で効率的です。)
新しい値を一度に1つのキーずつマップに繰り返し assoc
する必要があります。したがって、何らかのループ構造が必要です。私たちが望むのは、元のハッシュマップと単一のキーから始めて、「更新」することです。そのキーの値。次に、その新しいハッシュマップと次のキーを取得し、「更新」します。その次のキーの値。そして、これをキーごとに1つずつ繰り返し、最後に「蓄積」したハッシュマップを返します。これが reduce
の機能です。
-
reduce
の最初の引数は、2つの引数を取る関数です:" accumulator"値。これは、「更新」中に保持する値です。何度も;そして、いくつかの累積を行うために1回の反復で使用される単一の引数。 -
reduce
の2番目の引数は、このfn
の1番目の引数として渡される初期値です。 -
reduce
の3番目の引数は、このfn
の2番目の引数として1つずつ渡される引数のコレクションです。
だから:
(reduce fn-to-update-values-in-our-map
initial-value-of-our-map
collection-of-keys)
fn-to-update-values-in-our-map
は、上記の assoc
ステートメントであり、匿名関数でラップされています:
(fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))
それで reduce
にプラグインする:
(reduce (fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))
amap
keyseq)
Clojureには、匿名関数を記述するための短縮形があります。#(...)
は、単一のフォームで構成される匿名の fn
です。 1 は匿名関数の最初の引数にバインドされ、%2
は2番目の引数にバインドされます。したがって、上記の fn
は次のように書くことができます。
#(assoc %1 %2 (f (%1 %2)))
これにより、次のことがわかります。
(reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq)
他のヒント
(defn doto-map [m ks f & args]
(reduce #(apply update-in %1 [%2] f args) m ks))
呼び出しの例
user=> (doto-map {:a 1 :b 2 :c 3} [:a :c] + 2)
{:a 3, :b 2, :c 5}
これが役立つと思います。
次のように動作するようです:
(defn doto-map [ks f amap]
(into amap
(map (fn [[k v]] [k (f v)])
(filter (fn [[k v]] (ks k)) amap))))
user=> (doto-map #{:hello :foo} (fn [k] (.toUpperCase k)) {:hello "World" :try "This" :foo "bar"})
{:hello "WORLD", :try "This", :foo "BAR"}
これを行うより良い方法があるかもしれません。誰かが素敵なワンライナーを思いつくかもしれません:)