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

나는 동의한다. 나는 기능에 대해 몰랐지만 여기서 사용하는 것이 좋습니다.
Thomas

오, 당신은 보지 못했습니까? 당신은 치료를 받고 있습니다. 나는 기회가있을 때마다 그 기능에서 지옥을 남용한다. 매우 강력하고 유용합니다.
Runevault

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

이것이 클로저 1.3.0으로 만들었습니까?
AnnanFay

13
generic.function lib가 별도의 라이브러리로 옮겨졌습니다. org.clojure/algo.generic "0.1.0" 예제는 다음 과 같습니다. (use '[clojure.algo.generic.functor :only (fmap)]) (fmap inc {:a 1 :b 3 :c 5})
Travis Schneeberger

2
fmapClojureScript에서 어떻게 찾을 수 있습니까?
sebastibe

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
열쇠를 공급해야한다는 것은 짜증이 나지만 비용이 많이 들지는 않습니다. 그것은 내 원래 제안보다 훨씬 멋지게 보입니다.
Thomas

1
키와 val이 같은 순서로 해당 값을 반환한다고 보장합니까? 정렬 된 맵과 해시 맵 모두에 대해?
Rob Lachlan

4
Rob : 예, 키와 val은 모든 맵에 대해 동일한 순서를 사용합니다. 맵에서 사용되는 순서와 동일한 순서입니다. 해시, 정렬 및 배열 맵은 모두 변경할 수 없으므로 그 동안 순서가 변경 될 가능성은 없습니다.
Chouser 2018

2
그것은 사실 인 것 같지만 어디에나 문서화되어 있습니까? 적어도 키와 val에 대한 docstring은 이것을 언급하지 않습니다. 그것이 작동 할 것이라고 약속하는 공식 문서를 지적 할 수 있다면 이것을 사용하는 것이 더 편안 할 것입니다.
Jouni K. Seppänen

1
@Jason 그래, 그들은 1.6 버전의 문서에 이것을 추가했다고 생각한다. dev.clojure.org/jira/browse/CLJ-1302
Jouni K. Seppänen

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 에 키와 값 KV를 다음 해시 맵 매핑 반환 케이(FV) .
Siddhartha Reddy

6

map-map, map-map-keysmap-map-values

Clojure에는 기존 기능이 없다는 것을 알고 있지만 map-map-values자유롭게 복사 할 수있는 기능을 구현했습니다 . 이 두 밀접하게 관련 기능을 함께 제공 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"}

그리고 다른 두 가지 기능은 다음과 같습니다.

(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-mapmap-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) )

Parellel 버전 – 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
프리즘 맵 -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은 각 키와 f'ed 값에서 약간 2 개의 목록을 만듭니다. Mapcat은 맵의 키 시퀀스에서이 기능을 실행하고 전체 작업을 하나의 큰 목록으로 연결합니다. "apply hash-map"은 해당 시퀀스에서 새 맵을 만듭니다. (% m)는 조금 이상하게 보일 수 있습니다. 연관된 값을 찾기 위해 키를 맵에 적용하는 관용적 Clojure입니다.

가장 권장되는 독서 자료 : Clojure 치트 시트 .


나는 당신이 당신의 예에서 한 것처럼 물마루 시퀀스를 생각했습니다. 나는 또한 당신의 이름이 내 자신보다 훨씬 더 많은 것을 좋아합니다 :)
Thomas

Clojure에서 키워드는 전달 된 순서대로 자신을 찾는 함수입니다. 그것이 (: keyword a-map)이 작동하는 이유입니다. 그러나 키가 키워드가 아닌 경우 키를지도에서 찾는 기능으로 키를 사용하면 작동하지 않습니다. 따라서 위의 (% m)을 (m %)로 변경하면 키가 무엇이든 작동합니다.
Siddhartha Reddy

죄송합니다! 팁 주셔서 감사합니다, Siddhartha!
Carl Smotricz

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 하나는 당신이 기록 기능 (예를 들어 소형 메모리 표현을) 원하는 경우, 보유 할 수 있습니다.


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.