Skip to content

Commit 4d44a1c

Browse files
committed
Add CollectionInterface::unique()
1 parent 2a87351 commit 4d44a1c

4 files changed

Lines changed: 107 additions & 0 deletions

File tree

src/Collection/CollectionInterface.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,19 @@ public function filter(?callable $callback = null): CollectionInterface;
9898
*/
9999
public function reject(callable $callback): CollectionInterface;
100100

101+
/**
102+
* Loops through each value in the collection and returns a new collection
103+
* with only unique values based on the value returned by ``callback``.
104+
*
105+
* The callback is passed the value as the first argument and the key as the
106+
* second argument.
107+
*
108+
* @param callable $callback the method that will receive each of the elements and
109+
* returns the value used to determine uniqueness.
110+
* @return self
111+
*/
112+
public function unique(?callable $callback = null): CollectionInterface;
113+
101114
/**
102115
* Returns true if all values in this collection pass the truth test provided
103116
* in the callback.

src/Collection/CollectionTrait.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Cake\Collection\Iterator\StoppableIterator;
3030
use Cake\Collection\Iterator\TreeIterator;
3131
use Cake\Collection\Iterator\UnfoldIterator;
32+
use Cake\Collection\Iterator\UniqueIterator;
3233
use Cake\Collection\Iterator\ZipIterator;
3334
use Countable;
3435
use InvalidArgumentException;
@@ -95,6 +96,18 @@ public function reject(callable $callback): CollectionInterface
9596
return new FilterIterator($this->unwrap(), fn ($key, $value, $items) => !$callback($key, $value, $items));
9697
}
9798

99+
/**
100+
* @inheritDoc
101+
*/
102+
public function unique(?callable $callback = null): CollectionInterface
103+
{
104+
$callback ??= function ($v) {
105+
return $v;
106+
};
107+
108+
return new UniqueIterator($this->unwrap(), $callback);
109+
}
110+
98111
/**
99112
* @inheritDoc
100113
*/
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6+
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
7+
*
8+
* Licensed under The MIT License
9+
* For full copyright and license information, please see the LICENSE.txt
10+
* Redistributions of files must retain the above copyright notice.
11+
*
12+
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
13+
* @link https://cakephp.org CakePHP(tm) Project
14+
* @since 5.0.0
15+
* @license https://opensource.org/licenses/mit-license.php MIT License
16+
*/
17+
namespace Cake\Collection\Iterator;
18+
19+
use Cake\Collection\Collection;
20+
use Iterator;
21+
22+
/**
23+
* Creates a filtered iterator from another iterator. The filtering is done by
24+
* passing a callback function to each of the elements and taking them out if
25+
* the value returned is not unique.
26+
*/
27+
class UniqueIterator extends Collection
28+
{
29+
/**
30+
* Creates a filtered iterator using the callback to determine which items are
31+
* accepted or rejected.
32+
*
33+
* The callback is passed the value as the first argument and the key as the
34+
* second argument.
35+
*
36+
* @param iterable $items The items to be filtered.
37+
* @param callable $callback Callback.
38+
*/
39+
public function __construct(iterable $items, callable $callback)
40+
{
41+
if (!$items instanceof Iterator) {
42+
$items = new Collection($items);
43+
}
44+
45+
$unique = [];
46+
$uniqueValues = [];
47+
foreach ($items as $k => $v) {
48+
$compareValue = $callback($v, $k);
49+
if (!in_array($compareValue, $uniqueValues, true)) {
50+
$unique[$k] = $v;
51+
$uniqueValues[] = $compareValue;
52+
}
53+
}
54+
55+
parent::__construct($unique);
56+
}
57+
}

tests/TestCase/Collection/CollectionTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,30 @@ public function testReject(): void
291291
$this->assertInstanceOf('Cake\Collection\Collection', $result);
292292
}
293293

294+
public function testUnique(): void
295+
{
296+
$collection = new Collection([]);
297+
$result = $collection->unique();
298+
$this->assertSame([], iterator_to_array($result));
299+
$this->assertInstanceOf('Cake\Collection\Collection', $result);
300+
301+
$items = ['a' => 1, 'b' => 2, 'c' => 3];
302+
$collection = new Collection($items);
303+
$result = $collection->unique();
304+
$this->assertEquals(['a' => 1, 'b' => 2, 'c' => 3], iterator_to_array($result));
305+
306+
$items = ['a' => 1, 'b' => 2, 'c' => 1, 'd' => 2, 'e' => 1, 'f' => 3];
307+
$collection = new Collection($items);
308+
$result = $collection->unique();
309+
$this->assertEquals(['a' => 1, 'b' => 2, 'f' => 3], iterator_to_array($result));
310+
311+
$result = $collection->unique(fn ($v) => (string)$v);
312+
$this->assertEquals(['a' => 1, 'b' => 2, 'f' => 3], iterator_to_array($result));
313+
314+
$result = $collection->unique(fn ($v, $k) => $k);
315+
$this->assertEquals(['a' => 1, 'b' => 2, 'c' => 1, 'd' => 2, 'e' => 1, 'f' => 3], iterator_to_array($result));
316+
}
317+
294318
/**
295319
* Tests every when the callback returns true for all elements
296320
*/

0 commit comments

Comments
 (0)