clojure—将转换应用于数据映射的正确方法?
我试图想出一个好方法,将特定的转换函数应用于数据映射 以地图为例:clojure—将转换应用于数据映射的正确方法?,clojure,transformation,Clojure,Transformation,我试图想出一个好方法,将特定的转换函数应用于数据映射 以地图为例: {:wrapper {:firstName "foo" :lastName "bar" :addressLine1 "line1" :addressLine2 "line2" :birthDate {:iso "1930-03-12"}}} 并将其转换为: {:name "foo bar" :address "line1 /n line
{:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}}
并将其转换为:
{:name "foo bar"
:address "line1 /n line2"
:age 86}
我还希望转换以另一种方式工作,尽管我不介意编写一个单独的转换
到目前为止,我已经尝试编写了一系列转换函数:pseudo
(-> start-map
transform-name
transform-address
transform-age)
每个变换采用[起始映射{累加器映射}]。我还尝试编写一个映射,其中包含转换映射的键,以及作为其值的转换函数和参数。我觉得我错过了一个窍门。你的基本想法是对的。我会这样做:
(ns tst.clj.core
(:use clj.core
clojure.test))
(def data
{:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}
)
(def target
{:name "foo bar"
:address "line1\nline2"
; :age 86 ; left as an excercise to the reader :)
})
(defn transform-name [m]
{:name (str (:firstName m) " "
(:lastName m))})
(defn transform-addr [m]
{:address (str (:addressLine1 m) \newline
(:addressLine2 m))})
(defn transform-person-simple [m]
(merge (transform-name m)
(transform-addr m)))
; You could also use the obscure function `juxt`, although this is
; more likely to confuse people.
; See http://clojuredocs.org/clojure.core/juxt
(defn transform-person-juxt [m]
(let [tx-juxt (juxt transform-name transform-addr)
juxt-answers (tx-juxt m)
result (into {} juxt-answers) ]
result ))
(deftest t-tx
(is (= target (transform-person-simple data)))
(is (= target (transform-person-juxt data)))
)
结果如下:
> lein test
(:repositories detected in user-level profiles! [:user]
See https://github.com/technomancy/leiningen/wiki/Repeatability)
lein test tst.clj.core
Ran 1 tests containing 2 assertions.
0 failures, 0 errors.
您还可以使用提供的键和转换函数将映射定义为数据结构。例如
(def a->b
'[[:name (->fullname [:wrapper :firstName] [:wrapper :lastName])]
[:address [:wrapper :addressLine1]] ;; left as an exercise for the reader :)
[:age (->age [:wrapper :birthDate :iso])]])
在哪里
然后使用映射规则和源映射实现一个函数进行转换:
(transform a->b {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930/03/12"}}})
=>
{:name "foo bar", :address "line1", :age 86}
快速实现可以如下所示:
(defn get-val [src s]
(if-let [v (or (get src s)
(get-in src s))]
v
(let [[f & ss] s
mf (resolve f)]
(apply mf (map (partial get-val src) ss)))))
(defn transform [m src]
(reduce (fn [ans [t s]]
(let [af (if (coll? t) assoc-in assoc)]
(af ans t (get-val src s))))
(empty src)
m))
user> (def data {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}})
#'user/data
user> (def data-2
(let [tr (partial transform data)]
(-> {}
(tr [[:wrapper :firstName] [:wrapper :lastName]]
(fn [f l] {[:name] (str f \space l)}))
(tr [[:wrapper :addressLine1] [:wrapper :addressLine2]]
(fn [a1 a2] {[:address] (str a1 \newline a2)}))
(tr [[:wrapper :birthDate :iso]]
(fn [d] {[:age] (reverse d)})))))
#'user/data-2
;;{:name "foo bar",
;; :address "line1\nline2",
;; :age (\2 \1 \- \3 \0 \- \0 \3 \9 \1)}
转换:
(require '[clj-time.core :as t])
(require '[clj-time.format :as f])
(def data {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}})
(def transformed
{:name (str (get-in data [:wrapper :firstName])
" "
(get-in data [:wrapper :lastName]))
:address (str (get-in data [:wrapper :addressLine1])
"\n"
(get-in data [:wrapper :addressLine2]))
:age (t/in-years (t/interval (f/parse
(get-in data [:wrapper :birthDate :iso] data))
(t/now)))})
逆变换。请注意,日期失去了精度
(require '[clojure.string :as str])
(def untransformed
(let [[firstname lastname] (str/split
(:name transformed)
#" ")
[addressline1 addressline2] (str/split
(:address transformed)
#"\n")]
{:wrapper
{:firstName firstname
:lastName lastname
:addressLine1 addressline1
:addressLine2 addressline2
:birthDate
{:iso (f/unparse
(f/formatters :date)
(t/minus (t/now)
(t/years (:age transformed))))}}}))
为了使其通用,我将使用转换函数从源对象选择路径,将所选值处理为目标到值中的路径映射:
(defn transform [source target paths transformation]
(reduce (partial apply assoc-in)
target
(apply transformation
(map #(get-in source %) paths))))
然后你可以这样使用它:
(defn get-val [src s]
(if-let [v (or (get src s)
(get-in src s))]
v
(let [[f & ss] s
mf (resolve f)]
(apply mf (map (partial get-val src) ss)))))
(defn transform [m src]
(reduce (fn [ans [t s]]
(let [af (if (coll? t) assoc-in assoc)]
(af ans t (get-val src s))))
(empty src)
m))
user> (def data {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}})
#'user/data
user> (def data-2
(let [tr (partial transform data)]
(-> {}
(tr [[:wrapper :firstName] [:wrapper :lastName]]
(fn [f l] {[:name] (str f \space l)}))
(tr [[:wrapper :addressLine1] [:wrapper :addressLine2]]
(fn [a1 a2] {[:address] (str a1 \newline a2)}))
(tr [[:wrapper :birthDate :iso]]
(fn [d] {[:age] (reverse d)})))))
#'user/data-2
;;{:name "foo bar",
;; :address "line1\nline2",
;; :age (\2 \1 \- \3 \0 \- \0 \3 \9 \1)}
反之亦然:
user> (let [tr (partial transform data-2)]
(-> {}
(tr [[:name]]
(fn [n]
(let [[n1 n2] (clojure.string/split n #"\s")]
{[:wrapper :firstName] n1
[:wrapper :lastName] n2})))
(tr [[:address]]
(fn [a]
(let [[a1 a2] (clojure.string/split a #"\n")]
{[:wrapper :addressLine1] a1
[:wrapper :addressLine2] a2})))
(tr [[:age]]
(fn [a] {[:wrapper :birthDate :iso]
(apply str (reverse a))}))))
;;{:wrapper {:firstName "foo",
;; :lastName "bar",
;; :addressLine1 "line1",
;; :addressLine2 "line2",
;; :birthDate {:iso "1930-03-12"}}}
zipmap、juxt和destructuring在地图转换中非常方便
(defn unwrap [{person :wrapper}]
(let [date-format (java.text.SimpleDateFormat. "yyyy-MM-dd")
name-fn #(str (:firstName %) " " (:lastName %))
address-fn #(str (:addressLine1 %) \newline (:addressLine1 %))
age-fn #(- (.getYear (java.util.Date.))
(.getYear (.parse date-format (get-in % [:birthDate :iso]))))]
(zipmap [:name :address :age]
((juxt name-fn address-fn age-fn) person))))
有了它,您可以很容易地做到这一点,如下所示:
(defn get-val [src s]
(if-let [v (or (get src s)
(get-in src s))]
v
(let [[f & ss] s
mf (resolve f)]
(apply mf (map (partial get-val src) ss)))))
(defn transform [m src]
(reduce (fn [ans [t s]]
(let [af (if (coll? t) assoc-in assoc)]
(af ans t (get-val src s))))
(empty src)
m))
user> (def data {:wrapper {:firstName "foo"
:lastName "bar"
:addressLine1 "line1"
:addressLine2 "line2"
:birthDate {:iso "1930-03-12"}}})
#'user/data
user> (def data-2
(let [tr (partial transform data)]
(-> {}
(tr [[:wrapper :firstName] [:wrapper :lastName]]
(fn [f l] {[:name] (str f \space l)}))
(tr [[:wrapper :addressLine1] [:wrapper :addressLine2]]
(fn [a1 a2] {[:address] (str a1 \newline a2)}))
(tr [[:wrapper :birthDate :iso]]
(fn [d] {[:age] (reverse d)})))))
#'user/data-2
;;{:name "foo bar",
;; :address "line1\nline2",
;; :age (\2 \1 \- \3 \0 \- \0 \3 \9 \1)}
%/%{:name str%:firstName%:lastName
:address str%:addressLine1\n%:addressLine2
:年龄->%:出生日期:iso->age}
:包装器原始地图
其中->年龄来自rmcv的示例
很简单,对吧?而且我们甚至不必为东西起一堆新的本地名字 也许这会有用:谢谢Bojan-为什么在该链接中选择的答案会比Alan在下面建议的更好?好吧,至少没有必要要求额外的LIB,就像Alan的答案一样,需要tupelo和medley LIB。而且,我认为更通用的方法是将转换本身定义为键和键转换函数的映射,…Checkout:一个通过嵌套Clojure数据结构抽象导航概念的库。它有一个强大的转换功能。非常感谢艾伦-这看起来很有趣。如果人们能解释为什么他们投了反对票,那就太好了。如果让函数返回[kv],你能使用into{}然后映射到函数上吗?我修改了答案,让你如何使用模糊函数juxt从函数向量和单个参数创建答案向量。我还将tupelo.core/glue转换为{}[…],以显示合并映射的另一种方法。