Skip to content

Commit

Permalink
feat: introduce first_opt(), first_key_opt(), last_opt(), `last…
Browse files Browse the repository at this point in the history
…_key_opt()` and `search_opt()` (#467)

* feat: introduce `first_opt()`, `first_key_opt()`, `last_opt()`, `last_key_opt()` and `search_opt()`

* Update src/Psl/Iter/last_opt.php

Co-authored-by: Saif Eddin Gmati <29315886+azjezz@users.noreply.github.com>

* Update src/Psl/Iter/search_opt.php

* example

---------

Co-authored-by: Saif Eddin Gmati <29315886+azjezz@users.noreply.github.com>
  • Loading branch information
simPod and azjezz authored May 5, 2024
1 parent a06e6b0 commit 5e4e0ef
Show file tree
Hide file tree
Showing 12 changed files with 410 additions and 0 deletions.
5 changes: 5 additions & 0 deletions docs/component/iter.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,20 @@
- [count](./../../src/Psl/Iter/count.php#L23)
- [first](./../../src/Psl/Iter/first.php#L16)
- [first_key](./../../src/Psl/Iter/first_key.php#L17)
- [first_key_opt](./../../src/Psl/Iter/first_key_opt.php#L20)
- [first_opt](./../../src/Psl/Iter/first_opt.php#L19)
- [is_empty](./../../src/Psl/Iter/is_empty.php#L16)
- [last](./../../src/Psl/Iter/last.php#L17)
- [last_key](./../../src/Psl/Iter/last_key.php#L17)
- [last_key_opt](./../../src/Psl/Iter/last_key_opt.php#L20)
- [last_opt](./../../src/Psl/Iter/last_opt.php#L19)
- [random](./../../src/Psl/Iter/random.php#L21)
- [reduce](./../../src/Psl/Iter/reduce.php#L25)
- [reduce_keys](./../../src/Psl/Iter/reduce_keys.php#L26)
- [reduce_with_keys](./../../src/Psl/Iter/reduce_with_keys.php#L27)
- [rewindable](./../../src/Psl/Iter/rewindable.php#L20)
- [search](./../../src/Psl/Iter/search.php#L28)
- [search_opt](./../../src/Psl/Iter/search_opt.php#L30)
- [to_iterator](./../../src/Psl/Iter/to_iterator.php#L19)

#### `Classes`
Expand Down
5 changes: 5 additions & 0 deletions src/Psl/Internal/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,21 @@ final class Loader
'Psl\\Iter\\contains_key' => 'Psl/Iter/contains_key.php',
'Psl\\Iter\\count' => 'Psl/Iter/count.php',
'Psl\\Iter\\first' => 'Psl/Iter/first.php',
'Psl\\Iter\\first_opt' => 'Psl/Iter/first_opt.php',
'Psl\\Iter\\first_key' => 'Psl/Iter/first_key.php',
'Psl\\Iter\\first_key_opt' => 'Psl/Iter/first_key_opt.php',
'Psl\\Iter\\is_empty' => 'Psl/Iter/is_empty.php',
'Psl\\Iter\\last' => 'Psl/Iter/last.php',
'Psl\\Iter\\last_opt' => 'Psl/Iter/last_opt.php',
'Psl\\Iter\\last_key' => 'Psl/Iter/last_key.php',
'Psl\\Iter\\last_key_opt' => 'Psl/Iter/last_key_opt.php',
'Psl\\Iter\\random' => 'Psl/Iter/random.php',
'Psl\\Iter\\reduce' => 'Psl/Iter/reduce.php',
'Psl\\Iter\\reduce_keys' => 'Psl/Iter/reduce_keys.php',
'Psl\\Iter\\reduce_with_keys' => 'Psl/Iter/reduce_with_keys.php',
'Psl\\Iter\\rewindable' => 'Psl/Iter/rewindable.php',
'Psl\\Iter\\search' => 'Psl/Iter/search.php',
'Psl\\Iter\\search_opt' => 'Psl/Iter/search_opt.php',
'Psl\\Iter\\to_iterator' => 'Psl/Iter/to_iterator.php',
'Psl\\Vec\\chunk' => 'Psl/Vec/chunk.php',
'Psl\\Vec\\chunk_with_keys' => 'Psl/Vec/chunk_with_keys.php',
Expand Down
27 changes: 27 additions & 0 deletions src/Psl/Iter/first_key_opt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Psl\Iter;

use Psl\Option\Option;

/**
* Returns the first key of an iterable wrapped in {@see Option::some},
* if the iterable is empty, {@see Option::none} will be returned.
*
* @param iterable<Tk, Tv> $iterable
*
* @template Tk
* @template Tv
*
* @return Option<Tk>
*/
function first_key_opt(iterable $iterable): Option
{
foreach ($iterable as $k => $_) {
return Option::some($k);
}

return Option::none();
}
26 changes: 26 additions & 0 deletions src/Psl/Iter/first_opt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Psl\Iter;

use Psl\Option\Option;

/**
* Returns the first element of an iterable wrapped in {@see Option::some},
* if the iterable is empty, {@see Option::none} will be returned.
*
* @template T
*
* @param iterable<T> $iterable
*
* @return Option<T>
*/
function first_opt(iterable $iterable): Option
{
foreach ($iterable as $v) {
return Option::some($v);
}

return Option::none();
}
28 changes: 28 additions & 0 deletions src/Psl/Iter/last_key_opt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Psl\Iter;

use Psl\Option\Option;

/**
* Returns the first key of an iterable wrapped in {@see Option::some},
* if the iterable is empty, {@see Option::none} will be returned.
*
* @template Tk
* @template Tv
*
* @param iterable<Tk, Tv> $iterable
*
* @return Option<Tk>
*/
function last_key_opt(iterable $iterable): Option
{
$last = Option::none();
foreach ($iterable as $k => $_) {
$last = Option::some($k);
}

return $last;
}
27 changes: 27 additions & 0 deletions src/Psl/Iter/last_opt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Psl\Iter;

use Psl\Option\Option;

/**
* Returns the last element of an iterable wrapped in {@see Option::some},
* if the iterable is empty, {@see Option::none} will be returned.
*
* @template Tv
*
* @param iterable<Tv> $iterable
*
* @return Option<Tv>
*/
function last_opt(iterable $iterable): Option
{
$last = Option::none();
foreach ($iterable as $v) {
$last = Option::some($v);
}

return $last;
}
39 changes: 39 additions & 0 deletions src/Psl/Iter/search_opt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Psl\Iter;

use Closure;
use Psl\Option\Option;

/**
* Searches an iterable until a predicate returns true, then returns
* the value of the matching element wrapped in {@see Option::some}.
* If a predicate never returns true, {@see Option::none} will be returned.
*
* Examples:
*
* Iter\search_opt(['foo', 'bar', 'baz'], fn($v) => 'baz' === $v)
* => Option::some('baz')
*
* Iter\search_opt(['foo', 'bar', 'baz'], fn($v) => 'qux' === $v)
* => Option::none()
*
* @template T
*
* @param iterable<T> $iterable The iterable to search
* @param (Closure(T): bool) $predicate
*
* @return Option<T>
*/
function search_opt(iterable $iterable, Closure $predicate): Option
{
foreach ($iterable as $value) {
if ($predicate($value)) {
return Option::some($value);
}
}

return Option::none();
}
55 changes: 55 additions & 0 deletions tests/unit/Iter/FirstKeyOptTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Psl\Tests\Unit\Iter;

use PHPUnit\Framework\TestCase;
use Psl\Collection;
use Psl\Iter;
use SplDoublyLinkedList;

final class FirstKeyOptTest extends TestCase
{
/**
* @dataProvider provideDataSome
*/
public function testFirstKeyOptSome($expected, iterable $iterable): void
{
$result = Iter\first_key_opt($iterable);

static::assertSame($expected, $result->unwrap());
}

public function provideDataSome(): iterable
{
yield ['a', ['a' => 'b']];
yield [0, ['a', 'b']];
yield [0, new Collection\Vector(['a', 'b'])];
yield [0, new Collection\Vector(['a' => 'b'])];
yield ['a', new Collection\Map(['a' => 'b'])];
yield [null, (static function () {
yield null => null;
})()];
}

/**
* @dataProvider provideDataNone
*/
public function testFirstKeyOptNone(iterable $iterable): void
{
$result = Iter\first_key_opt($iterable);

static::assertTrue($result->isNone());
}

public function provideDataNone(): iterable
{
yield [[]];
yield [new SplDoublyLinkedList()];
yield [(static function () {
return;
yield;
})()];
}
}
55 changes: 55 additions & 0 deletions tests/unit/Iter/FirstOptTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Psl\Tests\Unit\Iter;

use PHPUnit\Framework\TestCase;
use Psl\Collection;
use Psl\Iter;
use SplDoublyLinkedList;

final class FirstOptTest extends TestCase
{
/**
* @dataProvider provideDataSome
*/
public function testFirstSome($expected, iterable $iterable): void
{
$result = Iter\first_opt($iterable);

static::assertSame($expected, $result->unwrap());
}

public function provideDataSome(): iterable
{
yield ['b', ['a' => 'b', 'c' => 'd']];
yield ['a', ['a', 'b']];
yield ['a', new Collection\Vector(['a', 'b'])];
yield ['b', new Collection\Vector(['b'])];
yield ['b', new Collection\Map(['a' => 'b', 'c' => 'd'])];
yield [null, (static function () {
yield null => null;
})()];
}

/**
* @dataProvider provideDataNone
*/
public function testFirstNone(iterable $iterable): void
{
$result = Iter\first_opt($iterable);

static::assertTrue($result->isNone());
}

public function provideDataNone(): iterable
{
yield [[]];
yield [new SplDoublyLinkedList()];
yield [(static function () {
return;
yield;
})()];
}
}
49 changes: 49 additions & 0 deletions tests/unit/Iter/LastKeyOptTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Psl\Tests\Unit\Iter;

use PHPUnit\Framework\TestCase;
use Psl\Iter;
use Psl\Vec;

final class LastKeyOptTest extends TestCase
{
/**
* @dataProvider provideDataSome
*/
public function testLastKeySome($expected, iterable $iterable): void
{
$result = Iter\last_key_opt($iterable);

static::assertSame($expected, $result->unwrap());
}

public function provideDataSome(): iterable
{
yield [3, [1, 2, 3, 4]];
yield [3, Iter\to_iterator([1, 2, 3, 4])];
yield [3, Vec\range(1, 4)];
yield [4, Vec\range(4, 8)];
yield [4, Iter\to_iterator(Vec\range(4, 8))];
yield [0, [null]];
yield [1, [null, null]];
yield [[1, 2], (static fn () => yield [1, 2] => 'hello')()];
}

/**
* @dataProvider provideDataNone
*/
public function testLastKeyNone(iterable $iterable): void
{
$result = Iter\last_key_opt($iterable);

static::assertTrue($result->isNone());
}

public function provideDataNone(): iterable
{
yield [[]];
}
}
Loading

0 comments on commit 5e4e0ef

Please sign in to comment.