From f1bfd8b6e87e9f41267288285fef9e36942b35ce Mon Sep 17 00:00:00 2001 From: Ian den Hartog Date: Tue, 18 Jul 2017 17:18:44 +0200 Subject: [PATCH] Add avg and median methods to CollectionTrait and Interface --- src/Collection/CollectionInterface.php | 57 ++++++++++++++++++++ src/Collection/CollectionTrait.php | 48 +++++++++++++++++ tests/TestCase/Collection/CollectionTest.php | 54 +++++++++++++++++++ 3 files changed, 159 insertions(+) diff --git a/src/Collection/CollectionInterface.php b/src/Collection/CollectionInterface.php index 970829ece88..3c7101770dc 100644 --- a/src/Collection/CollectionInterface.php +++ b/src/Collection/CollectionInterface.php @@ -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 diff --git a/src/Collection/CollectionTrait.php b/src/Collection/CollectionTrait.php index 7e86b85f7e9..391a58054b6 100644 --- a/src/Collection/CollectionTrait.php +++ b/src/Collection/CollectionTrait.php @@ -190,6 +190,54 @@ public function min($callback, $type = SORT_NUMERIC) return (new SortIterator($this->unwrap(), $callback, SORT_ASC, $type))->first(); } + /** + * {@inheritDoc} + */ + public function avg($matcher = null) + { + $iterator = $this->unwrap(); + $count = $iterator instanceof Countable ? + count($iterator) : + iterator_count($iterator); + + if ($count === 0) { + return null; + } + + return $this->sumOf($matcher) / $count; + } + + /** + * {@inheritDoc} + */ + public function median($matcher = null) + { + $iterator = $this->unwrap(); + $count = $iterator instanceof Countable ? + count($iterator) : + iterator_count($iterator); + + if ($count === 0) { + return null; + } + + $middle = (int)($count / 2); + $elements = $this; + if ($matcher != null) { + $elements = $elements->extract($matcher); + } + $values = $elements->toArray(); + sort($values); + + if ($count % 2) { + return $values[$middle]; + } + + return (new static([ + $values[$middle - 1], $values[$middle], + ]))->avg(); + } + /** * {@inheritDoc} */ diff --git a/tests/TestCase/Collection/CollectionTest.php b/tests/TestCase/Collection/CollectionTest.php index f8b4223c12e..b00529f49f4 100644 --- a/tests/TestCase/Collection/CollectionTest.php +++ b/tests/TestCase/Collection/CollectionTest.php @@ -59,6 +59,60 @@ public function testArrayIsWrapped() $this->assertEquals($items, iterator_to_array($collection)); } + /** + * Tests that it is possible to convert an array into a collection + * + * @return void + */ + public function testAvg() + { + $items = [1, 2, 3]; + $collection = new Collection($items); + $this->assertEquals(2, $collection->avg()); + + $collection = new Collection([]); + $this->assertNull($collection->avg()); + + $items = [['foo' => 1], ['foo' => 2], ['foo' => 3]]; + $collection = new Collection($items); + $this->assertEquals(2, $collection->avg('foo')); + $items = [ + ['invoice' => ['total' => 100]], + ['invoice' => ['total' => 200]] + ]; + + $this->assertEquals(150, (new Collection($items))->avg('invoice.total')); + } + + /** + * Tests that it is possible to convert an array into a collection + * + * @return void + */ + public function testMedian() + { + $items = [5, 2, 4]; + $collection = new Collection($items); + $this->assertEquals(4, $collection->median()); + + $collection = new Collection([]); + $this->assertNull($collection->median()); + + $items = [1, 2, 3, 4]; + $collection = new Collection($items); + $this->assertEquals(2.5, $collection->median()); + + $items = [ + ['invoice' => ['total' => 400]], + ['invoice' => ['total' => 500]], + ['invoice' => ['total' => 200]], + ['invoice' => ['total' => 100]], + ['invoice' => ['total' => 333]] + ]; + + $this->assertEquals(333, (new Collection($items))->median('invoice.total')); + } + /** * Tests that it is possible to convert an iterator into a collection *