Clojureの正規表現
以下全て、Clojure1.1.0 をベースにしています。
【正規表現オブジェクト】
正規表現(pattern)オブジェクトの作成方法は3通りあります。
;; どれでも結果は同じ。 ;; javax.util.regex.Patternオブジェクトが生成される。 (def re #"[\da-f]+") (def re (re-pattern "[\\da-f]+")) (def re (java.util.regex.Pattern/compile "[\\da-f]+"))
リテラルで書きたい場合は一つめ、文字列から生成したい場合は二つめの書きかたを使うのが良さそう。
なおリテラルで書くとraw string扱いになりますが、re-patternでは通常の文字列なので \d のようなパターンを書く時 \\d とエスケープが必要になります。
【探索】
●re-find
re-find はマッチした最初の1件のみ取得
グループが無い場合、結果は文字列。
(re-find #"[0-9a-f]+" "123abc 345") ;;=> "123abc"
グループがある場合、結果はvector。
(re-find #"(0x)?([0-9a-f]+)(h)?" "0x123abc 345h") ;;=> ["0x123abc" "0x" "123abc" nil] ;;↑ vectorの最初はマッチした全体。 ;; そのうしろに各グループに対応したマッチ文字列が続く。
●re-seq
re-seq はマッチ部分を全て取得(結果は遅延シーケンス)。繰り返しre-findを行って、その結果をシーケンスにしたような感じ?
(re-seq #"[0-9a-f]+" "123abc 345 fffa") ;;=> ("123abc" "345" "fffa") (re-seq #"(0x)?([0-9a-f]+)(h)?" "0x123abc 345h 0xfffa") ;;=> (["0x123abc" "0x" "123abc" nil] ["345h" nil "345" "h"] ["0xfffa" "0x" "fffa" nil])
【分割】
●clojure.contrib.str-utils.re-split
re-splitは正規表現で区切り文字を指定して分割
(re-split #"[^\da-f]+" "123ff-abc66-99dd") ;;=> ("123ff" "abc66" "99dd")
パターンにグループを作っても変化は無し。マッチ部分は捨てられてしまうので当然。
●clojure.contrib.str-utils.re-partition
re-partitionは区切り文字も結果に含みます。
(re-partition #"\W+" "http://www.google.ne.jp") ;;=> ("http" "://" "www" "." "google" "." "ne" "." "jp")
区切り文字にグループを作ったりも出来ます。あんまり使わなそう。
(re-partition #"(\W)\W*" "http://www.google.ne.jp") ;;=> ("http" ["://" ":"] "www" ["." "."] "google" ["." "."] "ne" ["." "."] "jp")
【置換】
●clojure.contrib.str-utils.re-sub
re-subはマッチした最初の文字列のみ置換
(re-sub #"ab" "XX" "abcabcaabcabc") ;;=> "XXcabcaabcabc"
【前方参照】
●パターン内でのグループ参照
パターン内でのグループ参照は \N(Nはグループ番号)
(re-find #"^(生)(.+?)\1(.+?)\1(.+?)$" "生むぎ生ごめ生たまご") ;;=> ["生むぎ生ごめ生たまご" "生" "むぎ" "ごめ" "たまご"]
●前方参照による置換
置換で前方参照は $N (Nはグループ番号)
(re-gsub #"([\da-f]+)" " <$1> " "90abcdefghi123zzzffff") ;;=> " <90abcdef> ghi <123> zzz <ffff> "
【フラグ】
java.util.regex.Pattern/compile には正規表現のフラグ指定が出来ます。
例えば大文字小文字を区別せずに処理する場合。
;;大文字小文字区別しない (import 'java.util.regex.Pattern) (re-seq (Pattern/compile "[\\da-f]+" Pattern/CASE_INSENSITIVE) "123abc@ffee@FF12AA") ;;=> ("123abc" "ffee" "FF12AA")
流石にこれはめんどくさい。代替手段としてパターンの中にフラグを埋め込む方法があります。こちらの方法ならリテラル内でも使えます。
;;大文字小文字区別しない ;;パターン内に (?i) を入れるとそれ以降に ;;CALSE_INSENSITIVEが適用される。 (re-seq #"(?i)[\da-f]+" "123abc@ffee@FF12AA") ;;=> ("123abc" "ffee" "FF12AA")
●フラグ一覧
埋め込みフラグと、Patternクラスでの定義。
(?i) | CASE_INSENSITIVE | 大文字小文字区別しない |
(?m) | MULTILINE | 複数行モード |
(?s) | DOTALL | DOTALL モード |
(?u) | UNICODE_CASE | 大文字と小文字を区別しない。ワイド文字列版。 |
(?d) | UNIX_LINES | Unixの改行のみマッチ(\rは改行文字とみなさない) |
(?x) | COMMENTS | Unixラインモードを有効にする |
なし | CANON_EQ | 正規等価を有効にする |
なし | LITERAL | パターンのリテラル構文解析を有効にする |
;;複数行モード (re-seq #"(?m)^..." "abcd\nefg\nhijk") ;;=> ("abc" "efg" "hij") ;; Clojureでは文字列内に普通に改行も入れることが出来ます。 (re-seq #"(?m)^..." "abcd efg hijk") ;;=> ("abc" "efg" "hij") ;;DOTAILモード。メタ文字 "." が行末にマッチ (re-find #"(?s)......" "abcd\nefg\nhijk") ;;=> "abcd\ne"
【参考サイト】
【補足】
マッチテストだけ行う場合 re-find の結果が nil? かどうかで判定できますが、matcher オブジェクトの matchesメソッドを使うほうがオーバーヘッドは小さいです。
注意:以下の計測結果は誤りです
;; re-find でマッチテスト (defn match-sample-1 [] (let [result (re-find #"[\da-f]+" "12345abcd")] (if (not result) (println "No Match")))) ;; matcherを使ってマッチテスト (defn match-sample-2 [] (let [m (re-matcher #"[\da-f]+" "12345abcd")] (if (not (.matches m)) (println "No Match")))) ;; 1回目 (time (take 10000 (repeatedly match-sample-1))) "Elapsed time: 1.583992 msecs" (time (take 10000 (repeatedly match-sample-2))) "Elapsed time: 0.10979 msecs" ;; 2回目 (time (take 10000 (repeatedly match-sample-1))) "Elapsed time: 0.320151 msecs" (time (take 10000 (repeatedly match-sample-2))) "Elapsed time: 0.067885 msecs"
記事訂正
使用するなら、(.matches m) ではなく、(.find m) でした。matchesでは意味が変わってしまいます。
また条件を変えて計測した結果、必ずしも matcherオブジェクトを使った方が速いとは言い切れないようです。