From 53c2e008a1517df73459e1ee03bd6b5e86a86c7c Mon Sep 17 00:00:00 2001 From: Aimeos Date: Tue, 30 Jul 2024 14:00:37 +0200 Subject: [PATCH] Allow closure for avg() method and changes NULL handling --- README.md | 9 ++++++--- src/Map.php | 25 +++++++++++++++++-------- tests/MapTest.php | 9 ++++++++- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 57b2f44..382d237 100644 --- a/README.md +++ b/README.md @@ -803,10 +803,10 @@ Map::from( [1, 3, 5] )->at( 3 ); Returns the average of all integer and float values in the map. ```php -public function avg( string $key = null ) : float +public function avg( string $col = null ) : float ``` -* @param **string|null** `$key` Key or path to the values in the nested array or object to compute the average for +* @param **Closure|string|null** `$col` Closure, key or path to the values in the nested array or object to compute the average for * @return **float** Average of all elements or 0 if there are no elements in the map This does also work for multi-dimensional arrays by passing the keys @@ -821,7 +821,7 @@ Map::from( [1, 3, 5] )->avg(); // 3 Map::from( [1, null, 5] )->avg(); -// 2 +// 3 Map::from( [1, 'sum', 5] )->avg(); // 2 @@ -831,6 +831,9 @@ Map::from( [['p' => 30], ['p' => 50], ['p' => 10]] )->avg( 'p' ); Map::from( [['i' => ['p' => 30]], ['i' => ['p' => 50]]] )->avg( 'i/p' ); // 40 + +Map::from( [30, 50, 10] )->avg( fn( $val, $key ) => $val < 50 ? $val : null ); +// 20 ``` diff --git a/src/Map.php b/src/Map.php index 48d340b..7b27080 100644 --- a/src/Map.php +++ b/src/Map.php @@ -520,25 +520,34 @@ public function at( int $pos ) * Map::from( [1, 'sum', 5] )->avg(); * Map::from( [['p' => 30], ['p' => 50], ['p' => 10]] )->avg( 'p' ); * Map::from( [['i' => ['p' => 30]], ['i' => ['p' => 50]]] )->avg( 'i/p' ); + * Map::from( [30, 50, 10] )->avg( fn( $val, $key ) => $val < 50 ? $val : null ); * * Results: - * The first line will return "3", the second and third one "2", the forth - * one "30" and the last one "40". + * The first and second line will return "3", the third one "2", the forth + * one "30", the fifth one "40" and the last one "20". * - * NULL values are treated as 0, non-numeric values will generate an error. + * NULL values are ignored, non-numeric values will generate an error. * * This does also work for multi-dimensional arrays by passing the keys * of the arrays separated by the delimiter ("/" by default), e.g. "key1/key2/key3" * to get "val" from ['key1' => ['key2' => ['key3' => 'val']]]. The same applies to * public properties of objects or objects implementing __isset() and __get() methods. * - * @param string|null $key Key or path to the values in the nested array or object to compute the average for + * @param Closure|string|null $col Closure, key or path to the values in the nested array or object to compute the average for * @return float Average of all elements or 0 if there are no elements in the map */ - public function avg( string $key = null ) : float + public function avg( $col = null ) : float { - $cnt = count( $this->list() ); - return $cnt > 0 ? $this->sum( $key ) / $cnt : 0; + if( $col instanceof \Closure ) { + $vals = array_map( $col, array_values( $this->list() ), array_keys( $this->list() ) ); + } else { + $vals = $col !== null ? $this->col( $col )->toArray() : $this->list(); + } + + $vals = array_filter( $vals, fn( $val ) => $val !== null ); + $cnt = count( $vals ); + + return $cnt > 0 ? array_sum( $vals ) / $cnt : 0; } @@ -4682,7 +4691,7 @@ public function sum( $col = null ) : float $vals = $col !== null ? $this->col( $col )->toArray() : $this->list(); } - return !empty( $vals ) ? array_sum( $vals ) : 0; + return array_sum( $vals ); } diff --git a/tests/MapTest.php b/tests/MapTest.php index 94de956..49b6bc3 100644 --- a/tests/MapTest.php +++ b/tests/MapTest.php @@ -141,11 +141,18 @@ public function testAt() public function testAvg() { $this->assertSame( 3.0, Map::from( [1, 3, 5] )->avg() ); - $this->assertSame( 2.0, Map::from( [1, null, 5] )->avg() ); + $this->assertSame( 3.0, Map::from( [1, null, 5] )->avg() ); $this->assertSame( 2.0, Map::from( [1, 0.0, 5] )->avg() ); } + public function testAvgClosure() + { + $this->assertSame( 20.0, Map::from( [30, 50, 10] )->avg( fn( $val ) => $val < 50 ? $val : null ) ); + $this->assertSame( 30.0, Map::from( [30, 50, 10] )->avg( fn( $val, $key ) => $key < 1 ? $val : null ) ); + } + + public function testAvgPath() { $this->assertSame( 30.0, Map::from( [['p' => 30], ['p' => 50], ['p' => 10]] )->avg( 'p' ) );