アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

('ω') < イザユケエンジニャー

正規表現で、複数のワードが順不同で含まれているかどうかを判定する

※PHP5.6前提。他だと方言差がある可能性もあり。


とりあえず結論から書くと、
文字列「abcde」に「b」「d」が順不同で含まれているかどうかは、
例えば以下のような書き方のいずれでもヒットさせられます。

preg_match("/(b.*d)|(d.*b)/", "abcde");
preg_match("/^(?=.*b)(?=.*d)/", "abcde");
 // ↑「^」はなくても動きますが、つけた方が速いようです

前者は正規表現初心者の方でもさほど難しくない考え方かと思います。

/b/             文中に「b」が含まれていればヒット、「abc」はOKだが「acd」はNG
/b.d/           文中に「b(任意の1文字)d」が含まれていればヒット、「abcde」はOKだが「abccde」はNG
/b.*d/          文中に「b(任意の0文字以上)d」が含まれていればヒット、「abde」でも「abccccde」でもOK
/d.*b/          ↑と同様に、「d(任意の0文字以上)b」が含まれていればヒット
/(b.*d)|(d.*b)/ ↑と↑↑のいずれかがヒットすればよいので、ORの意の「|」でつなぐ

後者の書き方は今まで知らなったのですが、
「先読み」「後読み」という概念のようです。

追記
別記事にまとめてみました。
正規表現の先読み・後読み - アナログCPU:5108843109
</追記

これを書いている時点ではかなりふんわりした認識なのですが、
例えば「/b(?=d)/」だと、「(直後にdをもつ)b」にマッチします。
(ヒットするかどうかでは「/bd/」と同じですが、これは「bd」にマッチします)
かつ、「(?=d)」の部分は「^」や「$」と同じように「位置」を表すものです。

先述のパターンに戻ってみます。

/^(?=.*b)(?=.*d)/

先読み後読みの前に、
「.*b」というパターンは「b以前の文字列すべて」であり
「.*d」というパターンは「d以前の文字列すべて」ですね。
そして「(?=○)」は○の位置を表す、と。

つまり、文字列「abcde」について言えば
「^」は「先頭位置」、つまりaの部分
「(?=.*b)」は「abの位置」、つまりaの部分
「(?=.*d)」は「abcdの位置」、つまりaの部分
…ということで、これら3つはすべて同じ位置を指しているということになります。
実質的に「/^/」と同じということになり、そりゃ順不同でもヒットするわ、なるほどなー…と納得しました。

実際には書かないでしょうが

/(?=.*b)(?=.*d)^/

で試してみてもヒットしました。

参考:
正規表現の先読み・後読みを極める! - あらびき日記