こんにちは、hachi8833です。先週のTechRacho記事「Ruby 2.4.1新機能: Onigmo正規表現の非包含演算子(?~ )をチェック」の続編です。
ここで行っている非包含演算子の単独利用は挙動を理解するための極端なケースであり、実用的な意味はありませんのでご了承ください。
非包含演算子の挙動
「Ruby 2.4.1新機能: Onigmo正規表現の非包含演算子(?~ )をチェック」記事を作成中に以下のサンプルコードを見ていて、ふと気になった点がありました。
"うらにはにわにわにはにわにわとりがいる".match?(/(?~でも)/) #=> true
"うらにはにわにわにはにわにわとりがいる".match?(/(?~には)/) #=> true
- 空文字列
""
は「指定の文字列を含まない部分文字列」に該当するのだろうか? - 集合論的には該当する気がする
- 空集合も集合だから
- ということは、空文字を含むどんな文字列に対しても空文字を見いだせてしまうことになる
- なぜなら文字と文字の間は空文字であり、どんな文字列でも文字と文字の間にもれなく空文字があることになるから
そうすると非包含演算子(?~)
のみを使った#match?
はどんな文字列に対してもtrue
になってしまうのだろうか?
やってみようみてみよう
そうなりました。空文字列に対する#match?
でもtrue
になりました。
"".match?(/(?~でも)/) #=> true
"This is a pen".match?(/(?~でも)/) #=> true
すべてを試したわけではありませんが、#match?
の中で非包含演算子だけを使ったが最後、対象の文字列の内容にかかわらずこのようにtrue
が返ると考えられます。
そしてさらに、非包含演算子(?~)
の中を空っぽにすると、#match?
は空文字を含めて、文字列の種類を問わずfalse
になります。
"".match?(/(?~)/) #=> false
"This is a pen".match?(/(?~)/) #=> false
最初この挙動を見たときにバグかと思ってしまいましたが、上述のように空文字列""
が部分文字列に該当するのであれば、この動作は筋が通っています。
中の人に聞いてみた
以上ざっくり仮説を立てたうえで、記事公開後の夜にOnigmoのIssue #86で聞いてみました。
その結果、上の非包含演算子の挙動はやはり正常であるとのことでした。以下に大意をメモします。
この挙動は正常です。
非包含演算子
(?~somestring)
は ""や"s"や"so"や"som"..."somestrin"にもマッチしますし、"somestring"を含まないあらゆる文字列にもマッチします。どんな文字列にも "" が含まれるので、あらゆる文字列とマッチします。
逆に空の非包含演算子(?~)
は、どんな文字列にも "" が含まれるので、どんな文字列とも決してマッチしません。非包含演算子を単独で使うのはあまり使いやすくないかもしれません。アンカーや前後の文字列(prefixやsuffix)と組み合わせて使うのがよいでしょう。
ご回答ありがとうございました ?
おかげさまで非包含演算子の挙動を飲み込めました。「空文字列""
は、空文字列を含むどんな文字列にも部分文字列として含まれる」のがポイントだったんですね。
まとめ
- 非包含演算子のみを含む正規表現
/(?~regex)/
は、対象の文字列にかかわらず#match?
で常にtrue
になる - 空の非包含演算子のみを含む正規表現
/(?~)/
は、対象の文字列にかかわらず#match?
で常にfalse
になる - 以下のように他の文字列などを非包含演算子の前後に置くのが実用的
/うらにわ(?~にわ)にわには/
関連記事
- Ruby 2.4.1新機能: Onigmo正規表現の非包含演算子(?~ )をチェック
- [連載:正規表現] Unicode文字プロパティについて(1)
- [連載:正規表現] Unicode文字プロパティについて(2) — Pの一族
- [連載:正規表現] Unicode文字プロパティについて(3) 文字プロパティとは
- [連載:正規表現] Unicode文字プロパティについて(4) 勉強会資料