Подтвердить что ты не робот

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

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

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?

4b9b3361

Ответ 1

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

Ответ изменился с течением времени, поскольку 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

Здесь код для sort(), использующий __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"

Ответ 2

В этом конкретном сценарии вы можете сортировать его с помощью функции 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 );

Ответ 3

Вы можете использовать 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+) куча будет быстрее и меньше памяти, хотя.

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

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

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

Ответ 4

Я просто закодировал это. Он должен быть быстрее, чем 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

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

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

usort($myArray, "cmp");

Ответ 6

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

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);

Ответ 7

Heres a 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 - это не сравнивающий алгоритм сортировки.

Ответ 8

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

Ответ 9

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

Ответ 11

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

Ответ 12

Да. Если вы реализуете spl ArrayObject в своем объекте person, все нормальные функции массива php будут работать с ним правильно.

Ответ 13

Попробуйте использовать: http://www.php.net/manual/en/function.usort.php

Пример:

<?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");

?>

Ответ 14

Если все переменные-члены, о которых идет речь, гарантированно будут разными, будет проще и быстрее создать новую коллекцию, индексированную этими значениями, а затем ksort it:

 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

Я думаю, это будет в 10000000 раз быстрее, чем usort

Ответ 15

Вот вариант, который учитывает следующие вещи:

  • Пространство имен
  • частные свойства
  • с использованием методов getter и setter
  • свойство для сортировки как параметра

PHP

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 );