通过在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
属性$people
arrays?
这个问题是关于使用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]; } } }
这只会花费O ( n ),因为基数sorting是一种非比较sortingalgorithm。
一种观察是,如果数据源来自数据库,那么使用SQL进行sorting可能比使用SQL更快。 当然,如果数据源来自CSV或XML文件,这是毫无意义的。
我采取了以下的方法:创build一个函数,接受一个对象数组,然后在函数内创build一个关联数组,使用属性作为数组的键,然后使用ksortsorting他们的数组键:
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 );