Last active
August 29, 2015 14:07
-
-
Save friemen/1aa93c14167fa1f0a61c to your computer and use it in GitHub Desktop.
Data transformation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns snippets.mapping | |
(:require [clojure.string :as s])) | |
(defn as-vector | |
"Returns a vector from x | |
42 -> [42] | |
'(1 2 3) -> [1 2 3] | |
[:foo :bar] -> [:foo :bar]" | |
[x] | |
(cond (vector? x) x | |
(coll? x) (vec x) | |
:else (vector x))) | |
(defn get-values | |
"Takes a seq of keys ks and a collection x and retrieves | |
values from x using get-in with each key of ks." | |
[ks x] | |
(map #(get-in x (as-vector %)) ks)) | |
(defn transform-map | |
"Takes a mapping description and a map m and returns a map | |
containing the results of applying all transformation rules." | |
[{:keys [init rules]} m] | |
(->> rules | |
(map (fn [{:keys [from to f]}] | |
[to (apply f (get-values from m))])) | |
(reduce (fn [acc [to v]] | |
(assoc-in acc (as-vector to) v)) | |
(init m)))) | |
(defn xf | |
"Returns a mapping rule. Function f is applied to values | |
retrieved using the keys in from-vector, the result of | |
application of f is set as value with to-key." | |
([from to] | |
(xf from to identity)) | |
([from to f] | |
{:from from | |
:to to | |
:f f})) | |
(defn const | |
"Returns a mapping rule that always produces the value x." | |
[to x] | |
(xf [] to (constantly x))) | |
;; example data | |
(def customer | |
{:name "Falko Riemenschneider" | |
:address {:street "Bonner Talweg 42" | |
:zipcode "53113" | |
:city "Bonn"} | |
:channels [{:type "phone" :link "+4915117396730"} | |
{:type "email" :link "[email protected]"} | |
{:type "phone" :link "+4922833646330"}]}) | |
;; and a mapping | |
(def customer-mapping | |
{:init (constantly {}) | |
:rules [(const :type "customer") | |
(xf [:name] :firstname #(-> % (s/split #" ") first)) | |
(xf [:name] :lastname #(-> % (s/split #" ") last)) | |
(xf [[:address :street]] :street) | |
(xf [:channels] :phone-numbers (fn [channels] | |
(->> channels | |
(filter (fn [{:keys [type]}] | |
(= type "phone"))) | |
(mapv :link))))]}) | |
#_(transform-map customer-mapping customer) | |
;; produces | |
#_ {:phone-numbers ["+4915117396730" "+4922833646330"], | |
:street "Bonner Talweg 42", | |
:lastname "Riemenschneider", | |
:firstname "Falko" | |
:type "customer"} | |
;; another example: starting again with the sample data | |
(def order | |
{:customer customer | |
:lineitems [{:pos 1 | |
:product-id 4711 | |
:product "Clojure T-Shirt" | |
:number 2 | |
:price 30.00 | |
:vat 20/100} | |
{:pos 2 | |
:product-id 42 | |
:product "Pair of Jeans" | |
:number 1 | |
:price 80.00 | |
:vat 20/100}]}) | |
;; here is a mapping that uses the customer-mapping from above | |
;; and produces a total sum for all lineitems | |
(def order-summary-mapping | |
{:init (constantly {}) | |
:rules [(xf [:customer] :customer (partial transform-map customer-mapping)) | |
(xf [:lineitems] :total-price (fn [lineitems] | |
(reduce (fn [sum {:keys [number price vat]}] | |
(+ sum (* number price (+ 1 vat)))) | |
0 | |
lineitems)))]}) | |
#_(transform-map order-summary-mapping order) | |
;; produces | |
#_ {:total 168.0, | |
:customer | |
{:phone-numbers ["+4915117396730" "+4922833646330"], | |
:street "Bonner Talweg 42", | |
:lastname "Riemenschneider", | |
:firstname "Falko" | |
:type "customer"}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment