【问题标题】:Iterate through a Clojure Instaparse Tree遍历 Clojure Instaparse 树
【发布时间】:2015-02-01 18:28:21
【问题描述】:
(def grammar
        "
        <root> = line*
        <line> = START REST
        <START> = #'[0-9]{4} '
        <REST> = NA | NZ | DATETIME
        <NA> = 'Nicht angemeldet '
        <NZ> = 'Nicht zugelassen '
        <DATETIME> = TAG ZEIT 
        <TAG> = 'Montag ' | 'Dienstag '
        <ZEIT> = #'[0-9]{1}.*Uhr '
        ")

(defn changed-x [tree]
  (postwalk
    (fn [node]
      (if (and (vector? node) (= (first node) :line))
        [:line (-> node rest 10)]
        node))
    tree))

(defn -main
  [& args]
;; Create the tree and save it to "tree" with a function (not included here) 
but it works
      (def tree (test-title-parser title-grammar-1 In))
;; Change the tree so every line just becomes "10" hardcoded in the changed-x function
      (changed-x tree)
;; print the tree
      (println tree)
    )

这是一个(小)要解析的测试字符串: 1017 Montag 13-14:30Uhr 1026 Nicht zugelassen

i want this to happen:
1017 Montag 13-14:30 Uhr 
1026 Nicht zugelassen

每行末尾只需一个小 CR-Enter,或打印到控制台,以便我可以将输出重定向到文件。 我希望在数字和文本之间有标签。所以我可以将结果粘贴到 Excel 中,每行有 2 个单独的字段。

my tree Looks like this 
(1017  Montag  13-14:30Uhr 1026 Nicht zugelassen .................. )

我把所有我不需要的东西都放了。

现在请为我解决这个该死的难题的最后一点(乞求),因为我花了几个小时来理解 instaparse 及其工作方式,只是为了发现它确实正确分离了我的字符串,但让我更接近 0%我真的很想。每周至少要取得某种成功。 srsly...我可以在几分钟内用 4 种不同的语言做到这一点,我需要的是一个该死的 for 循环和一个带有字符串的变量。

我试图了解您的功能: 这要怎么读?节点到底是什么?我放在语法左侧的所有内容? -> 在这里做什么?从未见过它以这种方式使用,为什么我们有 []-Brackets?最后一个节点是做什么的?

  (if (and (vector? node) (= (first node) :line))
    [:line (-> node rest 10)]
    node))

【问题讨论】:

    标签: parsing clojure tree nodes


    【解决方案1】:

    如果没有完整的语法,很难提供解释。比方说,它是这样定义的:

    (def xyz
      (insta/parse
        "S = A+
         A = X Y Z
         X = 'x'+
         Y = 'y'+
         Z = 'z'+"))
    

    它将基本上匹配正则表达式#"(x+y+z+)+"。现在让我们尝试从输入创建解析树:

    (def t (xyz "xyyzzzxxxyyz"))    
    t ; => [:S [:A [:X "x"] [:Y "y" "y"] [:Z "z" "z" "z"]] [:A [:X "x" "x" "x"] [:Y "y" "y"] [:Z "z"]]]
    

    这个问题有两个部分 - 如何修改这棵树以及如何在 Enlive 中使用它。

    激活部分答案

    Enlive 是一个基于选择器的 Clojure 模板库。要使用此树进行模板,您需要将键 :S:A:X:Y:Z 重命名为某些标签。让我们将它们分别替换为:div:p:h1:h2:h3。对于这样的键重命名,有postwalk-replace函数:

    (use 'clojure.walk)
    
    (def tags (postwalk-replace {:S :div :A :span :X :h1 :Y :h2 :Z :h3} t))
    tags ; => [:div [:span [:h1 "x"] [:h2 "y" "y"] [:h3 "z" "z" "z"]] [:span [:h1 "x" "x" "x"] [:h2 "y" "y"] [:h3 "z"]]]
    

    tags 向量已准备好在 Enlive 中使用:

    (use 'net.cgrand.enlive-html)
    
    (html tags) ; => ({:tag :div, :attrs {}, :content ({:tag :span, :attrs {}, :content ({:tag :h1, :attrs {}, :content ("x")} {:tag :h2, :attrs {}, :content ("y" "y")} {:tag :h3, :attrs {}, :content ("z" "z" "z")})} {:tag :span, :attrs {}, :content ({:tag :h1, :attrs {}, :content ("x" "x" "x")} {:tag :h2, :attrs {}, :content ("y" "y")} {:tag :h3, :attrs {}, :content ("z")})})})
    

    答案的树修改部分

    要修改:X 节点的树值,您可以使用postwalk 函数:

    (defn changed-x [tree f]
      (postwalk
        (fn [node]
          (if (and (vector? node) (= (first node) :X))
            [:X (-> node rest f)]
            node))
        tree))
    
    (changed-x t count) ; => [:S [:A [:X 1] [:Y "y" "y"] [:Z "z" "z" "z"]] [:A [:X 3] [:Y "y" "y"] [:Z "z"]]]
    

    在上面的示例中,所有:X 节点子节点(多个字符串"x")都被单个数字更改 - 它们的计数。最后如果你想丢弃除:X之外的所有节点,你可以使用tree-seq函数:

    (defn filter-by-key [tree node-key]
      (->> tree
           (tree-seq vector? identity)
           (filter #(and
                      (vector? %)
                      (= (first %) node-key)))))
    
    (filter-by-key t :X) ; => ([:X "x"] [:X "x" "x" "x"])
    

    【讨论】:

    • 我尝试了 2 小时(在您提供的帮助下),但仍然无法循环播放。请看树:=&gt; [:S [:A [:X "WHY"] [:Y "y" "y"] [:Z "z" "z" "z"] [:A [:X "NOT"] [:Y "y" "y"] [:Z "z" "z" "z"]] ****,我只想打印:=> WHY => NOT
    • (filter-by-key your-tree :X) 给出([:X "WHY"] [:X "NOT"])。如果你想得到“WHY”和“NOT”字符串——你可以使用(map second (filter-by-key your-tree :X))。它将产生("WHY" "NOT")。这还不够处理吗?
    • 对了,你也可以使用changed-x函数进行打印:(changed-x your-tree #(apply println %))。它将在单独的行中打印 WHY 和 NOT,然后返回树。
    • 可能会失去动力......但是我重新输入了完整的问题以使自己更清楚。还是谢谢
    【解决方案2】:

    实际上,解决方案非常接近。主要问题在于语法。您已经为每个非终结符号添加了&lt;&gt;。因此,所有语义都丢失了,树 ("1017 " "Montag " "13-14:30Uhr " "1026 " "Nicht zugelassen ") 仅由终端节点组成。

    考虑下一个语法:

    (def grammar
        "
        <root> = line*
        line = START REST
        <START> = #'[0-9]{4} '
        <REST> = NA | NZ | DATETIME
        <NA> = 'Nicht angemeldet '
        <NZ> = 'Nicht zugelassen '
        <DATETIME> = TAG ZEIT 
        <TAG> = 'Montag ' | 'Dienstag '
        <ZEIT> = #'[0-9]{1}.*Uhr '
        ") ; Note "line" non-terminal - it's not wrapped now
    

    因此,树看起来像([:line "1017 " "Montag " "13-14:30Uhr "] [:line "1026 " "Nicht zugelassen "])。现在,changed-x 的更通用版本:

    (defn tree-apply [tree tree-key f]
      (postwalk
        (fn [node]
          (if (and (vector? node) (= (first node) tree-key))
            [tree-key (-> node rest f)]
            node))
        tree))
    

    基本上,硬编码的:X 关键字更改为参数,函数重命名为更有意义的名称。

    最后,打印制表符分隔术语的用法:

    (require '[clojure.string :as s])
    
    (tree-apply t :line #(->> % (s/join "\t") println))
    1017    Montag  13-14:30Uhr 
    1026    Nicht zugelassen 
    

    一些解释tree-apply 是返回树的更新版本的函数,其中更改的节点由tree-key 给出。它适用于节点是一个向量的格式,第一个元素是节点键,其余的 - 树的叶子或子节点:

    [:a [:b "c"] "d" [:e]]
    

    这里:a:b:e是节点键; :b:e:a 的子代; "c", "d" 是叶子。对于下一棵树

    [:root [:a] [:a "a"] [:b] [:a 1] [:c ...]]
    

    (tree-apply t :a f) 将考虑节点 [:a][:a "a"][:a 1] 不接触 :b:c。函数f 将节点的“内部”参数作为参数。第一个节点[:a]()(空序列),[:a "a"]("a")[:a 1](1)f 的结果被放置到新建的树中,因此生成的树将如下所示:

    [:root [:a (f)] [:a (f '("a"))] [:b] [:a (f '(1))] [:c ...]]
    

    此函数可以提供如上例所示的副作用。功能

    #(->> % (s/join "\t") println)
    

    是快捷方式

    (fn [coll] (println (s/join "\t" coll)))
    

    基本上,它接受序列,将其连接到由制表符分隔的字符串,并在新行中打印结果字符串。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-15
      • 1970-01-01
      相关资源
      最近更新 更多