アナログCPU:5108843109

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

指定文字以降を削除する方法と速度検証

「hogehogefugafuga(piyo)」から
「(」以降を削除(=「(」より前を抜き出す)して
「hogehogefugafuga」にしたい

というとき。
まあやり方の想像は付くが、自分が思いつくのより良い案もあるかもしれないのでとりあえずググる

参考:【PHP】指定文字列以降を削除したい - teratail
https://teratail.com/questions/2281

現時点で回答が2つ付いているが要約すると以下のとおり。
①substrとstrcspnを組み合わせる
②strstrを使う

デスヨネー

①substrとstrcspnを組み合わせる

$sample = "hogehogefugafuga(piyo)";
$char = "(";

$str = substr($sample, 0, strcspn($sample, $char));

// hogehogefugafuga
echo str;

仮に $char が $sample に含まれない場合、$str は $sample と同一になります。

②strstrを使う

$sample = "hogehogefugafuga(piyo)";
$char = "(";

$str = strstr($sample, $char, true);

// hogehogefugafuga
echo $str;

いやーこっちの方がシンプルですわ、と思っていたのですが、
いろいろ試しているうちに一つ問題が。
①のパターンとは違い、$char が $sample に含まれない場合、$strは空になってしまいます。

①で使用しているstrcspnが「$charが含まれない長さを返す(含まれなければ全部)」のに対して、
②のstrstrは「文字列が最初に現れる位置を見つける(見つからなければfalse)」からですね。

参考:strcspn - php
http://php.net/manual/ja/function.strcspn.php
参考:strstr - php
http://php.net/manual/ja/function.strstr.php

ということで、それを考慮した版がこちら。

$sample = "hogehogefugafuga[piyo]";
$char = "(";

if (strpos($sample, $char))
{
    $str = strstr($sample, $char, true);
}
else
{
    $str = $sample;
}

// hogehogefugafuga[piyo]
echo $str;

シンプルじゃなくなったー!

一応、三項演算子利用版も。

$sample = "hogehogefugafuga[piyo]";
$char = "(";

$str = strpos($sample, $char) ? strstr($sample, $char, true) : $sample;

// hogehogefugafuga[piyo]
echo $str;

とりあえず一行に戻りましたが、まあ好みによりけりですかね。

速度検証

ということで、

  • ①-1 substrとstrcspnを組み合わせる(合致する文字がある場合)
  • ①-2 substrとstrcspnを組み合わせる(合致する文字がない場合)
  • ②-1 strstrを使う(合致する文字がある場合)
  • ②-2 strstrを使う(if版)(合致する文字がない場合)
  • ②-3 strstrを使う(三項演算子版)(合致する文字がある場合)
  • ②-4 strstrを使う(三項演算子版)(合致する文字がない場合)

で速度検証をしてみました。

以下は検証用コードから抜粋。
各パターンを10万回ずつ繰り返し、それぞれの時間を計測します。
(きったねーので繰り返し部分とか時間計測部分は省略してます)
試行回数は5回。

$char = "(";

-①-1 substrとstrcspnを組み合わせる(合致する文字がある場合)
$sample = "hogehogefugafuga(piyo)";
$str = substr($sample, 0, strcspn($sample, $char));

-①-2 substrとstrcspnを組み合わせる(合致する文字がない場合)
$sample = "hogehogefugafuga[piyo]";
$str = substr($sample, 0, strcspn($sample, $char));

// ②-1 strstrを使う(合致する文字がある場合)
$sample = "hogehogefugafuga(piyo)";
$str = strstr($sample, $char, true);

// ②-2 strstrを使う(if版)(合致する文字がない場合)
$sample = "hogehogefugafuga[piyo]";
if (strpos($sample, $char))
{
    $str = strstr($sample, $char, true);
}
else
{
    $str = $sample;
}

// ②-3 strstrを使う(三項演算子版)(合致する文字がある場合)
$sample = "hogehogefugafuga(piyo)";
$str = strpos($sample, $char) ? strstr($sample, $char, true) : $sample;

// ②-4 strstrを使う(三項演算子版)(合致する文字がない場合)
$sample = "hogehogefugafuga[piyo]";
$str = strpos($sample, $char) ? strstr($sample, $char, true) : $sample;

結果がこちら。単位は秒(小数点第4位切り捨て)。

①-1 ①-2 ②-1 ②-2 ②-3 ②-4
1回目 0.059 0.060 0.032 0.027 0.060 0.028
2回目 0.060 0.060 0.031 0.026 0.060 0.028
3回目 0.059 0.062 0.031 0.027 0.060 0.028
4回目 0.059 0.060 0.031 0.027 0.060 0.028
5回目 0.059 0.060 0.032 0.027 0.060 0.028

※これは10万回ずつ繰り返した際の時間なので、1回あたりでは0.3~0.6マイクロ秒、という世界の話です

指定文字が必ず含まれるのであれば②-1が最善のようです。
①に比べ実行速度が半分で済みますし、コードもシンプルです。
が、条件分岐を入れると①と同じくらいの実行速度になりますね。

含まれると限らない場合(②-2~4)は…
記述としては①がシンプルですが、含まれない場合の実行速度は②の方が速いです。
含まれているときはほぼ同じ速度ですね。
ということで、
含まれる可能性が高ければシンプルさを取って①、
含まれない可能性が高ければ速度を取って②…という感じでよいのではないかと思います。

ついでに他に気付くことといえば、↓のような感じですかね。

  • ①-1と①-2では①-1(パターン)の方がわずかに速い
    • たぶんstrcspnの文字列検索で先頭から順に見ているから。試してないけど文字列の最初でヒットするのが最速なんじゃないかと。
  • 三項演算子よりifの方が0.01マイクロ秒程度速い
    • だから何、ってレベルの違い。

尚、処理時間計測は以下ロジックで行っています。honey8823.hateblo.jp


<追記>
別案としてexplodeを使う案をいただいたのでとりあえず検証。

$char = "(";
$sample = "hogehogefugafuga(piyo)";

// ※この場合は$str[0]が欲しい値になる
$str = explode($char, $sample);

$char が $sample に含まれるかどうかに関わらず、10万回あたり0.054秒でした。
①より速いので、場合によってはアリですね。

<さらに追記>
explodeに第3引数を与えるとさらに速くなるのでは、とご意見いただきました。
複数含まれる可能性がある場合は特にそうですね。


ちなみに今回は含まれない可能性の方がかなり高いので②-3/4を採用しました。