Представляем: очень обобщенное решение для PHP 5.3+
Я хотел бы добавить сюда свое собственное решение, поскольку оно предлагает функции, которых нет в других ответах.
В частности, преимущества этого решения включают в себя:
- Его можно повторно использовать: вы указываете столбец сортировки как переменную, а не жестко кодируете его.
- Он гибкий: вы можете указать несколько столбцов сортировки (столько, сколько хотите) — дополнительные столбцы используются в качестве разрешения конфликтов между элементами, которые изначально сравниваются равными.
- Он обратим: вы можете указать, что сортировка должна быть обратной — индивидуально для каждого столбца.
- Он расширяемый: если набор данных содержит столбцы, которые нельзя сравнивать "тупым" способом (например, строки даты), вы также можете указать, как преобразовать эти элементы в значение, которое можно сравнивать напрямую (например, экземпляр
DateTime).
- Он ассоциативен, если хотите: этот код заботится о сортировке элементов, но вы выбираете фактическую функцию сортировки (
usort или uasort).
- Наконец, он не использует
array_multisort: хотя array_multisort удобен, он зависит от создания проекции всех ваших входных данных перед сортировкой. Это требует времени и памяти и может быть просто недопустимым, если ваш набор данных велик.
Код
function make_comparer() {
// Normalize criteria up front so that the comparer finds everything tidy
$criteria = func_get_args();
foreach ($criteria as $index => $criterion) {
$criteria[$index] = is_array($criterion)
? array_pad($criterion, 3, null)
: array($criterion, SORT_ASC, null);
}
return function($first, $second) use (&$criteria) {
foreach ($criteria as $criterion) {
// How will we compare this round?
list($column, $sortOrder, $projection) = $criterion;
$sortOrder = $sortOrder === SORT_DESC ? -1 : 1;
// If a projection was defined project the values now
if ($projection) {
$lhs = call_user_func($projection, $first[$column]);
$rhs = call_user_func($projection, $second[$column]);
}
else {
$lhs = $first[$column];
$rhs = $second[$column];
}
// Do the actual comparison; do not return if equal
if ($lhs < $rhs) {
return -1 * $sortOrder;
}
else if ($lhs > $rhs) {
return 1 * $sortOrder;
}
}
return 0; // tiebreakers exhausted, so $first == $second
};
}
Как использовать
В этом разделе я буду предоставлять ссылки, которые сортируют этот примерный набор данных:
$data = array(
array('zz', 'name' => 'Jack', 'number' => 22, 'birthday' => '12/03/1980'),
array('xx', 'name' => 'Adam', 'number' => 16, 'birthday' => '01/12/1979'),
array('aa', 'name' => 'Paul', 'number' => 16, 'birthday' => '03/11/1987'),
array('cc', 'name' => 'Helen', 'number' => 44, 'birthday' => '24/06/1967'),
);
Основы
Функция make_comparer принимает переменное количество аргументов, определяющих желаемую сортировку, и возвращает функцию, которую вы должны использовать в качестве аргумента для usort или uasort.
Самый простой вариант использования — передать ключ, который вы хотели бы использовать для сравнения элементов данных. Например, чтобы отсортировать $data по элементу name, вы должны сделать
usort($data, make_comparer('name'));
Посмотрите в действии.
Ключ также может быть числом, если элементы представляют собой массивы с числовым индексом. Для примера в вопросе это будет
usort($data, make_comparer(0)); // 0 = first numerically indexed column
Посмотрите в действии.
Несколько столбцов сортировки
Вы можете указать несколько столбцов сортировки, передав дополнительные параметры в make_comparer. Например, для сортировки по «числу», а затем по столбцу с нулевым индексом:
usort($data, make_comparer('number', 0));
Посмотрите в действии.
Расширенные возможности
Более продвинутые функции доступны, если вы укажете столбец сортировки как массив, а не как простую строку. Этот массив должен быть численно проиндексирован и должен содержать следующие элементы:
0 => the column name to sort on (mandatory)
1 => either SORT_ASC or SORT_DESC (optional)
2 => a projection function (optional)
Давайте посмотрим, как мы можем использовать эти функции.
Обратная сортировка
Чтобы отсортировать имя по убыванию:
usort($data, make_comparer(['name', SORT_DESC]));
Посмотрите в действии.
Чтобы отсортировать по убыванию номера, а затем по убыванию имени:
usort($data, make_comparer(['number', SORT_DESC], ['name', SORT_DESC]));
Посмотрите в действии.
Пользовательские проекции
В некоторых сценариях может потребоваться сортировка по столбцу, значения которого плохо поддаются сортировке. Столбец «день рождения» в выборке данных соответствует этому описанию: нет смысла сравнивать дни рождения как строки (поскольку, например, «01.01.1980» предшествует «10.10.1970»). В этом случае мы хотим указать, как проецировать фактические данные в форму, которую можно напрямую сравнивать с желаемой семантикой.
Проекции могут быть указаны как любой тип вызываемых: как строки, массивы или анонимные функции. Предполагается, что проекция принимает один аргумент и возвращает его спроецированную форму.
Следует отметить, что, хотя проекции похожи на пользовательские функции сравнения, используемые с usort и семейством, они проще (вам нужно только преобразовать одно значение в другое) и использовать все функции, уже встроенные в make_comparer.
Давайте отсортируем примерный набор данных без проекции и посмотрим, что получится:
usort($data, make_comparer('birthday'));
Посмотрите в действии.
Это не было желаемым результатом. Но мы можем использовать date_create в качестве проекции:
usort($data, make_comparer(['birthday', SORT_ASC, 'date_create']));
Посмотрите в действии.
Это правильный порядок, который мы хотели.
Есть много других вещей, которых можно достичь с помощью проекций. Например, быстрый способ получить сортировку без учета регистра — использовать strtolower в качестве проекции.
Тем не менее, я должен также упомянуть, что лучше не использовать проекции, если ваш набор данных большой: в этом случае было бы намного быстрее спроецировать все ваши данные вручную заранее, а затем отсортировать без использования проекции, хотя это приведет к обмену увеличенное использование памяти для более высокой скорости сортировки.
Наконец, вот пример, который использует все возможности: сначала сортирует по убыванию числа, затем по возрастанию дня рождения:
usort($data, make_comparer(
['number', SORT_DESC],
['birthday', SORT_ASC, 'date_create']
));
Посмотрите в действии.
person
Jon
schedule
28.05.2013