通过在PHP中的对象属性sorting数组?

如果我有这样一个对象:

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

我有任何Person的数组

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

有一种简单的方法来sorting$people Person->age属性$peoplearrays?

这个问题是关于使用usort的低效率,因为调用比较callback的开销。 这个答案着眼于使用内置sorting函数和非recursion快速sorting实现之间的区别。

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

TL; DR:从php 7.0.1开始,一个非recursion的快速sorting不再比用一个callback使用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年回答这个问题的时候,我使用了一个非recursion的快速sorting来比较是否有区别。 事实certificate,有显着的差异,快速sorting运行速度快3倍。

现在到了2015年,我认为这可能是有益的,所以我采取了使用usort和quicksort对15000个对象进行sorting的代码,并将其运行在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秒内sorting了15000个Person对象。

由于您担心比较函数的调用效率低下,我将其与非recursionQuicksort实现进行了比较。 实际上,这大概只有三分之一的时间,约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"; 

一个有趣的build议是添加一个__toString方法的类和使用sort(),所以我也试过了。 麻烦的是,你必须通过SORT_STRING作为第二个参数进行sorting才能实际调用魔术方法,这有一个string而不是数字sorting的副作用。 为了解决这个问题,你需要用零填充数字,以便正确sorting。 净结果是这比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" 

对于这个特定的场景,你可以使用usort()函数对它进行sorting,在这个函数中定义你自己的函数来比较数组中的项目。

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

你可以使用usort或堆 。

  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的目的是在任何时候都有一个有序的集合,而不是取代usort 。 对于大型集合(1000+)来说,堆将会更快,但内存密集程度更低。

拥有堆的一个额外的好处是能够使用他们的比较函数callback其他sorting函数,如usort 。 你只需要记住比较的顺序是相反的,所以任何与Heap的比较都会导致在usort中相反的顺序。

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

对于小型到中型的collections, usort是很好的,你可以在最后做一次分类。 当然,你不必有堆使用usort 。 您也可以添加任何其他有效的sortingcallback。

我刚刚编码。 它应该比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它的工作原理。

你只需要编写一个自定义的比较函数,然后使用像usort这样的东西来做实际的sorting。 例如,如果成员variables是myVar ,则可以按如下所示对其进行sorting:

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

我不build议我的解决scheme在你的例子,因为它会是丑陋的(我没有基准testing),但它的工作….并根据需要,这可能会有所帮助。 🙂

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

这是一个稳定的 基数sorting实现值为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]; } } } 

这只会花费On ),因为基数sorting是一种非比较sortingalgorithm。

一种观察是,如果数据源来自数据库,那么使用SQL进行sorting可能比使用SQL更快。 当然,如果数据源来自CSV或XML文件,这是毫无意义的。

我采取了以下的方法:创build一个函数,接受一个对象数组,然后在函数内创build一个关联数组,使用属性作为数组的键,然后使用kso​​rtsorting他们的数组键:

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

你可以做ouzo的好东西 :

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

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

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

是。 如果你在person对象中实现了spl ArrayObject ,那么所有正常的php数组函数都可以正常工作。

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

如果所有有问题的成员variables都保证是不同的,那么创build一个由这些值索引的新集合然后ksort会更简单快捷:

  foreach($obj_list as $obj) $map[$obj->some_var] = $obj; ksort($map); /// $map now contains the sorted list 

如果有重复的值,那么仍然可以通过利用一个较less已知的sort来避免使用usort ,即数组数组按照第一个标量成员的值sorting。

  foreach($obj_list as $obj) $map[] = array($obj->some_var, $obj); sort($map); // sorts $map by the value of ->some_var 

我猜这个比usort还快1000万倍

这是一个选项,考虑到以下几点:

  • 命名空间
  • 私人财产
  • 使用getter和setter方法
  • 属性作为参数sorting

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