RitoLabo

PHPによる文字列比較の基本形~確実な比較と落し穴ポイント~

  • 公開:
  • カテゴリ: PHP Basics
  • タグ: PHP,Basics,strcmp,TypeJuggling

PHPで文字列比較を行うには、どのような実装を行っていけばよいでしょうか。今回は、PHPによる文字列比較の基本形を見ていきます。

アジェンダ
  1. 「値を比較する」という極めてベーシックな処理の中に潜む落し穴
  2. strcmp()
  3. strcasecmp()
  4. strncmp()
  5. strncasecmp()
  6. strnatcmp()
  7. strnatcasecmp()

「値を比較する」という極めてベーシックな処理の中に潜む落し穴

PHPに限らず、プログラミングにおいて「数値を比較する」というのは最もポピュラーで基本的な処理の一つです。

では、「文字列を比較する」とはどういう事でしょうか?

「2つの文字列が同じか、そうでないか」が、文字列比較を行う最もあるあるな理由ではあります。そしてその場合、多くの人はこんな感じで比較を行うでしょう。

var_dump( !!( "こんにちは" === "こんにちは" ) );
// => boolean true

var_dump( !!( "こんにちは" === "こんにちわ") );
// => boolean false

比較演算子を用いて、その文字列同士が等しいかどうかを調べています。

もちろんこれでも比較は行えます。ですが反面、この場合は、「同じか同じでないか」だけが結果として返ります。そして、比較演算子での比較には一つ落し穴があります。片側が数値の場合の以下では、結果はどうなるでしょう。

var_dump( !!( "40こんにちは" == 40) );
// => boolean true

実は上記のような場合は、真(true)が返ります。これはPHP特有のType Juggling(タイプジャグリング)、つまり型の相互変換が行われる事で双方の比較が行われますが、さらに比較演算子が「==」の場合は型比較が行われないので、タイプジャグリング後、文字列が数値に変換され、数値としての比較が行われます。結果、左オペランドは数値として有効な「40」のみが比較対象となり、結果として真が返る事となります。

今回のテーマは文字列比較の中、敢えて数値を持ち出したのは、文字列同士でなくても、比較対象のどちらか一方でも文字列型の場合、感覚的に比較演算子だけで比較を行っていると、どこかで地雷を踏む可能性がある。という事です。

そんな中、PHPには、もっと正確に文字列比較を行える関数が用意されています。

文字列比較を行う関数の代表格としてstrcmp系の関数があります。これらは全てバイナリセーフで比較を行う事が出来ます。

これらは全て、PHP4以降から使用できる枯れに枯れた関数になります。つまり、超ベーシック且つレガシー且つレジェンドです。

strcmp()

strcmp()は文字列比較を行う関数です。

strcmp ( string $str1 , string $str2 )

引数に2つの文字列を取り、第一引数に対して第二引数を用いての比較が行われます。

結果としては以下の値が返ります。

  • 第一引数より第二引数が大きい場合は < 0(マイナスの値)
  • 第一引数より第二引数が小さい場合は > 0(プラスの値)
  • 第一引数と第二引数が同一の場合は 0(ゼロ)

詰まるところ、「0」が返れば「等しい」という事になりますが、「大きい」「小さい」というのは、バイト列の差になります。

また、先頭の文字列同士から順に比較が行われ、同一ではなかった時点で処理は終了し、差を返します。

つまりは、「AAAA」と「AABA」を比較した場合、3文字目で差が出るので処理が停止し、その差を返します。

var_dump(strcmp("40こんにちは", "40"));
// => 15

var_dump(strcmp("こんにちは", "こんにちは"));
// => 0

var_dump(strcmp("こんにちは", "こんにちわ"));
// => -1

var_dump(strcmp("\0a", "\0")); // バイナリセーフです
// => 1

var_dump(strcmp("ABC", "abc")); // 大文字と小文字を区別します
// => -32

strcasecmp()

strcasecmp()はstrcmp()と同等の機能を持ちますが、唯一違う部分として、こちらは大文字と小文字を区別しません。

var_dump(strcasecmp("ABC", "abc")); // 大文字と小文字を区別しません
// => 0

strncmp()

strncmp()は、指定した○文字目について文字列比較を行います。

var_dump(strncmp("ABC", "abc", 1)); //1文字目について比較します。
// => -32

大文字と小文字は区別するので、「a」よりも「A」の方が小さい という結果が返ります。

strncasecmp()

strncasecmp()は、strncmp()と同等の機能を持ちますが、こちらは大文字と小文字を区別しません。

var_dump(strncasecmp("ABC", "abc", 1));  //1文字目について比較します。
// => 0

等しい結果としての「0」が返されている事が確認できます。

strnatcmp()

strnatcmp()は、自然順アルゴリズムによる文字列比較を行います。

「自然順」というのは、数値に関するアルゴリズムですが、例えば、パソコンでフォルダを開いて名前の順に並べ替えた時に、こんな経験をした事もあると思います。

filename_1.php
filename_10.php
filename_2.php
filename_3.php

そうです。本当なら1から順番に並んでほしいのに、10が先にきてしまうあれです。自然順アルゴリズムでは、比較対象に数値を含む場合は10進整数部分文字列は数値で比較されます。

// 通常の文字列比較です
var_dump(strcmp("a1", "a2"));
// => -256

var_dump(strcmp("a2", "a10"));
// => 256

// 自然数アルゴリズムを用いた文字列比較です。
var_dump(strnatcmp("a1", "a2"));
// => -1

var_dump(strnatcmp("a2", "a10"));
// => -1

通常の文字列比較では a1 < a10 < a2 という結果になりますが、自然数アルゴリズムを用いるstrnatcmp()を使えば、a1 < a2 < a10 という結果を得る事が出来ます。

strnatcasecmp()

strnatcasecmp()は、strnatcmp()と同じく自然アルゴリズムを用いて文字列比較を行いますが、こちらは大文字と小文字を区別しません。

var_dump(strnatcasecmp("a1", "A2"));
// => -1

var_dump(strnatcasecmp("a2", "A10"));
// => -1

1つ目は、「a」よりも「A」の方が小さいはずですが、大文字と小文字を区別しないので、マイナスが返っている事が確認できます。

2つ目はさらに、自然数アルゴリズムによって、2 < 10の評価結果が返されている事が確認できます。

まとめ

strcmp系の関数は、比較する文字列がシングルバイトエンコーディングで符号化されている前提で処理を行うので、日本語など、文字列内の各バイトが必ずしも特定の文字に変換できなくても気にせずに純粋にバイト列を比較します。つまりは、細かい単位(バイト列)で比較を行う事が出来ます。

堅実ながら微妙に知られていないこれらの関数を用いれば、文字列比較もがっちり安定して実装出来ます。是非試してみてください。