Упорядочивание коллекции
Сортировка — это расположение элементов коллекции в определённом порядке.
У некоторых типов есть естественный порядок. Числа упорядочены по значению: 1 идёт перед 2. Строки упорядочены по алфавиту: "four" идёт перед "one". Для всего остального, а также когда нужен другой порядок, вы задаёте свой порядок: функцию сравнения, которая смотрит на два элемента и говорит, какой из них идёт первым.
Все функции сортировки в PHP работают на месте. Они принимают массив по ссылке, меняют его и возвращают true, а не отсортированный массив. $sorted = sort($words) — классическая ошибка: $sorted становится равным true.
sort($s)сортирует в естественном порядке, от наименьшего к наибольшему.rsort($s)сортирует в естественном порядке, от наибольшего к наименьшему.usort($s, $compare)сортирует с вашей собственной функцией сравнения.
Все три перенумеровывают ключи в 0, 1, 2 и так далее.
Чтобы сохранить исходный порядок, сортируйте копию. В PHP это просто, потому что массивы — это значения: $sorted = $words; даёт $sorted собственную копию, и её сортировка оставляет $words без изменений.
Функция сравнения получает два элемента, $a и $b, и возвращает число: отрицательное, когда первым идёт $a, положительное, когда первым идёт $b, и 0, когда они равны. Оператор «космический корабль» <=> даёт ровно это число: $a <=> $b равно -1, 0 или 1.
В примерах ниже используются слова one, two, three, four.
Естественный порядок
Естественный порядок — это порядок, который есть у типа сам по себе: у чисел по значению, у строк буква за буквой.
sort() располагает массив в естественном порядке. Для порядка по убыванию есть rsort(): та же сортировка, но наибольший элемент первым. Обе меняют переданный массив, поэтому сначала скопируйте его, если исходный ещё нужен.
У ваших собственных данных, например у номера версии с главной и второстепенной частью, своего естественного порядка нет. Вы задаёте его, написав функцию сравнения, которая возвращает отрицательное число, ноль или положительное число. Оператор «космический корабль» сравнивает и целые массивы, позиция за позицией, и решает первое различие — поэтому [$major, $minor] <=> [$otherMajor, $otherMinor] сначала сравнивает числа major, а числа minor — только тогда, когда major равны.
Отсортированная копия
$sorted = $words; копирует массив, а sort() располагает копию в алфавитном порядке. Исходный массив $words выводится после этого, чтобы показать, что он не изменился.
Вывод: PHP-скрипт
<?php
$words = ["one", "two", "three", "four"];
// Массивы копируются при присваивании, поэтому $sorted — отдельный массив.
// sort() меняет $sorted на месте; $words остаётся таким, каким был.
$sorted = $words;
sort($sorted);
echo implode(", ", $sorted), "\n";
echo implode(", ", $words), "\n";
Сортировка по убыванию
rsort() сортирует копию от наибольшего к наименьшему, поэтому слова идут от "two" до "four".
Вывод: PHP-скрипт
<?php
$words = ["one", "two", "three", "four"];
// rsort() сортирует копию на месте, от наибольшего к наименьшему.
$desc = $words;
rsort($desc);
echo implode(", ", $desc), "\n";
Тип со своим собственным порядком
Версия — это массив с числами major и minor. compareVersion() сравнивает пары [major, minor] с помощью <=>: сначала числа major; и только когда они равны, решают числа minor. Проверка compareVersion(...) > 0 отвечает на вопрос «больше ли первая версия?». json_encode() выводит логические значения как false и true.
Вывод: PHP-скрипт
<?php
declare(strict_types=1);
// compareVersion возвращает отрицательное число, ноль или положительное число,
// как функции сравнения, которые использует usort().
// Массивы сравниваются позиция за позицией: сначала major, затем minor.
function compareVersion(array $v, array $other): int {
return [$v["major"], $v["minor"]] <=> [$other["major"], $other["minor"]];
}
$v12 = ["major" => 1, "minor" => 2];
$v13 = ["major" => 1, "minor" => 3];
$v20 = ["major" => 2, "minor" => 0];
$v15 = ["major" => 1, "minor" => 5];
// Больше ли 1.2, чем 1.3?
echo json_encode(compareVersion($v12, $v13) > 0), "\n";
// Больше ли 2.0, чем 1.5?
echo json_encode(compareVersion($v20, $v15) > 0), "\n";
Свои порядки
Свой порядок — это любой порядок, который вы описываете функцией сравнения.
usort() сортирует массив на месте с помощью этой функции. Начиная с PHP 8.0 любая сортировка в PHP устойчива: элементы, которые сравниваются как равные, сохраняют свой исходный порядок. Это важно, например, при сортировке слов по длине.
Обычный случай — сортировка по одному свойству каждого элемента. Поставьте это свойство с обеих сторон оператора «космический корабль»: strlen($a) <=> strlen($b) сортирует по длине.
Для порядка по убыванию поменяйте стороны местами: $b <=> $a вместо $a <=> $b.
Чтобы отсортировать по одному признаку, а затем по другому, сравнивайте два массива: [strlen($a), $a] <=> [strlen($b), $b]. Сначала сравниваются длины; если они равны, ничью разрешают сами слова.
Сортировка с функцией сравнения
Функция сравнения возвращает strlen($a) <=> strlen($b). Отрицательный результат означает, что $a короче и идёт первым, поэтому слова оказываются от самого короткого к самому длинному.
Вывод: PHP-скрипт
<?php
$words = ["aaa", "bb", "c"];
// Более короткие строки идут первыми.
usort($words, fn($a, $b) => strlen($a) <=> strlen($b));
echo implode(", ", $words), "\n";
Сортировка по длине
Слова сравниваются по длине. "one" и "two" имеют длину 3 буквы; устойчивая сортировка сохраняет "one" перед "two".
Вывод: PHP-скрипт
<?php
$words = ["one", "two", "three", "four"];
// Сортировка устойчива: слова одинаковой длины сохраняют свой исходный порядок.
usort($words, fn($a, $b) => strlen($a) <=> strlen($b));
echo implode(", ", $words), "\n";
Сортировка по последней букве, по убыванию
$a[-1] — это последняя буква слова: отрицательное смещение в строке отсчитывается от конца. Буквы $b и $a сравниваются в обратном порядке, поэтому первым идёт слово с наибольшей последней буквой: "four" (r), затем "two" (o), затем два слова, оканчивающихся на «e», в их исходном порядке.
Вывод: PHP-скрипт
<?php
$words = ["one", "two", "three", "four"];
// Сравниваем последние буквы, поменяв $b и $a местами для порядка по убыванию.
usort($words, fn($a, $b) => $b[-1] <=> $a[-1]);
echo implode(", ", $words), "\n";
Сортировка по длине, затем по алфавиту
Оператор «космический корабль» сравнивает массивы [strlen($a), $a] и [strlen($b), $b] позиция за позицией. Первыми решают длины. Когда два слова одинаково длинные, сравниваются сами слова, что упорядочивает их по алфавиту.
Вывод: PHP-скрипт
<?php
$words = ["aaa", "bb", "c", "b", "a", "aa", "ccc"];
// Массивы сравниваются позиция за позицией: сначала длина, затем само слово.
usort($words, fn($a, $b) => [strlen($a), $a] <=> [strlen($b), $b]);
echo implode(", ", $words), "\n";
Проверка отсортированности
Иногда нужно лишь узнать, отсортирован ли массив уже, не сортируя его.
В PHP нет встроенной функции для этого, но достаточно небольшой вспомогательной функции: пройдите по массиву, сравните каждый элемент с его правым соседом и верните false, как только пара окажется в неверном порядке. Дайте этой функции необязательную функцию сравнения, и тот же цикл проверит любой порядок. Для порядка по убыванию передайте fn($a, $b) => $b <=> $a — сравнение с переставленными сторонами.
Чтобы проверить порядок по одному свойству, например по возрасту пользователя, сравнивайте это свойство внутри функции.
Проверка по возрастанию и по убыванию
isSorted() подтверждает, что 1, 2, 3, 4 идут по возрастанию. С функцией сравнения $b <=> $a она подтверждает, что 4, 3, 2, 1 идут по убыванию.
Вывод: PHP-скрипт
<?php
// isSorted проверяет каждый элемент с его правым соседом.
function isSorted(array $items, ?callable $compare = null): bool {
$compare ??= fn($a, $b) => $a <=> $b;
for ($i = 0; $i + 1 < count($items); $i++) {
if ($compare($items[$i], $items[$i + 1]) > 0) {
return false;
}
}
return true;
}
$ascending = [1, 2, 3, 4];
$descending = [4, 3, 2, 1];
echo json_encode(isSorted($ascending)), "\n";
// Поменянные местами операнды проверяют порядок по убыванию.
echo json_encode(isSorted($descending, fn($a, $b) => $b <=> $a)), "\n";
Проверка порядка по полю
Пользователи — это Alice (24), Bob (31) и Charlie (29). Функция сравнения смотрит только на 'age'. Bob старше Charlie, поэтому массив не отсортирован по возрасту и программа выводит false.
Вывод: PHP-скрипт
<?php
// isSortedBy проверяет каждую пару соседей функцией сравнения.
function isSortedBy(array $items, callable $compare): bool {
for ($i = 0; $i + 1 < count($items); $i++) {
if ($compare($items[$i], $items[$i + 1]) > 0) {
return false;
}
}
return true;
}
$users = [
["name" => "Alice", "age" => 24],
["name" => "Bob", "age" => 31],
["name" => "Charlie", "age" => 29],
];
// Идут ли пользователи в порядке возраста?
echo json_encode(isSortedBy($users, fn($a, $b) => $a["age"] <=> $b["age"])), "\n";
Обратный порядок
Разворот ставит последний элемент первым, а первый — последним.
В отличие от функций сортировки, array_reverse() не меняет переданный массив: она возвращает развёрнутую копию, а исходный массив сохраняет свой порядок.
Развёрнутая копия
Выводится копия, созданная array_reverse(), а затем исходный массив, чтобы показать, что он не изменился.
Вывод: PHP-скрипт
<?php
$words = ["one", "two", "three", "four"];
// array_reverse() возвращает перевёрнутую копию; исходный массив сохраняет свой порядок.
$reversed = array_reverse($words);
echo implode(", ", $reversed), "\n";
echo implode(", ", $words), "\n";
Случайный порядок
Перемешивание располагает элементы в случайном порядке.
Функция shuffle() в PHP работает как функции сортировки: на месте, с тем массивом, который вы передаёте. Перемешивайте копию, если исходный массив ещё нужен.
shuffle() берёт случайные числа из того же генератора, что и mt_rand(). Инициализируйте этот генератор вызовом mt_srand(seed), и вы будете получать один и тот же «случайный» порядок при каждом запуске программы с этим начальным значением. Это полезно для тестов и для примеров вроде этого. Без фиксированного начального значения порядок меняется при каждом запуске.
Перемешанная копия
mt_srand(14) фиксирует начальное значение, а затем shuffle() перемешивает копию слов. Она идёт от последней позиции к первой и меняет каждый элемент со случайно выбранным элементом на этой же позиции или раньше. С этим начальным значением результат при каждом запуске — two, three, one, four.
Вывод: PHP-скрипт
<?php
$words = ["one", "two", "three", "four"];
// Фиксированное начальное значение даёт один и тот же «случайный» порядок при каждом запуске.
mt_srand(14);
// shuffle() работает на месте, поэтому перемешиваем копию.
$shuffled = $words;
shuffle($shuffled);
echo implode(", ", $shuffled), "\n";