PHPで並び替えをしたいけど、第一ソートと第二ソート、第三ソート・・・で並び替えをしたい時ってどうやればいいんだろう、と昔思ったことがありました。 ということで、今回はそのソートアルゴリズムを考えたいと思います。
<?php class Hoge { public int $x; public int $y; public function __construct(int $x, int $y) { $this->x = $x; $this->y = $y; } } $list = []; $list [] = new Hoge(1, 1); $list [] = new Hoge(1, 2); $list [] = new Hoge(1, 3); $list [] = new Hoge(2, 1); $list [] = new Hoge(2, 2); $list [] = new Hoge(2, 3); $list [] = new Hoge(3, 1); $list [] = new Hoge(3, 2); $list [] = new Hoge(3, 3);
例えば、上記のようなソースコードで、以下のように並び替えたい。
- このような
x
とy
を持ったHoge
クラスがある - 第一ソートにy、第二ソートにxで昇順に並び替えたい
単純な実装
usort
を用いて以下のように実装してみました。
<?php class Hoge { public int $x; public int $y; public function __construct(int $x, int $y) { $this->x = $x; $this->y = $y; } } $list = []; $list [] = new Hoge(1, 1); $list [] = new Hoge(1, 2); $list [] = new Hoge(1, 3); $list [] = new Hoge(2, 1); $list [] = new Hoge(2, 2); $list [] = new Hoge(2, 3); $list [] = new Hoge(3, 1); $list [] = new Hoge(3, 2); $list [] = new Hoge(3, 3); usort($list, function(Hoge $a, Hoge $b) { if ($a->y !== $b->y) { return $a->y <=> $b->y; } return $a->x <=> $b->x; }); print_r($list);
これの実行結果は以下のようになります。
$ php sort.php Array ( [0] => Hoge Object ( [x] => 1 [y] => 1 ) [1] => Hoge Object ( [x] => 2 [y] => 1 ) [2] => Hoge Object ( [x] => 3 [y] => 1 ) [3] => Hoge Object ( [x] => 1 [y] => 2 ) [4] => Hoge Object ( [x] => 2 [y] => 2 ) [5] => Hoge Object ( [x] => 3 [y] => 2 ) [6] => Hoge Object ( [x] => 1 [y] => 3 ) [7] => Hoge Object ( [x] => 2 [y] => 3 ) [8] => Hoge Object ( [x] => 3 [y] => 3 ) )
無事、第一ソート、第二ソートの指定でうまくソートできました。
これだけだと面白くないので、ここからちょっといじっていきたいと思います。
工夫してスマートに実装
工夫してみました。
ちなみに、PHPの実行環境は以下になります。PHP8の新機能も使いたいために、PHP 8.0.0を利用しています。
php -v PHP 8.0.0 (cli) (built: Dec 1 2020 03:33:03) ( NTS ) Copyright (c) The PHP Group Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies with Zend OPcache v8.0.0, Copyright (c), by Zend Technologies with Xdebug v3.0.0, Copyright (c) 2002-2020, by Derick Rethans
以下がソースコードになります。
<?php class Hoge { public function __construct( public int $x, public int $y ){} } $list = []; $list []= new Hoge(1, 1); $list []= new Hoge(1, 2); $list []= new Hoge(1, 3); $list []= new Hoge(2, 1); $list []= new Hoge(2, 2); $list []= new Hoge(2, 3); $list []= new Hoge(3, 1); $list []= new Hoge(3, 2); $list []= new Hoge(3, 3); usort($list, fn(Hoge $a, Hoge $b) => $a->y <=> $b->y ?: $a->x <=> $b->x ); print_r($list);
上記について、解説をしていきます。
コンストラクタのプロモーション
PHP 8.0.0以降で使える、コンストラクタの引数を対応するオブジェクトのプロパティに昇格させる機能です。
どうせコンストラクタに渡している値はプロパティに代入するんでしょってことですね!
ドキュメントのサンプルコードを利用してみると、
<?php class Point { protected int $x; protected int $y; public function __construct(int $x, int $y = 0) { $this->x = $x; $this->y = $y; } }
というのを
<?php class Point { public function __construct(protected int $x, protected int $y = 0) { } }
と書くことができます。
これを利用して、Hoge
クラスの__construct
の中身を空っぽにすることができました。
class Hoge
{
public function __construct(
public int $x,
public int $y
){}
}
三項演算子の省略形
三項演算子を使って第一ソート、第二ソート、・・・を簡単に書いています。
このドキュメントにも書いていますが、
(expr1) ? (expr2) : (expr3) という式は、式1 が true の場合に 式2 を、 式1 が false の場合に 式3 を値とします。
三項演算子のまんなかの部分をなくすこともできます。 式 expr1 ?: expr3 の結果は、expr1 が true と同等の場合は expr1、 それ以外の場合は expr3 となります。
このように、真ん中を省略することができるので、これをうまく利用していきたいと思います。
ここでポイントなのが、expr1 が true と同等の場合は expr1、 それ以外の場合は expr3
と書いてあるtrue と同等
になります。trueと同等ということは、数値だとどうなるか?
上のドキュメントの表にも書いてあるように、関係するところだけピックアップすると以下のようになります。
式 | bool: if($x) |
---|---|
1 | true |
0 | false |
-1 | true |
上の文章をこの表に当てはめて数値に変換してみたいと思います。
三項演算子のまんなかの部分をなくすこともできます。 式 expr1 ?: expr3 の結果は、expr1 が 1または-1 の場合は expr1、 0の場合は expr3 となります。
わかりやすくなりましたね!
すなわち、上で書いた以下のような例だと、
$a->y <=> $b->y ?: $a->x <=> $b->x
$aと$bのyの値を比較して同じ(<=>の結果が0)の場合は、xの値の比較になる、ということになります。
この事によって、
if ($a->y !== $b->y) { return $a->y <=> $b->y; } return $a->x <=> $b->x;
のような書き方を
return $a->y <=> $b->y ?: $a->x <=> $b->x;
と書くことができました。
アロー関数
アロー関数はPHP7.4で追加された機能で、無名関数を簡潔に書ける文法です。
こちらもドキュメントを参考にしますが、
$y = 1; $fn1 = function ($x) use ($y) { return $x + $y; };
このような書き方を
$y = 1;
$fn1 = fn($x) => $x + $y;
と書くことができるようになりました。 これでわざわざreturnを書く必要もなくなり、場合によっては上記の例のようにuseも書く必要がなくなりますね。
これを利用して、usortの第2引数のコールバック関数をアロー関数で書くことができました。
usort($list, fn(Hoge $a, Hoge $b) => $a->y <=> $b->y ?: $a->x <=> $b->x );
このような感じで、シンプルに書くことができました。
さいごに
改めて、スマートな書き方にしてみたソースコードを見てみましょう。
<?php class Hoge { public function __construct( public int $x, public int $y ){} } $list = []; $list []= new Hoge(1, 1); $list []= new Hoge(1, 2); $list []= new Hoge(1, 3); $list []= new Hoge(2, 1); $list []= new Hoge(2, 2); $list []= new Hoge(2, 3); $list []= new Hoge(3, 1); $list []= new Hoge(3, 2); $list []= new Hoge(3, 3); usort($list, fn(Hoge $a, Hoge $b) => $a->y <=> $b->y ?: $a->x <=> $b->x ); print_r($list);
これの実行結果は以下のようになります。
$ php sort.php Array ( [0] => Hoge Object ( [x] => 1 [y] => 1 ) [1] => Hoge Object ( [x] => 2 [y] => 1 ) [2] => Hoge Object ( [x] => 3 [y] => 1 ) [3] => Hoge Object ( [x] => 1 [y] => 2 ) [4] => Hoge Object ( [x] => 2 [y] => 2 ) [5] => Hoge Object ( [x] => 3 [y] => 2 ) [6] => Hoge Object ( [x] => 1 [y] => 3 ) [7] => Hoge Object ( [x] => 2 [y] => 3 ) [8] => Hoge Object ( [x] => 3 [y] => 3 ) )
このようにして、無事シンプルに並び替えをすることができました。