土日とゴールデンウィークが仕事ばっかりで、全然更新できていませんでしたね…。なんとか時間が作れたので、久々の更新です。
前回のScalaの正規表現に続き、Clojureの正規表現を使ってみようと思います。Contribは見ずに、標準関数のみです。
re-pattern(java.util.regex.Patternの構築)
re-pattern関数に、正規表現を表す文字列を渡します。
(def number-pattern (re-pattern "\\d+")) (println (class number-pattern)) ;; => java.util.regex.Pattern
もしくは、#リーダーマクロでもOKです。
(def number-pattern #"\d+") (println (class #"\d+")) ;; => java.util.regex.Pattern
この場合、Javaで正規表現を記述する際に面倒な、バックスラッシュのエスケープが楽になります。
では、この辺りをスケープゴートにして、試していってみましょう。
(def target-str "Hello 1234 World 5678") (def number-pattern (re-pattern "\\d+")) (def alpha-pattern (re-pattern "[a-zA-Z]+")) (def not-match-pattern (re-pattern ":"))
re-matcher(java.util.regex.Matcherの構築)
re-matcher関数にjava.util.regex.Patternと正規表現を適用する文字列を渡すことで、java.util.regex.Matcherを取得することができます。
(println (class (re-matcher number-pattern target-str))) ;; => java.util.regex.Matcher (println (class (re-matcher alpha-pattern target-str))) ;; => java.util.regex.Matcher (println (class (re-matcher not-match-pattern target-str))) ;; => java.util.regex.Matcher
re-find
re-find関数を使うことで、正規表現にマッチした文字列を取得することができます。
(println (re-find number-pattern target-str)) ;; => 1234 (println (re-find alpha-pattern target-str)) ;; => Hello (println (re-find not-match-pattern target-str)) ;; => nil (println (re-find (re-matcher number-pattern target-str))) ;; => 1234 (println (re-find (re-matcher alpha-pattern target-str))) ;; => Hello (println (re-find (re-matcher not-match-pattern target-str))) ;; => nil (let [m (re-matcher number-pattern target-str)] (loop [match (re-find m)] (when match (println (str "matched => " match)) ;; => 1回目 1234、2回目 5678 (recur (re-find m)))))
Matcherオブジェクトをループに放り込むことで、マッチした分だけ文字列を取得していくことができます。
グループ化したパターンを渡すと、結果がちょっと変わります。
(def number-capture-pattern (re-pattern "(\\d+)")) (println (re-find number-capture-pattern target-str)) ;; => ["1234" "1234"] (println (re-find #"(\d+)" target-str)) ;; => ["1234" "1234"] (let [m (re-matcher number-capture-pattern target-str)] (loop [match (re-find m)] (when match (println (str "matched => " match)) ;; => 1回目 ["1234" "1234"]、2回目 ["5678" "5678"] (recur (re-find m))))) (def number-capture-pattern (re-pattern "(\\d+)[^0-9]+(\\d+)")) (println (re-find number-capture-pattern target-str)) ;; => ["1234 World 5678" "1234" "5678"] (let [m (re-matcher number-capture-pattern target-str)] (loop [match (re-find m)] (when match (println (str "matched => " match)) ;; => ループは1回のみ ["1234 World 5678" "1234" "5678"] (recur (re-find m)))))
結果としてベクタが返るようになり、ベクタの最初の要素はパターンにマッチした文字列全体、それ以降はキャプチャされた文字列が続きます。
re-seq
マッチした文字列が、リストとして返ります。
(def number-pattern #"\d+") (def number-capture-pattern #"(\d+)") (def alpha-number-capture-pattern #"([a-zA-Z]+) (\d+)") (println (re-seq number-pattern target-str)) ;; => ("1234" "5678") (println (re-seq number-capture-pattern target-str)) ;; => (["1234" "1234"] ["5678" "5678"]) (println (re-seq alpha-number-capture-pattern target-str)) ;; => (["Hello 1234" "Hello" "1234"] ["World 5678" "World" "5678"])
re-groups
re-matcher/re-findと組み合わせて使用します。java.util.regex.Matcherへre-find関数を適用した後に使うことで、マッチした文字列を取得することができます。グループ化した場合にベクタが取得できるようになるのは、re-findなどと同じです。違いは、Matcherに対してre-findを適用してマッチ位置を進めない限り、何度実行しても同じマッチ結果が取得できることです。
(def number-pattern #"\d+") (def number-capture-pattern #"(\d+)") (def alpha-number-capture-pattern #"([a-zA-Z]+) (\d+)") (def number-matcher (re-matcher number-pattern target-str)) (def number-capture-matcher (re-matcher number-capture-pattern target-str)) (def alpha-number-capture-matcher (re-matcher alpha-number-capture-pattern target-str)) (re-find number-matcher) (re-find number-capture-matcher) (re-find alpha-number-capture-matcher) (println (re-groups number-matcher)) ;; => 1234 (println (re-groups number-capture-matcher)) ;; => ["1234" "1234"] (println (re-groups alpha-number-capture-matcher)) ;; => ["Hello 1234" "Hello" "1234"] (re-find number-matcher) (re-find number-capture-matcher) (re-find alpha-number-capture-matcher) (println (re-groups number-matcher)) ;; => 5678 (println (re-groups number-capture-matcher)) ;; => ["5678" "5678"] (println (re-groups alpha-number-capture-matcher)) ;; => ["World 5678" "World" "5678"]
なお、Matcherに対してre-findを適用せずにre-groupsを適用すると、IllegalStateExceptionが飛んできます。
(try (println (re-groups (re-matcher number-pattern target-str))) (catch IllegalStateException e (println (.toString e)))) ;; => java.lang.IllegalStateException: No match found
re-matches
java.util.regex.Matcher#matchesを使っての正規表現の適用となります。結果は、re-groupsと同じです。
(def number-pattern #"\d+") (println (re-matches number-pattern target-str)) ;; => nil (def matches-pattern #"[a-zA-Z]+ \d+ [a-zA-Z]+ \d+") (println (re-matches matches-pattern target-str)) ;; => Hello 1234 World 5678 (def matches-capture-pattern #"([a-zA-Z]+) (\d+) ([a-zA-Z]+) (\d+)") (println (re-matches matches-capture-pattern target-str)) ;; => ["Hello 1234 World 5678" "Hello" "1234" "World" "5678"]
clojure.string/replace-first
正規表現にマッチした最初の文字または文字列の置換を行います。
(def target-str "Hello 1234 World 5678") (use '[clojure.string :only (replace-first)]) (println (replace-first target-str #"\d+" "NumberReplaced")) ;; => Hello NumberReplaced World 5678
グループ化を使用した場合は、「$n」で後方参照を行うこともできます。
(println (replace-first target-str #"(\w+) \d+ (\w+)" "$1 $2")) ;; => Hello World 5678
第2引数は、java.util.regex.Patternでなくても構いません。charやStringでもOKです。
(println (replace-first target-str "World" "Clojure")) ;; => Hello 1234 Clojure 5678 (println (replace-first target-str \o \O)) ;; => HellO 1234 World 567
(?i)を付けると、Case-Insensitiveになります。
(println (replace-first target-str #"(?i)world" "Clojure")) ;; => Hello 1234 Clojure 5678
ちなみに、ClojureDocsには関数を引数に取る以下のような例も載っていましたが、今回は試してません…。
user=> (import '(java.util.regex Matcher)) java.util.regex.Matcher user=> (defn re-qr [replacement] (Matcher/quoteReplacement replacement)) #'user/re-qr user=> (str/replace-first "fabulous fodder foo food" #"f(o+)(\S+)" (re-qr "$2$1")) "fabulous $2$1 foo food"
clojure.string/replace
使い方は、ほぼclojure.string/replace-firstと同じです。違いは、replace-firstが最初の1回しか置換しないのに対し、replaceはマッチした分だけ置換を行います。
(println (clojure.string/replace target-str #"\d+" "NumberReplaced")) ;; => Hello NumberReplaced World NumberReplaced (println (clojure.string/replace target-str #"[a-zA-Z]+" "Clojure")) ;; => Clojure 1234 Clojure 5678 (println (clojure.string/replace target-str #"(\w+) \d+ (\w+)" "$1 $2")) ;; => Hello World 5678
なお、useするとclojure.core/replaceを隠してしまうので、警告が出てしまいます。
(use '[clojure.string :only (replace)]) ;; WARNING: replace already refers to: #'clojure.core/replace in namespace: user, being replaced by: #'clojure.string/replace
よって、
(clojure.string/replace target-str pattern replace-string))
という形になります。…ちょっと微妙。
clojure.string/split
与えられた正規表現で、文字列をベクタに分割します。
(use '[clojure.string :only (split)]) (println (split target-str #"\s+")) ;; => ["Hello" "1234" "World" "5678"]
これのオマケで、\nまたは\r\nで分割するsplit-linesという関数もあります。
(use '[clojure.string :only (split-lines)]) (println (split-lines "The\nQuick\r\nBrown\nFox")) ;; => ["The" "Quick" "Brown" "Fox"]
以上、正規表現一巡りでした〜。