Skip to content

Instantly share code, notes, and snippets.

@friemen
Last active August 29, 2015 14:07
Show Gist options
  • Save friemen/1aa93c14167fa1f0a61c to your computer and use it in GitHub Desktop.
Save friemen/1aa93c14167fa1f0a61c to your computer and use it in GitHub Desktop.
Data transformation
(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