Skip to content

Commit

Permalink
Merge pull request #10922 from Iandenh/avg-median-collection
Browse files Browse the repository at this point in the history
Add avg and median methods to collection
  • Loading branch information
markstory committed Jul 28, 2017
2 parents 7b36bdd + beda674 commit 600663e
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 0 deletions.
57 changes: 57 additions & 0 deletions src/Collection/CollectionInterface.php
Expand Up @@ -285,6 +285,63 @@ public function max($callback, $type = SORT_NUMERIC);
*/
public function min($callback, $type = SORT_NUMERIC);

/**
* Returns the average of all the values extracted with $matcher
* or of this collection.
*
* ### Example:
*
* ```
* $items = [
* ['invoice' => ['total' => 100]],
* ['invoice' => ['total' => 200]]
* ];
*
* $total = (new Collection($items))->avg('invoice.total');
*
* // Total: 150
*
* $total = (new Collection([1, 2, 3]))->avg();
* // Total: 2
* ```
*
* @param string|callable|null $matcher The property name to sum or a function
* If no value is passed, an identity function will be used.
* that will return the value of the property to sum.
* @return float|int|null
*/
public function avg($matcher = null);

/**
* Returns the median of all the values extracted with $matcher
* or of this collection.
*
* ### Example:
*
* ```
* $items = [
* ['invoice' => ['total' => 400]],
* ['invoice' => ['total' => 500]]
* ['invoice' => ['total' => 100]]
* ['invoice' => ['total' => 333]]
* ['invoice' => ['total' => 200]]
* ];
*
* $total = (new Collection($items))->median('invoice.total');
*
* // Total: 333
*
* $total = (new Collection([1, 2, 3, 4]))->median();
* // Total: 2.5
* ```
*
* @param string|callable|null $matcher The property name to sum or a function
* If no value is passed, an identity function will be used.
* that will return the value of the property to sum.
* @return float|int|null
*/
public function median($matcher = null);

/**
* Returns a sorted iterator out of the elements in this collection,
* ranked in ascending order by the results of running each value through a
Expand Down
49 changes: 49 additions & 0 deletions src/Collection/CollectionTrait.php
Expand Up @@ -190,6 +190,55 @@ public function min($callback, $type = SORT_NUMERIC)
return (new SortIterator($this->unwrap(), $callback, SORT_ASC, $type))->first();
}

/**
* {@inheritDoc}
*/
public function avg($matcher = null)
{
$result = $this;
if ($matcher != null) {
$result = $result->extract($matcher);
}
$result = $result
->reduce(function ($acc, $current) {
list($count, $sum) = $acc;

return [$count + 1, $sum + $current];
}, [0, 0]);

if ($result[0] === 0) {
return null;
}

return $result[1] / $result[0];
}

/**
* {@inheritDoc}
*/
public function median($matcher = null)
{
$elements = $this;
if ($matcher != null) {
$elements = $elements->extract($matcher);
}
$values = $elements->toList();
sort($values);
$count = count($values);

if ($count === 0) {
return null;
}

$middle = (int)($count / 2);

if ($count % 2) {
return $values[$middle];
}

return ($values[$middle - 1] + $values[$middle]) / 2;
}

/**
* {@inheritDoc}
*/
Expand Down
151 changes: 151 additions & 0 deletions tests/TestCase/Collection/CollectionTest.php
Expand Up @@ -59,6 +59,157 @@ public function testArrayIsWrapped()
$this->assertEquals($items, iterator_to_array($collection));
}

/**
* Provider for average tests
*
* @return array
*/
public function avgProvider()
{
$items = [1, 2, 3];

return [
'array' => [$items],
'iterator' => [$this->yieldItems($items)]
];
}

/**
* Tests the avg method
*
* @dataProvider avgProvider
* @return void
*/
public function testAvg($items)
{
$collection = new Collection($items);
$this->assertEquals(2, $collection->avg());

$items = [['foo' => 1], ['foo' => 2], ['foo' => 3]];
$collection = new Collection($items);
$this->assertEquals(2, $collection->avg('foo'));
}

/**
* Tests the avg method when on an empty collection
*
* @return void
*/
public function testAvgWithEmptyCollection()
{
$collection = new Collection([]);
$this->assertNull($collection->avg());
}

/**
* Provider for average tests with use of a matcher
*
* @return array
*/
public function avgWithMatcherProvider()
{
$items = [['foo' => 1], ['foo' => 2], ['foo' => 3]];

return [
'array' => [$items],
'iterator' => [$this->yieldItems($items)]
];
}

/**
* ests the avg method
*
* @dataProvider avgWithMatcherProvider
* @return void
*/
public function testAvgWithMatcher($items)
{
$collection = new Collection($items);
$this->assertEquals(2, $collection->avg('foo'));
}

/**
* Provider for some median tests
*
* @return array
*/
public function medianProvider()
{
$items = [5, 2, 4];

return [
'array' => [$items],
'iterator' => [$this->yieldItems($items)]
];
}

/**
* Tests the median method
*
* @dataProvider medianProvider
* @return void
*/
public function testMedian($items)
{
$collection = new Collection($items);
$this->assertEquals(4, $collection->median());
}

/**
* Tests the median method when on an empty collection
*
* @return void
*/
public function testMedianWithEmptyCollection()
{
$collection = new Collection([]);
$this->assertNull($collection->median());
}

/**
* Tests the median method
*
* @dataProvider simpleProvider
* @return void
*/
public function testMedianEven($items)
{
$collection = new Collection($items);
$this->assertEquals(2.5, $collection->median());
}

/**
* Provider for median tests with use of a matcher
*
* @return array
*/
public function medianWithMatcherProvider()
{
$items = [
['invoice' => ['total' => 400]],
['invoice' => ['total' => 500]],
['invoice' => ['total' => 200]],
['invoice' => ['total' => 100]],
['invoice' => ['total' => 333]]
];

return [
'array' => [$items],
'iterator' => [$this->yieldItems($items)]
];
}

/**
* Tests the median method
*
* @dataProvider medianWithMatcherProvider
* @return void
*/
public function testMedianWithMatcher($items)
{
$this->assertEquals(333, (new Collection($items))->median('invoice.total'));
}

/**
* Tests that it is possible to convert an iterator into a collection
*
Expand Down

0 comments on commit 600663e

Please sign in to comment.