Skip to content

Commit

Permalink
Merge pull request #6570 from cakephp/collection-zip
Browse files Browse the repository at this point in the history
Implemented Collection::zip() and zipWith()
  • Loading branch information
markstory committed May 17, 2015
2 parents d3f84c1 + cfd0cf3 commit 35f7408
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 2 deletions.
58 changes: 57 additions & 1 deletion src/Collection/CollectionInterface.php
Expand Up @@ -482,6 +482,15 @@ public function sample($size = 10);
*/
public function take($size = 1, $from = 0);

/**
* Returns a new collection that will skip the specified amount of elements
* at the beginning of the iteration.
*
* @param int $howMany The number of elements to skip.
* @return \Cake\Collection\CollectionInterface
*/
public function skip($howMany);

/**
* Looks through each value in the list, returning a Collection of all the
* values that contain all of the key-value pairs listed in $conditions.
Expand Down Expand Up @@ -527,6 +536,14 @@ public function firstMatch(array $conditions);
* @return mixed The first value in the collection will be returned.
*/
public function first();

/**
* Returns the last result in this collection
*
* @return mixed The last value in the collection will be returned.
*/
public function last();

/**
* Returns a new collection as the result of concatenating the list of elements
* in this collection with the passed list of elements
Expand Down Expand Up @@ -819,7 +836,7 @@ public function unfold(callable $transformer = null);
* ### Example:
*
* ```
* $items [1, 2, 3];
* $items = [1, 2, 3];
* $decorated = (new Collection($items))->through(function ($collection) {
* return new MyCustomCollection($collection);
* });
Expand All @@ -831,6 +848,44 @@ public function unfold(callable $transformer = null);
*/
public function through(callable $handler);

/**
* Combines the elements of this collection with each of the elements of the
* passed iterables, using their positional index as a reference.
*
* ### Example:
*
* ```
* $collection = new Collection([1, 2]);
* $collection->zip([3, 4], [5, 6])->toList(); // returns [[1, 3, 5], [2, 4, 6]]
* ```
*
* @param array|\Traversable ...$items The collections to zip.
* @return \Cake\Collection\CollectionInterface
*/
public function zip($items);

/**
* Combines the elements of this collection with each of the elements of the
* passed iterables, using their positional index as a reference.
*
* The resulting element will be the return value of the $callable function.
*
* ### Example:
*
* ```
* $collection = new Collection([1, 2]);
* $zipped = $collection->zipWith([3, 4], [5, 6], function () {
* return array_sum(func_get_args());
* });
* $zipped->toList(); // returns [9, 12]; [(1 + 3 + 5), (2 + 4 + 6)]
* ```
*
* @param array|\Traversable ...$items The collections to zip.
* @param callable $callable The function to use for zipping the elements together.
* @return \Cake\Collection\CollectionInterface
*/
public function zipWith($items, $callable);

/**
* Returns whether or not there are elements in this collection
*
Expand All @@ -840,6 +895,7 @@ public function through(callable $handler);
* $items [1, 2, 3];
* (new Collection($items))->isEmpty(); // false
* ```
*
* ```
* (new Collection([]))->isEmpty(); // true
* ```
Expand Down
49 changes: 48 additions & 1 deletion src/Collection/CollectionTrait.php
Expand Up @@ -28,6 +28,7 @@
use Cake\Collection\Iterator\StoppableIterator;
use Cake\Collection\Iterator\TreeIterator;
use Cake\Collection\Iterator\UnfoldIterator;
use Cake\Collection\Iterator\ZipIterator;
use Iterator;
use LimitIterator;
use RecursiveIteratorIterator;
Expand Down Expand Up @@ -281,6 +282,15 @@ public function take($size = 1, $from = 0)
return new Collection(new LimitIterator($this->unwrap(), $from, $size));
}

/**
* {@inheritDoc}
*
*/
public function skip($howMany)
{
return new Collection(new LimitIterator($this->unwrap(), $howMany));
}

/**
* {@inheritDoc}
*
Expand Down Expand Up @@ -310,6 +320,21 @@ public function first()
}
}

/**
* {@inheritDoc}
*
*/
public function last()
{
$iterator = $this->unwrap();
$count = $iterator instanceof Countable ?
count($iterator) :
iterator_count($iterator);
foreach ($this->take(1, $count - 1) as $last) {
return $last;
}
}

/**
* {@inheritDoc}
*
Expand Down Expand Up @@ -530,6 +555,29 @@ public function through(callable $handler)
return $result instanceof CollectionInterface ? $result: new Collection($result);
}

/**
* {@inheritDoc}
*
*/
public function zip($items)
{
return new ZipIterator(array_merge([$this], func_get_args()));
}

/**
* {@inheritDoc}
*
*/
public function zipWith($items, $callable)
{
$items = [$items];
if (func_num_args() > 2) {
$items = func_get_args();
$callable = array_pop($items);
}
return new ZipIterator(array_merge([$this], $items), $callable);
}

/**
* {@inheritDoc}
*
Expand All @@ -539,7 +587,6 @@ public function isEmpty()
return iterator_count($this->take(1)) === 0;
}


/**
* {@inheritDoc}
*
Expand Down
92 changes: 92 additions & 0 deletions src/Collection/Iterator/ZipIterator.php
@@ -0,0 +1,92 @@
<?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 3.0.5
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Collection\Iterator;

use Cake\Collection\Collection;
use Cake\Collection\CollectionInterface;
use Cake\Collection\CollectionTrait;
use MultipleIterator;

/**
* Creates an iterator that returns elements grouped in pairs
*
* ### Example
*
* ```
* $iterator = new ZipIterator([[1, 2], [3, 4]]);
* $iterator->toList(); // Returns [[1, 3], [2, 4]]
* ```
*
* You can also chose a custom function to zip the elements together, such
* as doing a sum by index:
*
* ### Example
*
* ```
* $iterator = new ZipIterator([[1, 2], [3, 4]], function ($a, $b) {
* return $a + $b;
* });
* $iterator->toList(); // Returns [4, 6]
* ```
*
*/
class ZipIterator extends MultipleIterator implements CollectionInterface
{

use CollectionTrait;

/**
* The function to use for zipping items together
*
* @var callable
*/
protected $_callback;

/**
* Creates the iterator to merge together the values by for all the passed
* iterators by their corresponding index.
*
* @param array $sets The list of array or iterators to be zipped.
* @param callable $callable The function to use for zipping the elements of each iterator.
*/
public function __construct(array $sets, $callable = null)
{
$sets = array_map(function ($items) {
return (new Collection($items))->unwrap();
}, $sets);

$this->_callback = $callable;
parent::__construct(MultipleIterator::MIT_NEED_ALL | MultipleIterator::MIT_KEYS_NUMERIC);

foreach ($sets as $set) {
$this->attachIterator($set);
}
}

/**
* Returns the value resulting out of zipping all the elements for all the
* iterators with the same positional index.
*
* @return mixed
*/
public function current()
{
if ($this->_callback === null) {
return parent::current();
}

return call_user_func_array($this->_callback, parent::current());
}
}
71 changes: 71 additions & 0 deletions tests/TestCase/Collection/CollectionTest.php
Expand Up @@ -1285,4 +1285,75 @@ public function testIsEmpty()
$collection = $collection->filter();
$this->assertTrue($collection->isEmpty());
}

/**
* Tests the zip() method
*
* @return void
*/
public function testZip()
{
$collection = new Collection([1, 2]);
$zipped = $collection->zip([3, 4]);
$this->assertEquals([[1, 3], [2, 4]], $zipped->toList());

$collection = new Collection([1, 2]);
$zipped = $collection->zip([3]);
$this->assertEquals([[1, 3]], $zipped->toList());

$collection = new Collection([1, 2]);
$zipped = $collection->zip([3, 4], [5, 6], [7, 8], [9, 10, 11]);
$this->assertEquals([
[1, 3, 5, 7, 9],
[2, 4, 6, 8, 10]
], $zipped->toList());
}

/**
* Tests the zipWith() method
*
* @return void
*/
public function testZipWith()
{
$collection = new Collection([1, 2]);
$zipped = $collection->zipWith([3, 4], function ($a, $b) {
return $a * $b;
});
$this->assertEquals([3, 8], $zipped->toList());

$zipped = $collection->zipWith([3, 4], [5, 6, 7], function () {
return array_sum(func_get_args());
});
$this->assertEquals([9, 12], $zipped->toList());
}

/**
* Tests the skip() method
*
* @return void
*/
public function testSkip()
{
$collection = new Collection([1, 2, 3, 4, 5]);
$this->assertEquals([3, 4, 5], $collection->skip(2)->toList());

$this->assertEquals([5], $collection->skip(4)->toList());
}

/**
* Tests the last() method
*
* @return void
*/
public function testLast()
{
$collection = new Collection([1, 2, 3]);
$this->assertEquals(3, $collection->last());

$collection = $collection->map(function ($e) {
return $e * 2;
});
$this->assertEquals(6, $collection->last());
}
}

0 comments on commit 35f7408

Please sign in to comment.