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:如何将映射条目的惰性序列转换为结构映射?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 clojure 中映射很少使用的状态?

如何在 Clojure/Compojure/Ring 中将映射转换为 URL 查询字符串?

我如何帮助 Clojure 理解 0 是最小的自然数?

在 Clojure 中将元组数组转换为哈希映射

在 Clojure 中,如何将字符串转换为数字?

如何在Clojure中将字符强制转换为int?