From 8f08be614da6649d50c81041aceff495cac119b5 Mon Sep 17 00:00:00 2001 From: Aimeos Date: Sat, 30 Nov 2024 18:00:10 +0100 Subject: [PATCH] Added findKey() method --- README.md | 42 +++++++++++++++++++++++++++++++++++++++++- src/Map.php | 45 ++++++++++++++++++++++++++++++++++++++++++++- tests/MapTest.php | 41 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e2a19cb..736d1b5 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ will return: explode filter find +findKey first firstKey flat @@ -314,7 +315,8 @@ will return: * [at()](#at) : Returns the value at the given position * [bool()](#bool) : Returns an element by key and casts it to boolean * [call()](#call) : Calls the given method on all items -* [find()](#find) : Returns the first/last matching element +* [find()](#find) : Returns the first/last element where the callback returns TRUE +* [findKey()](#findkey) : Returns the first/last key where the callback returns TRUE * [first()](#first) : Returns the first element * [firstKey()](#firstkey) : Returns the first key * [get()](#get) : Returns an element by key @@ -1968,6 +1970,44 @@ Map::from( [] )->find( function( $value, $key ) { ``` +### findKey() + +Returns the first matching element where the callback returns TRUE. + +```php +public function findKey( \Closure $callback, $default = null, bool $reverse = false ) +``` + +* @param **\Closure** `$callback` Function with (value, key) parameters and returns TRUE/FALSE +* @param **mixed** `$default` Default value or exception if the map contains no elements +* @param **bool** `$reverse` TRUE to test elements from back to front, FALSE for front to back (default) +* @return **mixed|null** First matching value, passed default value or an exception + +**Examples:** + +```php +Map::from( ['a', 'c', 'e'] )->findKey( function( $value, $key ) { + return $value >= 'b'; +} ); +// 1 because array has keys 0, 1 and 2 + +Map::from( ['a', 'c', 'e'] )->findKey( function( $value, $key ) { + return $value >= 'b'; +}, null, true ); +// 2 because array is reversed and 'e' >= 'b' + +Map::from( [] )->findKey( function( $value, $key ) { + return $value >= 'b'; +}, 'none' ); +// default value 'none' + +Map::from( [] )->findKey( function( $value, $key ) { + return $value >= 'b'; +}, new \Exception( 'error' ) ); +// throws exception +``` + + ### first() Returns the first element from the map. diff --git a/src/Map.php b/src/Map.php index 500e1ff..b4b5238 100644 --- a/src/Map.php +++ b/src/Map.php @@ -1526,7 +1526,7 @@ public function filter( ?callable $callback = null ) : self */ public function find( \Closure $callback, $default = null, bool $reverse = false ) { - foreach( ( $reverse ? array_reverse( $this->list() ) : $this->list() ) as $key => $value ) + foreach( ( $reverse ? array_reverse( $this->list(), true ) : $this->list() ) as $key => $value ) { if( $callback( $value, $key ) ) { return $value; @@ -1541,6 +1541,49 @@ public function find( \Closure $callback, $default = null, bool $reverse = false } + /** + * Returns the first/last key where the callback returns TRUE. + * + * Examples: + * Map::from( ['a', 'c', 'e'] )->findKey( function( $value, $key ) { + * return $value >= 'b'; + * } ); + * Map::from( ['a', 'c', 'e'] )->findKey( function( $value, $key ) { + * return $value >= 'b'; + * }, null, true ); + * Map::from( [] )->findKey( function( $value, $key ) { + * return $value >= 'b'; + * }, 'none' ); + * Map::from( [] )->findKey( function( $value, $key ) { + * return $value >= 'b'; + * }, new \Exception( 'error' ) ); + * + * Results: + * The first example will return '1' while the second will return '2' (last element). + * The third one will return "none" and the last one will throw the exception. + * + * @param \Closure $callback Function with (value, key) parameters and returns TRUE/FALSE + * @param mixed $default Default value or exception if the map contains no elements + * @param bool $reverse TRUE to test elements from back to front, FALSE for front to back (default) + * @return mixed Key of first matching element, passed default value or an exception + */ + public function findKey( \Closure $callback, $default = null, bool $reverse = false ) + { + foreach( ( $reverse ? array_reverse( $this->list(), true ) : $this->list() ) as $key => $value ) + { + if( $callback( $value, $key ) ) { + return $key; + } + } + + if( $default instanceof \Throwable ) { + throw $default; + } + + return $default; + } + + /** * Returns the first element from the map. * diff --git a/tests/MapTest.php b/tests/MapTest.php index 8faa22f..a1057a6 100644 --- a/tests/MapTest.php +++ b/tests/MapTest.php @@ -967,11 +967,48 @@ public function testFindException() { $m = new Map( ['foo', 'bar', 'baz'] ); - $this->expectException( \RuntimeException::class ); + $this->expectException( \Exception::class ); $result = $m->find( function( $value ) { return false; - }, new \RuntimeException( 'error' ) ); + }, new \Exception( 'error' ) ); + } + + + public function testFindKey() + { + $result = Map::from( ['a', 'c', 'e'] )->findKey( function( $value, $key ) { + return $value >= 'b'; + } ); + $this->assertSame( 1, $result ); + } + + + public function testFindKeyLast() + { + $result = Map::from( ['a', 'c', 'e'] )->findKey( function( $value, $key ) { + return $value >= 'b'; + }, null, true ); + $this->assertSame( 2, $result ); + } + + + public function testFindKeyDefault() + { + $result = Map::from( [] )->findKey( function( $value, $key ) { + return $value >= 'b'; + }, 'none' ); + $this->assertSame( 'none', $result ); + } + + + public function testFindKeyException() + { + $this->expectException( \Exception::class ); + + $result = Map::from( [] )->findKey( function( $value, $key ) { + return $value >= 'b'; + }, new \Exception( 'error' ) ); }