сортировать массив по свойству объекта в php?

<p>если у меня есть объект как таковой:</p><pre><code>class person { var $age; function __construct($age) { $this->age = $age; } } </code></pre><p>и у меня есть любой массив <code>person</code>s</p><pre><code>$person1 = new person(14); $person2 = new person(5); $people = array($person1, $person2); существует ли простой способ сортировки массива $people</code> по свойству <code>person->age</code>?</p>

<p>вопрос был связан с неэффективностью использования <a href="http://www.php.net/usort">sort</a> из-за накладных расходов при вызове обратного вызова сравнения. этот ответ рассматривает разницу между использованием встроенных функций сортировки и нерекурсивной реализацией быстрой сортировки.</p><p>ответ менялся со временем по мере развития php с 2009 года, поэтому я постоянно обновлял его. более старый материал, хотя и более не актуален, но все же интересен!</p><p><strong> tl; dr: с php 7.0.1 нерекурсивная быстрая сортировка больше не быстрее, чем использование usort с обратным вызовом. </strong> это было не всегда случай, именно поэтому детали ниже делают интересное чтение. реальный вывод заключается в том, что если вы сравните свою проблему и попробуете альтернативные подходы, вы можете получить удивительные результаты.</p><h2>январь 2016 обновление</h2><p>ну вот мы с php 7.0 выпущены и 7.1 на подходе! наконец, для этого набора данных встроенный usort работает <em> очень-чуть </em> быстрее!</p><pre><code>+-----------+------------+------------+------------+------------+------------+ | operation | hhvm | php7.0.1 | php5.6.3 | 5.4.35 | 5.3.29 | +-----------+------------+------------+------------+------------+------------+ | usort | *0.0445 | *0.0139 | 0.1503 | 0.1388 | 0.2390 | | quicksort | 0.0467 | 0.0140 | *0.0912 | *0.1190 | *0.1854 | | | 5% slower | 1% slower | 40% faster | 15% faster | 23% faster | +-----------+------------+------------+------------+------------+------------+ </code></pre><h2>январь 2015 обновление</h2><p>когда я впервые ответил на это в 2009 году, я сравнил использование usort с нерекурсивной быстрой сортировкой, чтобы увидеть, есть ли разница. как оказалось, существовала <em> значительная </em> разница: быстрая сортировка работала в 3 раза быстрее.</p><p>поскольку сейчас 2015 год, я подумал, что может быть полезно вернуться к нему, поэтому я взял код, который сортирует 15000 объектов, используя usort и quicksort, и запустил его на 3v4l.org, который запускает его на множестве разных версий php. полные результаты здесь: <a href="http://3v4l.org/wseeq">http://3v4l.org/wseeq</a> </p><pre><code>+-----------+------------+------------+------------+------------+------------+ | operation | hhvm | php7alpha1 | php5.6.3 | 5.4.35 | 5.3.29 | +-----------+------------+------------+------------+------------+------------+ | usort | *0.0678 | 0.0438 | 0.0934 | 0.1114 | 0.2330 | | quicksort | 0.0827 | *0.0310 | *0.0709 | *0.0771 | *0.1412 | | | 19% slower | 30% faster | 25% faster | 31% faster | 40% faster | +-----------+------------+------------+------------+------------+------------+ </code></pre><h2>оригинальные заметки 2009 года</h2><p>я попытался <a href="http://www.php.net/usort">sort</a> и отсортировал 15000 объектов person примерно за 1,8 секунды.</p><p>поскольку вы обеспокоены неэффективностью вызовов функции сравнения, я сравнил ее с нерекурсивной реализацией <a href="http://en.wikipedia.org/wiki/quicksort">quicksort</a> . это на самом деле длилось около трети времени, примерно 0,5 секунды.</p><p>вот мой код, который сравнивает два подхода</p><pre><code>// non-recurive quicksort for an array of person objects // adapted from http://www.algorithmist.com/index.php/quicksort\_non-recursive.php function quicksort( &$array ) { $cur = 1; $stack[1]['l'] = 0; $stack[1]['r'] = count($array)-1; do { $l = $stack[$cur]['l']; $r = $stack[$cur]['r']; $cur--; do { $i = $l; $j = $r; $tmp = $array[(int)( ($l+$r)/2 )]; // partion the array in two parts. // left from $tmp are with smaller values, // right from $tmp are with bigger ones do { while( $array[$i]->age < $tmp->age ) $i++; while( $tmp->age < $array[$j]->age ) $j--; // swap elements from the two sides if( $i <= $j) { $w = $array[$i]; $array[$i] = $array[$j]; $array[$j] = $w; $i++; $j--; } }while( $i <= $j ); if( $i < $r ) { $cur++; $stack[$cur]['l'] = $i; $stack[$cur]['r'] = $r; } $r = $j; }while( $l < $r ); }while( $cur != 0 ); } // usort() comparison function for person objects function personsort( $a, $b ) { return $a->age == $b->age ? 0 : ( $a->age > $b->age ) ? 1 : -1; } // simple person object class person { var $age; function __construct($age) { $this->age = $age; } } //---------test internal usort() on 15000 person objects------ srand(1); $people=array(); for ($x=0; $x<15000; $x++) { $people[]=new person(rand(1,100)); } $start=microtime(true); usort( $people, 'personsort' ); $total=microtime(true)-$start; echo "usort took $total\n"; //---------test custom quicksort on 15000 person objects------ srand(1); $people=array(); for ($x=0; $x<15000; $x++) { $people[]=new person(rand(1,100)); } $start=microtime(true); quicksort( $people ); $total=microtime(true)-$start; echo "quicksort took $total\n"; </code></pre><p>интересным предложением было добавить метод <code>\_\_tostring</code> в класс и использовать sort (), поэтому я тоже попробовал это. проблема в том, что вы должны передать sort\_string в качестве второго параметра для сортировки, чтобы он фактически вызывал магический метод, который имеет побочный эффект выполнения строки, а не числовой сортировки. чтобы противостоять этому, вам нужно дополнить числа нулями, чтобы они правильно сортировались. чистый результат состоял в том, что это было медленнее, чем usort и пользовательская быстрая сортировка</p><pre><code>sort 10000 items took 1.76266698837 usort 10000 items took 1.08757710457 quicksort 10000 items took 0.320873022079 </code></pre><p>вот код для sort () с использованием \_\_tostring ():</p><pre><code>$size=10000; class person { var $age; function \_\_construct($age) { $this->age = $age; $this->sortable=sprintf("%03d", $age); } public function __tostring() { return $this->sortable; } } srand(1); $people=array(); for ($x=0; $x<$size; $x++) { $people[]=new person(rand(1,100)); } $start=microtime(true); sort( $people, sort\_string); $total=microtime(true)-$start; echo "sort($size) took $total\n" </code></pre>

<p>для этого конкретного сценария вы можете отсортировать его с помощью функции usort (), где вы определяете свою собственную функцию для сравнения элементов в массиве.</p><pre><code><?php class person { var $age; function __construct($age) { $this->age = $age; } } function personsort( $a, $b ) { return $a->age == $b->age ? 0 : ( $a->age > $b->age ) ? 1 : -1; } $person1 = new person(14); $person2 = new person(5); $person3 = new person(32); $person4 = new person(150); $person5 = new person(39); $people = array($person1, $person2, $person3, $person4, $person5); print_r( $people ); usort( $people, 'personsort' ); print_r( $people ); </code></pre>

<p>вы можете использовать <a href="http://de3.php.net/manual/en/function.usort.php"><code>usort</code></a> или <a href="http://github.com/salathe/spl-examples/wiki/splmaxheap">кучу</a> .</p><pre><code> class sortpeoplebyage extends splmaxheap { function compare($person1, $person2) { return $person1->age - $person2->age; } } $people = array(new person(30), new person(22), new person(40)); $sorter = new sortpeoplebyage; array\_map(array($sorter, 'insert'), $people); print\_r(iterator\_to\_array($sorter)); // people sorted from 40 to 22 обратите внимание, что цель кучи - всегда иметь упорядоченную коллекцию и не заменять usort. <a href="http://matthewturland.com/2010/05/20/new-spl-features-in-php-5-3/">для больших коллекций (1000+), куча будет быстрее и потреблять меньше памяти. дополнительным преимуществом наличия heaps является возможность использовать их функцию сравнения для обратных вызовов к другим функциям сортировки, таким как usort. вам просто нужно помнить, что порядок сравнения обратный, поэтому любое сравнение, выполненное с кучей, приведет к измененному порядку в usort.// using $people array and $sorter usort($people, array($sorter, 'compare')); print_r($people); // people sorted from 22 to 40 </code></pre><p><code>usort</code> подходит для небольших и средних коллекций, где вы выполните сортировку один раз в конце. конечно, вам не нужно иметь кучу, чтобы использовать <code>usort</code>. вы также можете добавить любой другой действительный обратный вызов для сортировки.</p>

<p>я только что закодировал это. он должен быть быстрее, чем <code>usort</code>, поскольку он не зависит от многочисленных вызовов функций.</p><pre><code>function sortbyprop($array, $propname, $reverse = false) { $sorted = []; foreach ($array as $item) { $sorted[$item->$propname][] = $item; } if ($reverse) krsort($sorted); else ksort($sorted); $result = []; foreach ($sorted as $subarray) foreach ($subarray as $item) { $result[] = $item; } return $result; } использование:$sorted = sortbyprop($people, 'age'); да, и он использует ksort, но работает, даже если многие $people</code> имеют одинаковый <code>$age.

вам просто нужно написать собственную функцию сравнения, а затем использовать что-то вроде <a href="http://uk.php.net/usort">sort , чтобы выполнить фактическую сортировку. например, если переменная-член была myvar, вы можете отсортировать ее следующим образом:function cmp($a, $b) { if ($a->myvar == $b->myvar) { return 0; } return ($a->myvar < $b->myvar) ? -1 : 1; } usort($myarray, "cmp"); </code></pre>

<p>вы можете сделать это с <a href="https://github.com/letsdrink/ouzo-goodies">узо вкусности</a> :</p><pre><code>$result = arrays::sort(array($person1, $person2), comparator::compareby('age'));  <a href="http://ouzo.readthedocs.org/en/latest/utils/comparators.html">http://ouzo.readthedocs.org/en/latest/utils/comparators.html 

я не советую свое решение в вашем примере, потому что это было бы некрасиво (и я не проверял его), но оно работает .... и в зависимости от необходимости, это может помочь. :)class person { public $age; function \_\_construct($age) { $this->age = $age; } public function __tostring() { return $this->age; } } $person1 = new person(14); $person2 = new person(5); $persons = array($person1, $person2); asort($persons); </code></pre>

<p>одним из наблюдений является то, что если источником данных является база данных, то, вероятно, быстрее сортировать с использованием sql, чем в php. конечно, это спорный вопрос, если источником данных является файл csv или xml.</p>

<p>вот реализация <a href="http://en.wikipedia.org/wiki/stable\_sort#stability">stable</a> <a href="http://en.wikipedia.org/wiki/radix\_sort">radix sort</a> для значений 0 ... 256:</p><pre><code>function radixsort(&$a) { $n = count($a); $partition = array(); for ($slot = 0; $slot < 256; ++$slot) { $partition[] = array(); } for ($i = 0; $i < $n; ++$i) { $partition[$a[$i]->age & 0xff][] = &$a[$i]; } $i = 0; for ($slot = 0; $slot < 256; ++$slot) { for ($j = 0, $n = count($partition[$slot]); $j < $n; ++$j) { $a[$i++] = &$partition[$slot][$j]; } } } это стоит только o ( n ), поскольку radix sort является несравниваемый алгоритм сортировки.

я использовал следующий подход: создал функцию, которая принимает массив объектов, затем внутри функции я создаю ассоциативный массив, используя свойство в качестве ключа для массива, а затем сортирую ключи массива с помощью ksort:class person { var $age; function \_\_construct($age) { $this->age = $age; } } function sortperson($persons = array()){ foreach($persons as $person){ $sorted[$person->age] = $person; } ksort($sorted); return array\_values($sorted); } $person1 = new person(14); $person2 = new person(5); $persons = array($person1, $person2); $person = sortperson($persons); echo $person[0]->age."n".$person[1]->age; /* output: 5 14 */ </code></pre>

<p><code>usort()</code> или <code>uasort() /* to maintain index association if you were using an associative array */</code></p>

<p>попробуйте usort: <a href="http://www.php.net/manual/en/function.usort.php">http://www.php.net/manual/en/function.usort.php</a> </p><p>пример:</p><pre><code><?php function cmp($obja, $objb) { $a = $obja->sortfield; $b = $objb->sortfield; if ($a == $b) { return 0; } return ($a < $b) ? -1 : 1; } $a = array( / your objects / ); usort($a, "cmp"); ?> </code></pre>

<p>если все рассматриваемые переменные-члены гарантированно будут разными, будет проще и быстрее создать новую коллекцию, индексированную этими значениями, а затем <code>ksort</code> it:</p><pre><code> foreach($obj_list as $obj) $map[$obj->some\_var] = $obj; ksort($map); /// $map now contains the sorted list если есть повторяющиеся значения, вы все равно можете избежать usort, используя менее известную функцию sort, что массивы массивов сортируются по значению первого скалярного члена. foreach($obj\_list as $obj) $map[] = array($obj->some_var, $obj); sort($map); // sorts $map by the value of ->some\_var </code></pre><p>я думаю, это все равно будет в 10000000 раз быстрее, чем <code>usort</code></p>

<p>да. если вы реализуете <a href="http://us2.php.net/manual/en/book.spl.php">spl arrayobject</a> в вашем объекте person, все обычные функции массива php будут работать с ним правильно.</p>

<p>вот вариант, который учитывает следующие вещи:</p><ul> <li>пространства имен</li> <li>частная собственность</li> <li>используя методы получения и установки</li> <li>свойство для сортировки в качестве параметра</li> </ul><p><strong>php</strong></p><pre><code>namespace dummy; class person { private $age; function __construct($age) { $this->setage($age); } public function getage() { return $this->age; } public function setage($age) { $this->age = $age; } } class customsort{ public $field = ''; public function cmp($a, $b) { return strcmp($a->{'get'.ucfirst($this->field)}(), $b->{'get'.ucfirst($this->field)}()); } public function sortobjectarraybyfield($array, $field) { $this->field = $field; usort($array, array("dummy\customsort", "cmp")); return $array; } } $robert = new person(20); $peter = new person(12); $robin = new person(44); $people = array($robert, $peter, $robin); var\_dump( $people ); $customsort = new customsort(); $people = $customsort->sortobjectarraybyfield($people, 'age'); var_dump( $people ); </code></pre>