PHP
ngram
バイグラム

PHPでNgram(バイグラム)

経緯

MySQLでの全文検索用をしたかったので知らべていたところ以下の記事
でNgram(バイグラム)の実装が必要になりそうだったので作成してみました。

MYSQL5.7以降だと既にNgramパーサーが入っているそうです。但しこんな動作もあるらしいです。

http://blog.wackwack.net/entry/2016/03/21/221821

以下の設定は忘れやすいので注意

[mysqld]
innodb_ft_min_token_size=2

概要

CMSでhtmlを保存するのとは別に検索用カラムを用意して検索用文字列として使用する想定です。

コード

class Bigram
{
    /**
     * 文字列を登録・検索用バイグラムに変換する
     *
     * @param string|null $string
     * @param boolean $for_search_flag
     *     false:DB保存時に使用する変換方法 あいう→あい いう う
     *     true:検索時に使用する変換方法 あいう→あい いう 1文字の場合は空文字を返します(そもそも2文字以上で検索しないとヒットしないので構わない)
     * @return string|null
     */
    public function convert_to_bigram(string $string = null, bool $for_search_flag = false)
    {
        if (is_null($string))
        {
            return null;
        }

        $string = str_replace(array(" ", " ", "\r", "\n", "\t"), "", $string);
        $string = strip_tags($string);
        $character_list = preg_split("//u", $string, -1, PREG_SPLIT_NO_EMPTY);// 1文字づつ配列に分ける

        $bigram = '';// バイグラム

        $glue = '';
        foreach ($character_list as $index => $character)
        {
            if (isset($character_list[$index + 1]))
            {
                $bigram .= $glue.$character.$character_list[$index + 1];
            }
            else
            {
                if ($for_search_flag === false)
                {
                    $bigram .= $glue.$character;
                }
            }
             $glue = ' ';
        }

        return $bigram;
    }

}

$string = "<p><div><a>あ い\t う え\nおか</a></div></p>";
$bigram = new Bigram();
var_dump($bigram->convert_to_bigram($string));
var_dump($bigram->convert_to_bigram($string, true));

結果

string(38) "あい いう うえ えお おか か"
string(34) "あい いう うえ えお おか"

確認

http://sandbox.onlinephpfunctions.com/code/c19755ed0c4e24b889069c286a1e6ec190ca4804

その他

for_search_flagで結果を分けたのは検索時用のバイグラムが必要になったためです。
※バイグラムで検索する際は2文字以上で検索すること

「あいうえおか」から「うえお」で検索する例
DBで持つのは「あい いう うえ えお おか か」

match(`bigram`) against('+"うえ えお" in boolean mode')
ではヒットするが

match(`bigram`) against('+"うえ えお お" in boolean mode')
だとヒットしない