diff --git a/README.md b/README.md index 02c33d9..f06fc13 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Features -------- - [x] Verify at least times: `once()`, `twice()`, `times()` +- [x] Verify at most times: `once()`, `twice()`, `times()` - [x] Verify exact times: `once()`, `twice()`, `times()` - [x] Search data: `first()`, `last()`, `rows()`, `partition()` - [x] Collect data with filter and transform @@ -129,7 +130,78 @@ $times = 3; var_dump(AtLeast::times($data, $filter, $times)) // false ``` -**B. Only** +**B. AtMost** +--------------- + +#### 1. `AtMost::once()` + +It verify that data has filtered found item at most once. + +```php +use ArrayLookup\AtMost; + +$data = [1, 2, 3]; +$filter = static fn($datum): bool => $datum === 1; + +var_dump(AtMost::once($data, $filter)) // true + +$data = [1, "1", 3]; +$filter = static fn($datum): bool => $datum == 1; + +var_dump(AtMost::once($data, $filter)) // false + +// WITH key array included, pass $key variable as 2nd arg on filter to be used in filter + +$data = ['abc', 'def', 'some test']; +$filter = static fn(string $datum, int $key): bool => $datum === 'def' && $key === 1; + +var_dump(AtMost::once($data, $filter)) // true + +$data = ['abc', 'def', 'some test']; +$filter = static fn(string $datum, int $key): bool => $key > 0; + +var_dump(AtMost::once($data, $filter)) // false +``` + +#### 2. `AtMost::twice()` + +It verify that data has filtered found items at most twice. + +```php +use ArrayLookup\AtMost; + +$data = [1, "1", 2]; +$filter = static fn($datum): bool => $datum == 1; + +var_dump(AtMost::twice($data, $filter)) // true + +$data = [1, "1", 2, 1]; +$filter = static fn($datum): bool => $datum == 1; + +var_dump(AtMost::twice($data, $filter)) // false +``` + +#### 3. `AtMost::times()` + +It verify that data has filtered found items at most times passed in 3rd arg. + +```php +use ArrayLookup\AtMost; + +$data = [false, null, 0]; +$filter = static fn($datum): bool => ! $datum; +$times = 3; + +var_dump(AtMost::times($data, $filter, $times)) // true + +$data = [false, null, 0, 0]; +$filter = static fn($datum): bool => ! $datum; +$times = 3; + +var_dump(AtMost::times($data, $filter, $times)) // false +``` + +**C. Only** --------------- #### 1. `Only::once()` @@ -230,7 +302,7 @@ $times = 2; var_dump(Only::times($data, $filter, $times)) // false ``` -**C. Finder** +**D. Finder** --------------- #### 1. `Finder::first()` @@ -401,7 +473,7 @@ var_dump($even); // [0 => 10, 2 => 30] var_dump($odd); // [1 => 20, 3 => 40] ``` -**D. Collector** +**E. Collector** --------------- It collect filtered data, with new transformed each data found: diff --git a/src/AtMost.php b/src/AtMost.php new file mode 100644 index 0000000..d2d9bdb --- /dev/null +++ b/src/AtMost.php @@ -0,0 +1,71 @@ +|Traversable $data + * @param callable(mixed $datum, int|string|null $key): bool $filter + */ + public static function once(iterable $data, callable $filter): bool + { + return self::atMostFoundTimes($data, $filter, 1); + } + + /** + * @param array|Traversable $data + * @param callable(mixed $datum, int|string|null $key): bool $filter + */ + public static function twice(iterable $data, callable $filter): bool + { + return self::atMostFoundTimes($data, $filter, 2); + } + + /** + * @param array|Traversable $data + * @param callable(mixed $datum, int|string|null $key): bool $filter + */ + public static function times(iterable $data, callable $filter, int $count): bool + { + return self::atMostFoundTimes($data, $filter, $count); + } + + /** + * @param array|Traversable $data + * @param callable(mixed $datum, int|string|null $key): bool $filter + */ + private static function atMostFoundTimes( + iterable $data, + callable $filter, + int $maxCount + ): bool { + // usage must be higher than 0 + Assert::greaterThan($maxCount, 0); + // filter must be a callable with bool return type + Filter::boolean($filter); + + $totalFound = 0; + foreach ($data as $key => $datum) { + $isFound = $filter($datum, $key); + + if (! $isFound) { + continue; + } + + ++$totalFound; + + if ($totalFound > $maxCount) { + return false; + } + } + + return true; + } +} diff --git a/tests/AtMostTest.php b/tests/AtMostTest.php new file mode 100644 index 0000000..ff9639c --- /dev/null +++ b/tests/AtMostTest.php @@ -0,0 +1,146 @@ +assertSame( + $expected, + AtMost::once($data, $filter) + ); + } + + // phpcs:disable + /** + * @return Iterator + */ + public static function onceDataProvider(): Iterator + { + yield [ + [1, 2, 3], + static fn($datum): bool => $datum === 4, + true, + ]; + yield [ + [1, 2, 3], + static fn($datum): bool => $datum === 1, + true, + ]; + yield [ + [1, '1', 3], + static fn($datum): bool => $datum == 1, + false, + ]; + yield [ + ['abc', 'def', 'some test'], + static fn(string $datum, int $key): bool => $datum === 'def' && $key === 1, + true, + ]; + yield [ + ['abc', 'def', 'some test'], + static fn(string $datum, int $key): bool => $key > 0, + false, + ]; + } + + // phpcs:enable + + /** + * @param int[]|string[] $data + */ + #[DataProvider('twiceDataProvider')] + public function testTwice(array $data, callable $filter, bool $expected): void + { + $this->assertSame( + $expected, + AtMost::twice($data, $filter) + ); + } + + // phpcs:disable + /** + * @return Iterator + */ + public static function twiceDataProvider(): Iterator + { + yield [ + [1, '1', 3], + static fn($datum): bool => $datum == 1, + true, + ]; + yield [ + [1, '1', 2, 1], + static fn($datum): bool => $datum == 1, + false, + ]; + yield [ + ['abc', 'def', 'some test'], + static fn(string $datum, int $key): bool => $datum !== 'abc' && $key > 0, + true, + ]; + yield [ + ['abc', 'def', 'some test', 'another'], + static fn(string $datum, int $key): bool => $datum !== 'abc' && $key > 0, + false, + ]; + } + + // phpcs:enable + + /** + * @param int[]|bool[]|null[]|string[] $data + */ + #[DataProvider('timesDataProvider')] + public function testTimes(array $data, callable $filter, bool $expected): void + { + $this->assertSame( + $expected, + AtMost::times($data, $filter, 3) + ); + } + + /** + * @return Iterator + */ + public static function timesDataProvider(): Iterator + { + yield [ + [0, false, null], + static fn($datum): bool => ! $datum, + true, + ]; + yield [ + [0, false, null, 'x'], + static fn($datum): bool => ! $datum, + true, + ]; + yield [ + [0, false, null, 0], + static fn($datum): bool => ! $datum, + false, + ]; + yield [ + ['abc', 'def', 'some test', 'another test'], + static fn(string $datum, int $key): bool => $datum !== 'abc' && $key > 0, + true, + ]; + yield [ + ['abc', 'def', 'some test', 'another test', 'yet another'], + static fn(string $datum, int $key): bool => $datum !== 'abc' && $key > 0, + false, + ]; + } +}