Clojureでマップの値に関数をマッピングする


138

ある値のマップを、キーは同じであるが値に適用された別のマップに変換したいと思います。clojure apiでこれを行うための関数があったと思いますが、それを見つけることができませんでした。

これが私が探しているものの実装例です

(defn map-function-on-map-vals [m f]
  (reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) {} m))
(println (map-function-on-map-vals {:a "test" :b "testing"} #(.toUpperCase %)))
{:b TESTING, :a TEST}

map-function-on-map-valsすでに存在するかどうか誰かが知っていますか?私はそれをしたと思います(おそらくもっと良い名前でも)。

回答:


153

私はあなたのreduceバージョンで大丈夫です。慣用だと思います。とにかくリスト内包表記を使ったバージョンです。

(defn foo [m f]
  (into {} (for [[k v] m] [k (f v)])))

1
私がこのバージョンを気に入っているのは、使用するすべての機能などを理解すれば、非常に短くて明白であるためです。そして、あなたがそうしないなら、それらを学ぶ言い訳です!
Runevault、2009年

同意する。私はinto関数を知りませんでしたが、ここでそれを使用することは完全に理にかなっています。
トーマス

ああ、見たことのない人?あなたはごちそうにいます。私はその機能を地獄で悪用します。とても強力で便利です。
Runevault 2009年

3
私はそれが好きですが、(質問以外に)引数の順序に理由はありますか?mapなんらかの理由で[fm]を期待していた。
nha

2
{}ではなく(空のm)を使用することを強くお勧めします。したがって、特定のタイプのマップであり続けるでしょう。
nickik

96

あなたは使うことができますclojure.algo.generic.functor/fmap

user=> (use '[clojure.algo.generic.functor :only (fmap)])
nil
user=> (fmap inc {:a 1 :b 3 :c 5})
{:a 2, :b 4, :c 6}

私はこの答えが日付を見るのが少し遅かったことを知っています、しかし私はそれがその場であると思います。
edwardsmatt 2011

これはclojure 1.3.0になりましたか?
AnnanFay

13
generic.function 11bが別のライブラリに移動しました: org.clojure/algo.generic "0.1.0" 例では、今読んでください: (use '[clojure.algo.generic.functor :only (fmap)]) (fmap inc {:a 1 :b 3 :c 5})
トラヴィスシュネーベルガー

2
fmapClojureScriptで検索する方法はありますか?
sebastibe 2017年

37

これは、マップを変換するかなり一般的な方法です。 zipmap キーのリストと値のリストを取り、「正しいことを行う」新しいClojureマップを生成します。mapキーの周りにキーを置いて変更することもできます。

(zipmap (keys data) (map #(do-stuff %) (vals data)))

またはそれを関数でラップするには:

(defn map-function-on-map-vals [m f]
    (zipmap (keys m) (map f (vals m))))

1
鍵を渡さなければならないのはイライラしますが、高額ではありません。それは間違いなく私の最初の提案よりもずっとよく見えます。
トーマス

1
キーと値が対応する値を同じ順序で返すことが保証されていますか?ソートされたマップとハッシュマップの両方について?
Rob Lachlan、

4
ロブ:はい、キーと値はすべてのマップで同じ順序を使用します-マップ上のシーケンスが使用するのと同じ順序です。ハッシュ、ソート、および配列マップはすべて不変であるため、その間に順序が変わる可能性はありません。
Chouser 2009年

2
それは事実のように思われますが、どこかに文書化されていますか?少なくともキーと値のドキュメント文字列はこれについて言及していません。これが機能することを約束する公式のドキュメントを指すことができれば、これを使用するほうが快適です。
Jouni K.Seppänen11年

1
@ジェイソンうん、彼らはバージョン1.6のドキュメントにこれを追加したと思います。dev.clojure.org/jira/browse/CLJ-1302
Jouni

23

Clojureクックブックから抜粋した、reduce-kvがあります。

(defn map-kv [m f]
  (reduce-kv #(assoc %1 %2 (f %3)) {} m))

8

これを行うためのかなり慣用的な方法を次に示します。

(defn map-function-on-map-vals [m f]
        (apply merge
               (map (fn [[k v]] {k (f v)})
                    m)))

例:

user> (map-function-on-map-vals {1 1, 2 2, 3 3} inc))
{3 4, 2 3, 1 2}

2
それは明らかではない場合:匿名関数はdestructuresのキーと値K及びVを、その後、ハッシュマップマッピング戻りKをする(FV)
Siddhartha Reddy、

6

map-map、、map-map-keysおよびmap-map-values

Clojureには既存の関数がないことは知ってmap-map-valuesいますが、自由にコピーできる関数の実装を次に示します。これは、2つの密接に関連する機能が付属しています、map-mapそしてmap-map-keys、また、標準ライブラリから欠落しています:

(defn map-map
    "Returns a new map with each key-value pair in `m` transformed by `f`. `f` takes the arguments `[key value]` and should return a value castable to a map entry, such as `{transformed-key transformed-value}`."
    [f m]
    (into (empty m) (map #(apply f %) m)) )

(defn map-map-keys [f m]
    (map-map (fn [key value] {(f key) value}) m) )

(defn map-map-values [f m]
    (map-map (fn [key value] {key (f value)}) m) )

使用法

次のmap-map-valuesように呼び出すことができます:

(map-map-values str {:a 1 :b 2})
;;           => {:a "1", :b "2"}

そして、このような他の2つの関数:

(map-map-keys str {:a 1 :b 2})
;;         => {":a" 1, ":b" 2}
(map-map (fn [k v] {v k}) {:a 1 :b 2})
;;    => {1 :a, 2 :b}

代替実装

より一般的な関数なしで、map-map-keysまたはのみが必要な場合はmap-map-valuesmap-mapこれらに依存しない実装を使用できますmap-map

(defn map-map-keys [f m]
    (into (empty m)
        (for [[key value] m]
            {(f key) value} )))

(defn map-map-values [f m]
    (into (empty m)
        (for [[key value] m]
            {key (f value)} )))

また、この言い回しを好む場合は、の代わりにmap-mapを使用した代替の実装を以下に示します。clojure.walk/walkinto

(defn map-map [f m]
    (clojure.walk/walk #(apply f %) identity m) )

パラレルバージョン– pmap-map、など

必要に応じて、これらの関数の並列バージョンもあります。彼らは単にのpmap代わりに使用しますmap

(defn pmap-map [f m]
    (into (empty m) (pmap #(apply f %) m)) )
(defn pmap-map-keys [f m]
    (pmap-map (fn [key value] {(f key) value}) m) )
(defn pmap-map-values [f m]
    (pmap-map (fn [key value] {key (f value)}) m) )

1
また、prismatics map-vals関数も確認してください。トランジェントgithub.com/Prismatic/plumbing/blob/…
ClojureMostly

2

私はClojure n00bなので、もっとエレガントな解決策があるかもしれません。これが私のものです:

(def example {:a 1 :b 2 :c 3 :d 4})
(def func #(* % %))

(prn example)

(defn remap [m f]
  (apply hash-map (mapcat #(list % (f (% m))) (keys m))))

(prn (remap example func))

anon funcは、各キーとその値から小さな2つのリストを作成します。Mapcatは、マップのキーのシーケンスに対してこの関数を実行し、作業全体を1つの大きなリストに連結します。「apply hash-map」は、そのシーケンスから新しいマップを作成します。(%m)は少し奇妙に見えるかもしれませんが、マップにキーを適用して関連する値を検索するのは慣用的なClojureです。

最も強くお勧めの資料Clojureチートシート


例で行ったように、シーケンスをトラフに移動することを考えました。私もあなたの名前が私自身のものよりも機能しているのが好きです:)
トーマス

Clojureでは、キーワードは、渡された順序で自分自身を検索する関数です。(:keyword a-map)が機能するのはそのためです。ただし、キーがキーワードでない場合、キーを関数として使用してマップ内を検索することはできません。したがって、上記の(%m)を(m%)に変更すると、キーが何であっても機能します。
Siddhartha Reddy、

おっとっと!先端をありがとう、シッダールタ!
Carl Smotricz、2009年

2
(defn map-vals
  "Map f over every value of m.
   Returns a map with the same keys as m, where each of its values is now the result of applying f to them one by one.
   f is a function of one arg, which will be called which each value of m, and should return the new value.
   Faster then map-vals-transient on small maps (8 elements and under)"
  [f m]
  (reduce-kv (fn [m k v]
               (assoc m k (f v)))
             {} m))

(defn map-vals-transient
  "Map f over every value of m.
   Returns a map with the same keys as m, where each of its values is now the result of applying f to them one by one.
   f is a function of one arg, which will be called which each value of m, and should return the new value.
   Faster then map-vals on big maps (9 elements or more)"
  [f m]
  (persistent! (reduce-kv (fn [m k v]
                            (assoc! m k (f v)))
                          (transient {}) m)))

1

私はあなたのreduceバージョンが好きです。非常にわずかなバリエーションで、レコード構造のタイプも保持できます。

(defn map-function-on-map-vals [m f]
  (reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) m m))

{}に置き換えられましたm。その変更により、レコードはレコードのままになります。

(defrecord Person [firstname lastname])

(def p (map->Person {}))
(class p) '=> Person

(class (map-function-on-map-vals p
  (fn [v] (str v)))) '=> Person

で開始することにより{}、レコードはその失いrecordiness 1を使用すると、レコード機能(例えば、コンパクトなメモリ表現を)望むならば、保持したい場合があります。


0

なぜ誰もが妖怪ライブラリーについて言及していないのかと思います。この種類の変換を簡単にコーディングできるように(さらに重要なのは、作成したコードを理解しやすくするために)作成されている一方で、パフォーマンスは非常に優れています。

(require '[com.rpl.specter :as specter])

(defn map-vals [m f]
  (specter/transform
   [specter/ALL specter/LAST]
   f m))

(map-vals {:a "test" :b "testing"}
          #(.toUpperCase %))

純粋なClojureでこのような関数を書くのは簡単ですが、さまざまなデータ構造で構成された高度にネストされたコードに移行すると、コードはかなり複雑になります。そして、これが妖怪の登場です。

背後にある動機と妖怪の詳細を説明するClojure TVでこのエピソードを視聴することをお勧めします。


0

Clojure 1.7(2015年6月30日リリース)は、このためのエレガントなソリューションを提供しますupdate

(defn map-function-on-map-vals [m f]
    (map #(update % 1 f) m))

(map-function-on-map-vals {:a "test" :b "testing"} #(.toUpperCase %))
;; => ([:a "TEST"] [:b "TESTING"])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.