Сортировать массив по свойству объекта в PHP? - PullRequest
52 голосов
/ 23 сентября 2009

Если у меня есть объект как таковой:

class Person {
  var $age;
  function __construct($age) {
    $this->age = $age;
  }
}

и у меня есть любой массив Person s

$person1 = new Person(14);
$person2 = new Person(5);
$people = array($person1, $person2);

Существует ли простой способ сортировки массива $people по свойству Person->age?

Ответы [ 15 ]

87 голосов
/ 23 сентября 2009

Вопрос касался неэффективности использования usort из-за накладных расходов при вызове обратного вызова сравнения. В этом ответе рассматривается различие между использованием встроенных функций сортировки и нерекурсивной реализацией быстрой сортировки.

Ответ менялся со временем по мере развития PHP с 2009 года, поэтому я постоянно обновлял его. Более старый материал, хотя и более не актуален, но все же интересен!

TL; DR: с php 7.0.1 нерекурсивная быстрая сортировка больше не работает быстрее, чем использование usort с обратным вызовом. Это не всегда так, поэтому подробности ниже сделать интересное чтение. Реальный вывод заключается в том, что если вы сравните свою проблему и попробуете альтернативные подходы, вы можете получить удивительные результаты.

Январь 2016, обновление

Ну вот мы и выпустили php 7.0 и 7.1 в пути! Наконец, для этого набора данных встроенный usort чуть-чуть быстрее!

+-----------+------------+------------+------------+------------+------------+
| 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 |
+-----------+------------+------------+------------+------------+------------+

Обновление за январь 2015

Когда я первоначально ответил на это в 2009 году, я сравнил использование usort с нерекурсивной быстрой сортировкой, чтобы увидеть, есть ли разница. Как оказалось, разница была значительная , при этом быстрая сортировка работала в 3 раза быстрее.

Поскольку сейчас 2015 год, я подумал, что, возможно, будет полезно вернуться к нему, поэтому я взял код, который сортирует 15000 объектов, используя usort и quicksort, и запустил его на 3v4l.org, который запускает его на множестве различных версий PHP. Полные результаты здесь: http://3v4l.org/WsEEQ

+-----------+------------+------------+------------+------------+------------+
| 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 |
+-----------+------------+------------+------------+------------+------------+

Оригинальные заметки с 2009 года

Я попробовал usort и отсортировал 15000 объектов Person примерно за 1,8 секунды.

Поскольку вы обеспокоены неэффективностью вызовов функции сравнения, я сравнил ее с нерекурсивной реализацией Quicksort . Это на самом деле длилось примерно треть времени, примерно 0,5 секунды.

Вот мой код, который сравнивает два подхода

// 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";

Интересным предложением было добавить метод __toString в класс и использовать sort (), поэтому я тоже попробовал это. Проблема в том, что вы должны передать SORT_STRING в качестве второго параметра для сортировки, чтобы он фактически вызывал магический метод, который имеет побочный эффект выполнения строки, а не числовой сортировки. Чтобы противостоять этому, вам нужно дополнить числа нулями, чтобы они правильно сортировались. Чистый результат состоял в том, что это было медленнее, чем usort и пользовательская quickSort

sort 10000 items took      1.76266698837
usort 10000 items took     1.08757710457
quickSort 10000 items took 0.320873022079

Вот код для сортировки () с использованием __toString ():

$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"
42 голосов
/ 23 сентября 2009

Для этого конкретного сценария вы можете отсортировать его с помощью функции usort (), где вы определяете свою собственную функцию для сравнения элементов в массиве.

<?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 );
10 голосов
/ 01 ноября 2010

Вы можете использовать usort или heap .

 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. Для больших коллекций (1000+), куча будет быстрее и потреблять меньше памяти.

Дополнительным преимуществом наличия кучи является возможность использовать их функцию сравнения для обратных вызовов к другим функциям сортировки, таким как usort. Вам просто нужно помнить, что порядок сравнения поменялся, поэтому любое сравнение, выполненное с кучей, приведет к изменению порядка в usort.

// using $people array and $sorter
usort($people, array($sorter, 'compare'));
print_r($people); // people sorted from 22 to 40

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

8 голосов
/ 16 декабря 2013

Я только что закодировал это. Он должен быть быстрее, чем usort, поскольку он не зависит от многочисленных вызовов функций.

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 имеют одинаковые $age.

5 голосов
/ 01 ноября 2010

Вам просто нужно написать собственную функцию сравнения, а затем использовать что-то вроде usort , чтобы выполнить фактическую сортировку. Например, если переменная-член была myVar, вы можете отсортировать ее следующим образом:

function cmp($a, $b)
{
    if ($a->myVar == $b->myVar) {
        return 0;
    }
    return ($a->myVar < $b->myVar) ? -1 : 1;
}

usort($myArray, "cmp");
2 голосов
/ 07 января 2015

Вы можете сделать это с вкусностями узо :

$result = Arrays::sort(array($person1, $person2), Comparator::compareBy('age'));

http://ouzo.readthedocs.org/en/latest/utils/comparators.html

2 голосов
/ 08 января 2012

Я выбрал следующий подход: создал функцию, которая принимает массив объектов, затем внутри функции я создаю ассоциативный массив, используя свойство в качестве ключа для массива, а затем сортирую ключи массива, используя 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
*/
2 голосов
/ 10 сентября 2010

Одно наблюдение состоит в том, что если источником данных является база данных, то, вероятно, быстрее будет выполнять сортировку с использованием SQL, чем в PHP. Конечно, это спорный вопрос, если источником данных является файл CSV или XML.

2 голосов
/ 23 сентября 2009

Вот stable Radix Sort реализация для значений 0 ... 256:

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 - это алгоритм сравнения без сравнения.

2 голосов
/ 23 сентября 2009

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

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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...