Сортировка многомерных массивов по ключу на PHP

31 комментарий

Практически перед каждым PHP-программистом становится задача отсортировать многомерный массив. К примеру вот такой вот:

По, допустим, ключу year, как же сделать это грамотно?

Как просили меня в комментах — забегу немного вперед. В PHP есть встроенная функция array_multisort — она позволяет сортировать многомерные массивы. Но если вам интересно давайте рассмотрим все возможные методы сортировки многомерных массивов и их плюсы и минусы в сравнении с array_multisort.

Классическое решение

Естественно первое про что вы подумали, это сортировка многомерного массива с помощью uasort, да? Набросаем вот такой вот код:

Запускаем, и засекаем время выполнения… Итого: 13.15 сек. Долговато и не впечатляет.

Ищем пути решения проблемы, находим на php.net, другой вариант функции сравнения, который, как там написано, должен работать быстрее:

Итого: 23.11 сек. Хреновая оптимизация…

Ладно, хорошо, со временем выполнения мы определились. Давайте попробуем определится с «расширяемостью кода». Допустим нам поставили задачу отсортировать сначала по ключу year а затем по ключу author. Для этого нам приходится переписывать всю «дополнительную функцию», в итоге получаем что то похожее на это:

Громоздко. Сложно изменять. Вообщем отстой, на мой взгляд.

Итак, подведем итоги. Минусы:

  • Долго выполняется
  • Сложно расширять
  • Под каждую сортировку нужна своя, новая функция

Плюсы:

  • Единственный очевидный вариант (?)

Пробуем костыли

Попробуем написать свою функцию для сортировки массивов с блекджеком и шлюхами. Напомню, наша задача сортировать многомерный массив по ключу:

Засекаем. Получаем: 7.90 сек. Ну уже не плохо впринципе.

Да вот только заставить этот костыль сортировать по двум ключам уже не получится, к сожалению. Подведем итоги. Минусы:

  • Невозможно расширять
  • Сортировка только по одному ключу

Плюсы

  • Приемлимая скорость выполнения
  • Одна функция для разных видов сортировки

Функция array_multisort

Оказывается разработчики PHP уже давным давно все придумали до нас. Оказывается есть функция array_multisort. Как работает эта функция:
array_multisort( array &$arr [, array &$arr [, array &$arr... ]] )
Грубо говоря каждый массив будет отсортирован в соответствии с предыдущим массивом. Вообщем пример:

Выведет:

А это как раз то что нам надо! Возвращаемся к тестам на скорость:

Засекаем. Получаем: 3.87 сек. Это рекорд!

Ну то что это самый быстрый вариант мы определили. Это хорошо. А как насчет расширяемости? Как к примеру заставить сортировать массив по двум ключам? Оказывается с этой функцией очень просто! Достаточно добавить еще один «определяющий массив», вот так:

На выходе получим вот такой массив:

Как видите функция справилась со своей задачей. Наш массив отсортирован сначала по year, затем по author. А с помощью различных флагов типа SORT_DESC, SORT_ASC и тд можно добится любой сортировки (про них подробнее смотрите в мане), так что это на мой взгляд самый лучший вариант для использования в своих скриптах.

Минусы

  • ??

Плюсы

  • Лучшая скорость
  • Расширяемость
  • Функциональность

Заключение

Вообщем используйте array_multisort для сортировки многомерных массивов. И будет вам счастье.

Спасибо за внимание =)

  1. NightWolf

    Блин, а я целый класс писал для таких сортировок… А оно уже оказывается есть встроеное(

  2. Dimazan

    Последний тест на скорость не корректный. В нем ключевой массив создается только один раз. А тестируется на время с уже созданным массивом.

    1. Дмитрий Амиров Автор

      Почему же неккоректный? Тестируется же имено скорость сортировки. Как и в остальных, предыдущих тестах.

  3. marat

    можно сделать так:

    1. Дмитрий Амиров Автор

      Так то сделать можно, и да, будет быстрее и короче. Только первоочередная цель кода приведенного в моей статье, не быть быстрым и коротким, а быть наглядным.

  4. Rayaz

    То есть если нужна наглядность, то можно применять некорректные примеры сравнений? :)

    Самая интересная часть статьи — рассказ про array_multisort() проходит в духе: «ух ты, смотри как клево!», но по сути тема не раскрывается. К примеру, я так и не понял что такое определяющий массив. Да, и к тому же, если задачей стояла наглядность, то почему в результирующем примере приводится решение с каким-то левым массивом, а не с тем, который был в самом начале. Уж он то куда нагляднее и раскрывает суть.

    В общем, уныло как-то. Но за наводку на array_multisort() спасибо :)

    1. Дмитрий Амиров Автор

      Гм…) Может я где-то неправильно изложил суть материала, но пример по сортировке исходного массива был приведен:

      Возвращаемся к тестам на скорость:

      Засекаем. Получаем: 3.87 сек. Это рекорд!

      А тот пример, который вы называете некорректным — вполне корректен. В нем демонстрируется сортировка по двум ключам.

      Насчет «уныло» — я конечно уважаю критику, но зря вы так жестко) моей целью дествительно было подсказать о существовании данной функции, и в кратце объяснить как ей пользоваться. Но возможно вы правы, стоило об этом написать подробнее

    2. Александр

      Абсолютно солидарен.
      Автор видимо полагает, что это и так всем известно. Я, если честно, кроме как нужно использовать функцию array_multisort(), ни хрена не понял. А как пользоваться, в каким случаях куда и что совать…
      Автор силен в языке, но как преподаватель — хреновый.

      1. Дмитрий Амиров Автор

        Ребят (

        Ставил я задачу перед собой подсказать о существовании данной функции. А по поводу того как ее использовать, достаточно внятно описано в официальном мануале, не захотел дублировать его.

        Но добавил в TODO написать подробнее об использовании.

        1. karbofos

          «Оказывается есть функция array_multisort.» — я бы сократил всю статью до этой фразы. Мне кажется, она бы стала только лучше.

  5. Александр

    Благодарствую за нужный материал. Как раз столкнулся с необходимостью сортировки массива по 2м ключам. Благодаря Вашей подсказки сделал под себя сортировку)

  6. Аким

    Доброго времени суток, все вроде отлично. Кроме одного, совсем мелкое замечание, но «Вообщем» слова не существует есть «Вообще» и «В общем»(2 слова). Прошу прощения, не хотел найти за что бы придраться, просто хотел бы помочь в искоренении мелких ошибочек. И к этому добавлю обращение в ед. множестве к человеку пишется с большой буквы. Во множественном числе можно написать с маленькой. В остальном, уважаю Ваш труд.
    Насчет дебатов, с Александром.
    Ув. Александр, преподаватели в университете, школах, пр. учебных заведениях, на тренингах и прочих познавательных семинарах. Человек, потратил время расписать полезную функцию PHP в читаемой форме, будем же ему просто благодарны, ведь, он же ни где не писал о своей цели преподнести доступно ЯП для всех читателей его трудов.

    1. Дмитрий Амиров Автор

      Здравствуйте!

      Спасибо за ваш комментарий. Да, если честно, я знаю про правильное написание этих слов. Постоянно бью себя по рукам и заставляю себя обращать свое внимание на это. Но, к сожалению, — не выходит, иногда ошибки проскакивают. Буду исправляться)

      И еще, спасибо за поддержку. Мне очень не нравится та потребительская позиция, которую выбирают некоторые мои читатели, и я рад что хоть кто-то встал на мою защиту)

      1. Аким

        Было бы за что, просто, действительно, не справедливо. Сам все собираюсь завести «напоминалку», порой делаешь что-то интересное, спустя время забываешь, а потом как начнешь копаться по проектах. Но такие моменты заставляют задуматься, а стоит ли в открытом доступе выставлять свои заметки.
        Кстати, спасибо и Вам так же, пару раз сталкивался, но решалось на уровне оформления бд для такой цели и запросом. А в данном случае, идеальное решение без лишних усилий)

        1. Дмитрий Амиров Автор

          Но такие моменты заставляют задуматься, а стоит ли в открытом доступе выставлять свои заметки.

          Я думаю, в любом случае, стОит. Надо нести добро людям)

    2. Александр

      Здравствуйте!
      Аким, если берете на себя бремя исправлений, желателен личный пример.
      1. «Кроме одного, совсем мелкое замечание, но «Вообщем» слова не существует есть «Вообще» и «В общем»(2 слова). »
      Уместно двоеточие (уточняющее слово) после «Кроме одного».
      Запятая перед «есть» (сложносочиненное).
      2. «И к этому добавлю обращение в ед. множестве…»
      Уместно двоеточие после «добавлю» (между частями бессоюзного сложного предложения)
      3. «В остальном, уважаю Ваш труд.»
      Запятая не нужна (наречное выражение, к тематике вводных слов не относится и не обособляется)
      4. «Насчет дебатов, с Александром.»
      Видимо, «с Александром» в данном случае играет роль уточнения, можно списать на «авторский текст». Но лучше без запятой.
      5. «Человек, потратил время расписать…»
      Запятая не нужна. Даже в случае «авторского текста» не нужна :).
      6. «…будем же ему просто благодарны, ведь, он же ни где не писал о своей…»
      Запятая перед «ведь» лишняя.
      «ни где» пишется вместе.

      По смыслу комментария с Акимом полностью согласен. Спасибо за развернутое пояснение с примерами. Сэкономили время, а это деньги! Благодарю, Дмитрий!
      «Собаки лают — караван идет». Негативные отзывы не стоят Вашего внимания.

      1. Дмитрий Амиров Автор

        Ух, Александр, как вы разошлись) Вообще, я отчасти благодарен Акиму, за то что он обратил внимание на эту ошибку, так как я заметил что много где ее допускаю.
        И спасибо за поддержку :)

  7. jimmiz

    Спасибо за материал, лучший что я нашел и все понял хотя и являюсь лишь прикладным разработчиком, и не знаю PHP.
    Хотя отчасти согласен с критикой, в том плане что когда знаешь, все сразу предельно понятно, а если не знаешь — то желательно что бы все было очень(!) подробно разжевано. Видимо некоторые посетители сложнее соображают и им остается непонятным данный пример.

  8. privet

    Тестировать алгоритмы сортировки на массиве из 8 элементов — смешно)
    Тут самая тупая сортировка вставками будет работать быстрее любого хитрого алгоритма.
    И все же по поводу расширяемости поспорю, «оперделяющим массивом» определить нужный компоратор можно далеко не всегда, а только когда все элементы сравниваются с «чем-то», а не между собой.
    P.S. Как расширить самый расширяемый метод например, если нам нужно N полей для сравнения?

    1. Дмитрий Амиров Автор

      Ну главное, что тест даже и из 8 элементов вполне показательный.

  9. Виталий

    Сей способ сортирует по двум ключам (f1, f2) один миллион массивов за 41 секунду.

  10. JFresh

    use:
    isort::sort($array, 'key');

  11. Oleg

    Спасибо!
    Автор, не обращайте внимание на критику. Критика будет всегда.
    Например, до этого сколько я не читал об array_multisort на php.su и php.net, не понимал сходу.
    Возможно потому, что громоздкие там примеры подаются в качестве объяснения. Да и не было стимула разбираться, так как не уверен был что функция умеет делать, то что мне нужно.
    Вас же пример для функции array_multisort прост и понятен.
    Сел, разобрался, написал функцию себе для Opencart, время сортировки сравнил с функцией, найденной в Интернет.
    Выводы:
    — array_multisort() умеет сортировать
    — и делает это быстрее, чем моя предыдущая функция.

    Код моей функции

  12. Сергей

    Вот пример моей функции

  13. Алла

    Спасибо! Спасли! Половина курсовой сделана!
    Может есть где-то еще выборка из многомерного массива?
    Буду премного благодарна)))

  14. Алексей

    Автор конечно рассказчик от бога, нихрена не понятно ))) заплутали в трех соснах

  15. Art

    Тут я нашел ошибку, а именно в скобках
    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);
    }

Добавить комментарий

Прочли запись? Понравилась? Не стесняйтесь, оставьте, пожалуйста, свой комментарий. Мне очень интересно, что вы думаете об этом. Кстати в комментарии вы можете задать мне любой вопрос. Я обязательно отвечу.

Вы можете оставить коментарий анонимно, для этого можно не указывать Имя и email. Все комментарии проходят модерацию, поэтому ваш комментарий появится не сразу.