Практически перед каждым PHP-программистом становится задача отсортировать многомерный массив. К примеру вот такой вот:
$data=array( array('text'=>'str1', 'year'=>'2010', 'author'=>10), array('text'=>'str2', 'year'=>'2011', 'author'=>10), array('text'=>'str3', 'year'=>'2009', 'author'=>20), array('text'=>'str4', 'year'=>'2010', 'author'=>30), array('text'=>'str5', 'year'=>'2010', 'author'=>20), array('text'=>'str6', 'year'=>'2011', 'author'=>10), array('text'=>'str7', 'year'=>'2011', 'author'=>20), array('text'=>'str8', 'year'=>'2009', 'author'=>20), );
По, допустим, ключу year, как же сделать это грамотно?
Как просили меня в комментах — забегу немного вперед. В PHP есть встроенная функция array_multisort — она позволяет сортировать многомерные массивы. Но если вам интересно давайте рассмотрим все возможные методы сортировки многомерных массивов и их плюсы и минусы в сравнении с array_multisort.
Классическое решение
Естественно первое про что вы подумали, это сортировка многомерного массива с помощью uasort, да? Набросаем вот такой вот код:
function cmp($a, $b) { if ($a['year'] == $b['year']) { return 0; } return ($a['year'] < $b['year']) ? -1 : 1; } for($i=0; $i<100000; $i++){ $data_tmp=$data; uasort($data_tmp, 'cmp'); }
Запускаем, и засекаем время выполнения… Итого: 13.15 сек. Долговато и не впечатляет.
Ищем пути решения проблемы, находим на php.net, другой вариант функции сравнения, который, как там написано, должен работать быстрее:
function cmp($a, $b) { return strcmp($a['year'], $b['year']); } for($i=0; $i<100000; $i++){ $data_tmp=$data; uasort($data_tmp, 'cmp'); }
Итого: 23.11 сек. Хреновая оптимизация…
Ладно, хорошо, со временем выполнения мы определились. Давайте попробуем определится с «расширяемостью кода». Допустим нам поставили задачу отсортировать сначала по ключу year а затем по ключу author. Для этого нам приходится переписывать всю «дополнительную функцию», в итоге получаем что то похожее на это:
function cmp($a, $b) { if ($a['year'] == $b['year']) { if ($a['author'] == $b['author']){ return 0; }else{ return ($a['author'] < $b['author']) ? -1 : 1; } }else{ return ($a['year'] < $b['year']) ? -1 : 1; } }
Громоздко. Сложно изменять. Вообщем отстой, на мой взгляд.
Итак, подведем итоги. Минусы:
- Долго выполняется
- Сложно расширять
- Под каждую сортировку нужна своя, новая функция
Плюсы:
- Единственный очевидный вариант (?)
Пробуем костыли
Попробуем написать свою функцию для сортировки массивов с блекджеком и шлюхами. Напомню, наша задача сортировать многомерный массив по ключу:
//Функция сортировки по ключу function amsort($array, $key) { $result = array(); $values = array(); foreach ($array as $id => $value) { $values[$id] = $value[$key]; } asort($values); foreach ($values as $key => $value) { $result[$key] = $array[$key]; } return $result; } for($i=0; $i<100000; $i++){ $data_tmp=$data; amsort($data_tmp); }
Засекаем. Получаем: 7.90 сек. Ну уже не плохо впринципе.
Да вот только заставить этот костыль сортировать по двум ключам уже не получится, к сожалению. Подведем итоги. Минусы:
- Невозможно расширять
- Сортировка только по одному ключу
Плюсы
- Приемлимая скорость выполнения
- Одна функция для разных видов сортировки
Функция array_multisort
Оказывается разработчики PHP уже давным давно все придумали до нас. Оказывается есть функция array_multisort. Как работает эта функция:
array_multisort( array &$arr [, array &$arr [, array &$arr... ]] )
Грубо говоря каждый массив будет отсортирован в соответствии с предыдущим массивом. Вообщем пример:
//Сортируемый масив $array_main=array( 'foo', 'bar', 'foobar', ); //Определяющий массив $array_id=array( 3, 1, 2, ); array_multisort($array_id, $array_main); var_dump($array_main);
Выведет:
array(4) { [0]=> string(3) "bar" [1]=> string(3) "foobar" [2]=> string(3) "foo" }
А это как раз то что нам надо! Возвращаемся к тестам на скорость:
$data_year=array(); //Генерируем "определяющий" массив foreach($data as $key=>$arr){ $data_year[$key]=$arr['year']; } for($i=0; $i<100000; $i++){ $data_tmp=$data; array_multisort($data_year, SORT_NUMERIC, $data_tmp); }
Засекаем. Получаем: 3.87 сек. Это рекорд!
Ну то что это самый быстрый вариант мы определили. Это хорошо. А как насчет расширяемости? Как к примеру заставить сортировать массив по двум ключам? Оказывается с этой функцией очень просто! Достаточно добавить еще один «определяющий массив», вот так:
$data_author=array(); foreach($data as $key=>$arr){ $data_author[$key]=$arr['author']; } $data_year=array(); foreach($data as $key=>$arr){ $data_year[$key]=$arr['year']; } array_multisort($data_year, SORT_NUMERIC, $data_author, $data); var_export($data);
На выходе получим вот такой массив:
array( 0 => array('text' => 'str3', 'year' => '2009', 'author' => 20, ), 1 => array('text' => 'str8', 'year' => '2009', 'author' => 20, ), 2 => array('text' => 'str1', 'year' => '2010', 'author' => 10, ), 3 => array('text' => 'str5', 'year' => '2010', 'author' => 20, ), 4 => array('text' => 'str4', 'year' => '2010', 'author' => 30, ), 5 => array('text' => 'str2', 'year' => '2011', 'author' => 10, ), 6 => array('text' => 'str6', 'year' => '2011', 'author' => 10, ), 7 => array('text' => 'str7', 'year' => '2011', 'author' => 20, ), );
Как видите функция справилась со своей задачей. Наш массив отсортирован сначала по year, затем по author. А с помощью различных флагов типа SORT_DESC, SORT_ASC и тд можно добится любой сортировки (про них подробнее смотрите в мане), так что это на мой взгляд самый лучший вариант для использования в своих скриптах.
Минусы
- ??
Плюсы
- Лучшая скорость
- Расширяемость
- Функциональность
Заключение
Вообщем используйте array_multisort для сортировки многомерных массивов. И будет вам счастье.
Спасибо за внимание =)
Блин, а я целый класс писал для таких сортировок… А оно уже оказывается есть встроеное(
Последний тест на скорость не корректный. В нем ключевой массив создается только один раз. А тестируется на время с уже созданным массивом.
Почему же неккоректный? Тестируется же имено скорость сортировки. Как и в остальных, предыдущих тестах.
можно сделать так:
Так то сделать можно, и да, будет быстрее и короче. Только первоочередная цель кода приведенного в моей статье, не быть быстрым и коротким, а быть наглядным.
То есть если нужна наглядность, то можно применять некорректные примеры сравнений? :)
Самая интересная часть статьи — рассказ про array_multisort() проходит в духе: «ух ты, смотри как клево!», но по сути тема не раскрывается. К примеру, я так и не понял что такое определяющий массив. Да, и к тому же, если задачей стояла наглядность, то почему в результирующем примере приводится решение с каким-то левым массивом, а не с тем, который был в самом начале. Уж он то куда нагляднее и раскрывает суть.
В общем, уныло как-то. Но за наводку на array_multisort() спасибо :)
Гм…) Может я где-то неправильно изложил суть материала, но пример по сортировке исходного массива был приведен:
А тот пример, который вы называете некорректным - вполне корректен. В нем демонстрируется сортировка по двум ключам.
Насчет "уныло" - я конечно уважаю критику, но зря вы так жестко) моей целью дествительно было подсказать о существовании данной функции, и в кратце объяснить как ей пользоваться. Но возможно вы правы, стоило об этом написать подробнее
Абсолютно солидарен.
Автор видимо полагает, что это и так всем известно. Я, если честно, кроме как нужно использовать функцию array_multisort(), ни хрена не понял. А как пользоваться, в каким случаях куда и что совать…
Автор силен в языке, но как преподаватель — хреновый.
Ребят (
Ставил я задачу перед собой подсказать о существовании данной функции. А по поводу того как ее использовать, достаточно внятно описано в официальном мануале, не захотел дублировать его.
Но добавил в TODO написать подробнее об использовании.
«Оказывается есть функция array_multisort.» — я бы сократил всю статью до этой фразы. Мне кажется, она бы стала только лучше.
Добавил абзац в самое начало статьи.
Благодарствую за нужный материал. Как раз столкнулся с необходимостью сортировки массива по 2м ключам. Благодаря Вашей подсказки сделал под себя сортировку)
Доброго времени суток, все вроде отлично. Кроме одного, совсем мелкое замечание, но «Вообщем» слова не существует есть «Вообще» и «В общем»(2 слова). Прошу прощения, не хотел найти за что бы придраться, просто хотел бы помочь в искоренении мелких ошибочек. И к этому добавлю обращение в ед. множестве к человеку пишется с большой буквы. Во множественном числе можно написать с маленькой. В остальном, уважаю Ваш труд.
Насчет дебатов, с Александром.
Ув. Александр, преподаватели в университете, школах, пр. учебных заведениях, на тренингах и прочих познавательных семинарах. Человек, потратил время расписать полезную функцию PHP в читаемой форме, будем же ему просто благодарны, ведь, он же ни где не писал о своей цели преподнести доступно ЯП для всех читателей его трудов.
Здравствуйте!
Спасибо за ваш комментарий. Да, если честно, я знаю про правильное написание этих слов. Постоянно бью себя по рукам и заставляю себя обращать свое внимание на это. Но, к сожалению, — не выходит, иногда ошибки проскакивают. Буду исправляться)
И еще, спасибо за поддержку. Мне очень не нравится та потребительская позиция, которую выбирают некоторые мои читатели, и я рад что хоть кто-то встал на мою защиту)
Было бы за что, просто, действительно, не справедливо. Сам все собираюсь завести «напоминалку», порой делаешь что-то интересное, спустя время забываешь, а потом как начнешь копаться по проектах. Но такие моменты заставляют задуматься, а стоит ли в открытом доступе выставлять свои заметки.
Кстати, спасибо и Вам так же, пару раз сталкивался, но решалось на уровне оформления бд для такой цели и запросом. А в данном случае, идеальное решение без лишних усилий)
Я думаю, в любом случае, стОит. Надо нести добро людям)
Здравствуйте!
Аким, если берете на себя бремя исправлений, желателен личный пример.
1. «Кроме одного, совсем мелкое замечание, но «Вообщем» слова не существует есть «Вообще» и «В общем»(2 слова). »
Уместно двоеточие (уточняющее слово) после «Кроме одного».
Запятая перед «есть» (сложносочиненное).
2. «И к этому добавлю обращение в ед. множестве…»
Уместно двоеточие после «добавлю» (между частями бессоюзного сложного предложения)
3. «В остальном, уважаю Ваш труд.»
Запятая не нужна (наречное выражение, к тематике вводных слов не относится и не обособляется)
4. «Насчет дебатов, с Александром.»
Видимо, «с Александром» в данном случае играет роль уточнения, можно списать на «авторский текст». Но лучше без запятой.
5. «Человек, потратил время расписать…»
Запятая не нужна. Даже в случае «авторского текста» не нужна :).
6. «…будем же ему просто благодарны, ведь, он же ни где не писал о своей…»
Запятая перед «ведь» лишняя.
«ни где» пишется вместе.
По смыслу комментария с Акимом полностью согласен. Спасибо за развернутое пояснение с примерами. Сэкономили время, а это деньги! Благодарю, Дмитрий!
«Собаки лают — караван идет». Негативные отзывы не стоят Вашего внимания.
Ух, Александр, как вы разошлись) Вообще, я отчасти благодарен Акиму, за то что он обратил внимание на эту ошибку, так как я заметил что много где ее допускаю.
И спасибо за поддержку :)
Спасибо за материал, лучший что я нашел и все понял хотя и являюсь лишь прикладным разработчиком, и не знаю PHP.
Хотя отчасти согласен с критикой, в том плане что когда знаешь, все сразу предельно понятно, а если не знаешь — то желательно что бы все было очень(!) подробно разжевано. Видимо некоторые посетители сложнее соображают и им остается непонятным данный пример.
Тестировать алгоритмы сортировки на массиве из 8 элементов — смешно)
Тут самая тупая сортировка вставками будет работать быстрее любого хитрого алгоритма.
И все же по поводу расширяемости поспорю, «оперделяющим массивом» определить нужный компоратор можно далеко не всегда, а только когда все элементы сравниваются с «чем-то», а не между собой.
P.S. Как расширить самый расширяемый метод например, если нам нужно N полей для сравнения?
Ну главное, что тест даже и из 8 элементов вполне показательный.
Сей способ сортирует по двум ключам (f1, f2) один миллион массивов за 41 секунду.
use:
isort::sort($array, 'key');
Спасибо!
Автор, не обращайте внимание на критику. Критика будет всегда.
Например, до этого сколько я не читал об array_multisort на php.su и php.net, не понимал сходу.
Возможно потому, что громоздкие там примеры подаются в качестве объяснения. Да и не было стимула разбираться, так как не уверен был что функция умеет делать, то что мне нужно.
Вас же пример для функции array_multisort прост и понятен.
Сел, разобрался, написал функцию себе для Opencart, время сортировки сравнил с функцией, найденной в Интернет.
Выводы:
— array_multisort() умеет сортировать
— и делает это быстрее, чем моя предыдущая функция.
Код моей функции
Благодарю за поддержку :)
Вот пример моей функции
Спасибо! Спасли! Половина курсовой сделана!
Может есть где-то еще выборка из многомерного массива?
Буду премного благодарна)))
Автор конечно рассказчик от бога, нихрена не понятно ))) заплутали в трех соснах
Спасибо автору! Пример отличный!
отличная работа автору респект, как раз в тему !!)))
Тут я нашел ошибку, а именно в скобках
array_multisort в конце не хватает $data.
data_year=array();
//Генерируем «определяющий» массив
foreach($data as $key=>$arr){
$data_year[$key]=$arr[‘year’];
}
for($i=0; $i<100000; $i++){
$data_tmp=$data;
array_multisort($data_year, SORT_NUMERIC, $data_tmp);
}
Почему
а не
— с чем это связано?
Пример с
не работает
Разобрался, спасибо.
вот так написал:
Мудрено как-то написано, можно проще. Есть массив ar из 4 столбцов и 5 строк. Присваиваем значения:
$ar[номер_столбца][номер_строки]=значение
Сортируем, чтобы сохранилось соответствие между строками:
array_multisort($ar[3],$ar[2],$ar[1],$ar[0]; — т.е. у нас 4 столбца.
Выводим в таблицу:
for ($n = 0; $n<$5; $n++)
{
echo '’;
for($st=0;$st<=4;$st++)
{
echo '’.$ar[$st][$n].»;
}
echo »;
}
Все просто и понятнр.