[PHP]usort関数の罠にハマった話

公開日:2018-12-15
最終更新:2018-12-16

はじめに

usort関数が出てきた初見、リーダブルさも何もなくて憤りを感じました。
なんなんでしょうかこの関数。
ただ、その性質を理解すればかなり使いやすい関数な気もします。
usort関数の罠にハマった私のメモです。

usort関数とは

<?php  

$list = [12, 24, 9];  
usort($list, 'userDefMethod');  

function userDefMethod($a, $b) {  
  //strcmpなどバイナリな結果を返す処理  
}  

基本的な使い方

第一引数に配列、第二引数に配列を比較するユーザー定義の比較関数を指定します。
第二引数には直接比較処理を書くこともできるそうですが、usortの性質上複雑な条件を用いたソートに使用するため、あんまり出番はないかな。

ハマりポイン

結論です。
usort関数はユーザー定義関数から受け取った結果を昇順で並び替えます。
ここで私は誤解していてユーザー定義関数の結果をそのまま使ってソートするものだと考えていました。
なのでユーザー定義関数で降順で出力した結果(追記:①)が、昇順に入れ替わって出てきて、あっれー?となったわけです。しかも複雑な条件式?を用いていたので結果と入れ替え結果が直感的でないので気付きにくい...精進していきたいものです。

追記:①
ユーザー定義の比較関数・strcmp関数自体は昇順・降順関係なく1か-1を返しているだけ。順番の繰り下げ繰り上げが発生するのみで降順とか昇順は決まっていない。usortで初めて昇順で出力される。

実例

<?php  
//与えられた配列の数字を並び替えて可能な限り最大の値を作る処理  
$list = array(12, 24, 9);  

echo '<pre>';  

//$listをcompで処理して返り値から昇順でソートを実施する。  
usort($list, 'comp');  

//implodeで配列を文字列に結合して出力。  
print_r(implode('', $list));  
echo '</pre>';  

//usortの並び替え条件の関数。これはusortの性質上  
//並び替えに必要な情報が揃うまで比較は実施される見たいです。  
function comp($int1, $int2) {  
//あとでstrcmpで比較するので  
//結合演算子を使用するのでに型キャストしてます。  
  $str1 = (string)$int1;  
  $str2 = (string)$int2;  
//同じなら並び替えは発生しない処理  
  if ($str1 === $str2) {  
    return 0;  
  }  
//以下に詳細  
  return (-1 * strcmp($str1 . $str2, $str2 . $str1));  
}  

=>92412  

今回の配列の場合比較は2回実施されて結果が返りusortによってソートが行われる。
1回目の処理

str1は"24"  
str2は"12"  
-1 * strcmp($str1 . $str2, $str2 . $str1)というのは  
-1 * strcmp("2412", "1224")の比較した結果  
=> -1 で並び替えが発生して [12, 24]  

2回目

str1は"9"  
str2は"24"  
-1 * strcmp($str1 . $str2, $str2 . $str1)というのは  
-1 * strcmp("924", "249")の比較した結果  
=> -1 で並び替えが発生して [24, 9]  

よってユーザー定義の比較関数の結果降順で
[12, 24, 9]という結果が返り値となる。
usortで昇順に並び替えが起きる。
よって[9, 24, 12]になり、92412という最大値が出力される。

要検証

まだまだわからないことがあります。およそこの2点。
①usortに渡したユーザー比較関数の数字の取り出し方。
上記の例でいうと int1 と int2 を配列から取り出すとき、このような順番になります。

$list = array(12, 24, 9);    
//~~~~~~~~~~~~~~~~~~~~~~~~~~//  
function comp($int1, $int2) {    
  $str1 = (string)$int1;    
    //str1はstring(2) "24"  
  $str2 = (string)$int2;    
    //str2はstring(2) "12"  
  if ($str1 === $str2) {    
    return 0;    
  }    
  return (strcmp($str1, $str2));    
}    

なぜこの順番なのだろう。

②strcmpの挙動が完全にわかりません。

<?php  

$list = array(12, 24, 9);  
echo '<pre>';  
usort($list, 'comp');  
print_r($list);  
echo '</pre>';  

function comp($int1, $int2) {  
  $str1 = (string)$int1;  
  $str2 = (string)$int2;  
  if ($str1 === $str2) {  
    return 0;  
  }  
  return (strcmp($str1, $str2));  
}  

予想では[9, 12, 24]という並びになるはずなのですが
結果は[12, 24, 9]でした。文字のバイト数で比べてるのかな??
分かる方おられましたらコメント頂ければ幸いです。
ちなみにstrcmpを使わずに三項演算子で比較すると問題ありません。

<?php  

$list = array(12, 24, 9);  
echo '<pre>';  
usort($list, 'comp');  
print_r($list);  
echo '</pre>';  

function comp($int1, $int2) {  
  if ($int1 === $int2) {  
    return 0;  
  }  
  return ($int1 < $int2 ? -1 : 1 );  
    //int2の方がint1より大きければ(trueならば)  
    //この二つの数を入れ替える(-1)。  
}  

=>答え  
Array  
(  
    [0] => 9  
    [1] => 12  
    [2] => 24  
)  

PHPは奥が深くて面白いですね。

記事が少しでもいいなと思ったらクラップを送ってみよう!
18
+1
@yu9penguinの技術ブログ

よく一緒に読まれている記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる

技術ブログをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

技術ブログを開設する

Qrunchでアウトプットをはじめよう

Qrunch(クランチ)は、ITエンジニアリングに携わる全ての人のための技術ブログプラットフォームです。

Markdownで書ける

ログ機能でアウトプットを加速

デザインのカスタマイズが可能

技術ブログ開設

ここから先はアカウント(ブログ)開設が必要です

英数字4文字以上
.qrunch.io
英数字6文字以上
ログインする