スポンサーリンク

2016年11月27日日曜日

Kotlin の正規表現を使う

Kotlin を使い始めても色々な点で未だ Java 的なコーディングから抜けきれません。正規表現もそんな一つです。こんなことじゃいけないと思い、Kotlin の正規表現検索をちゃんと勉強すべく、色々情報をを探し回ったのですが、意外なことにまとまった情報が見つかりません。断片的な解説ページはいくつか有りますが、ちゃんと体系立って説明したものが見つからないのです。日本語、英語どちらにもです。

なので試行錯誤的に使い込んで分かってきたことを整理しておきます。

Regexクラス

正規表現検索を行なうにはまず正規表現文字列を定義しなくてはなりません。この時トリプルクオート("""〜""")を使うと便利です。特殊文字をエスケープする必要がなくなります。例えば通常の文字列表現では "\\d+" と書くところを """\d+""" と書けます。複雑な正規表現を書く場合のあの悪夢のような苦痛が少しは軽減できます。

正規表現文字列が定義できたら、それを基に Regex オブジェクトを作成します。これには二通りの方法があります。Regex のコンストラクタを使う方法と String#toRegex() を使う方法です。
val regex = Regex("""\d+""")
val regex = """\d+""".toRegex()

Regex クラスの検索メソッド

Regex クラスには様々な正規表現検索メソッドがあります。それらを一つずつ説明していきます。
containsMatchIn() メソッド
containsMatchIn() は文字列を引数にとり、正規表現が部分一致するかどうかをチェックします。戻り値は Boolean です。文字列の中に正規表現に一致する部分が一つでもあると true を返し、無ければ false を返します。

val regex = Regex("Kotlin")
regex.containsMatchIn("Hello Kotlin!")  ===> true

val regex = Regex("^Kotlin")
regex.containsMatchIn("Hello Kotlin!")  ===> false

matches() メソッド
matches() は全文一致チェックを行ない Boolean を返します。文字列全体が正規表現に一致すれば true、それ意外は false を返します。なので一致する部分以外に一文字でも余計な文字があると false になります。

val regex = Regex("^Kotlin")
regex.matches("Kotlin")      ===> true
regex.matches("Kotlin ")     ===> false

find() メソッド
find() は部分一致検索を行ない、一致した文字列に対応する MatchedResult オブジェクトを返します。マッチが無い場合は null を返します。引数は文字列と何番目のマッチから検索を行なうかを示すインデックスを渡します。インデックスを省略した場合は先頭からの検索となります。検索開始した位置から最初に一致した文字列に対応する MatchedResult オブジェクトを返します。
matchEntire() メソッド
matchEntire() は全文一致検索を行ない、MatchedResult オブジェクトを返します。一致が無い場合は null を返します。
findAll() メソッド
findAll() は部分一致で検索で、一致した複数の文字列を Sequence<MatchedResult> オブジェクトの形で返します。一致が無かった場合は null を返します。引数は文字列と検索開始の位置のインデックスを渡し、インデックス省略時は先頭からになります。検索開始位置以降の全ての一致を返します。

さて、ここまで Regex の検索メソッドを見てきましたが、contains, find, match と様々な動詞を使っていて,どれがどれだかすぐ分からなくなります。名前のセンスがあまりよくないような気がします。

MatchedResult クラス

検索一致した文字列から様々なデータを取り出す場合、上の各メソッドが返す MatchedResult オブジェクトを使うことになります。MatchedResult オブジェクトは一つの一致を表現し、その中にはグループ化された文字列や、繰り返し回数等様々な情報が含まれています。

MatchedResult は以下のプロパティを持ちます。
  • value: String
  • groupValue: List<String>
  • range: IntRange
  • groups: MatchedGroupCollection
  • destructed: Destructed

value: String
value プロパティは一致した文字列全体を表わします。

val userData = "123,Taro Yamada,1980-11-20"
val userRegex = """\d+,[a-zA-Z ]+,[0-9-]+""".toRegex()
val matchResult = userRegex.matchEntire(userData)
println(matchResult?.value)

123,Taro Yamada,1980-11-20

groupValue: List<String>
groupValue プロパティはグループ化された文字列のリストです。

val userData = "123,Taro Yamada,1980-11-20"
val userRegex = """(\d+),([a-zA-Z ]+),([0-9-]+)""".toRegex()
val matchResult = userRegex.matchEntire(userData)
println(matchResult?.groupValues)

[123,Taro Yamada,1980-11-20, 123, Taro Yamada, 1980-11-20]

正規表現をグループ化して検索し、得られた groupValue を表示してみました。カンマが被って見にくいのですが、最初の要素は "123,Taro Yamada,1980-11-20" です。これは一致文字列全体に相当します。以降、"123", "Taro Yamada", "1980-11-20" とグループ化された文字列要素が順に格納されています。
range: IntRange
range プロパティは正規表現の繰り返しが発生した位置を表わします。

val regex = Regex("""\d+""")
val matchedResult = regex.find("12345")
println("range=" + matchedResult?.range)

range=0..4

検索文字列を少し変えてみると、

val regex = Regex("""\d+""")
val matchedResult = regex.find(" 12345")
println("range=" + matchedResult?.range)

range=1..5

レンジが変わりました。
groups: MatchGroupCollection
group プロパティは MatchedGroupCollection 型のオブジェクトです。このクラスはグループ化された文字列とその繰り返し range を一つのクラスにしただけのものです。Pair クラスを使って表わしてもいいような気がしますが、わざわざ専用のクラスを定義したのですね。
destructured: Destructured
厄介なのがこの destructured プロパティです。多分目的は以下の様に、Destructuring Declarations (分解宣言)として使いたいのだと思います。

val userData = "123,Taro Yamada,1980-11-20"
val userRegex = """(\d+),([a-zA-Z ]+),([0-9-]+)""".toRegex()
val matchResult = userRegex.matchEntire(userData)
val (id, name, birthday) = matchResult?.destructured

しかしこの最後ステートメントが何故かエラーになってしまいます。もしこの部分のコードが正しければ、以下のコードに落とし込まれる筈です。

val id       = matchResult?.destructured?.component1()
val name     = matchResult?.destructured?.component2()
val birthday = matchResult?.destructured?.component3()

直接こう書くと完全に正しく動きます。ひょっとして Kotlin のバグかもしれません。

この destructured プロパティの使い方が一つでも見つかればいいのですが、Google様を使っても世界中どこにも見つかりませんでした。誰も使っている人いないのかなあ?これ、ちゃんと動けばこれ結構便利だと思うのですが。

Regex クラス にはまだ置換機能とか色々あるのですが、今回はここまでとします。

0 件のコメント :

コメントを投稿