Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 75 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()`
Expand Down Expand Up @@ -230,7 +302,7 @@ $times = 2;
var_dump(Only::times($data, $filter, $times)) // false
```

**C. Finder**
**D. Finder**
---------------

#### 1. `Finder::first()`
Expand Down Expand Up @@ -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:
Expand Down
71 changes: 71 additions & 0 deletions src/AtMost.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

namespace ArrayLookup;

use ArrayLookup\Assert\Filter;
use Traversable;
use Webmozart\Assert\Assert;

final class AtMost
{
/**
* @param array<int|string, mixed>|Traversable<int|string, mixed> $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<int|string, mixed>|Traversable<int|string, mixed> $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<int|string, mixed>|Traversable<int|string, mixed> $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<int|string, mixed>|Traversable<int|string, mixed> $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;
}
}
146 changes: 146 additions & 0 deletions tests/AtMostTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

declare(strict_types=1);

namespace ArrayLookup\Tests;

use ArrayLookup\AtMost;
use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class AtMostTest extends TestCase
{
/**
* @param int[]|string[] $data
*/
#[DataProvider('onceDataProvider')]
public function testOnce(array $data, callable $filter, bool $expected): void
{
$this->assertSame(
$expected,
AtMost::once($data, $filter)
);
}

// phpcs:disable
/**
* @return Iterator<mixed>
*/
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<mixed>
*/
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<mixed>
*/
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,
];
}
}