Ruby正規表現の後読みでは長さ不定の量指定子は原則使えない

こんにちは、hachi8833です。「ライフ」カテゴリの記事でアドベント書きたかったのですが、こちらの小ネタにします。

正規表現の先読みと後読みについては「正規表現の先読み・後読み(look ahead、look behind)を活用しよう」をご覧ください。

以下は基本的にRubyの正規表現(onigmo)を使います。他の正規表現ライブラリではこのとおりにならない可能性があります。

Rubyの正規表現の後読みは長さを不定にできない

以下の文字列が対象です。

word work wording working interesting partitioning subscribe subscriber subscription

たとえば、ingで終わる1文字以上の長さの英単語のingだけにマッチさせたいと思って次の正規表現を書いたとします。+は1文字以上のマッチを表します。

(?<=[\p{L}]+)ing

しかしやってみると、Invalid pattern in look-behind.と表示されます。なお、+を最小一致の+?に変えてもだめでした。

任意の長さの代わりに、長さの異なる特定の語のリストを後読みで使うとどうなるでしょうか。

(?<=(word|work|interest|partition))ing

これも同じくInvalid pattern in look-behind.になります。なお、リスト内の各語の長さをすべて同じにすると通ります。

後読みは本体がマッチした後で文字どおり遡ってチェックされるはずなので、量指定子(quantifier: 量化子とも呼ばれます)の長さが不定だと効率が非常に落ちることは想像がつきます。Onigmoの仕様まではチェックしていませんが、おそらくそうした理由で長さ不定の後読みをサポートしていないのではないかと推測しています。

ちょっとだけPerlでも試してみましたが、こちらもnot implementedだそうです。

$ perl -e '"word work wording working interesting partitioning subscribe subscriber subscription" =~ /(?<=[\\p{L}]+)ing/;'
Variable length lookbehind not implemented in regex m/(?<=[\\p{L}]+)ing/ at -e line 1.

後読みに使える量指定子

Rubyの場合、少なくとも次のように{, 10}などで長さに制約を与えた場合は後読みが機能します。これがなかったらわたし的につらいです。

(?<=[\p{L}{, 10}])ing

他にも使えるものがあるかもしれませんが、いずれにしろ量指定子を不用意に使うと効率が落ちるので、あまりやんちゃしないようにしましょう。

先読みでは長さを不定にできる

Ruby正規表現の先読み(look ahead)では、次のように長さ不定の量指定子を使えます。

work(?=[\p{L}]+)

おまけ: .NET Frameworkだとできる

遠い昔の記憶では、.NET Frameworkでは後読みで長さ不定の量指定子を使えたはずだったので、チェックしてみました。当時はこれが当たり前だと思っていたので、他のライブラリでできないことを知ったときはショックでした。

たった今見つけたregexstorm.netというサイトで.NET Frameworkの正規表現をチェックしたところ、後読みであっさり長さ不定の量指定子を使えました。Mac環境だとおいそれと.NET Frameworkの正規表現を確認できないので、このサイトは助かります。

また、.NET Frameworkの正規表現ライブラリをGo言語に移植したdlclark/regexp2という私の大好きなパッケージで試したところ、こちらでも長さ不定の量指定子を使えました。

package main

import (
    "fmt"

    "github.com/dlclark/regexp2"
)

func main() {
    re, err := regexp2.Compile("(?<=[\\p{L}]+)ing", 0)
    if err != nil {
        fmt.Println("err compile: ", err)
    }

    ma, err := re.FindStringMatch("word work wording working interesting partitioning subscribe subscriber subscription")
    if err != nil {
        fmt.Println("err match: ", err)
    }

    fmt.Println(ma)
}
$ go run regexp2.go
ing

効率を犠牲にしても後読みで長さ不定の量指定子をサポートしているのか、それとも実装が凄いのかは調べていませんが、私の中ではやはり.NET Frameworkの正規表現が今のところ最強です。

関連記事

正規表現: 文字クラス [ ] 内でエスケープしなくてもよい記号

正規表現の先読み・後読み(look ahead、look behind)を活用しよう

Ruby 2.4.1新機能: Onigmo正規表現の非包含演算子(?~ )をチェック

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833

コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。
これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。
かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。
実は最近Go言語が好き。
仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ