Clojure中的简单模式匹配

时间:2016-04-19 03:37:13

标签: string clojure pattern-matching

我在Clojure中有一个字符串,我想命名并提取匹配的各个部分。执行此操作的标准方法是:

(re-seq #"\d{3}-\d{4}" "My phone number is 000-1234")
;; returns ("000-1234")

但是我希望能够命名和访问匹配的部分。

以下是一个例子:

(def mystring "Find sqrt of 6 and the square of 2")
(def patterns '(#"sqrt of \d" #"square of \d"))

当我将mystring与我的模式列表匹配时,我希望结果类似于{:sqrt 6, :root 2}

更新

我找到了一个名为https://github.com/rufoa/named-re的第三方软件包支持命名组,但我希望核心库中有一个解决方案。

3 个答案:

答案 0 :(得分:4)

你可以使用java的正则表达式的命名组来完成它。问题是没有api来获取所有组的名称,所以你必须从正则表达式中获取它们:

(defn find-named [re s]
  (let [m (re-matcher re s)
        names (map second (re-seq #"\(\?<([\w\d]+)>" (str re)))]
    (when (.find m)
      (into {} (map (fn [name]
                      [(keyword name) (.group m name)])
                    names)))))

在repl中:

user> (find-named #"sqrt of (?<sqrt>\d).*?square of (?<root>\d)"
                  "Find sqrt of 6 and the square of 2")
{:sqrt "6", :root "2"}

user> (find-named #"sqrt of (?<sqrt>\d).*?square of (?<root>\d)"
                  "Find sqrt of 6 and the square of fff")
nil

<强>更新

这次谈话引发了我的想法,你在这里并不真正需要命名组,而是命名模式:

user> 
(defn get-named [patterns s]
  (into {} (for [[k ptrn] patterns]
             [k (second (re-find ptrn s))])))
#'user/get-named

user> (get-named {:sq #"sqrt of (\d)"
                  :rt #"square of (\d)"}
                 "Find sqrt of 6 and the square of 2")
{:sq "6", :rt "2"}

user> (get-named {:sq #"sqrt of (\d)"
                  :rt #"square of (\d)"}
                 "Find sqrt of 6 and the square of xxx")
{:sq "6", :rt nil}

答案 1 :(得分:1)

你需要捕捉你想要的模式,例如:

(re-seq #"sqrt of (\d)" "Find sqrt of 6")

或者如果你想要第一组比赛:

(def matcher #"sqrt of (\d)" "Find sqrt of 6")
(re-find matcher)
(second (re-groups matcher))

请参阅re-groups的文档。

就命名捕获的组而言,我没有仔细查看您在问题中提到的库,但我认为唯一的实际区别在于为捕获组分配名称而不是仅仅通过其数字引用正则表达式中从左到右的位置(从1开始)。

答案 2 :(得分:1)

根据你打算对'命名匹配'做什么,你可能会发现简单地解构匹配并将它们绑定到符号是有用的。

单场比赛:

(if-let [[_ digit letter] (re-find #"(\d)([a-z])" "1x 2y 3z")]
  [digit letter])  ; => ["1" "x"]

对于多场比赛:

(for [[_ digit letter] (re-seq #"(\d)([a-z])" "1x 2y 3z")]
  [digit letter])  ; => (["1" "x"] ["2" "y"] ["3" "z"])