Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
clojure—将转换应用于数据映射的正确方法?_Clojure_Transformation - Fatal编程技术网

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转换为{}[…],以显示合并映射的另一种方法。