[Vec] add filter and map functions (#136)

This commit is contained in:
Saif Eddin Gmati 2021-02-19 21:04:54 +01:00 committed by GitHub
parent a48ed5caa7
commit b33d6b2003
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 519 additions and 13 deletions

View File

@ -14,11 +14,11 @@ use Closure;
*
* Example:
*
* Arr\filter(['', '0', 'a', 'b'])
* => Arr('a', 'b')
* Dict\filter(['', '0', 'a', 'b'])
* => Dict(2 => 'a', 3 => 'b')
*
* Arr\filter(['foo', 'bar', 'baz', 'qux'], fn(string $value): bool => Str\contains($value, 'a'));
* => Arr('bar', 'baz')
* Dict\filter(['foo', 'bar', 'baz', 'qux'], fn(string $value): bool => Str\contains($value, 'a'));
* => Dict(1 => 'bar', 2 => 'baz')
*
* @psalm-template Tk of array-key
* @psalm-template Tv

View File

@ -7,18 +7,18 @@ namespace Psl\Dict;
use Closure;
/**
* Returns an array containing only the keys for which the given predicate
* Returns a dict containing only the keys for which the given predicate
* returns `true`.
*
* The default predicate is casting the key to boolean.
*
* Example:
*
* Arr\filter_keys([0 => 'a', 1 => 'b'])
* => Arr(1 => 'b')
* Dict\filter_keys([0 => 'a', 1 => 'b'])
* => Dict(1 => 'b')
*
* Arr\filter_keys([0 => 'a', 1 => 'b', 2 => 'c'], fn(int $key): bool => $key <= 1);
* => Arr(0 => 'a', 1 => 'b')
* Dict\filter_keys([0 => 'a', 1 => 'b', 2 => 'c'], fn(int $key): bool => $key <= 1);
* => Dict(0 => 'a', 1 => 'b')
*
* @psalm-template Tk of array-key
* @psalm-template Tv

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Psl\Dict;
/**
* Filter out null values from the given iterable.
*
* Example:
* Dict\filter_nulls([1, null, 5])
* => Dict(0 => 1, 2 => 5)
*
* @psalm-template Tk of array-key
* @psalm-template Tv
*
* @psalm-param iterable<Tk, Tv|null> $iterable
*
* @psalm-return array<Tk, Tv>
*/
function filter_nulls(iterable $iterable): array
{
/** @var array<Tk, Tv> $result */
$result = [];
foreach ($iterable as $key => $value) {
if (null !== $value) {
$result[$key] = $value;
}
}
return $result;
}

View File

@ -14,14 +14,14 @@ use Psl;
*
* Example:
*
* Arr\filter_with_key(['a', '0', 'b', 'c'])
* => Iter('b', 'c')
* Dict\filter_with_key(['a', '0', 'b', 'c'])
* => Dict(2 => 'b', 3 => 'c')
*
* Arr\filter_with_key(
* Dict\filter_with_key(
* ['foo', 'bar', 'baz', 'qux'],
* fn(int $key, string $value): bool => $key > 1 && Str\contains($value, 'a')
* );
* => Arr('baz')
* => Dict(2 => 'baz')
*
* @psalm-template Tk of array-key
* @psalm-template Tv

View File

@ -102,6 +102,7 @@ final class Loader
'Psl\Dict\drop_while',
'Psl\Dict\equal',
'Psl\Dict\filter',
'Psl\Dict\filter_nulls',
'Psl\Dict\filter_keys',
'Psl\Dict\filter_with_key',
'Psl\Dict\flatten',
@ -199,12 +200,17 @@ final class Loader
'Psl\Vec\concat',
'Psl\Vec\enumerate',
'Psl\Vec\fill',
'Psl\Vec\filter',
'Psl\Vec\filter_keys',
'Psl\Vec\filter_nulls',
'Psl\Vec\filter_with_key',
'Psl\Vec\flat_map',
'Psl\Vec\keys',
'Psl\Vec\partition',
'Psl\Vec\range',
'Psl\Vec\reductions',
'Psl\Vec\map',
'Psl\Vec\map_with_key',
'Psl\Vec\reproduce',
'Psl\Vec\reverse',
'Psl\Vec\shuffle',

42
src/Psl/Vec/filter.php Normal file
View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Psl\Vec;
use Closure;
/**
* Returns a vec containing only the values for which the given predicate
* returns `true`.
*
* The default predicate is casting the value to boolean.
*
* Example:
*
* Vec\filter(['', '0', 'a', 'b'])
* => Vec('a', 'b')
*
* Vec\filter(['foo', 'bar', 'baz', 'qux'], fn(string $value): bool => Str\contains($value, 'a'));
* => Vec('bar', 'baz')
*
* @psalm-template T
*
* @psalm-param iterable<T> $iterable
* @psalm-param (callable(T): bool)|null $predicate
*
* @psalm-return list<T>
*/
function filter(iterable $iterable, ?callable $predicate = null): array
{
/** @psalm-var (callable(T): bool) $predicate */
$predicate = $predicate ?? Closure::fromCallable('Psl\Internal\boolean');
$result = [];
foreach ($iterable as $v) {
if ($predicate($v)) {
$result[] = $v;
}
}
return $result;
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Psl\Vec;
use Closure;
/**
* Returns a vec containing only the values for which the given predicate
* returns `true`.
*
* The default predicate is casting the key to boolean.
*
* Example:
*
* Vec\filter_keys([0 => 'a', 1 => 'b'])
* => Vec('b')
*
* Vec\filter_keys([0 => 'a', 1 => 'b', 2 => 'c'], fn(int $key): bool => $key <= 1);
* => Vec('a', 'b')
*
* @psalm-template Tk
* @psalm-template Tv
*
* @psalm-param iterable<Tk, Tv> $iterable
* @psalm-param (callable(Tk): bool)|null $predicate
*
* @psalm-return list<Tv>
*/
function filter_keys(iterable $iterable, ?callable $predicate = null): array
{
/** @psalm-var (callable(Tk): bool) $predicate */
$predicate = $predicate ?? Closure::fromCallable('Psl\Internal\boolean');
$result = [];
foreach ($iterable as $k => $v) {
if ($predicate($k)) {
$result[] = $v;
}
}
return $result;
}

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Psl\Vec;
use Psl;
/**
* Returns a vec containing only the values for which the given predicate
* returns `true`.
*
* The default predicate is casting the value to boolean.
*
* Example:
*
* Vec\filter_with_key(['a', '0', 'b', 'c'])
* => Vec('b', 'c')
*
* Vec\filter_with_key(
* ['foo', 'bar', 'baz', 'qux'],
* fn(int $key, string $value): bool => $key > 1 && Str\contains($value, 'a')
* );
* => Vec('baz')
*
* @psalm-template Tk
* @psalm-template Tv
*
* @psalm-param iterable<Tk, Tv> $iterable
* @psalm-param (callable(Tk, Tv): bool)|null $predicate
*
* @psalm-return list<Tv>
*/
function filter_with_key(iterable $iterable, ?callable $predicate = null): array
{
$predicate = $predicate ??
/**
* @psalm-param Tk $k
* @psalm-param Tv $v
*/
static fn ($k, $v): bool => Psl\Internal\boolean($v);
$result = [];
foreach ($iterable as $k => $v) {
if ($predicate($k, $v)) {
$result[] = $v;
}
}
return $result;
}

35
src/Psl/Vec/map.php Normal file
View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Psl\Vec;
/**
* Applies a mapping function to all values of an iterable.
*
* The function is passed the current iterable value and should return a
* modified value.
*
* Examples:
*
* Vec\map([1, 2, 3, 4, 5], fn($i) => $i * 2);
* => Vec(2, 4, 6, 8, 10)
*
* @psalm-template Tk
* @psalm-template Tv
* @psalm-template T
*
* @psalm-param iterable<Tk, Tv> $iterable Iterable to be mapped over
* @psalm-param (callable(Tv): T) $function
*
* @psalm-return list<T>
*/
function map(iterable $iterable, callable $function): array
{
$result = [];
foreach ($iterable as $value) {
$result[] = $function($value);
}
return $result;
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Psl\Vec;
/**
* Applies a mapping function to all values of an iterable.
*
* The function is passed the current iterable key and value and should return a
* modified value.
*
* Examples:
*
* Vec\map_with_key([1, 2, 3, 4, 5], fn($k, $v) => $k + $v);
* => Vec(1, 3, 5, 7, 9)
*
* @psalm-template Tk
* @psalm-template Tv
* @psalm-template T
*
* @psalm-param iterable<Tk, Tv> $iterable Iterable to be mapped over
* @psalm-param (callable(Tk,Tv): T) $function
*
* @psalm-return list<T>
*/
function map_with_key(iterable $iterable, callable $function): array
{
$result = [];
foreach ($iterable as $key => $value) {
$result[] = $function($key, $value);
}
return $result;
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Dict;
use PHPUnit\Framework\TestCase;
use Psl\Collection;
use Psl\Dict;
use Psl\Iter;
final class FilterNullsTest extends TestCase
{
public function testFilterNulls(): void
{
static::assertCount(0, Dict\filter_nulls([]));
static::assertCount(0, Dict\filter_nulls([null, null]));
static::assertCount(1, Dict\filter_nulls([null, false]));
static::assertCount(1, Dict\filter_nulls([null, 'null']));
static::assertCount(1, Dict\filter_nulls(['null']));
static::assertCount(1, Dict\filter_nulls(Iter\Iterator::create(['null'])));
static::assertCount(0, Dict\filter_nulls(Iter\Iterator::create([null])));
static::assertCount(0, Dict\filter_nulls(Iter\Iterator::create([null, null])));
static::assertCount(3, Dict\filter_nulls(Iter\Iterator::create([null, false, '', 0])));
static::assertCount(3, Dict\filter_nulls(new Collection\Vector([null, false, '', 0])));
static::assertCount(3, Dict\filter_nulls(new Collection\Map([null, false, '', 0])));
static::assertCount(3, Dict\filter_nulls((static function (): iterable {
yield null;
yield false;
yield '';
yield 0;
yield null;
})()));
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Vec;
use PHPUnit\Framework\TestCase;
use Psl\Vec;
final class ChunkTest extends TestCase
{
/**
* @psalm-template T
*
* @psalm-param list<list<T>> $expected
* @psalm-param iterable<T> $iterable
*
* @dataProvider provideData
*/
public function testChunk(array $expected, iterable $iterable, int $size): void
{
static::assertSame($expected, Vec\chunk($iterable, $size));
}
public function provideData(): iterable
{
yield [[[1], [2], [3]], Vec\range(1, 3), 1];
yield [[], [], 4];
yield [[[1, 2, 3], [4, 5, 6], [7, 8, 9]], Vec\range(1, 9), 3];
yield [[[1, 3, 5], [7, 9]], Vec\range(1, 9, 2), 3];
yield [[[1, 3], [5, 7], [9]], Vec\range(1, 9, 2), 2];
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Vec;
use PHPUnit\Framework\TestCase;
use Psl\Vec;
final class FilterKeysTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function testFilter(array $expected, array $array, ?callable $predicate = null): void
{
$result = Vec\filter_keys($array, $predicate);
static::assertSame($expected, $result);
}
public function provideData(): iterable
{
yield [[], []];
yield [['b'], ['a', 'b']];
yield [['a'], ['a', 'b'], static fn (int $k): bool => $k !== 1];
yield [['b'], ['a', 'b'], static fn (int $k): bool => $k !== 0];
yield [[], ['a', 'b'], static fn (int $_) => false];
yield [['a', 'b'], ['a', 'b'], static fn (int $_): bool => true];
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Vec;
use PHPUnit\Framework\TestCase;
use Psl\Vec;
final class FilterTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function testFilter(array $expected, array $array, ?callable $predicate = null): void
{
$result = Vec\filter($array, $predicate);
static::assertSame($expected, $result);
}
public function provideData(): iterable
{
yield [[], []];
yield [['a', 'b'], ['a', 'b']];
yield [[], ['a', 'b'], static fn () => false];
yield [['a', 'b'], ['a', 'b'], static fn (string $_): bool => true];
yield [['a'], ['a', 'b'], static fn (string $v): bool => 'b' !== $v];
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Vec;
use PHPUnit\Framework\TestCase;
use Psl\Vec;
final class FilterWithKeyTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function testFilterWithKey(array $expected, array $array, ?callable $predicate = null): void
{
$result = Vec\filter_with_key($array, $predicate);
static::assertSame($expected, $result);
}
public function provideData(): iterable
{
yield [[], []];
yield [['a', 'b'], ['a', 'b']];
yield [[], ['a', 'b'], static fn (int $_k, string $_v) => false];
yield [['a', 'b'], ['a', 'b'], static fn (int $_k, string $_v): bool => true];
yield [['a'], ['a', 'b'], static fn (int $k, string $v): bool => 'b' !== $v];
yield [[], ['a', 'b'], static fn (int $k, string $v): bool => 'b' !== $v && 0 !== $k];
yield [['a'], ['a', 'b'], static fn (int $k, string $v): bool => 'b' !== $v && 1 !== $k];
yield [[], ['a', 'b'], static fn (int $k, string $v): bool => 'a' !== $v && 1 !== $k];
yield [['b'], ['a', 'b'], static fn (int $k, string $v): bool => 'a' !== $v && 0 !== $k];
}
}

29
tests/Psl/Vec/MapTest.php Normal file
View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Vec;
use PHPUnit\Framework\TestCase;
use Psl\Vec;
final class MapTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function testMap(array $expected, array $array, callable $function): void
{
$result = Vec\map($array, $function);
static::assertSame($expected, $result);
}
public function provideData(): iterable
{
yield [[1, 2, 3], [1, 2, 3], static fn (int $v): int => $v];
yield [[2, 4, 6], [1, 2, 3], static fn (int $v): int => $v * 2];
yield [['1', '2', '3'], [1, 2, 3], static fn (int $v): string => (string)$v];
yield [[], [], static fn (int $v): string => (string)$v];
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Vec;
use PHPUnit\Framework\TestCase;
use Psl\Vec;
final class MapWithKeyTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function testMapWithKey(array $expected, array $array, callable $function): void
{
$result = Vec\map_with_key($array, $function);
static::assertSame($expected, $result);
}
public function provideData(): iterable
{
yield [[1, 2, 3], ['a' => 1, 'b' => 2, 'c' => 3], static fn (string $k, int $v): int => $v];
yield [[1, 3, 5], [1, 2, 3], static fn (int $k, int $v): int => $k + $v];
yield [[0, 4, 16], [1, 2, 3], static fn (int $k, int $v): int => $k * (2 ** $v)];
yield [['1', '3', '5'], [1, 2, 3], static fn (int $k, int $v): string => (string) ($k + $v)];
yield [[], [], static fn (int $k, int $v): string => (string) ($k + $v)];
}
}

40
tests/Psl/Vec/ZipTest.php Normal file
View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Psl\Tests\Vec;
use PHPUnit\Framework\TestCase;
use Psl\Vec;
final class ZipTest extends TestCase
{
/**
* @dataProvider provideData
*/
public function testZip(array $expected, array $first, array $second): void
{
static::assertSame($expected, Vec\zip($first, $second));
}
public function provideData(): iterable
{
yield [
[['foo', 'baz'], ['bar', 'qux']],
['foo', 'bar'],
['baz', 'qux']
];
yield [
[],
[],
[],
];
yield [
[],
['foo', 'bar'],
[],
];
}
}