diff --git a/README.md b/README.md index f06fc13..44174e9 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,12 @@ ArrayLookup is a fast lookup library that help you verify and search `array` and 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 +- [x] Verify at least times: [`once()`](#1-atleastonce), [`twice()`](#2-atleasttwice), [`times()`](#3-atleasttimes) +- [x] Verify at most times: [`once()`](#1-atmostonce), [`twice()`](#2-atmosttwice), [`times()`](#3-atmosttimes) +- [x] Verify exact times: [`once()`](#1-onlyonce), [`twice()`](#2-onlytwice), [`times()`](#3-onlytimes) +- [x] Verify in interval range: [`isInclusiveOf()`](#1-intervalisinclusiveof), [`isExclusiveOf()`](#2-intervalisexclusiveof) +- [x] Search data: [`first()`](#1-finderfirst), [`last()`](#2-finderlast), [`rows()`](#3-finderrows), [`partition()`](#4-finderpartition) +- [x] Collect data with [filter and transform](#e-collector) Installation ------------ @@ -302,7 +303,52 @@ $times = 2; var_dump(Only::times($data, $filter, $times)) // false ``` -**D. Finder** +**D. Interval** +--------------- + +#### 1. `Interval::isInclusiveOf()` + +It verify that data has filtered found items within min and max (inclusive). + +```php +use ArrayLookup\Interval; + +$orders = [ + ['status' => 'paid'], + ['status' => 'paid'], + ['status' => 'pending'], + ['status' => 'paid'], +]; + +$filter = static fn(array $order): bool => $order['status'] === 'paid'; + +// inclusive means min and max boundaries are allowed +var_dump(Interval::isInclusiveOf($orders, $filter, 3, 5)) // true +var_dump(Interval::isInclusiveOf($orders, $filter, 2, 5)) // true +``` + +#### 2. `Interval::isExclusiveOf()` + +It verify that data has filtered found items between min and max (exclusive). + +```php +use ArrayLookup\Interval; + +$orders = [ + ['status' => 'paid'], + ['status' => 'paid'], + ['status' => 'pending'], + ['status' => 'paid'], +]; + +$filter = static fn(array $order): bool => $order['status'] === 'paid'; + +// exclusive means strictly between min and max +var_dump(Interval::isExclusiveOf($orders, $filter, 3, 5)) // false +var_dump(Interval::isExclusiveOf($orders, $filter, 2, 5)) // true +``` + +**E. Finder** --------------- #### 1. `Finder::first()` @@ -473,7 +519,7 @@ var_dump($even); // [0 => 10, 2 => 30] var_dump($odd); // [1 => 20, 3 => 40] ``` -**E. Collector** +**F. Collector** --------------- It collect filtered data, with new transformed each data found: diff --git a/src/Interval.php b/src/Interval.php new file mode 100644 index 0000000..6a21aac --- /dev/null +++ b/src/Interval.php @@ -0,0 +1,82 @@ +|Traversable $data + * @param callable(mixed $datum, int|string|null $key): bool $filter + */ + public static function isInclusiveOf( + iterable $data, + callable $filter, + int $min, + int $max + ): bool { + Assert::greaterThan($min, 0); + Assert::greaterThan($max, 0); + Assert::lessThanEq($min, $max); + Filter::boolean($filter); + + $totalFound = 0; + foreach ($data as $key => $datum) { + $isFound = $filter($datum, $key); + + if (! $isFound) { + continue; + } + + ++$totalFound; + + if ($totalFound > $max) { + return false; + } + } + + return $totalFound >= $min; + } + + /** + * @param array|Traversable $data + * @param callable(mixed $datum, int|string|null $key): bool $filter + */ + public static function isExclusiveOf( + iterable $data, + callable $filter, + int $min, + int $max + ): bool { + Assert::greaterThan($min, 0); + Assert::greaterThan($max, 0); + Assert::lessThan($min, $max); + Filter::boolean($filter); + + if ($max - $min <= 1) { + return false; + } + + $totalFound = 0; + foreach ($data as $key => $datum) { + $isFound = $filter($datum, $key); + + if (! $isFound) { + continue; + } + + ++$totalFound; + + if ($totalFound >= $max) { + return false; + } + } + + return $totalFound > $min && $totalFound < $max; + } +} diff --git a/tests/IntervalTest.php b/tests/IntervalTest.php new file mode 100644 index 0000000..85aaad2 --- /dev/null +++ b/tests/IntervalTest.php @@ -0,0 +1,145 @@ +assertSame( + $expected, + Interval::isInclusiveOf($data, $filter, $min, $max) + ); + } + + /** + * @return Iterator + */ + public static function inclusiveDataProvider(): Iterator + { + yield 'min boundary' => [ + [1, 2, 3], + static fn($datum): bool => $datum > 1, + 2, + 5, + true, + ]; + yield 'inside range' => [ + [1, 2, 3, 4, 5], + static fn($datum): bool => $datum > 2, + 2, + 5, + true, + ]; + yield 'max boundary' => [ + [1, 2, 3, 4, 5], + static fn($datum): bool => $datum >= 1, + 2, + 5, + true, + ]; + yield 'below min' => [ + [1, 2, 3], + static fn($datum): bool => $datum === 3, + 2, + 5, + false, + ]; + yield 'above max' => [ + [1, 2, 3, 4, 5, 6], + static fn($datum): bool => $datum >= 1, + 2, + 5, + false, + ]; + yield 'with key in filter' => [ + ['a', 'b', 'c', 'd', 'e'], + static fn(string $datum, int $key): bool => $datum !== 'a' && $key > 0, + 2, + 5, + true, + ]; + } + + /** + * @param int[]|string[] $data + */ + #[DataProvider('exclusiveDataProvider')] + public function testIsExclusiveOf( + array $data, + callable $filter, + int $min, + int $max, + bool $expected + ): void { + $this->assertSame( + $expected, + Interval::isExclusiveOf($data, $filter, $min, $max) + ); + } + + /** + * @return Iterator + */ + public static function exclusiveDataProvider(): Iterator + { + yield 'inside range' => [ + [1, 2, 3, 4, 5], + static fn($datum): bool => $datum > 2, + 2, + 5, + true, + ]; + yield 'above min only' => [ + [1, 2, 3], + static fn($datum): bool => $datum > 1, + 2, + 5, + false, + ]; + yield 'at min boundary' => [ + [1, 2, 3], + static fn($datum): bool => $datum > 2, + 2, + 5, + false, + ]; + yield 'at max boundary' => [ + [1, 2, 3, 4, 5], + static fn($datum): bool => $datum >= 1, + 2, + 5, + false, + ]; + yield 'above max' => [ + [1, 2, 3, 4, 5, 6], + static fn($datum): bool => $datum >= 1, + 2, + 5, + false, + ]; + yield 'no space between bounds' => [ + [1, 2, 3], + static fn($datum): bool => $datum > 1, + 2, + 3, + false, + ]; + } +}