【PHP】usortとは?配列のカスタムソート方法と使い方入門

はい、承知いたしました。【PHP】usortとは?配列のカスタムソート方法と使い方入門 の詳細な説明を含む記事を作成します。記事の内容を直接表示します。


【PHP】usortとは?配列のカスタムソート方法と使い方入門

はじめに:配列のソート、その重要性と限界

PHPにおいて、配列は最も基本的なデータ構造の一つであり、様々な情報を格納し、操作するために頻繁に利用されます。プログラムの多くは、配列に格納されたデータを特定の順序で処理したり、表示したりする必要があります。例えば、商品のリストを価格順に並べ替えたり、ユーザーリストを名前のアルファベット順に並べ替えたり、ブログ記事を日付順に並べ替えたりといった具合です。

PHPには、このような配列のソート(並べ替え)を行うための豊富な標準関数が用意されています。代表的なものとしては、以下の関数が挙げられます。

  • sort():配列の値を昇順にソートします。キー(インデックス)は破棄され、再割り当てされます。
  • rsort():配列の値を降順にソートします。キーは破棄され、再割り当てされます。
  • asort():配列の値を昇順にソートします。キーと値の関連は維持されます。
  • arsort():配列の値を降順にソートします。キーと値の関連は維持されます。
  • ksort():配列のキーを昇順にソートします。キーと値の関連は維持されます。
  • krsort():配列のキーを降順にソートします。キーと値の関連は維持されます。
  • natsort():”自然順アルゴリズム” を使用して配列の値をソートします(数値を含む文字列のソートに適しています)。キーは破棄され、再割り当てされます。
  • natcasesort()natsort() と同様ですが、大文字小文字を区別しません。キーは破棄され、再割り当てされます。

これらの標準ソート関数は、数値、文字列、またはこれらの組み合わせといった比較的単純なデータ型が配列に格納されている場合に非常に便利です。ほとんどの基本的なソート要件はこれらの関数で満たされるでしょう。

しかし、現実世界のプログラミングでは、配列に格納されるデータは常に単純とは限りません。配列が多次元配列(連想配列の配列など)であったり、オブジェクトの配列であったりすることも多いです。また、ソートの基準が単純な値の比較ではなく、複数の条件を組み合わせたり、独自の複雑なロジックに基づいたりする場合もあります。

例えば、以下のようなケースを考えてみましょう。

  • ユーザー情報の配列(各要素が ‘name’, ‘age’, ‘city’ などのキーを持つ連想配列)を、’city’ でまずソートし、同じ ‘city’ の中では ‘age’ でソートしたい。
  • 商品オブジェクトの配列を、在庫数と価格を考慮した独自の優先度でソートしたい。
  • 文字列の配列を、文字列長が短い順にソートしたい。

このような場合、PHPの標準ソート関数だけでは対応できません。標準関数はあらかじめ定義されたルール(数値として、文字列として、キーとしてなど)に基づいてしかソートできないからです。ここで必要となるのが、ユーザーが独自の比較ルールを定義してソートを行う「カスタムソート」です。

PHPには、カスタムソートを実現するための関数として、usort()uasort()uksort() が用意されています。この記事では、これらのカスタムソート関数の中でも最も基本的で汎用性の高い usort() に焦点を当て、その詳細な使い方、仕組み、そして様々な具体的な例を通じて、PHPにおける配列のカスタムソート方法をマスターすることを目指します。

PHPにおける配列のソートの基本と限界(再確認)

カスタムソートの必要性をより深く理解するために、まずはPHPの標準ソート関数の振る舞いとその限界をもう少し詳しく見てみましょう。

標準ソート関数の振る舞い

  • sort() / rsort(): これらの関数は、配列ののみを比較して並べ替えます。並べ替え後、元の数値キー(インデックス)は破棄され、0 から始まる新しい数値キーが自動的に割り当てられます。連想配列に使用した場合、キーと値の関連は失われます。
    “`php
    $fruits = [‘lemon’, ‘orange’, ‘banana’, ‘apple’];
    sort($fruits); // $fruits は [‘apple’, ‘banana’, ‘lemon’, ‘orange’] になります
    // キーは 0, 1, 2, 3 になります
    print_r($fruits);
    /
    Array
    (
    [0] => apple
    [1] => banana
    [2] => lemon
    [3] => orange
    )
    /

    $data = [‘a’ => ‘lemon’, ‘b’ => ‘orange’, ‘c’ => ‘banana’, ‘d’ => ‘apple’];
    sort($data); // $data は [‘apple’, ‘banana’, ‘lemon’, ‘orange’] になります
    // キーは 0, 1, 2, 3 になり、元の ‘a’, ‘b’, ‘c’, ‘d’ は失われます
    print_r($data);
    /
    Array
    (
    [0] => apple
    [1] => banana
    [2] => lemon
    [3] => orange
    )
    /
    * **`asort()` / `arsort()`**: これらの関数も配列の**値**を比較して並べ替えますが、元の**キーと値の関連を維持**します。連想配列を値でソートする場合によく使われます。php
    $data = [‘a’ => ‘lemon’, ‘b’ => ‘orange’, ‘c’ => ‘banana’, ‘d’ => ‘apple’];
    asort($data); // $data は [‘d’ => ‘apple’, ‘c’ => ‘banana’, ‘a’ => ‘lemon’, ‘b’ => ‘orange’] になります
    // キー ‘d’, ‘c’, ‘a’, ‘b’ はそれぞれの値と結びついたままです
    print_r($data);
    /
    Array
    (
    [d] => apple
    [c] => banana
    [a] => lemon
    [b] => orange
    )
    /
    * **`ksort()` / `krsort()`**: これらの関数は配列の**キー**を比較して並べ替えます。キーと値の関連は維持されます。php
    $data = [‘b’ => ‘orange’, ‘a’ => ‘lemon’, ‘d’ => ‘apple’, ‘c’ => ‘banana’];
    ksort($data); // $data は [‘a’ => ‘lemon’, ‘b’ => ‘orange’, ‘c’ => ‘banana’, ‘d’ => ‘apple’] になります
    // キー ‘a’, ‘b’, ‘c’, ‘d’ の順に並びます
    print_r($data);
    /
    Array
    (
    [a] => lemon
    [b] => orange
    [c] => banana
    [d] => apple
    )
    /
    “`

標準ソート関数の限界

これらの標準関数は、主に以下の点において限界があります。

  1. 比較基準の固定: 比較は値(またはキー)そのものに対して行われ、そのデータ型に応じたデフォルトの比較(数値比較、文字列比較)が使用されます。カスタムの比較ロジックを適用することはできません。
  2. 複雑なデータ構造への不対応: 多次元配列の特定の要素の値や、オブジェクトの特定のプロパティの値に基づいてソートすることは直接できません。例えば、以下のような配列を ‘age’ の若い順にソートすることは、sort()asort() ではできません。
    php
    $users = [
    ['name' => 'Alice', 'age' => 30, 'city' => 'Tokyo'],
    ['name' => 'Bob', 'age' => 25, 'city' => 'Osaka'],
    ['name' => 'Charlie', 'age' => 35, 'city' => 'Tokyo'],
    ];
    // この配列を 'age' でソートしたい
  3. 複数条件でのソートの困難さ: 複数の基準(例えば、まず都市でソートし、次に年齢でソートする)を組み合わせてソートすることは、標準関数では単一の呼び出しで行えません(複数のソート関数を組み合わせるなどの工夫が必要になる場合がありますが、複雑になりがちです)。

このような限界を超えるために、カスタムソート関数 usort() が存在します。usort() は、ソートの比較ロジックをユーザーが自由に定義できるという点で、これらの問題を解決します。

カスタムソートとは?なぜ usort が必要なのか?

カスタムソートとは、配列の要素を並べ替える際に、PHPにあらかじめ定義されている比較ルールではなく、プログラマー自身が定義した独自の比較ルール(ロジック)に基づいて行うソートのことです。

そして、PHPでカスタムソートを実現するための主要な関数が usort() です。usort() は “User-defined sort” を意味します。

usort が必要な理由

前述の標準ソート関数の限界を踏まえると、usort() が必要となる具体的な状況は以下のようになります。

  • 複雑なデータ構造のソート: 多次元配列(例: 連想配列の配列)やオブジェクトの配列を、特定のキーの値やプロパティの値に基づいてソートしたい場合。
  • 独自の比較ロジックの適用: 値の大小や辞書順といった標準的な比較ではなく、文字列長、日付、計算結果、あるいは複数の条件を組み合わせた複雑なロジックで要素間の順序を決定したい場合。
  • 柔軟なソート順序の指定: 昇順・降順だけでなく、特定の条件を満たす要素を優先させるといった、より複雑な順序指定を行いたい場合。

usort() を使うことで、これらのニーズに柔軟に対応できるようになります。その鍵となるのが、「比較関数(Comparator Function)」です。usort() は、このユーザー定義の比較関数を使って、配列内の任意の2つの要素を受け取り、それらの相対的な順序を判断します。

usort 関数の詳細解説

それでは、usort() 関数の具体的な使い方とその仕組みについて詳しく見ていきましょう。

usort() 関数の基本書式

php
bool usort ( array &$array , callable $callback )

  • $array:
    • ソートしたい対象の配列を指定します。
    • この引数は参照渡し& が付いていることに注目)である点に注意が必要です。つまり、usort() 関数内で配列が直接変更され、関数の呼び出し後にはソート済みの状態になっています。関数が新しい配列を返すわけではありません。
  • $callback:
    • 要素間の比較を行うためのユーザー定義の関数を指定します。これがカスタムソートの心臓部となります。
    • この引数には callable 型を指定します。callable とは、「呼び出し可能なもの」を意味し、具体的には以下のいずれかを指定できます。
      • 通常の関数名(文字列)。
      • 無名関数(クロージャ)。
      • クラスの静的メソッドを示す配列(['ClassName', 'methodName'])。
      • オブジェクトのメソッドを示す配列([$object, 'methodName'])。
      • アロー関数(PHP 7.4+)。
  • 戻り値:
    • ソートが成功した場合は true、失敗した場合は false を返します。通常、ソートが失敗することは稀ですが、コールバック関数が無効であるなどの場合に発生する可能性があります。

比較関数 ($callback) の役割と規約

usort() の最も重要な部分はこの $callback で指定する比較関数です。usort() は、ソート処理の中で配列内の任意の2つの要素を選び出し、この比較関数に渡します。比較関数は、受け取った2つの要素の相対的な順序を判断し、その結果を数値で返します。

比較関数は、以下の書式で定義する必要があります。

php
int comparison_function ( mixed $a , mixed $b )

  • 引数:
    • $a: 比較対象となる最初の配列要素。
    • $b: 比較対象となる二番目の配列要素。
  • 戻り値:

    • 比較関数は、$a$b の比較結果に基づいて、整数値を返さなければなりません。この戻り値によって、usort() は2つの要素のどちらを先に配置すべきかを判断します。戻り値には以下の規約があります。

      • $a$b より小さい場合: 負の整数を返す。
      • $a$b より大きい場合: 正の整数を返す。
      • $a$b等しい場合: 0 を返す。
    • 戻り値に関する注意(PHPバージョンによる違い):

      • PHP 7.0 より前のバージョンでは、厳密に -1, 0, 1 を返す必要がありました。
      • PHP 7.0 以降のバージョンでは、負の整数、ゼロ、正の整数であれば、その絶対値は任意で構いません(例: $a < $b の場合に -100 を返しても動作します)。しかし、多くのソートアルゴリズムとの互換性や可読性を考慮すると、-1, 0, 1 を返すのが一般的であり、推奨されるスタイルです。
      • 特に、<=> 演算子(宇宙船演算子、PHP 7.0+)を使うと、この -1, 0, 1 という戻り値を簡単に生成できます。後述の例で詳しく解説します。

比較関数の実装方法

比較関数は、様々な方法で実装し、usort() に渡すことができます。

  1. 通常の関数として定義:

    “`php
    function compareElements($a, $b) {
    if ($a == $b) {
    return 0;
    }
    return ($a < $b) ? -1 : 1;
    // または PHP 7.0+ では return $a <=> $b;
    }

    $data = [3, 1, 4, 1, 5, 9, 2, 6];
    usort($data, ‘compareElements’); // 関数名を文字列で渡す
    print_r($data); // [1, 1, 2, 3, 4, 5, 6, 9]
    “`

  2. 無名関数(クロージャ)として定義:
    ソートが必要な箇所で比較関数をインラインで定義できるため、コードの局所性が高まります。外部のスコープにある変数を使用したい場合は、use キーワードを使用します。

    “`php
    $data = [3, 1, 4, 1, 5, 9, 2, 6];
    usort($data, function($a, $b) {
    if ($a == $b) {
    return 0;
    }
    return ($a < $b) ? -1 : 1;
    // または PHP 7.0+ では return $a <=> $b;
    });
    print_r($data); // [1, 1, 2, 3, 4, 5, 6, 9]

    // 外部変数を使う例 (use キーワード)
    $sortOrder = ‘desc’;
    $data = [3, 1, 4, 1, 5, 9, 2, 6];
    usort($data, function($a, $b) use ($sortOrder) {
    if ($sortOrder === ‘desc’) {
    return ($a < $b) ? 1 : -1; // 降順
    // または PHP 7.0+ では return $b <=> $a;
    } else {
    return ($a < $b) ? -1 : 1; // 昇順
    // または PHP 7.0+ では return $a <=> $b;
    }
    });
    print_r($data); // [9, 6, 5, 4, 3, 2, 1, 1]
    “`

  3. アロー関数(PHP 7.4+)として定義:
    無名関数をさらに簡潔に書ける記法です。アロー関数内では、外部スコープの変数が自動的に $this と同様に利用可能になるため、use キーワードは不要です。

    “`php
    $data = [3, 1, 4, 1, 5, 9, 2, 6];
    usort($data, fn($a, $b) => $a <=> $b); // 昇順 (PHP 7.0+ & 7.4+)
    print_r($data); // [1, 1, 2, 3, 4, 5, 6, 9]

    $sortOrder = ‘desc’;
    $data = [3, 1, 4, 1, 5, 9, 2, 6];
    usort($data, fn($a, $b) => ($sortOrder === ‘desc’) ? ($b <=> $a) : ($a <=> $b)); // 昇順/降順 (PHP 7.0+ & 7.4+)
    print_r($data); // [9, 6, 5, 4, 3, 2, 1, 1]
    “`
    アロー関数は非常に簡潔に比較関数を記述できるため、短い比較ロジックの場合に特に便利です。

  4. クラスのメソッドとして定義:
    特定のクラスに関連するデータをソートする場合や、複雑な比較ロジックをクラス内にカプセル化したい場合に有用です。静的メソッドまたはインスタンスメソッドを使用できます。

    “`php
    class DataSorter {
    // 静的メソッド
    public static function compareNumbers($a, $b) {
    return $a <=> $b; // PHP 7.0+
    }

    // インスタンスメソッド (特定のプロパティや状態に依存する場合)
    private $sortOrder;
    
    public function __construct($sortOrder = 'asc') {
        $this->sortOrder = $sortOrder;
    }
    
    public function compareStringsLength($a, $b) {
        $lenA = strlen($a);
        $lenB = strlen($b);
        if ($this->sortOrder === 'desc') {
            return $lenB <=> $lenA; // 文字列長で降順
        } else {
            return $lenA <=> $lenB; // 文字列長で昇順
        }
    }
    

    }

    $data = [3, 1, 4, 1, 5, 9, 2, 6];
    usort($data, [‘DataSorter’, ‘compareNumbers’]); // 静的メソッドを指定
    print_r($data); // [1, 1, 2, 3, 4, 5, 6, 9]

    $strings = [‘apple’, ‘banana’, ‘lemon’, ‘orange’];
    $sorter = new DataSorter(‘desc’);
    usort($strings, [$sorter, ‘compareStringsLength’]); // インスタンスメソッドを指定
    print_r($strings); // [‘banana’, ‘orange’, ‘lemon’, ‘apple’] (文字列長の降順)
    “`

宇宙船演算子 (<=>) について (PHP 7.0+)

PHP 7.0で導入された宇宙船演算子 (<=>) は、3方向比較演算子とも呼ばれ、比較関数の戻り値を簡潔に記述するために非常に役立ちます。

書式: ($a <=> $b)

戻り値:
* $a$b より小さい場合: -1
* $a$b より大きい場合: 1
* $a$b が等しい場合: 0

これは、比較関数が必要とする戻り値の規約(負、正、ゼロ)と完全に一致します。したがって、単純な昇順ソートを行う比較関数は、以下のように非常に簡潔に書くことができます。

php
function compareAscending($a, $b) {
return $a <=> $b;
}
// または無名関数で
fn($a, $b) => $a <=> $b // PHP 7.4+

降順にしたい場合は、比較対象の順序を逆にします。

php
function compareDescending($a, $b) {
return $b <=> $a; // $b と $a を逆にする
}
// または無名関数で
fn($a, $b) => $b <=> $a // PHP 7.4+

<=> 演算子は、数値、文字列、配列、オブジェクトなど、スカラー値に対して自然な比較を提供します。カスタム比較が必要な場合は、やはり独自のロジックが必要ですが、基本的な比較はこの演算子でカバーできます。

usort 関数の具体的な使い方(実践例)

ここからは、様々なケースでの usort() の具体的な使い方を、豊富なサンプルコードと共に見ていきます。

例1: 単純な数値配列のカスタムソート

まずは最も基本的な例として、数値配列を昇順・降順にソートしてみましょう。標準の sort() / rsort() と同じ結果になりますが、usort() の仕組みを理解するのに役立ちます。

数値の昇順ソート

“`php

1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => 8
[8] => 9
)
*/

// PHP 7.0+ で <=> 演算子を使うともっと簡潔に
echo “— 数値の昇順ソート (<=> 演算子) —” . PHP_EOL;
$data_asc_spaceship = $numbers;
usort($data_asc_spaceship, fn($a, $b) => $a <=> $b); // PHP 7.4+ ならさらに簡潔
print_r($data_asc_spaceship);
/*
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => 8
[8] => 9
)
*/
?>

“`

数値の降順ソート

“`php

9
[1] => 8
[2] => 7
[3] => 6
[4] => 5
[5] => 4
[6] => 3
[7] => 2
[8] => 1
)
*/

// PHP 7.0+ で <=> 演算子を使うともっと簡潔に
echo “— 数値の降順ソート (<=> 演算子) —” . PHP_EOL;
$data_desc_spaceship = $numbers;
usort($data_desc_spaceship, fn($a, $b) => $b <=> $a); // PHP 7.4+ ならさらに簡潔 ($b と $a を逆にする)
print_r($data_desc_spaceship);
/*
Array
(
[0] => 9
[1] => 8
[2] => 7
[3] => 6
[4] => 5
[5] => 4
[6] => 3
[7] => 2
[8] => 1
)
*/
?>

“`

例2: 文字列配列のカスタムソート

文字列長でソートしたり、特定の条件でソートしたりといったカスタムソートを行います。

文字列長で昇順ソート

“`php

strlen($b);
});
print_r($data_len_asc);
/*
Array
(
[0] => fig (3文字)
[1] => date (4文字)
[2] => apple (5文字)
[3] => banana (6文字)
[4] => cherry (6文字) // bananaとcherryは文字列長が同じなので順序は保証されない
[5] => elderberry (10文字)
)
*/

// PHP 7.4+ + <=> でさらに簡潔に
echo “— 文字列長で昇順ソート (簡潔版) —” . PHP_EOL;
$data_len_asc_short = $fruits;
usort($data_len_asc_short, fn($a, $b) => strlen($a) <=> strlen($b));
print_r($data_len_asc_short);
/*
Array
(
[0] => fig
[1] => date
[2] => apple
[3] => banana
[4] => cherry
[5] => elderberry
)
*/
?>

“`

特定の文字 (‘a’) を含むものを優先してソート

含むもの同士、含まないもの同士では、元の順序を維持(厳密には安定ソートではないので順序は保証されない)とします。

“`php

$b;
}
});
print_r($data_contains_a);
/*
Array
(
[0] => apple (‘a’ 含む)
[1] => banana (‘a’ 含む)
[2] => date (‘a’ 含む)
[3] => elderberry (‘a’ 含む)
[4] => avocado (‘a’ 含む)
[5] => cherry (‘a’ 含まない)
[6] => fig (‘a’ 含まない)
)
*/
?>

“`

例3: 連想配列の配列(多次元配列)のソート

これが usort() の最も一般的な使い方の1つです。特定のキーの値に基づいてソートを行います。

キー ‘age’ で昇順ソート

“`php

‘Alice’, ‘age’ => 30, ‘city’ => ‘Tokyo’],
[‘name’ => ‘Bob’, ‘age’ => 25, ‘city’ => ‘Osaka’],
[‘name’ => ‘Charlie’, ‘age’ => 35, ‘city’ => ‘Tokyo’],
[‘name’ => ‘David’, ‘age’ => 25, ‘city’ => ‘Kyoto’], // Bobと同じ年齢
];

echo “— キー ‘age’ で昇順ソート —” . PHP_EOL;
$data_age_asc = $users;
usort($data_age_asc, function($a, $b) {
// 連想配列の特定のキー (‘age’) の値を取得して比較
$ageA = $a[‘age’];
$ageB = $b[‘age’];

if ($ageA == $ageB) {
return 0;
}
return ($ageA < $ageB) ? -1 : 1; // PHP 7.0+ なら return $a['age'] <=> $b[‘age’];
});
print_r($data_age_asc);
/*
Array
(
[0] => Array
(
[name] => Bob
[age] => 25
[city] => Osaka
)

[1] => Array
(
[name] => David
[age] => 25
[city] => Kyoto
)

[2] => Array
(
[name] => Alice
[age] => 30
[city] => Tokyo
)

[3] => Array
(
[name] => Charlie
[age] => 35
[city] => Tokyo
)
)
*/
// 注: BobとDavidは年齢が同じ25歳なので、元の順序は保証されません。
// この例では Bob -> David の順になっていますが、逆になる可能性もあります。
?>

“`

複数のキーを組み合わせてソート(第一キー ‘city’ 昇順、第二キー ‘age’ 昇順)

まず ‘city’ でアルファベット順にソートし、同じ ‘city’ の中では ‘age’ の若い順にソートします。

“`php

‘Alice’, ‘age’ => 30, ‘city’ => ‘Tokyo’],
[‘name’ => ‘Bob’, ‘age’ => 25, ‘city’ => ‘Osaka’],
[‘name’ => ‘Charlie’, ‘age’ => 35, ‘city’ => ‘Tokyo’],
[‘name’ => ‘David’, ‘age’ => 25, ‘city’ => ‘Kyoto’],
[‘name’ => ‘Eve’, ‘age’ => 28, ‘city’ => ‘Tokyo’],
];

echo “— キー ‘city’ 昇順、次に ‘age’ 昇順でソート —” . PHP_EOL;
$data_city_age_asc = $users;
usort($data_city_age_asc, function($a, $b) {
// まず ‘city’ で比較
$cityComparison = $a[‘city’] <=> $b[‘city’]; // PHP 7.0+

if ($cityComparison !== 0) {
// city が異なる場合は、city の比較結果をそのまま返す
return $cityComparison;
} else {
// city が等しい場合は、’age’ で比較
return $a[‘age’] <=> $b[‘age’]; // PHP 7.0+
}
});
print_r($data_city_age_asc);
/*
Array
(
[0] => Array // Kyoto グループ (David)
(
[name] => David
[age] => 25
[city] => Kyoto
)

[1] => Array // Osaka グループ (Bob)
(
[name] => Bob
[age] => 25
[city] => Osaka
)

[2] => Array // Tokyo グループ (Alice, Charlie, Eve)
( // age 順に 28, 30, 35
[name] => Eve
[age] => 28
[city] => Tokyo
)

[3] => Array
(
[name] => Alice
[age] => 30
[city] => Tokyo
)

[4] => Array
(
[name] => Charlie
[age] => 35
[city] => Tokyo
)
)
*/
?>

“`
このように、比較関数内で複数の条件を段階的にチェックすることで、複雑な多段階ソートを実現できます。最初の条件で順序が決まればその結果を返し、等しい場合に次の条件で比較を進める、という流れが一般的です。

例4: オブジェクトの配列のソート

配列にオブジェクトが格納されている場合も、usort() を使って特定のプロパティやメソッドの戻り値に基づいてソートできます。

Product クラスのオブジェクトの配列を、price プロパティでソートする例です。

“`php

name = $name;
$this->price = $price;
$this->stock = $stock;
}
}

$products = [
new Product(‘Laptop’, 120000, 15),
new Product(‘Mouse’, 3000, 150),
new Product(‘Keyboard’, 8000, 80),
new Product(‘Monitor’, 25000, 20),
];

echo “— Product オブジェクトを price 昇順でソート —” . PHP_EOL;
$data_price_asc = $products;
usort($data_price_asc, function($a, $b) {
// オブジェクトのプロパティにアクセスして比較
if ($a->price == $b->price) {
return 0;
}
return ($a->price < $b->price) ? -1 : 1;
// PHP 7.0+ なら return $a->price <=> $b->price;
});
print_r($data_price_asc);
/*
Array
(
[0] => Product Object
(
[name] => Mouse
[price] => 3000
[stock] => 150
)

[1] => Product Object
(
[name] => Keyboard
[price] => 8000
[stock] => 80
)

[2] => Product Object
(
[name] => Monitor
[price] => 25000
[stock] => 20
)

[3] => Product Object
(
[name] => Laptop
[price] => 120000
[stock] => 15
)
)
*/
?>

“`

オブジェクトの特定のメソッドの戻り値でソートすることも同様に可能です。

例5: 複雑な条件でのソート

より独自の、特定のビジネスロジックに基づいたソートも usort() で実現できます。例えば、商品の在庫が0でないものを優先し、その中では価格の安い順にソート、在庫が0のもの同士は名前でソート、といったルールです。

“`php

name = $name;
$this->price = $price;
$this->stock = $stock;
}
}

$products = [
new Product(‘Laptop’, 120000, 15),
new Product(‘Mouse’, 3000, 0), // 在庫0
new Product(‘Keyboard’, 8000, 80),
new Product(‘Monitor’, 25000, 0), // 在庫0
new Product(‘Webcam’, 5000, 5),
new Product(‘Adapter’, 2000, 0), // 在庫0
];

echo “— 在庫ありを優先、次に価格昇順、在庫なしは名前昇順でソート —” . PHP_EOL;
$data_complex_sort = $products;
usort($data_complex_sort, function($a, $b) {
$a_in_stock = $a->stock > 0;
$b_in_stock = $b->stock > 0;

if ($a_in_stock && !$b_in_stock) {
return -1; // $a が在庫あり、$b が在庫なし -> $a 優先
} elseif (!$a_in_stock && $b_in_stock) {
return 1; // $b が在庫あり、$a が在庫なし -> $b 優先
} else {
// 両方在庫あり、または両方在庫なし の場合
if ($a_in_stock) {
// 両方在庫ありの場合: 価格で昇順
return $a->price <=> $b->price; // PHP 7.0+
} else {
// 両方在庫なしの場合: 名前で昇順
return $a->name <=> $b->name; // PHP 7.0+
}
}
});
print_r($data_complex_sort);
/*
Array
(
[0] => Product Object // 在庫ありグループ (price 順)
(
[name] => Webcam
[price] => 5000
[stock] => 5
)

[1] => Product Object
(
[name] => Keyboard
[price] => 8000
[stock] => 80
)

[2] => Product Object
(
[name] => Laptop
[price] => 120000
[stock] => 15
)

[3] => Product Object // 在庫なしグループ (name 順)
(
[name] => Adapter
[price] => 2000
[stock] => 0
)

[4] => Product Object
(
[name] => Monitor
[price] => 25000
[stock] => 0
)

[5] => Product Object
(
[name] => Mouse
[price] => 3000
[stock] => 0
)
)
*/
?>

“`

このように、usort() の比較関数内では、PHPのあらゆるロジック(条件分岐、関数の呼び出し、計算など)を記述できるため、非常に柔軟なカスタムソートが可能です。

usort を使う上での注意点とヒント

usort() は強力ですが、いくつかの注意点があります。これらを理解しておくことで、意図しない挙動を防ぎ、より効果的に usort() を活用できます。

  1. 参照渡しである: usort() はソート対象の配列を直接変更します。関数を呼び出した後、元の配列変数にはソート済みの配列が格納されます。もし元の順序を保持したい場合は、usort() を呼び出す前に配列をコピーしてください(例: $copied_array = $original_array; usort($copied_array, ...);)。
  2. キーが再割り当てされる: usort() は配列の要素の値に基づいて並べ替えます。ソート後、元の数値キーや文字列キーは破棄され、0 から始まる連続した新しい数値キーが要素に割り当てられます。もしキーと値の関連を維持したままカスタムソートを行いたい場合は、代わりに uasort() 関数を使用する必要があります。
  3. 安定ソートではない: PHPのソート関数(usort() を含む)は、一般的に「安定ソート」ではありません。安定ソートとは、比較関数が等しいと判断した要素間の相対的な順序が、ソート後も維持されるソートのことです。usort() の比較関数が 0 を返した場合、PHPのソートアルゴリズムはその2つの要素の元の順序を維持する保証はありません。上記の多段階ソートの例で、「同じcityの場合はageでソート」のように、等しい場合の比較ロジックを明確に定義しておけば問題ありません。しかし、最後の比較でも 0 を返した場合、その順序は不定となります。
  4. 比較関数の効率性: 大規模な配列をソートする場合、比較関数は何度も(最悪の場合、要素数の対数に要素数を掛けた回数、またはそれ以上に)呼び出されます。比較関数内で複雑な計算や時間のかかる処理を行うと、ソート全体のパフォーマンスに大きく影響します。可能な限り、比較関数はシンプルかつ高速に処理できるように記述することが重要です。
  5. 比較関数の戻り値の規約を守る: 前述の通り、比較関数は厳密に負、正、またはゼロの整数を返す必要があります。PHP 7.0+ であれば a <=> b の形式で比較すれば間違いがありません。規約に反する戻り値を返すと、ソートが正しく行われなかったり、警告やエラーが発生したりする可能性があります。
  6. デバッグ方法: カスタムソートが期待通りに動作しない場合、比較関数内で echovar_dump() を使って、比較対象となる $a$b の値、そして比較関数の戻り値を確認するとデバッグに役立ちます。ただし、ソート処理中は比較関数が頻繁に呼ばれるため、大量の出力が発生する可能性がある点に注意してください。
  7. PHP 7.0+ と <=> 演算子: PHP 7.0以降を利用できる環境であれば、比較関数内で a <=> b を積極的に利用することで、コードを簡潔かつ分かりやすく記述できます。特に数値や文字列、あるいはオブジェクトのプロパティなど、単純なスカラー値を比較する場合に効果的です。

関連するカスタムソート関数:uasort() と uksort()

PHPには、usort() の他にもカスタムソート関数が用意されています。これらは、usort() と同様にユーザー定義の比較関数を使いますが、キーの扱いが異なります。

  • uasort() (User-defined Array Sort – maintains keys)

    • usort() と同じように、配列の値をユーザー定義の比較関数で比較してソートします。
    • しかし、usort() と異なり、元のキーと値の関連を維持します。
    • 連想配列を値でソートしたいが、元のキー(文字列キーなど)を失いたくない場合に最適です。
    • 書式: bool uasort ( array &$array , callable $callback )
    • 比較関数の規約は usort() と同じです。

    “`php
    <?php
    $data = [‘id_1’ => 30, ‘id_2’ => 25, ‘id_3’ => 35, ‘id_4’ => 25];

    echo “— uasort で値を昇順ソート (キー維持) —” . PHP_EOL;
    uasort($data, fn($a, $b) => $a <=> $b); // PHP 7.0+ & 7.4+
    print_r($data);
    /
    Array
    (
    [id_2] => 25 // 値 25
    [id_4] => 25 // 値 25 (id_2 と id_4 は値が同じため、元の順序は保証されない)
    [id_1] => 30 // 値 30
    [id_3] => 35 // 値 35
    )
    /
    ?>
    ``
    この例では、値 25 の要素はキー
    id_2id_4を保持しています。元のid_2が先、id_4` が後という順序は、安定ソートではないため保証されませんが、キー自体は値と結びついたまま維持されています。

  • uksort() (User-defined Key Sort)

    • 配列のキーをユーザー定義の比較関数で比較してソートします。
    • キーと値の関連は維持されます。
    • キー自体に特定の比較ルールを適用したい場合に利用します。例えば、キーが複雑な文字列パターンや特定の形式を持っていて、それらを独自のルールで並べ替えたい場合などです。
    • 書式: bool uksort ( array &$array , callable $callback )
    • 比較関数の引数はキーの値になります: int comparison_function ( mixed $key_a , mixed $key_b )

    “`php
    <?php
    $data = [‘item_10’ => ‘Apple’, ‘item_1’ => ‘Banana’, ‘item_2’ => ‘Cherry’];

    echo “— uksort でキーを自然順ソート —” . PHP_EOL;
    uksort($data, ‘strnatcmp’); // strnatcmp は自然順比較を行う標準関数
    print_r($data);
    /
    Array
    (
    [item_1] => Banana // item_1, item_2, item_10 の順
    [item_2] => Cherry
    [item_10] => Apple
    )
    /

    echo “— uksort でキーの数値部分でソート —” . PHP_EOL;
    $data_custom_key_sort = [‘item_10’ => ‘Apple’, ‘item_1’ => ‘Banana’, ‘item_2’ => ‘Cherry’];
    uksort($data_custom_key_sort, function($keyA, $keyB) {
    // キーから数値部分を抽出して比較
    $numA = (int) str_replace(‘item_’, ”, $keyA);
    $numB = (int) str_replace(‘item_’, ”, $keyB);
    return $numA <=> $numB; // PHP 7.0+
    });
    print_r($data_custom_key_sort);
    /
    Array
    (
    [item_1] => Banana
    [item_2] => Cherry
    [item_10] => Apple
    )
    /
    ?>
    ``
    この例では、キー
    ‘item_10’,‘item_1’,‘item_2’を、数値部分10,1,2` として比較することで、期待通りの順序に並べ替えることができています。

usort() は値を比較しキーを破棄、uasort() は値を比較しキーを維持、uksort() はキーを比較しキーを維持、という違いを理解して使い分けることが重要です。

まとめ:usort をマスターして配列操作の柔軟性を高める

この記事では、PHPの配列をカスタムソートするための主要な関数である usort() について、その基本的な使い方から詳細な仕組み、そして多様な実践例を通じて解説しました。

  • PHPの標準ソート関数は便利ですが、複雑なデータ構造や独自の比較ルールには対応できません。
  • usort() は、ユーザーが定義した比較関数を使用して配列の要素を並べ替えることで、この限界を克服します。
  • usort() の鍵は、比較対象の2つの要素を受け取り、それらの相対的な順序を示す整数(負、ゼロ、正)を返す比較関数です。
  • 比較関数は、通常の関数、無名関数(クロージャ)、アロー関数(PHP 7.4+)、クラスのメソッドとして実装できます。
  • PHP 7.0以降で利用可能な <=> 演算子(宇宙船演算子)は、比較関数の実装を簡潔にするのに非常に役立ちます。
  • usort() は配列を参照渡しで受け取り直接変更します。また、ソート後にキーは再割り当てされます。キーを維持したい場合は uasort() を使用します。
  • usort() を含むPHPのソート関数は一般的に安定ソートではないため、比較結果が等しい要素間の元の順序は保証されません。
  • 比較関数の効率は、大規模な配列のソートパフォーマンスに大きく影響します。

多次元配列やオブジェクトの配列を特定の基準でソートしたり、複数の条件を組み合わせた複雑なソートロジックを適用したりする場合に、usort() は不可欠なツールとなります。

また、関連関数である uasort()uksort() を理解することで、カスタムソートの選択肢がさらに広がります。キーを維持したい場合は uasort()、キー自体を基準にソートしたい場合は uksort() を選択します。

これらのカスタムソート関数を使いこなすことで、PHPでのデータ操作の柔軟性が格段に向上し、より高度で要求の厳しいデータ処理も効率的に行えるようになります。配列データの並べ替えにカスタムロジックが必要になった際は、ぜひ usort()(あるいは uasort(), uksort())の活用を検討してみてください。

終わりに

この記事が、PHPの usort() 関数、そして配列のカスタムソートに関する理解を深める一助となれば幸いです。もし記事の内容についてご不明な点があったり、さらに詳しく知りたいことがあったりする場合は、お気軽にお問い合わせください。

Happy Coding!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール