Skip to content

Commit 83ad00b

Browse files
authored
Merge pull request #38 from samsonasik/add-interval
Add Interval feature to verify in interval range: isInclusiveOf() and isExclusiveOf()
2 parents dd3f2b8 + 46b1ec6 commit 83ad00b

File tree

3 files changed

+280
-7
lines changed

3 files changed

+280
-7
lines changed

README.md

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ ArrayLookup is a fast lookup library that help you verify and search `array` and
1515
Features
1616
--------
1717

18-
- [x] Verify at least times: `once()`, `twice()`, `times()`
19-
- [x] Verify at most times: `once()`, `twice()`, `times()`
20-
- [x] Verify exact times: `once()`, `twice()`, `times()`
21-
- [x] Search data: `first()`, `last()`, `rows()`, `partition()`
22-
- [x] Collect data with filter and transform
18+
- [x] Verify at least times: [`once()`](#1-atleastonce), [`twice()`](#2-atleasttwice), [`times()`](#3-atleasttimes)
19+
- [x] Verify at most times: [`once()`](#1-atmostonce), [`twice()`](#2-atmosttwice), [`times()`](#3-atmosttimes)
20+
- [x] Verify exact times: [`once()`](#1-onlyonce), [`twice()`](#2-onlytwice), [`times()`](#3-onlytimes)
21+
- [x] Verify in interval range: [`isInclusiveOf()`](#1-intervalisinclusiveof), [`isExclusiveOf()`](#2-intervalisexclusiveof)
22+
- [x] Search data: [`first()`](#1-finderfirst), [`last()`](#2-finderlast), [`rows()`](#3-finderrows), [`partition()`](#4-finderpartition)
23+
- [x] Collect data with [filter and transform](#e-collector)
2324

2425
Installation
2526
------------
@@ -302,7 +303,52 @@ $times = 2;
302303
var_dump(Only::times($data, $filter, $times)) // false
303304
```
304305

305-
**D. Finder**
306+
**D. Interval**
307+
---------------
308+
309+
#### 1. `Interval::isInclusiveOf()`
310+
311+
It verify that data has filtered found items within min and max (inclusive).
312+
313+
```php
314+
use ArrayLookup\Interval;
315+
316+
$orders = [
317+
['status' => 'paid'],
318+
['status' => 'paid'],
319+
['status' => 'pending'],
320+
['status' => 'paid'],
321+
];
322+
323+
$filter = static fn(array $order): bool => $order['status'] === 'paid';
324+
325+
// inclusive means min and max boundaries are allowed
326+
var_dump(Interval::isInclusiveOf($orders, $filter, 3, 5)) // true
327+
var_dump(Interval::isInclusiveOf($orders, $filter, 2, 5)) // true
328+
```
329+
330+
#### 2. `Interval::isExclusiveOf()`
331+
332+
It verify that data has filtered found items between min and max (exclusive).
333+
334+
```php
335+
use ArrayLookup\Interval;
336+
337+
$orders = [
338+
['status' => 'paid'],
339+
['status' => 'paid'],
340+
['status' => 'pending'],
341+
['status' => 'paid'],
342+
];
343+
344+
$filter = static fn(array $order): bool => $order['status'] === 'paid';
345+
346+
// exclusive means strictly between min and max
347+
var_dump(Interval::isExclusiveOf($orders, $filter, 3, 5)) // false
348+
var_dump(Interval::isExclusiveOf($orders, $filter, 2, 5)) // true
349+
```
350+
351+
**E. Finder**
306352
---------------
307353

308354
#### 1. `Finder::first()`
@@ -473,7 +519,7 @@ var_dump($even); // [0 => 10, 2 => 30]
473519
var_dump($odd); // [1 => 20, 3 => 40]
474520
```
475521

476-
**E. Collector**
522+
**F. Collector**
477523
---------------
478524

479525
It collect filtered data, with new transformed each data found:

src/Interval.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ArrayLookup;
6+
7+
use ArrayLookup\Assert\Filter;
8+
use Traversable;
9+
use Webmozart\Assert\Assert;
10+
11+
final class Interval
12+
{
13+
/**
14+
* @param array<int|string, mixed>|Traversable<int|string, mixed> $data
15+
* @param callable(mixed $datum, int|string|null $key): bool $filter
16+
*/
17+
public static function isInclusiveOf(
18+
iterable $data,
19+
callable $filter,
20+
int $min,
21+
int $max
22+
): bool {
23+
Assert::greaterThan($min, 0);
24+
Assert::greaterThan($max, 0);
25+
Assert::lessThanEq($min, $max);
26+
Filter::boolean($filter);
27+
28+
$totalFound = 0;
29+
foreach ($data as $key => $datum) {
30+
$isFound = $filter($datum, $key);
31+
32+
if (! $isFound) {
33+
continue;
34+
}
35+
36+
++$totalFound;
37+
38+
if ($totalFound > $max) {
39+
return false;
40+
}
41+
}
42+
43+
return $totalFound >= $min;
44+
}
45+
46+
/**
47+
* @param array<int|string, mixed>|Traversable<int|string, mixed> $data
48+
* @param callable(mixed $datum, int|string|null $key): bool $filter
49+
*/
50+
public static function isExclusiveOf(
51+
iterable $data,
52+
callable $filter,
53+
int $min,
54+
int $max
55+
): bool {
56+
Assert::greaterThan($min, 0);
57+
Assert::greaterThan($max, 0);
58+
Assert::lessThan($min, $max);
59+
Filter::boolean($filter);
60+
61+
if ($max - $min <= 1) {
62+
return false;
63+
}
64+
65+
$totalFound = 0;
66+
foreach ($data as $key => $datum) {
67+
$isFound = $filter($datum, $key);
68+
69+
if (! $isFound) {
70+
continue;
71+
}
72+
73+
++$totalFound;
74+
75+
if ($totalFound >= $max) {
76+
return false;
77+
}
78+
}
79+
80+
return $totalFound > $min && $totalFound < $max;
81+
}
82+
}

tests/IntervalTest.php

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ArrayLookup\Tests;
6+
7+
use ArrayLookup\Interval;
8+
use Iterator;
9+
use PHPUnit\Framework\Attributes\DataProvider;
10+
use PHPUnit\Framework\TestCase;
11+
12+
final class IntervalTest extends TestCase
13+
{
14+
/**
15+
* @param int[]|string[] $data
16+
*/
17+
#[DataProvider('inclusiveDataProvider')]
18+
public function testIsInclusiveOf(
19+
array $data,
20+
callable $filter,
21+
int $min,
22+
int $max,
23+
bool $expected
24+
): void {
25+
$this->assertSame(
26+
$expected,
27+
Interval::isInclusiveOf($data, $filter, $min, $max)
28+
);
29+
}
30+
31+
/**
32+
* @return Iterator<mixed>
33+
*/
34+
public static function inclusiveDataProvider(): Iterator
35+
{
36+
yield 'min boundary' => [
37+
[1, 2, 3],
38+
static fn($datum): bool => $datum > 1,
39+
2,
40+
5,
41+
true,
42+
];
43+
yield 'inside range' => [
44+
[1, 2, 3, 4, 5],
45+
static fn($datum): bool => $datum > 2,
46+
2,
47+
5,
48+
true,
49+
];
50+
yield 'max boundary' => [
51+
[1, 2, 3, 4, 5],
52+
static fn($datum): bool => $datum >= 1,
53+
2,
54+
5,
55+
true,
56+
];
57+
yield 'below min' => [
58+
[1, 2, 3],
59+
static fn($datum): bool => $datum === 3,
60+
2,
61+
5,
62+
false,
63+
];
64+
yield 'above max' => [
65+
[1, 2, 3, 4, 5, 6],
66+
static fn($datum): bool => $datum >= 1,
67+
2,
68+
5,
69+
false,
70+
];
71+
yield 'with key in filter' => [
72+
['a', 'b', 'c', 'd', 'e'],
73+
static fn(string $datum, int $key): bool => $datum !== 'a' && $key > 0,
74+
2,
75+
5,
76+
true,
77+
];
78+
}
79+
80+
/**
81+
* @param int[]|string[] $data
82+
*/
83+
#[DataProvider('exclusiveDataProvider')]
84+
public function testIsExclusiveOf(
85+
array $data,
86+
callable $filter,
87+
int $min,
88+
int $max,
89+
bool $expected
90+
): void {
91+
$this->assertSame(
92+
$expected,
93+
Interval::isExclusiveOf($data, $filter, $min, $max)
94+
);
95+
}
96+
97+
/**
98+
* @return Iterator<mixed>
99+
*/
100+
public static function exclusiveDataProvider(): Iterator
101+
{
102+
yield 'inside range' => [
103+
[1, 2, 3, 4, 5],
104+
static fn($datum): bool => $datum > 2,
105+
2,
106+
5,
107+
true,
108+
];
109+
yield 'above min only' => [
110+
[1, 2, 3],
111+
static fn($datum): bool => $datum > 1,
112+
2,
113+
5,
114+
false,
115+
];
116+
yield 'at min boundary' => [
117+
[1, 2, 3],
118+
static fn($datum): bool => $datum > 2,
119+
2,
120+
5,
121+
false,
122+
];
123+
yield 'at max boundary' => [
124+
[1, 2, 3, 4, 5],
125+
static fn($datum): bool => $datum >= 1,
126+
2,
127+
5,
128+
false,
129+
];
130+
yield 'above max' => [
131+
[1, 2, 3, 4, 5, 6],
132+
static fn($datum): bool => $datum >= 1,
133+
2,
134+
5,
135+
false,
136+
];
137+
yield 'no space between bounds' => [
138+
[1, 2, 3],
139+
static fn($datum): bool => $datum > 1,
140+
2,
141+
3,
142+
false,
143+
];
144+
}
145+
}

0 commit comments

Comments
 (0)