Help us understand the problem. What is going on with this article?

【PHP】 空白文字で文字列を分割して検索用キーワードの配列を作る

これは何?

データベースの LIKE 検索用途とかでキーワードを抽出するやつ。
例えば

PHP Qiita Laravel

と入力したら,

PHP Qiita Laravel

として取り出す関数を作ります。

実装

実装したい関数の形を示し, 2 つの異なるアプローチでの実装例を記載します。

関数シグネチャ

$input に入力文字列を渡し, $limit にキーワード数制限を指定します。 -1 を指定した場合は無制限になり,これをデフォルトとします。

function extractKeywords(string $input, int $limit = -1): array

手法の比較

※ 「手法」の名称は一般的に提唱されているものではありません

手法 説明 よく使われる関数
空白分割法 空白文字で分割し,残った部分をキーワードとする。キーワード数制限を超過する場合,最後の1つの中に空白文字を含む残りのすべてが含まれる。 preg_split()
explode()
非空白抽出法 空白文字ではない部分だけを直接キーワードとして抽出する。キーワード数制限を超過する場合,単に超過分は無視される。 preg_match_all()
preg_replace_callback()

実装が簡単なのは空白分割法のほうですが,キーワード数制限超過時の挙動がそれぞれ異なるので注意してください。例えば a b c d e において $limit = 3 で抽出する場合,

  • 空白分割法の場合, a b c d e となる
  • 非空白抽出法の場合, a b c となる

という違いがあります。

空白分割法

半角スペースで分割

一番シンプルな実装。 explode() を使う手法もありますが, $limit の適用や空文字列の除外を考えると,こちらのほうが基本的に上位互換です。

function extractKeywords(string $input, int $limit = -1): array
{
    return preg_split('/ ++/', $input, $limit, PREG_SPLIT_NO_EMPTY);
}

あらゆる空白文字で分割

半角スペースの他に全角スペース,改行,タブ,ノーブレークスペースなどあらゆる空白系の制御文字を対象とする場合はこちら。 \p{Z} は ASCII 範囲にある制御文字の集合, \p{Cc} は Unicode 範囲にある制御文字の集合を表しています。またこれらを適用するためには u フラグが必須になります。

function extractKeywords(string $input, int $limit = -1): array
{
    return preg_split('/[\p{Z}\p{Cc}]++/u', $input, $limit, PREG_SPLIT_NO_EMPTY);
}

非空白抽出法

半角スペース以外を抽出

function extractKeywords(string $input, int $limit = -1): array
{
    $matches = [];
    preg_replace_callback(
        '/[^ ]++/',
        function (array $match) use (&$matches) {
            $matches[] = $match[0];
        },
        $input,
        $limit,
        $_,
        PREG_SET_ORDER
    );
    return $matches;
}

「なんで preg_match_all() じゃないんだ!?」

が素直な感想だと思います。 preg_replace_callback() で参照代入を使うみたいな変なことをやっているのは, $limit 適用に対応するためです。 preg_match_all() にはマッチング回数を制限する仕組みがありません。

あらゆる空白文字以外を抽出

function extractKeywords(string $input, int $limit = -1): array
{
    $matches = [];
    preg_replace_callback(
        '/[^\p{Z}\p{Cc}]++/u',
        function (array $match) use (&$matches) {
            $matches[] = $match[0];
        },
        $input,
        $limit,
        $_,
        PREG_SET_ORDER
    );
    return $matches;
}

パターンの部分を変えるだけで全体的な体裁は同じですね。

ダブルクオーテーションで括った部分は保持したまま,あらゆる空白文字以外の部分を抽出

こちらは,非空白抽出法でしか実装できません。

Laravel "Taylor Otwell" PHP と入力したら Laravel Taylor Otwell PHP として欲しい場合はこちら。 厳密にイコールではないですが,ある程度 Google 検索っぽい動きになります。

function extractKeywords(string $input, int $limit = -1): array
{
    $matches = [];
    preg_replace_callback(
        '/""(*SKIP)(*FAIL)|"([^"]++)"|([^"\p{Z}\p{Cc}]++)/u',
        function (array $match) use (&$matches) {
            $matches[] = $match[2] ?? $match[1];
        },
        $input,
        $limit,
        $_,
        PREG_SET_ORDER
    );
    return $matches;
}

ダブルクオーテーションで括られた空文字列は, PCRE 特有の機能である (*SKIP)(*FAIL) でマッチングを強制終了して $limit 適用の際に 1 キーワードとしてカウントしない,など少し気を利かせています。

補足

重複を削除する

もし重複したキーワードを削除する場合は, array_unique() + array_values() の処理を組み合わせてください。以下に使用率の高そうな,適用版の一部を記載しておきます。

あらゆる空白文字で分割し,重複を除外する
function extractKeywords(string $input, int $limit = -1): array
{
    return array_values(array_unique(preg_split('/[\p{Z}\p{Cc}]++/u', $input, $limit, PREG_SPLIT_NO_EMPTY)));
}
ダブルクオーテーションで括った部分は保持したまま,あらゆる空白文字以外の部分を抽出し,重複を除外する
function extractKeywords(string $input, int $limit = -1): array
{
    $matches = [];
    preg_replace_callback(
        '/""(*SKIP)(*FAIL)|"([^"]++)"|([^"\p{Z}\p{Cc}]++)/u',
        function (array $match) use (&$matches) {
            $matches[] = $match[2] ?? $match[1];
        },
        $input,
        $limit,
        $_,
        PREG_SET_ORDER
    );
    return array_values(array_unique($matches));
}
mpyw
PHP(Laravel) / JavaScript(React/Redux/ReactNative/Vue) / MySQL あたりが得意分野なWeb系エンジニア。最近マンネリ化がひどいので Go / Kotlin / Rust / Swift あたりから何か掘り下げたいと思っている。Go は 2.x 出てから書きます。古い記事はそのまま参考にしないようにご注意ください
http://gravatar.com/mpyw
synapse
Synapseは、オンラインサロンサービスにおけるパイオニアとして、かつて存在していたスタートアップです。
https://synapseam.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした