読者です 読者をやめる 読者になる 読者になる

アナログCPU:5108843109

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

ある複数の単語が文章中に含まれるかどうかを調べる方法

タイトルの通り、ある文章の中に特定の単語(複数)が含まれるかどうかを
チェックする機能が欲しくていろいろ調べていました。
掲示板やブログコメントのNGワードチェックのような用途です。

軽く調べても、自分でも思いつくような正攻法しか見つからず…

参考:[PHP][疑問]ある文字列が、複数のキーワードのうち一つでもマッチするか判定する方法について - HappyQuality
http://www.happyquality.com/2012/02/04/1959.htm

自分で作ったのは下記のような感じです。(方法A)
単語リストをひたすらforeachで回して文章中を検索、引っ掛かればアウト。
(今回はNGワードに該当=だめ なので、参考サイトとは判定が逆です)

$text = "こんにちはhoge";           // 文章
$word_list = array("hoge", "fuga"); // 単語リスト
foreach ($word_list as $word)
{
    if (strpos($text, $word) !== false)
    {
        // NGワードに該当
        return false;
    }
}
return true;

…とやってると、友人から別の手段を教えてもらいました。
https://twitter.com/yamotonalds/status/423130328640004096

短い! すごっ!!
わたしが作ったのに合わせて書き起こすとこんな感じですね。(方法B)
(これも判定逆にしています)

$text = "こんにちはhoge";           // 文章
$word_list = array("hoge", "fuga"); // 単語リスト
str_replace($word_list, "", $text, $count);
return $count == 0;

…と感激していたら、@yamotonalds氏よりさらに助言が。
「str_replace($word_list, $word_list, $text, $count);
にすれば内部でデータ変更が起こらないので速くなるのでは」とのこと。
今回は1か所しかヒットしないようなデータでの検証だったので再検証はしませんが、
ヒットする箇所が多いと推測できるときは有効そうです!


さっそく上記の方法AとBで速度検証してみました。
・文章は1万字程度
・単語リストは100個
・1000回実行するのに要する時間を計測
・試行は5回
・単位は秒

単語リストの最後の単語が文章の最後に存在する場合

A 3.17969 3.17950 3.18077 3.18005 3.17854
B 3.94688 3.94768 3.93431 3.93399 3.92960

Aの方がちょっと速いですが、1000回実行してこれなので
1回あたりは誤差のようなものですね。
文章中に単語リストの単語がない場合も、
これとほぼ同じ結果になると思われます。

単語リストの最初の単語が文章の最初に存在する場合

A 0.00035 0.00036 0.00037 0.00035 0.00036 
B 3.90755 3.90483 3.89117 3.91311 3.89769 

繰り返しの最初で抜けるので、さすがにAはかなり速いですね。

これだとAの方法でよさそうです。
とはいえ、Bも問題あるような速度ではないと思うので
コードを短くしたい場合や、
文章が長くならない・単語が少ないなどの場合は使えそうな気がします。
(というか個人的にコレすごく好みなのでどっかで使いたい)