在PHP中按对象属性排序数组?

时间:2009-09-22 20:46:53

标签: php sorting

如果我有这样的对象:

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 个答案:

答案 0 :(得分:87)

由于调用比较回调的开销,问题是关注使用usort的效率低下。这个答案着眼于使用内置排序函数和非递归快速排序实现之间的区别。

随着PHP自2009年以来的发展,答案随着时间的推移而发生变化,因此我保持更新。较旧的材料虽然不再相关,但仍然很有趣!

TL; DR:从php 7.0.1开始,一个非递归的快速排序不再比使用带回调的usort更快。情况并非总是这样,这就是为什么细节下面有趣的阅读。真正的要点是,如果您对问题进行基准测试并尝试其他方法,您可能会得出令人惊讶的结果。

2016年1月更新

好了,我们在发布了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年1月更新

当我在2009年最初回答这个问题时,我将使用usort与非递归快速排序进行比较,看看是否存在差异。事实证明,显着的差异,快速排序运行速度提高了3倍。

就像现在的2015年,我认为重新审视它可能会有用,所以我使用usort和quicksort对15000个对象进行排序,然后在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,并在大约1.8秒内对15000个Person对象进行了排序。

由于您担心调用比较函数的效率低下,我将其与非递归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()的sort()代码:

$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"

答案 1 :(得分:42)

对于该特定场景,您可以使用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 );

答案 2 :(得分:10)

您可以使用usortheap

 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

请注意,Heap的目的是始终拥有有序集合,而不是替换usortFor large collections (1000+), a heap will be faster and less memory intensive though.

使用Heaps的另一个好处是能够使用其比较函数回调其他排序函数,例如usort。您只需要记住比较的顺序是相反的,因此使用堆进行的任何比较都会导致usort中的顺序颠倒。

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

usort适用于中小型集合,您可以在最后进行一次排序。当然,您不必使用堆usort。您也可以为排序添加任何其他有效的回调。

答案 3 :(得分:8)

我刚编了这个。它应该比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,它也能正常工作。

答案 4 :(得分:5)

您只需编写自定义比较函数,然后使用类似usort的内容进行实际排序。例如,如果成员变量为myVar,则可以按如下方式对其进行排序:

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

usort($myArray, "cmp");

答案 5 :(得分:2)

我不会在你的例子中提出我的解决方案,因为它会很难看(而且我没有对它进行基准测试),但是它有效......而且根据需要,它可能有所帮助。 :)

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

答案 6 :(得分:2)

这是值{0}的stable Radix Sort实现... 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是一种非比较排序算法。

答案 7 :(得分:2)

一个观察结果是,如果数据源来自数据库,使用SQL排序可能比在PHP中排序更快。当然,如果数据源来自CSV或XML文件,这是没有意义的。

答案 8 :(得分:2)

我采用了以下方法:创建了一个获取对象数组的函数,然后在函数内部创建了一个关联数组,使用属性作为数组的键,然后使用kso​​rt对它们进行排序:

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
*/

答案 9 :(得分:2)

您可以使用ouzo goodies

执行此操作
$result = Arrays::sort(array($person1, $person2), Comparator::compareBy('age'));

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

答案 10 :(得分:1)

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

答案 11 :(得分:1)

是。如果在person对象中实现spl ArrayObject,所有正常的php数组函数都可以正常使用它。

答案 12 :(得分:1)

尝试使用: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");

?>

答案 13 :(得分:1)

如果所有相关成员变量保证不同,那么创建一个由这些值索引的新集合然后ksort它会更简单,更快捷:

 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

我猜这仍然比usort

快10000000倍

答案 14 :(得分:0)

以下是一个考虑以下事项的选项:

  • 命名空间
  • 私有财产
  • 使用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 );