Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Adding SortIterator
- Loading branch information
Showing
2 changed files
with
272 additions
and
0 deletions.
There are no files selected for viewing
135 changes: 135 additions & 0 deletions
135
Cake/Test/TestCase/Utility/Iterator/SortIteratorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
<?php | ||
/** | ||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | ||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* | ||
* Licensed under The MIT License | ||
* For full copyright and license information, please see the LICENSE.txt | ||
* Redistributions of files must retain the above copyright notice. | ||
* | ||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* @link http://cakephp.org CakePHP(tm) Project | ||
* @since CakePHP(tm) v 3.0.0 | ||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php) | ||
*/ | ||
namespace Cake\Test\TestCase\Utility\Iterator; | ||
|
||
use ArrayObject; | ||
use Cake\TestSuite\TestCase; | ||
use Cake\Utility\Iterator\SortIterator; | ||
|
||
/** | ||
* SortIterator Test | ||
* | ||
*/ | ||
class SortIteratorTest extends TestCase { | ||
|
||
/** | ||
* Tests sorting numbers with an identity callbacks | ||
* | ||
* @return void | ||
*/ | ||
public function testSortNumbersIdentity() { | ||
$items = new ArrayObject([3, 5, 1, 2, 4]); | ||
$identity = function($a) { | ||
return $a; | ||
}; | ||
$sorted = new SortIterator($items, $identity); | ||
$expected = array_combine(range(4, 0), range(5, 1)); | ||
$this->assertEquals($expected, iterator_to_array($sorted)); | ||
|
||
$sorted = new SortIterator($items, $identity, SORT_ASC); | ||
$expected = array_combine(range(4, 0), range(1, 5)); | ||
$this->assertEquals($expected, iterator_to_array($sorted)); | ||
} | ||
|
||
/** | ||
* Tests sorting numbers with custom callback | ||
* | ||
* @return void | ||
*/ | ||
public function testSortNumbersCustom() { | ||
$items = new ArrayObject([3, 5, 1, 2, 4]); | ||
$callback = function($a) { | ||
return sin($a); | ||
}; | ||
$sorted = new SortIterator($items, $callback); | ||
$expected = array_combine(range(4, 0), [2, 1, 3, 5, 4]); | ||
$this->assertEquals($expected, iterator_to_array($sorted)); | ||
|
||
$sorted = new SortIterator($items, $callback, SORT_ASC); | ||
$expected = array_combine(range(4, 0), [4, 5, 3, 1, 2]); | ||
$this->assertEquals($expected, iterator_to_array($sorted)); | ||
} | ||
|
||
/** | ||
* Tests sorting a complex structure with numeric sort | ||
* | ||
* @return void | ||
*/ | ||
public function testSortComplexNumeric() { | ||
$items = new ArrayObject([ | ||
['foo' => 1, 'bar' => 'a'], | ||
['foo' => 10, 'bar' => 'a'], | ||
['foo' => 2, 'bar' => 'a'], | ||
['foo' => 13, 'bar' => 'a'], | ||
]); | ||
$callback = function($a) { | ||
return $a['foo']; | ||
}; | ||
$sorted = new SortIterator($items, $callback, SORT_DESC, SORT_NUMERIC); | ||
$expected = [ | ||
3 => ['foo' => 13, 'bar' => 'a'], | ||
2 => ['foo' => 10, 'bar' => 'a'], | ||
1 => ['foo' => 2, 'bar' => 'a'], | ||
0 => ['foo' => 1, 'bar' => 'a'], | ||
]; | ||
$this->assertEquals($expected, iterator_to_array($sorted)); | ||
|
||
$sorted = new SortIterator($items, $callback, SORT_ASC, SORT_NUMERIC); | ||
$expected = [ | ||
3 => ['foo' => 1, 'bar' => 'a'], | ||
2 => ['foo' => 2, 'bar' => 'a'], | ||
1 => ['foo' => 10, 'bar' => 'a'], | ||
0 => ['foo' => 13, 'bar' => 'a'], | ||
]; | ||
$this->assertEquals($expected, iterator_to_array($sorted)); | ||
} | ||
|
||
|
||
/** | ||
* Tests sorting a complex structure with natural sort | ||
* | ||
* @return void | ||
*/ | ||
public function testSortComplexNatural() { | ||
$items = new ArrayObject([ | ||
['foo' => 'foo_1', 'bar' => 'a'], | ||
['foo' => 'foo_10', 'bar' => 'a'], | ||
['foo' => 'foo_2', 'bar' => 'a'], | ||
['foo' => 'foo_13', 'bar' => 'a'], | ||
]); | ||
$callback = function($a) { | ||
return $a['foo']; | ||
}; | ||
$sorted = new SortIterator($items, $callback, SORT_DESC, SORT_NATURAL); | ||
$expected = [ | ||
3 => ['foo' => 'foo_13', 'bar' => 'a'], | ||
2 => ['foo' => 'foo_10', 'bar' => 'a'], | ||
1 => ['foo' => 'foo_2', 'bar' => 'a'], | ||
0 => ['foo' => 'foo_1', 'bar' => 'a'], | ||
]; | ||
$this->assertEquals($expected, iterator_to_array($sorted)); | ||
|
||
$sorted = new SortIterator($items, $callback, SORT_ASC, SORT_NATURAL); | ||
$expected = [ | ||
3 => ['foo' => 'foo_1', 'bar' => 'a'], | ||
2 => ['foo' => 'foo_2', 'bar' => 'a'], | ||
1 => ['foo' => 'foo_10', 'bar' => 'a'], | ||
0 => ['foo' => 'foo_13', 'bar' => 'a'], | ||
]; | ||
$this->assertEquals($expected, iterator_to_array($sorted)); | ||
$this->assertEquals($expected, iterator_to_array($sorted), 'Iterator should rewind'); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
<?php | ||
/** | ||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | ||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* | ||
* Licensed under The MIT License | ||
* For full copyright and license information, please see the LICENSE.txt | ||
* Redistributions of files must retain the above copyright notice. | ||
* | ||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* @link http://cakephp.org CakePHP(tm) Project | ||
* @since CakePHP(tm) v 3.0.0 | ||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php) | ||
*/ | ||
namespace Cake\Utility\Iterator; | ||
|
||
use SplHeap; | ||
|
||
/** | ||
* An iterator that will return the passed items in order. The order is given by | ||
* the value returned in a callback function that maps each of the elements. | ||
* | ||
* ###Example: | ||
* | ||
* {{{ | ||
* $items = [$user1, $user2, $user3]; | ||
* $sorted = new SortIterator($items, function($user) { | ||
* return $user->age; | ||
* }); | ||
* | ||
* // output all user name order by their age in descending order | ||
* foreach ($sorted as $user) { | ||
* echo $user->name; | ||
* } | ||
* }}} | ||
* | ||
* This iterator does not preserve the keys passed in the original elements. | ||
*/ | ||
class SortIterator extends SplHeap { | ||
|
||
/** | ||
* Original items passed to this iterator | ||
* | ||
* @var array|\Traversable | ||
*/ | ||
protected $_items; | ||
|
||
/** | ||
* The callback used to extract the column or property from the elements | ||
* | ||
* @var callable | ||
*/ | ||
protected $_callback; | ||
|
||
/** | ||
* The direction in which the elements should be sorted. The constants | ||
* `SORT_ASC` and `SORT_DESC` are the accepted values | ||
* | ||
* @var string | ||
*/ | ||
protected $_dir; | ||
|
||
/** | ||
* The type of sort comparison to perform. | ||
* | ||
* @var string | ||
*/ | ||
protected $_type; | ||
|
||
/** | ||
* Wraps this iterator around the passed items so when iterated they are returned | ||
* in order. | ||
* | ||
* The callback will receive as first argument each of the elements in $items, | ||
* the value returned in the callback will be used as the value for sorting such | ||
* element. Please not that the callback function could be called more than once | ||
* per element. | ||
* | ||
* @param array|\Traversable $items The values to sort | ||
* @param callable $callback A function used to return the actual value to be | ||
* compared | ||
* @param integer $dir either SORT_DESC or SORT_ASC | ||
* @param integer $type the type of comparison to perform, either SORT_STRING | ||
* SORT_NUMERIC or SORT_NATURAL | ||
* @return void | ||
*/ | ||
public function __construct($items = [], callable $c, $dir = SORT_DESC, $type = SORT_STRING) { | ||
$this->_items = $items; | ||
$this->_callback = $c; | ||
$this->_dir = $dir; | ||
$this->_type = $type; | ||
} | ||
|
||
/** | ||
* The comparison function used to sort the elements | ||
* | ||
* @param mixed $a an element in the list | ||
* @param mixed $b an element in the list | ||
* @return integer | ||
*/ | ||
public function compare($a, $b) { | ||
if ($this->_dir === SORT_ASC) { | ||
list($a, $b) = [$b, $a]; | ||
} | ||
|
||
$callback = $this->_callback; | ||
$a = $callback($a); | ||
$b = $callback($b); | ||
|
||
if ($this->_type === SORT_NUMERIC) { | ||
return $a - $b; | ||
} | ||
|
||
if ($this->_type === SORT_NATURAL) { | ||
return strnatcmp($a, $b); | ||
} | ||
|
||
if ($this->_type === SORT_STRING) { | ||
return strcmp($a, $b); | ||
} | ||
|
||
return strcoll($a, $b); | ||
} | ||
|
||
/** | ||
* SplHeap removes elements upon iteration. Implementing rewind so that | ||
* this iterator can be reused, at least at a cost. | ||
* | ||
* @return void | ||
*/ | ||
public function rewind() { | ||
foreach ($this->_items as $item) { | ||
$this->insert($item); | ||
} | ||
} | ||
|
||
} |