Clojure:如何将哈希映射中的字符串键转换为关键字?

41

我正在使用Aleph从Redis中获取数据:

(apply hash-map @(@r [:hgetall (key-medication id)]))

问题在于该数据的键以字符串形式返回,例如:

({"name" "Tylenol", "how" "instructions"})

当我需要它时:

({:name "泰诺", :how "使用说明"})

以前我是通过以下方式创建一个新的map:

{ :name (m "name"), :how (m "how")}

但是对于大量的键来说这是低效的。

是否有一个函数可以做到这一点?还是我必须循环遍历每个键?

7个回答

94

您还可以使用 clojure.walk 库以使用函数 keywordize-keys 实现所需的结果。

(use 'clojure.walk)
(keywordize-keys {"name" "Tylenol", "how" "instructions"})
;=> {:name "Tylenol", :how "instructions"}

这将递归地遍历映射,因此它也会将嵌套映射中的键“关键字化”。

http://clojuredocs.org/clojure_core/clojure.walk/keywordize-keys


最重要的是,这适用于嵌套地图! - kopos

47

有一个方便的函数叫做关键字(keyword),可以将字符串转换为相应的关键字:

(keyword "foo")
=> :foo

所以这只是将您的映射中的所有键使用此函数进行转换的情况。

我可能会使用解构和列表推导式来完成这个任务,例如:

(into {} 
  (for [[k v] my-map] 
    [(keyword k) v]))

是的,看起来基本的迭代是最简单的解决方案,我正在检查是否有地图的标准函数。但抽象化并不难。谢谢。 - kush
3
有一个标准函数(在核心库中)可以完成这个任务,详见我下面的回答。 - djhworld

9

我同意djhworld的观点,clojure.walk/keywordize-keys是你想要的。

值得一看的是clojure.walk/keywordize-keys的源代码:

(defn keywordize-keys
  "Recursively transforms all map keys from strings to keywords."
  [m]
  (let [f (fn [[k v]] (if (string? k) [(keyword k) v] [k v]))]
    (clojure.walk/postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))

逆变换有时对于Java交互很有用:

(defn stringify-keys
  "Recursively transforms all map keys from keywords to strings."
  [m]
  (let [f (fn [[k v]] (if (keyword? k) [(name k) v] [k v]))]
    (clojure.walk/postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))

7

也许值得注意的是,如果传入的数据是 json 并且您正在使用 clojure.data.json,您可以为解析字符串时的结果指定 key-fnvalue-fn 以进行操作 (docs) -

;; Examples from the docs

(ns example
  (:require [clojure.data.json :as json]))


(json/read-str "{\"a\":1,\"b\":2}" :key-fn keyword) 
;;=> {:a 1, :b 2}

(json/read-str "{\"a\":1,\"b\":2}" :key-fn #(keyword "com.example" %))
;;=> {:com.example/a 1, :com.example/b 2}

6
您可以使用zipmap非常优雅地实现这一点:
(defn modify-keys [f m] (zipmap (map f (keys m)) (vals m)))
(modify-keys keyword {"name" "Tylenol", "how" "instructions"})
; {:how "instructions", :name "Tylenol"}

基本上,zipmap 允许通过分别指定键和值来创建一个映射表。

4
键和值是否保证按照相同的顺序? - gtrak
不确定。我不知道如何检查。 - viebel
https://dev59.com/5Ggv5IYBdhLWcg3wNeDS - Inshallah
1
Clojure文档指出,(keys map)和(vals map)函数的结果与(seq map)函数具有相同的顺序。因此,通过关联性,它们的顺序都是相同的。 :) @gtrak - Yonathan W'Gebriel
这是最优雅的解决方案。 - danfromisrael

1
使用keyword函数和reduce-kv
如果我有一个地图,例如。
 (def test-map {"key1" "val1" "key2" "val2"}

我能做到

(reduce-kv 
     (fn [m k v] 
        (assoc m (keyword k) v)) 
      {} test-map)

 => {:key1 "val1" :key2 "val2"}

0

我支持 @mikera 的 基于的答案。或者,虽然不是最简洁的方法,但另一种使用 assoc+dissoc/reduce 的选项是:

(reduce #(dissoc (assoc %1 (keyword %2) (get %1 %2)) %2) my-map (keys may-map))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接