web-dev-qa-db-fra.com

Clojure: Convertir des chaînes de clé de hachage en mots clés

Je tire des données de Redis en utilisant Aleph:

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

Le problème est que ces données sont renvoyées avec des chaînes pour les clés, par exemple:

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

Quand j'en ai besoin pour être:

({: name "Tylenol",: comment "instructions})

J'étais en train de créer une nouvelle carte via:

{: nom (m "nom"),: comment (m "comment")}

Mais ceci est inefficace pour une grande quantité de clés.

S'il y a une fonction qui fait ça? Ou dois-je les parcourir en boucle?

33
dMix

Il existe une fonction pratique appelée mot clé qui convertit les chaînes en mots clés appropriés:

(keyword "foo")
=> :foo

Il ne s'agit donc que de transformer toutes les clés de votre carte à l'aide de cette fonction.

Pour ce faire, j'utiliserais probablement une compréhension de liste avec déstructuration, par exemple:

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

Vous pouvez également utiliser la bibliothèque clojure.walk pour obtenir le résultat souhaité avec la fonction keywordize-keys.

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

Cela permettra également de parcourir la carte de manière récursive, de sorte que les clés de la carte imbriquée seront également "mot-clé".

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

76
djhworld

Je suis d'accord avec djhworld, clojure.walk/keywordize-keys est ce que vous voulez.

Cela vaut la peine de jeter un coup d'œil au code source de 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)))

La transformation inverse est parfois utile pour Java interop:

(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)))
5
Matt W-D

Vous pouvez y parvenir très élégamment en utilisant 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 permet de créer une carte en spécifiant les clés et les valeurs séparément.

3
viebel

Il convient peut-être de noter que, si les données entrantes sont json et que vous utilisez clojure.data.json, vous pouvez spécifier un key-fn et un value-fn pour manipuler les résultats lors de l'analyse syntaxique de la chaîne ( 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}
0
nrako

Je partage la réponse into de @ mikera. Alternativement, pas la plus concise, mais une autre option utilisant assoc + dissoc/reduction serait:

(reduce #(dissoc (assoc %1 (keyword %2) (get %1 %2)) %2) my-map (keys may-map))
0
Yonathan W'Gebriel