June 12, 2020
By: kevin

clj&cljs中使用正则表达式(regex)

  1. regex
  2. 在clj&cljs中如何用regex?
  3. regex在clj和cljs的底层实现是什么?
  4. 一些匹配规则(模式)
    1. i:大小写敏感
    2. m:多行模式
    3. i:单行模式
    4. +? *? 贪婪模式
  5. 应用情景
    1. 匹配
    2. 查找
    3. 替换
    4. 分割

regex

clojureclojurescript正则表达式都是#开头的字符串, 比如: #"regex".

实现上两者差异巨大, 应用上差别细微,形式上则统一, 除特殊说明,以下模式clj/cljs均支持.

文章介绍clojure语言中的正则表达式的用法, 正则表达式本身不是本文的介绍范围, 请参考其他资料.

在clj&cljs中如何用regex?

  1. 通过字面量:不同语言,正则的字面量不一样。clojure的是用 #"xxx" 井号打头的字符串这种形式,reader会自动将其读取成一个regex。

  2. 还可以通过函数动态生成: (re-pattern "cat") ;=> #"cat"

regex在clj和cljs的底层实现是什么?

在JVM上会产生一个 java.util.regex.Pattern

  • (type #"cat") ;; => java.util.regex.Pattern

在JS上会产生一个 RegExp

  • (type #"cat") ;; => #object[RegExp]

一些匹配规则(模式)

i:大小写敏感

是否大小敏感? insensitive的缩写i

;; => ("abc" "ABC" "AbC")
(re-seq #"(?i)abc" "abc-ABC-AbC" )
;; => ("abc")
(re-seq #"abc" "abc-ABC-AbC" )

m:多行模式

是否把换行作为特殊字符? multiple-line缩写m,single-line缩写s。

;;;;;; 多行模式
;; => nil
(re-seq #"^a.*b$" "ab\nab")
;; => ("ab" "ab")
(re-seq #"(?m)^a.*b$" "ab\nab")
;; => ("ab" "ab")
(re-seq #"(?m)^a.*b$" "ab\n\rab")

i:单行模式

;;;;;; 单行模式
;; => ("ab\n\rab")  ;;把换行符也作为普通字符
(re-seq #"(?s)^a.*b$" "ab\n\rab")

+? *? 贪婪模式

是否限制贪婪?

;;横向伸展时,*和+都是默认贪婪的,也就是找到最后一个符合的条件才罢休。
;; => ("abcdxyzc")
(re-seq #"a.*c" "abcdxyzc")
;; => ("abcdxyzc")
(re-seq #"a.+c" "abcdxyzc")
;;如果在贪婪后加一个拷问?就会在第一次匹配后收手。
;; => ("abc")
(re-seq #"a.+?c" "abcdxyzc")
;; => ("abc")
(re-seq #"a.*?c" "abcdxyzc")

应用情景

匹配

(用和整个字符串同宽的筛子) 这时使用 re-match

;;结果会有以下几种情况:
(re-matches #"abc" "zzzabcxxx") ;;=> nil 因为不整体匹配。
;;=> "abc" 全匹配,返回该匹配。
(re-matches #"abc" "abc")
;;=> "abcxyz" 通配全匹配,返回该匹配。
(re-matches #"abc.*" "abcxyz")
;;=> ["abcxyz" "xyz"] 不光通配全匹配,还通过group,达到解构的效果。
(re-matches #"abc(.*)" "abcxyz")
;;配合(group),还可以结合解构

;=> Kevin Li
(let [[_ first-name last-name] (re-matches #"(\w+)\s(\w+)" "Kevin Li")]
  (if first-name ;; successful match
      (println first-name last-name)
      (println "Unparsable name")))

查找

(用小筛子找局部符合的部分) 这时用 re-find和re-seq

;; 仅查找第一个局部匹配 用re-find
;; 结果会有以下几种情况:

;=> Nil 一个都没筛选到。
(re-find #"cat" "the best of best")
;=> "best" 找到第一个就结束。
(re-find #"best" "the best of best")
;=> "best of best" re-find加通配符,可能会疑惑:结果怎么不是我想要的。
(re-find #"b.*t" "the best of best")
;=> "best" 限制一下贪婪可能是你的原意。
(re-find #"b.*?t" "the best of best")
;=> ["best" "be"] 依然可以利用解构。
(re-find #"(be)st" "the best of best")
;; 穷尽查找所有局部匹配 用re-seq
;; 结果会有以下几种情况:

;=> nil
(re-seq #"cat" "the best of best")
;=> ("best" "best") 穷尽所有匹配,才止步。
(re-seq #"best" "the best of best")
;=> (["best" "be"] ["best" "be"])
(re-seq #"(be)st" "the best of best")

替换

替换所有发现,用clojure.string/replace

;=> "the beast of beast" 两个be都替换了。
(clojure.string/replace "the best of best" #"be" "bea")
;=> "theee beeest of beeest"
(clojure.string/replace "the best of best" #"(e)" "$1$1$1")
;=> "the bank of bank" 插播时间:...表示3个占位
(clojure.string/replace "the best of best" #"b..." "bank")
;=> "the beauty of beauty" replace还可以结合解构+函数。
(clojure.string/replace "the best of best" #"(be.)(.)"
                        (fn [[_ a b]]
                          (str "beau"
                               (str b "y"))))

仅替换第一个发现,用clojure.string/replace-first

(defn id-replaced-by-new-initial
  "按约定,id的打头initial往往就是company_id,比如Woo7,邦8,城9."
  [id old-initial new-initial]
  (if (clojure.string/starts-with? id old-initial)
      (clojure.string/replace-first id (re-pattern old-initial) new-initial) ;;replace-first避免把后面的7/8/9替代, re-pattern动态生成regexPattern.
      id))

;=> "9008"
(id-replaced-by-new-initial "8008" "8" "9")

分割

利用空格分割

;=> ["This" "is" "a" "string" "that" "I" "am" "splitting."]
(clojure.string/split "This is a string    that I am splitting." #"\s+")
Tags: regex