Browse Source

Merge pull request #10922 from Iandenh/avg-median-collection

Add avg and median methods to collection
Mark Story 8 years ago
parent
commit
600663e75a

+ 57 - 0
src/Collection/CollectionInterface.php

@@ -286,6 +286,63 @@ interface CollectionInterface extends Iterator, JsonSerializable
     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
      * callback. $callback can also be a string representing the column or property

+ 49 - 0
src/Collection/CollectionTrait.php

@@ -193,6 +193,55 @@ trait CollectionTrait
     /**
      * {@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}
+     */
     public function sortBy($callback, $dir = SORT_DESC, $type = SORT_NUMERIC)
     {
         return new SortIterator($this->unwrap(), $callback, $dir, $type);

+ 151 - 0
tests/TestCase/Collection/CollectionTest.php

@@ -60,6 +60,157 @@ class CollectionTest extends TestCase
     }
 
     /**
+     * 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
      *
      * @return void