Clojure:如何将映射条目的惰性序列转换为结构映射?
Posted
技术标签:
【中文标题】Clojure:如何将映射条目的惰性序列转换为结构映射?【英文标题】:Clojure: How do you transform a lazyseq of map entries into a structmap? 【发布时间】:2012-06-16 23:39:20 【问题描述】:我是 clojure 的新手,一直在使用 enlive 来转换 html 文档的文本节点。我的最终目标是将结构转换回 html、标签和所有内容。
我目前能够获取 enlive-html/html-resource 返回的 structmap 并使用
将其转换回 html(apply str (html/emit* nodes))
其中节点是结构映射。
我还可以根据需要转换 structmap 的 :content 文本节点。但是,在转换了 structmap 的内容文本节点之后,我最终得到了 MapEntries 的lazyseq。我想将其转换回结构图,以便可以在其上使用 emit*。这有点棘手,因为lazyseqs & structmaps 是嵌套的。
tldr:
我如何转换:
([:tag :html]
[:attrs nil]
[:content
("\n"
([:tag :head]
[:attrs nil]
[:content
("\n "
([:tag :title] [:attrs nil] [:content ("Page Title")])
" \n")])
"\n"
([:tag :body]
[:attrs nil]
[:content
("\n "
([:tag :div]
[:attrs :id "wrap"]
[:content
("\n "
([:tag :h1] [:attrs nil] [:content ("header")])
"\n "
([:tag :p] [:attrs nil] [:content ("some paragrah text")])
"\n ")])
"\n")])
"\n\n")])
进入:
:tag :html,
:attrs nil,
:content
("\n"
:tag :head,
:attrs nil,
:content
("\n " :tag :title, :attrs nil, :content ("Page Title") " \n")
"\n"
:tag :body,
:attrs nil,
:content
("\n "
:tag :div,
:attrs :id "wrap",
:content
("\n "
:tag :h1, :attrs nil, :content ("header")
"\n "
:tag :p, :attrs nil, :content ("some paragrah text")
"\n ")
"\n")
"\n\n")
更新
kotarak's response 将我指向update-in
的方向,我可以使用它来修改地图而不将其转换为序列,从而使我的问题变得无关紧要。
(defn modify-or-go-deeper
"If item is a map, updates its content, else if it's a string, modifies it"
[item]
(declare update-content)
(cond
(map? item) (update-content item)
(string? item) (modify-text item)))
(defn update-content
"Calls modify-or-go-deeper on each element of the :content sequence"
[coll]
(update-in coll [:content] (partial map modify-or-go-deeper)))
我之前在地图上使用for
,但update-in
是要走的路。
【问题讨论】:
在实践中,我会敦促读者使用trampoline
,避免相互递归的陷阱......
【参考方案1】:
试试
(def mp '([:tag :html] [:attrs nil] [:content
(""
([:tag :head] [:attrs nil] [:content
("\n\t\t"
([:tag :title] [:attrs nil] [:content ("page title")])
"\n\t\t")])
"\n\t"
([:tag :body] [:attrs nil] [:content
("\n\t\t"
([:tag :div] [:attrs :id "wrapper"] [:content
("\n\t\t "
([:tag :h1] [:attrs nil] [:content
("\n \t\t\tpage title"
([:tag :br] [:attrs nil] [:content ()])
"\n \t\t\tand more title\n \t\t")])
"\n \t\t"
([:tag :p] [:attrs nil] [:content
("\n \t\tSome paragraph text"
([:tag :img] [:attrs :src "images/image.png", :id "image"] [:content nil])
"\n \t\t")])
"\n\t\t")]
"\n\t \n\t\t"))]
"\n\n"))]))
(clojure.walk/postwalk (fn [x]
(if (and (list? x) (vector? (first x)))
(into x)
x))
mp)
它会抛出一个错误,但如果你将输入更改为
([:tag :html]
[:attrs nil]
[:content
(""
([:tag :head]
[:attrs nil]
[:content
("\n\t\t"
([:tag :title] [:attrs nil] [:content ("page title")])
"\n\t\t")])
"\n\t"
([:tag :body]
[:attrs nil]
[:content
("\n\t\t"
([:tag :div]
[:attrs :id "wrapper"]
[:content
("\n\t\t "
([:tag :h1]
[:attrs nil]
[:content
("\n \t\t\tpage title"
([:tag :br] [:attrs nil] [:content ()])
"\n \t\t\tand more title\n \t\t")])
"\n \t\t"
([:tag :p]
[:attrs nil]
[:content
("\n \t\tSome paragraph text"
([:tag :img]
[:attrs :src "images/image.png", :id "image"]
[:content nil])
"\n \t\t")])
"\n\t\t")]
))]))]))
然后它工作正常。不同之处在于,在编辑后的输入中,您将从包含键值对的同一列表中删除类似“\n\t\t”的字符串。希望这会有所帮助。
编辑: 以下对我有用:
(def mp '([:tag :html]
[:attrs nil]
[:content
(""
([:tag :head]
[:attrs nil]
[:content
("\n\t\t"
([:tag :title] [:attrs nil] [:content ("page title")])
"\n\t\t")])
"\n\t"
([:tag :body]
[:attrs nil]
[:content
("\n\t\t"
([:tag :div]
[:attrs :id "wrapper"]
[:content
("\n\t\t "
([:tag :h1]
[:attrs nil]
[:content
("\n \t\t\tpage title"
([:tag :br] [:attrs nil] [:content ()])
"\n \t\t\tand more title\n \t\t")])
"\n \t\t"
([:tag :p]
[:attrs nil]
[:content
("\n \t\tSome paragraph text"
([:tag :img]
[:attrs :src "images/image.png", :id "image"]
[:content nil])
"\n \t\t")])
"\n\t\t")]
))]))]))
(clojure.walk/postwalk (fn [x]
(if (and (list? x) (vector? (first x)))
(into x)
x))
mp)
尝试将其复制并粘贴到 repl 中。您应该得到以下信息:
:tag :html,
:attrs nil,
:content
(""
:tag :head,
:attrs nil,
:content
("\n\t\t"
:tag :title, :attrs nil, :content ("page title")
"\n\t\t")
"\n\t"
:tag :body,
:attrs nil,
:content
("\n\t\t"
:tag :div,
:attrs :id "wrapper",
:content
("\n\t\t "
:tag :h1,
:attrs nil,
:content
("\n \t\t\tpage title"
:tag :br, :attrs nil, :content ()
"\n \t\t\tand more title\n \t\t")
"\n \t\t"
:tag :p,
:attrs nil,
:content
("\n \t\tSome paragraph text"
:tag :img,
:attrs :src "images/image.png", :id "image",
:content nil
"\n \t\t")
"\n\t\t")))
【讨论】:
java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.ClassCastException: java.lang.Character 不能被强制转换为 java.util.Map$Entry 嘿,我实际上没有收到错误,所以可能是我错误地粘贴了输入。但这似乎也根本没有改变集合,它仍然返回相同的 MapEntries 的lazyseq。就像我说的,我是 clojure 的新手,所以我不确定我是否以正确的方式使用它,但我把它塞进了一个函数中,比如:(defn retransform [mp] (clojure.walk/postwalk (fn [x] (if (and (list? x) (vector? (first x))) (into x) x)) mp))
嗯,它对我有用。我已经用一个示例更新了我的帖子,您应该可以复制和粘贴。【参考方案2】:
只需将所有内容放回地图并递归遍历内容。
(defn into-xml
[coll]
(let [tag (into coll)]
(update-in tag [:content] (partial map into-xml))))
请注意,内容只会在您访问时进行转换。
编辑: 糟糕,错过了字符串部分。这是一个工作版本:
(defn into-xml
[coll]
(if-not (string? coll)
(let [tag (into coll)]
(update-in tag [:content] (partial map into-xml)))
coll))
【讨论】:
谢谢。这似乎可以解决问题,除非它一旦进入内容序列就会出错。将我的收藏传递给这个函数,我得到::tag :html, :attrs nil, :content (IllegalArgumentException Don't know how to create ISeq from: java.lang.Character clojure.lang.RT.seqFrom (RT.java:487)
我现在正在使用你的解决方案,看看我是否能弄清楚发生了什么。
嘿,正如我所说,我是 clojure 的新手。您的解决方案将我指向update-in
的方向,我可以在原始集合上使用它,而不是for
,因此我保留了地图结构,而不是转换为一系列 MapEntries。在我的问题结束时,我将我的解决方案放在了如何浏览集合上。谢谢!
@jmw 啊,是的。弦乐。我添加了一个固定版本,但您的解决方案当然更好,首先使用update-in
。但请注意,declare
应该位于顶层。把它放到一个函数中是没有意义的。以上是关于Clojure:如何将映射条目的惰性序列转换为结构映射?的主要内容,如果未能解决你的问题,请参考以下文章