Skip to content

Refactor: move filter / map logic into IterableObject #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 11, 2021
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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"php": "^7.3 || ^8.0"
},
"require-dev": {
"bentools/cartesian-product": "^1.3",
"doctrine/coding-standard": "^8.2",
"pestphp/pest": "^1.0",
"phpstan/extension-installer": "^1.1",
Expand Down
75 changes: 29 additions & 46 deletions src/IterableObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@

namespace BenTools\IterableFunctions;

use EmptyIterator;
use CallbackFilterIterator;
use IteratorAggregate;
use IteratorIterator;
use Traversable;

use function array_filter;
use function array_map;
use function iterator_to_array;

/**
* @internal
*
Expand All @@ -18,73 +23,51 @@ final class IterableObject implements IteratorAggregate
/** @var iterable<mixed> */
private $iterable;

/** @var callable|null */
private $filterFn;

/** @var callable|null */
private $mapFn;

/**
* @param iterable<mixed>|null $iterable
*/
private function __construct(?iterable $iterable = null, ?callable $filter = null, ?callable $map = null)
{
$this->iterable = $iterable ?? new EmptyIterator();
$this->filterFn = $filter;
$this->mapFn = $map;
}

/**
* @param iterable<mixed>|null $iterable
* @param iterable<mixed> $iterable
*/
public static function new(?iterable $iterable = null): self
public function __construct(iterable $iterable)
{
return new self($iterable);
$this->iterable = $iterable;
}

public function filter(?callable $filter = null): self
{
$filter = $filter ?? function ($value): bool {
return (bool) $value;
};
if ($this->iterable instanceof Traversable) {
$filter = $filter ??
/** @param mixed $value */
static function ($value): bool {
return (bool) $value;
};

return new self(new CallbackFilterIterator(new IteratorIterator($this->iterable), $filter));
}

$filtered = $filter === null ? array_filter($this->iterable) : array_filter($this->iterable, $filter);

return new self($this->iterable, $filter, $this->mapFn);
return new self($filtered);
}

public function map(callable $map): self
public function map(callable $mapper): self
{
return new self($this->iterable, $this->filterFn, $map);
if ($this->iterable instanceof Traversable) {
return new self(new MappedTraversable($this->iterable, $mapper));
}

return new self(array_map($mapper, $this->iterable));
}

/**
* @return Traversable<mixed>
*/
public function getIterator(): Traversable
{
$iterable = $this->iterable;
if ($this->filterFn !== null) {
$iterable = iterable_filter($iterable, $this->filterFn);
}

if ($this->mapFn !== null) {
$iterable = iterable_map($iterable, $this->mapFn);
}

return iterable_to_traversable($iterable);
yield from $this->iterable;
}

/** @return array<mixed> */
public function asArray(): array
{
$iterable = $this->iterable;
if ($this->filterFn !== null) {
$iterable = iterable_filter($iterable, $this->filterFn);
}

if ($this->mapFn !== null) {
$iterable = iterable_map($iterable, $this->mapFn);
}

return iterable_to_array($iterable);
return $this->iterable instanceof Traversable ? iterator_to_array($this->iterable) : $this->iterable;
}
}
41 changes: 41 additions & 0 deletions src/MappedTraversable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace BenTools\IterableFunctions;

use IteratorAggregate;
use Traversable;

/**
* @internal
*
* @implements IteratorAggregate<mixed>
*/
final class MappedTraversable implements IteratorAggregate
{
/** @var Traversable<mixed> */
private $traversable;

/** @var callable */
private $mapper;

/**
* @param Traversable<mixed> $traversable
*/
public function __construct(Traversable $traversable, callable $mapper)
{
$this->traversable = $traversable;
$this->mapper = $mapper;
}

/**
* @return Traversable<mixed>
*/
public function getIterator(): Traversable
{
foreach ($this->traversable as $key => $value) {
yield $key => ($this->mapper)($value);
}
}
}
33 changes: 11 additions & 22 deletions src/iterable-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
namespace BenTools\IterableFunctions;

use ArrayIterator;
use CallbackFilterIterator;
use IteratorIterator;
use EmptyIterator;
use Traversable;

use function array_filter;
use function array_values;
use function is_array;
use function iterator_to_array;

/**
Expand All @@ -22,9 +21,9 @@
*/
function iterable_map(iterable $iterable, callable $map): iterable
{
foreach ($iterable as $key => $item) {
yield $key => $map($item);
}
$mapped = iterable($iterable)->map($map);

return is_array($iterable) ? $mapped->asArray() : $mapped;
}

/**
Expand Down Expand Up @@ -65,23 +64,13 @@ function iterable_to_traversable(iterable $iterable): Traversable
*
* @param iterable<mixed> $iterable
*
* @return array<mixed>|CallbackFilterIterator
* @return iterable<mixed>
*/
function iterable_filter(iterable $iterable, ?callable $filter = null)
function iterable_filter(iterable $iterable, ?callable $filter = null): iterable
{
if ($filter === null) {
$filter =
/** @param mixed $value */
static function ($value): bool {
return (bool) $value;
};
}

if ($iterable instanceof Traversable) {
return new CallbackFilterIterator(new IteratorIterator($iterable), $filter);
}
$filtered = iterable($iterable)->filter($filter);

return array_filter($iterable, $filter);
return is_array($iterable) ? $filtered->asArray() : $filtered;
}

/**
Expand Down Expand Up @@ -111,7 +100,7 @@ function iterable_reduce(iterable $iterable, callable $reduce, $initial = null)
/**
* @param iterable<mixed> $iterable
*/
function iterable(iterable $iterable): IterableObject
function iterable(?iterable $iterable): IterableObject
{
return IterableObject::new($iterable);
return new IterableObject($iterable ?? new EmptyIterator());
}
18 changes: 12 additions & 6 deletions tests/IterableFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@
namespace BenTools\IterableFunctions\Tests;

use SplFixedArray;
use Traversable;

use function assert;
use function BenTools\IterableFunctions\iterable_filter;
use function BenTools\IterableFunctions\iterable_to_array;
use function it;
use function PHPUnit\Framework\assertEquals;
use function iterator_to_array;
use function PHPUnit\Framework\assertSame;

it('filters an array', function (): void {
$iterable = [false, true];
assertEquals([1 => true], iterable_to_array(iterable_filter($iterable)));
assertSame([1 => true], iterable_filter($iterable));
});

it('filters a Traversable object', function (): void {
$iterable = SplFixedArray::fromArray([false, true]);
assertEquals([1 => true], iterable_to_array(iterable_filter($iterable)));
$filtered = iterable_filter($iterable);
assert($filtered instanceof Traversable);
assertSame([1 => true], iterator_to_array($filtered));
});

it('filters an array with a callback', function (): void {
Expand All @@ -28,7 +32,7 @@
static function ($input): bool {
return $input === 'bar';
};
assertEquals([1 => 'bar'], iterable_to_array(iterable_filter($iterable, $filter)));
assertSame([1 => 'bar'], iterable_filter($iterable, $filter));
});

it('filters a Travsersable object with a callback', function (): void {
Expand All @@ -38,5 +42,7 @@ static function ($input): bool {
static function ($input): bool {
return $input === 'bar';
};
assertEquals([1 => 'bar'], iterable_to_array(iterable_filter($iterable, $filter)));
$filtered = iterable_filter($iterable, $filter);
assert($filtered instanceof Traversable);
assertSame([1 => 'bar'], iterator_to_array($filtered));
});
11 changes: 7 additions & 4 deletions tests/IterableMapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,27 @@
use PHPUnit\Framework\Assert;
use SplFixedArray;
use stdClass;
use Traversable;

use function assert;
use function BenTools\IterableFunctions\iterable_map;
use function BenTools\IterableFunctions\iterable_to_array;
use function it;
use function PHPUnit\Framework\assertEquals;
use function iterator_to_array;
use function PHPUnit\Framework\assertInstanceOf;
use function PHPUnit\Framework\assertSame;

it('maps an array', function (): void {
$iterable = ['foo', 'bar'];
$map = 'strtoupper';
assertEquals(['FOO', 'BAR'], iterable_to_array(iterable_map($iterable, $map)));
assertSame(['FOO', 'BAR'], iterable_map($iterable, $map));
});

it('maps a Traversable object', function (): void {
$iterable = SplFixedArray::fromArray(['foo', 'bar']);
$map = 'strtoupper';
assertEquals(['FOO', 'BAR'], iterable_to_array(iterable_map($iterable, $map)));
$mapped = iterable_map($iterable, $map);
assert($mapped instanceof Traversable);
assertSame(['FOO', 'BAR'], iterator_to_array($mapped));
});

it('maps iterable with object keys', function (): void {
Expand Down
Loading