specter是Clojure(Script)的数据处理API,它的作者就是大名鼎鼎的Storm的作者NathanMarz.作者开发这套函数的目的是弥补Clojure自带函数在查询或更改复杂数据结构时的不足。使用specter不仅可以使数据处理变得更简单,处理的效率也比使用Clojure自带函数更好。specter还有一个优点,即不会更改数据结构,例如不会像filter
那样会把vector的输入转换为list
输出。但specter的语法可能需要一点时间去熟悉,下面就通过例子的方式简单的介绍下specter的用法。
引入specter包
在project.clj
中加入依赖
[com.rpl/specter "1.1.3"]
选择
使用select
选择输入数据的子集:
(require
[com.rpl.specter :refer :all])
(filter odd? [1 2 3 4 5 6]) ;; (1 3 5)
(select [ALL odd?] [1 2 3 4 5 6]) ;; [1 3 5]
选择奇数位的元素
(select [INDEXED-VALS (comp odd? first) LAST] [:a :b :c :d :e]) ;; [:b :d]
修改
使用transform
修改选中的元素:
;; 选中所有map的val,并替换为(count val)的值
(transform [MAP-VALS] count {:a "hello" :b "world"})
;; {:a 5, :b 5}
如果只需要替换为同一值,可使用setval
:
(setval [MAP-VALS] "w" {:a "hello" :b "world"})
;; {:a "w", :b "w"}
删除
将值设为NONE
表示删除此元素:
(setval [:b] NONE {:a "hello" :b "world"})
;; {:a "hello"}
一个稍复杂的例子
此示例输入是一条常见的hiccup数据,其中包含网站列表,每个网站有名称、网址、成立日期及简介内容。 我们希望将此输入转换为clojure数据结构,即提取名称、网址、成立日期及简介再转换为map队列输出
(def root {:content
[{:type :element,
:attrs nil,
:tag :h3,
:content
[{:type :element,
:attrs
{:title "Permanent Link to Bcelebrated",
:href
"http://www.thedigitalbeyond.com/online-services-list/bcelebrated/",
:rel "bookmark"},
:tag :a,
:content ["Bcelebrated"]}]}
"\n"
{:type :element,
:attrs
{:href "http://www.bcelebrated.com",
:name "Link to Bcelebrated"},
:tag :a,
:content ["www.bcelebrated.com"]}
{:type :element,
:attrs nil,
:tag :p,
:content
[{:type :element,
:attrs nil,
:tag :strong,
:content ["Founded: "]}
"July, 2009"]}
{:type :element,
:attrs nil,
:tag :p,
:content
["Bcelebrated enables members to create a multi-media website that will become their autobiographical memorial site when the time comes."]}
{:type :element,
:attrs nil,
:tag :h3,
:content
[{:type :element,
:attrs
{:title "Permanent Link to Boxego",
:href
"http://www.thedigitalbeyond.com/online-services-list/boxego/",
:rel "bookmark"},
:tag :a,
:content ["Boxego"]}]}
"\n"
{:type :element,
:attrs
{:href "http://www.boxego.com",
:name "Link to Boxego"},
:tag :a,
:content ["www.boxego.com"]}
{:type :element,
:attrs nil,
:tag :p,
:content
[{:type :element,
:attrs nil,
:tag :strong,
:content ["Founded: "]}
"2013"]}
{:type :element,
:attrs nil,
:tag :p,
:content
["Boxego is a private journal that can be shared privately and socially, now and in the future."]}]})
选取标题,使用pred
自定义路径选择器:
(select [:content ALL (pred #(= :h3 (:tag %))) :content ALL :content FIRST] root)
;; ["Bcelebrated" "Boxego"]
选取网站链接:
(select [:content ALL (pred #(= :a (:tag %))) :attrs :href] root)
;; ["http://www.bcelebrated.com" "http://www.boxego.com"]
选取时间与文章描述:
(select [:content ALL (pred #(= :p (:tag %))) :content LAST] root)
;; ["July, 2009"
;; "Bcelebrated enables members to create a multi-media website that will become their autobiographical memorial site when the time comes."
;; "2013"
;; "Boxego is a private journal that can be shared privately and socially, now and in the future."]
同时取上面三类内容,使用multi-path同时选择多个路径:
(def paths [[(pred #(= :h3 (:tag %))) :content ALL :content FIRST]
[(pred #(= :a (:tag %))) :attrs :href]
[(pred #(= :p (:tag %))) :content LAST]])
(select [:content ALL (apply multi-path paths)] root)
;; ["Bcelebrated"
;; "http://www.bcelebrated.com"
;; "July, 2009"
;; "Bcelebrated enables members to create a multi-media website that will become their autobiographical memorial site when the time comes."
;; "Boxego"
;; "http://www.boxego.com"
;; "2013"
;; "Boxego is a private journal that can be shared privately and socially, now and in the future."]
下面我们来看如何转换成我们期望的map集合的结构。这里要用到transform
(->>
(select [:content ALL (apply multi-path paths)] root)
(partition 3)
(transform [ALL] (fn [[title url description]] {:title title :url url :description description})))
;; ({:title "Bcelebrated",
;; :url "http://www.bcelebrated.com",
;; :description "July, 2009"}
;; {:title
;; "Bcelebrated enables members to create a multi-media website that will become their autobiographical memorial site when the time comes.",
;; :url "Boxego",
;; :description "http://www.boxego.com"})
递归
(def MAP-NODES
(recursive-path [] p
(if-path map?
(continue-then-stay MAP-VALS p))))
(setval [MAP-NODES MAP-VALS nil?] NONE m)