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

6

map-map, map-map-keys및map-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.