Skip to content
Draft
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Added all `_(u)diff_`, `_(u)assoc` and `_(u)intersect` methods as iterator functions.

## [1.0.1] - 2021-08-29
### Changed
Expand Down
16 changes: 13 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"php": "^7.4|^8.0"
},
"require-dev": {
"pestphp/pest": "^1.15",
"vimeo/psalm": "^4.9"
"pestphp/pest": "^1.17",
"vimeo/psalm": "^4.9",
"phpbench/phpbench": "^1.1"
},
"autoload": {
"psr-4": {
Expand All @@ -17,11 +18,20 @@
"src/iterator_functions.php"
]
},
"autoload-dev": {
"psr-4": {
"DoekeNorg\\IteratorFunctions\\Tests\\": "tests"
}
},
"license": "MIT",
"authors": [
{
"name": "Doeke Norg",
"email": "doekenorg@gmail.com"
}
]
],
"scripts": {
"test": "./vendor/bin/pest",
"fix": "php-cs-fixer fix --config=.php_cs.dist.php --allow-risky=yes"
}
}
4 changes: 4 additions & 0 deletions phpbench.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema":"./vendor/phpbench/phpbench/phpbench.schema.json",
"runner.bootstrap": "vendor/autoload.php"
}
165 changes: 165 additions & 0 deletions src/Iterator/DiffIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php

namespace DoekeNorg\IteratorFunctions\Iterator;

/**
* Iterator that computes the difference between multiple iterators.
*/
class DiffIterator extends \FilterIterator
{
/**
* Iterators to compare against.
* @var \AppendIterator
*/
private \AppendIterator $iterator_compare;

/**
* The value of {@see DiffIterator::accept()} when the values equal.
* @var bool
*/
protected bool $equal_accept = false;

/**
* Callback to use for _assoc comparing.
* @var null|callable
*/
private $assoc_compare;

/**
* Callback to use for key comparison.
* @var null|callable
*/
private $key_compare;

/**
* callback to use for value comparison.
* @var null|callable
*/
private $value_compare;

/**
* @inheritdoc
*/
public function __construct(\Iterator $iterator, \Iterator ...$iterators)
{
parent::__construct($iterator);

$this->iterator_compare = new \AppendIterator();
$this->value_compare = self::defaultCompare();

foreach ($iterators as $iterator_compare) {
$this->iterator_compare->append($iterator_compare);
}
}

/**
* Extracts the params from a function call.
* @param array $params The provided arguments.
* @return mixed The params.
*/
final public static function extractParams(array $params): array
{
$result = ['iterator' => null, 'iterators' => [], 'callbacks' => []];

$iterator = array_shift($params);
if (!$iterator instanceof \Iterator) {
throw new \InvalidArgumentException('First parameter must be an iterator.');
}

$result['iterator'] = $iterator;

while (($argument = array_shift($params))) {
if (!$argument instanceof \Iterator && !is_callable($argument)) {
throw new \InvalidArgumentException(sprintf(
'Argument should be an iterator or callback; "%s" given.',
is_string($argument) ? $argument : get_class($argument),
));
}
$type = $argument instanceof \Iterator
? 'iterators'
: 'callbacks';

if ($type === 'iterators' && count($result['callbacks']) !== 0) {
throw new \InvalidArgumentException('An iterator may not be provided after a callback.');
}
$result[$type][] = $argument;
}

if (count($result['iterators']) === 0) {
throw new \InvalidArgumentException('There is no iterator to match against.');
}

return array_values($result);
}

/**
* @inheritdoc
*/
public function accept(): bool
{
if ($this->key_compare && $this->assoc_compare) {
throw new \InvalidArgumentException('Can only use one of "withKey" or "withAssociative", not both.');
}

foreach ($this->iterator_compare as $key => $value) {
if ($this->key_compare && ($this->key_compare)($this->key(), $key) === 0) {
return $this->equal_accept;
}

if (($this->value_compare)($this->current(), $value) === 0) {
if ($this->assoc_compare && (($this->assoc_compare)($this->key(), $key) !== 0)) {
continue;
}

return $this->equal_accept;
}
}

return !$this->equal_accept;
}

/**
* Sets the iterator whether to compare with an extra key check.
* @param callable|null $callback Optional callback to use for comparison.
* @return $this The iterator.
*/
public function withAssociative(?callable $callback = null): self
{
$this->assoc_compare = $callback ?? self::defaultCompare();

return $this;
}

/**
* Sets the iterator to compare against the key.
* @param null|callable $callback Optional callable to perform as key compare function.
* @return $this The iterator.
*/
public function withKey(?callable $callback = null): self
{
$this->key_compare = $callback ?? self::defaultCompare();

return $this;
}

/**
* Sets the iterator to compare the value by callback.
* @param callable $callback Callable to perform as compare function.
* @return $this The iterator.
*/
public function withCallback(callable $callback): self
{
$this->value_compare = $callback;

return $this;
}

/**
* The default function to use for comparing.
* @return callable(mixed $current, mixed $compare):int 0 when the same, -1 of 1 when different.
*/
final protected static function defaultCompare(): callable
{
return static fn ($current, $compare) => $current <=> $compare;
}
}
14 changes: 14 additions & 0 deletions src/Iterator/IntersectIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace DoekeNorg\IteratorFunctions\Iterator;

/**
* Iterator that computes the intersection between multiple iterators.
*/
class IntersectIterator extends DiffIterator
{
/**
* @inheritdoc
*/
protected bool $equal_accept = true;
}
4 changes: 3 additions & 1 deletion src/Iterator/MapIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ public function __construct(callable $callback, iterable ...$iterators)
{
try {
$function = new \ReflectionFunction(\Closure::fromCallable($callback));
// @codeCoverageIgnoreStart
} catch (\ReflectionException $e) {
throw new \RuntimeException($e->getMessage(), (int) $e->getCode(), $e);
return; // Will not happen.
}
// @codeCoverageIgnoreEnd

if (!$function->isVariadic() && $function->getNumberOfParameters() !== count($iterators)) {
throw new \InvalidArgumentException('The callback needs as many arguments as provided iterators.');
Expand Down
Loading