From bd744eb9f0eefe079565b9d072fc4619d5f08085 Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Tue, 16 Nov 2021 14:22:20 +0300 Subject: [PATCH 01/21] Php 8.0 compatibility. --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 75fe27a..9f4c2f0 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['7.1', '7.2', '7.3', '7.4'] + php-versions: ['7.1', '7.2', '7.3', '7.4', '8.0'] name: Testing with PHP ${{ matrix.php-versions }} steps: From f95b3375fc975c1bfb8a1dd391ad66414093ebf8 Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Tue, 16 Nov 2021 14:29:53 +0300 Subject: [PATCH 02/21] Increase phpunit version to 9.5. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index db37332..b903195 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^9.5" }, "suggest": { "phpbench/phpbench": "Uses only for benchmark purposes" From 15a8dab16f6a9fceb68b53fe79280bc2e2258945 Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Tue, 16 Nov 2021 15:46:29 +0300 Subject: [PATCH 03/21] Increase phpunit version to 9.5. --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 9f4c2f0..40639f1 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-versions: ['7.1', '7.2', '7.3', '7.4', '8.0'] + php-versions: ['7.3', '7.4', '8.0'] name: Testing with PHP ${{ matrix.php-versions }} steps: From 53471bab21cb84c8bc4e8873533c6eb79e03914b Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Tue, 16 Nov 2021 15:46:47 +0300 Subject: [PATCH 04/21] Increase phpunit version to 9.5. --- tests/WS/Utils/Collections/ConditionsStreamTest.php | 2 +- .../UnitConstraints/CollectionComparingConstraint.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/WS/Utils/Collections/ConditionsStreamTest.php b/tests/WS/Utils/Collections/ConditionsStreamTest.php index 5e9cd17..e27e8d8 100644 --- a/tests/WS/Utils/Collections/ConditionsStreamTest.php +++ b/tests/WS/Utils/Collections/ConditionsStreamTest.php @@ -5,8 +5,8 @@ namespace WS\Utils\Collections; -use PHPUnit\Framework\MockObject\Matcher\InvokedCount; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\Rule\InvokedCount; use PHPUnit\Framework\TestCase; use WS\Utils\Collections\Functions\Predicates; use WS\Utils\Collections\UnitConstraints\CollectionIsEqual; diff --git a/tests/WS/Utils/Collections/UnitConstraints/CollectionComparingConstraint.php b/tests/WS/Utils/Collections/UnitConstraints/CollectionComparingConstraint.php index 4843256..45badba 100644 --- a/tests/WS/Utils/Collections/UnitConstraints/CollectionComparingConstraint.php +++ b/tests/WS/Utils/Collections/UnitConstraints/CollectionComparingConstraint.php @@ -16,7 +16,6 @@ abstract class CollectionComparingConstraint extends Constraint implements Stati public function __construct($expectedCollection) { - parent::__construct(); $this->expectedCollection = $this->normalize($expectedCollection); } @@ -43,7 +42,7 @@ public function toString(): string { return sprintf( 'is accepted by %s', - $this->exporter->export($this->expectedCollection) + $this->exporter()->export($this->expectedCollection) ); } From 3287b1e1de08d985628176bac224b15e45fef36c Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Tue, 16 Nov 2021 17:42:38 +0300 Subject: [PATCH 05/21] Increase package version. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b903195..9608051 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.0.9", + "version": "1.1.0", "authors": [ { "name": "Maxim Sokolovsky", From 2acd1531b824b894aee07dc7913c1d7bfa9e07e6 Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Tue, 16 Nov 2021 17:48:55 +0300 Subject: [PATCH 06/21] Increase php version to 8.0. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9608051..ba10166 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ } ], "require": { - "php": ">=7.1", + "php": "8.0", "ext-json": "*" }, "require-dev": { From a7332d619c84c6c496e6acec34055743bafcfb38 Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Tue, 16 Nov 2021 18:09:12 +0300 Subject: [PATCH 07/21] Define php version from 7.3 to 8.0. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ba10166..b25773d 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ } ], "require": { - "php": "8.0", + "php": ">=7.3 <=8.0", "ext-json": "*" }, "require-dev": { From ab0b42af698a5034a46eadf9a862ba43d415c63f Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Tue, 16 Nov 2021 18:16:48 +0300 Subject: [PATCH 08/21] Define php version from 7.3 to 8.0. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b25773d..32abbf8 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ } ], "require": { - "php": ">=7.3 <=8.0", + "php": "^7.3 || 8.0", "ext-json": "*" }, "require-dev": { From 4b221cefa8dcd8c6348a52f380c526114eb65031 Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Tue, 16 Nov 2021 18:17:57 +0300 Subject: [PATCH 09/21] Define php version from 7.3 to 8.0. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 32abbf8..6b967a6 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ } ], "require": { - "php": "^7.3 || 8.0", + "php": "^7.3 || ~8.0", "ext-json": "*" }, "require-dev": { From da70a10f158681ed4af5077b739171fa37bfc5f1 Mon Sep 17 00:00:00 2001 From: Maxim Sokolovsky Date: Thu, 16 Dec 2021 18:24:00 +0300 Subject: [PATCH 10/21] Fix Stream::sotBy method working with float values --- src/WS/Utils/Collections/SerialStream.php | 2 +- .../WS/Utils/Collections/SerialStreamTest.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/WS/Utils/Collections/SerialStream.php b/src/WS/Utils/Collections/SerialStream.php index b06eef3..7f272f9 100644 --- a/src/WS/Utils/Collections/SerialStream.php +++ b/src/WS/Utils/Collections/SerialStream.php @@ -137,7 +137,7 @@ public function sortBy(callable $extractor): Stream sort($values); $newList = $this->emptyList(); foreach ($values as $value) { - $els = $map[$value] ?? []; + $els = $map[$value.''] ?? []; $newList->addAll($els); } $this->list = $newList; diff --git a/tests/WS/Utils/Collections/SerialStreamTest.php b/tests/WS/Utils/Collections/SerialStreamTest.php index fac1ae9..8e47bba 100644 --- a/tests/WS/Utils/Collections/SerialStreamTest.php +++ b/tests/WS/Utils/Collections/SerialStreamTest.php @@ -8,6 +8,7 @@ use Exception; use PHPUnit\Framework\TestCase; use WS\Utils\Collections\UnitConstraints\CollectionIsEqual; +use WS\Utils\Collections\Utils\ExampleObject; use WS\Utils\Collections\Utils\InvokeCounter; use WS\Utils\Collections\Utils\TestInteger; @@ -502,6 +503,23 @@ public function sortingWithNotScalarValue(): void ; } + /** + * @test + */ + public function sortingWithSingleValue(): void + { + $obj = new ExampleObject(); + $obj->property = 1.12; + $sortedFirstElement = $this->createCollection([$obj]) + ->stream() + ->sortBy(static function (ExampleObject $object) { + return $object->property; + }) + ->findFirst() + ; + self::assertNotNull($sortedFirstElement); + } + /** * @dataProvider sortCases * @test From fbc8076f0b995e9a7dfd19ca3330806768c9644e Mon Sep 17 00:00:00 2001 From: Maxim Sokolovsky Date: Thu, 16 Dec 2021 18:38:28 +0300 Subject: [PATCH 11/21] Increase fix version for php 8 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6b967a6..6870327 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.1.0", + "version": "1.1.1", "authors": [ { "name": "Maxim Sokolovsky", From c1775b5617131b86f53b4ea682eaa10fbcca0c96 Mon Sep 17 00:00:00 2001 From: Maxim Sokolovsky Date: Sun, 19 Dec 2021 17:41:00 +0300 Subject: [PATCH 12/21] Support iterator state pattern --- composer.json | 2 +- .../Utils/Collections/CollectionFactory.php | 35 +- .../Exception/UnsupportedException.php | 9 + .../Utils/Collections/IteratorCollection.php | 135 +++++++ src/WS/Utils/Collections/IteratorStream.php | 348 ++++++++++++++++++ src/WS/Utils/Collections/Stream.php | 8 +- .../Iterator/IteratorHandlingTest.php | 341 +++++++++++++++++ .../Iterator/StatePatternIterator.php | 63 ++++ .../Collections/Iterator/ValueKeeper.php | 8 + .../WS/Utils/Collections/SerialStreamTest.php | 12 +- 10 files changed, 948 insertions(+), 13 deletions(-) create mode 100644 src/WS/Utils/Collections/Exception/UnsupportedException.php create mode 100644 src/WS/Utils/Collections/IteratorCollection.php create mode 100644 src/WS/Utils/Collections/IteratorStream.php create mode 100644 tests/WS/Utils/Collections/Iterator/IteratorHandlingTest.php create mode 100644 tests/WS/Utils/Collections/Iterator/StatePatternIterator.php create mode 100644 tests/WS/Utils/Collections/Iterator/ValueKeeper.php diff --git a/composer.json b/composer.json index 6870327..5f2e011 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.1.1", + "version": "1.1.2", "authors": [ { "name": "Maxim Sokolovsky", diff --git a/src/WS/Utils/Collections/CollectionFactory.php b/src/WS/Utils/Collections/CollectionFactory.php index 19206cd..a4fb7e5 100644 --- a/src/WS/Utils/Collections/CollectionFactory.php +++ b/src/WS/Utils/Collections/CollectionFactory.php @@ -5,7 +5,10 @@ namespace WS\Utils\Collections; +use Iterator; +use IteratorAggregate; use RuntimeException; +use WS\Utils\Collections\Exception\UnsupportedException; class CollectionFactory { @@ -37,7 +40,7 @@ public static function generate(int $times, ?callable $generator = null): Collec /** * Generate collection of int numbers between $from and $to. If $to arg is absent $from - is count of numbers * @param int $from - * @param int $to + * @param int|null $to * @return Collection */ public static function numbers(int $from, ?int $to = null): Collection @@ -68,8 +71,21 @@ public static function fromStrict(array $values): Collection return new ArrayStrictList($values); } + /** + * @throws UnsupportedException + */ public static function fromIterable(iterable $iterable): Collection { + if (self::isStatePatternIterator($iterable)) { + if ($iterable instanceof IteratorAggregate) { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterable = $iterable->getIterator(); + } + if (!$iterable instanceof Iterator) { + throw new UnsupportedException('Only Iterator interface can be applied to IteratorCollection'); + } + return new IteratorCollection($iterable); + } $list = ArrayList::of(); foreach ($iterable as $item) { $list->add($item); @@ -82,4 +98,21 @@ public static function empty(): Collection { return ArrayList::of(); } + + private static function isStatePatternIterator(iterable $iterable): bool + { + $i = 2; + $lastItem = null; + foreach ($iterable as $item) { + if ($i === 0) { + break; + } + if (is_object($item) && $item === $lastItem) { + return true; + } + $lastItem = $item; + $i--; + } + return false; + } } diff --git a/src/WS/Utils/Collections/Exception/UnsupportedException.php b/src/WS/Utils/Collections/Exception/UnsupportedException.php new file mode 100644 index 0000000..19c866c --- /dev/null +++ b/src/WS/Utils/Collections/Exception/UnsupportedException.php @@ -0,0 +1,9 @@ +iterator = $iterator; + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function add($element): bool + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function addAll(iterable $elements): bool + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function merge(Collection $collection): bool + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function clear(): void + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function remove($element): bool + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function contains($element): bool + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function equals(Collection $collection): bool + { + throw new UnsupportedException(); + } + + public function size(): int + { + $this->iterator->rewind(); + $count = 0; + while ($this->iterator->valid()) { + $this->iterator->next(); + $count++; + } + + return $count; + } + + /** + * @codeCoverageIgnore + * @return bool + */ + public function isEmpty(): bool + { + return $this->size() === 0; + } + + public function stream(): Stream + { + return new IteratorStream($this); + } + + /** + * @codeCoverageIgnore + * @return array + */ + public function toArray(): array + { + throw new UnsupportedException(); + } + + /** + * @codeCoverageIgnore + * @return Collection + */ + public function copy(): Collection + { + throw new UnsupportedException(); + } + + public function getIterator() + { + return $this->iterator; + } +} diff --git a/src/WS/Utils/Collections/IteratorStream.php b/src/WS/Utils/Collections/IteratorStream.php new file mode 100644 index 0000000..065fe95 --- /dev/null +++ b/src/WS/Utils/Collections/IteratorStream.php @@ -0,0 +1,348 @@ +collection = $collection; + } + + public function each(callable $consumer): Stream + { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterator = $this->collection->getIterator(); + $iterator->rewind(); + $i = 0; + while ($iterator->valid()) { + if (!$this->isExcluded($i)) { + $item = $iterator->current(); + $consumer($item); + } + $iterator->next(); + $i++; + } + + return $this; + } + + public function walk(callable $consumer, ?int $limit = null): Stream + { + $iterationsCount = $limit ?? $this->collection->size(); + /** @noinspection PhpUnhandledExceptionInspection */ + $iterator = $this->collection->getIterator(); + $iterator->rewind(); + + $i = 0; + while ($iterator->valid()) { + if (!$this->isExcluded($i)) { + $item = $iterator->current(); + $consumerRes = $consumer($item, $i); + if ($consumerRes === false) { + break; + } + if ($i +1 >= $iterationsCount) { + break; + } + } + + $iterator->next(); + $i++; + } + return $this; + } + + public function filter(callable $predicate): Stream + { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterator = $this->collection->getIterator(); + $iterator->rewind(); + $i = 0; + while ($iterator->valid()) { + if (!$this->isExcluded($i)){ + $item = $iterator->current(); + !$predicate($item) && $this->exclude($i); + } + $iterator->next(); + $i++; + } + + return $this; + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function reorganize(callable $reorganizer): Stream + { + throw new UnsupportedException(); + } + + public function allMatch(callable $predicate): bool + { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterator = $this->collection->getIterator(); + $iterator->rewind(); + $i = 0; + while ($iterator->valid()) { + if (!$this->isExcluded($i)) { + $current = $iterator->current(); + if (!$predicate($current)) { + return false; + } + } + $iterator->next(); + $i++; + } + return true; + } + + public function anyMatch(callable $predicate): bool + { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterator = $this->collection->getIterator(); + $iterator->rewind(); + $i = 0; + while ($iterator->valid()) { + if (!$this->isExcluded($i)) { + $current = $iterator->current(); + if ($predicate($current)) { + return true; + } + } + $iterator->next(); + $i++; + } + return false; + } + + /** + * @throws UnsupportedException + */ + public function map(callable $converter): Stream + { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterator = $this->collection->getIterator(); + $iterator->rewind(); + $i = 0; + $list = new ArrayList(); + while ($iterator->valid()) { + if (!$this->isExcluded($i)) { + $item = $iterator->current(); + $converterRes = $converter($item); + if ($converterRes === $item) { + throw new UnsupportedException('Item must be another different from sourced'); + } + $list->add($converterRes); + } + $iterator->next(); + $i++; + } + + return new SerialStream($list); + } + + /** + * @codeCoverageIgnore + */ + public function collect(callable $collector) + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function findAny() + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function findFirst(callable $filter = null) + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function findLast() + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function min(callable $comparator) + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function max(callable $comparator) + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function sort(callable $comparator): Stream + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function sortBy(callable $extractor): Stream + { + throw new UnsupportedException(); + } + + /** + * @codeCoverageIgnore + */ + public function sortDesc(callable $comparator): Stream + { + throw new UnsupportedException(); + } + + /** + * @codeCoverageIgnore + */ + public function sortByDesc(callable $extractor): Stream + { + throw new UnsupportedException(); + } + + /** + * @codeCoverageIgnore + */ + public function reverse(): Stream + { + throw new UnsupportedException(); + } + + public function reduce(callable $accumulator, $initialValue = null) + { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterator = $this->collection->getIterator(); + $iterator->rewind(); + $i = 0; + $accumulate = $initialValue; + while ($iterator->valid()) { + if (!$this->isExcluded($i)) { + $accumulate = $accumulator($iterator->current(), $accumulate); + } + + $i++; + $iterator->next(); + } + + return $accumulate; + } + + public function limit(int $size): Stream + { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterator = $this->collection->getIterator(); + $iterator->rewind(); + $i = 0; + $countdown = $size; + while ($iterator->valid()) { + if (!$this->isExcluded($i)) { + if ($countdown <= 0) { + $this->exclude($i); + } + $countdown--; + } + $iterator->next(); + $i++; + } + + return $this; + } + + public function when(bool $condition): Stream + { + if (!$condition) { + return new DummyStreamDecorator($this); + } + + return $this; + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function getCollection(): Collection + { + throw new UnsupportedException(); + } + + public function always(): Stream + { + return $this; + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function toArray(): array + { + throw new UnsupportedException(); + } + + /** + * @throws UnsupportedException + * @codeCoverageIgnore + */ + public function getSet(): Set + { + throw new UnsupportedException(); + } + + /** + * @param int $index + * @return void + */ + private function exclude(int $index): void + { + $this->excluded[$index] = $index; + } + + private function isExcluded(int $index): bool + { + return isset($this->excluded[$index]); + } +} diff --git a/src/WS/Utils/Collections/Stream.php b/src/WS/Utils/Collections/Stream.php index 3248840..e15bad6 100644 --- a/src/WS/Utils/Collections/Stream.php +++ b/src/WS/Utils/Collections/Stream.php @@ -8,14 +8,14 @@ interface Stream { /** - * Call function for each element in collection + * Calls function for each element in collection * @param callable $consumer Function with f(mixed $element, int $index): void interface * @return Stream */ public function each(callable $consumer): Stream; /** - * Call function for $limit element in collection. If limit is null all elements will. If consumer will return false walk stop + * Call function for $limit element in collection. If limit is null all elements will. If consumer return false walk stop * @param callable $consumer Function with f(mixed $element, int $index): ?false|mixed interface. * @param int|null $limit * @return Stream @@ -57,7 +57,7 @@ public function anyMatch(callable $predicate): bool; public function map(callable $converter): Stream; /** - * Call collector function for collection. It is terminate function + * Call collector function for collection. It is terminated function * @param callable $collector Function f(Collection $c): mixed * @return mixed */ @@ -130,7 +130,7 @@ public function reverse(): Stream; /** * Reduce collection to single value with accumulator - * @param callable $accumulator + * @param callable $accumulator * @param mixed|null $initialValue * @return mixed */ diff --git a/tests/WS/Utils/Collections/Iterator/IteratorHandlingTest.php b/tests/WS/Utils/Collections/Iterator/IteratorHandlingTest.php new file mode 100644 index 0000000..15fa58d --- /dev/null +++ b/tests/WS/Utils/Collections/Iterator/IteratorHandlingTest.php @@ -0,0 +1,341 @@ +stream() + ->each(static function ($i) use ($intGenerator) { + self::assertEquals($intGenerator()->getValue(), $i); + }) + ; + } + + /** + * @test + */ + public function checkPhpDirectoryIterator() + { + $iterable = new DirectoryIterator(__DIR__); + $files = CollectionFactory::fromIterable($iterable) + ->stream() + ->map(static function (DirectoryIterator $current) { + return $current->getBasename(); + }) + ->filter(Predicates::lockDuplicated()) + ->toArray() + ; + self::assertTrue(count($files) > 3); + } + + /** + * @test + */ + public function checkCustomIterator() + { + $iterable = new StatePatternIterator(5); + $differenceCount = CollectionFactory::fromIterable($iterable) + ->stream() + ->map(static function (ValueKeeper $valueKeeper) { + return $valueKeeper->getValue(); + }) + ->filter(Predicates::lockDuplicated()) + ->getCollection() + ->size() + ; + self::assertEquals(5, $differenceCount); + } + + /** + * @test + */ + public function checkStateIteratorFilter() + { + $iterable = new StatePatternIterator(6); + $result = CollectionFactory::fromIterable($iterable) + ->stream() + ->filter(static function (ValueKeeper $valueKeeper) { + return $valueKeeper->getValue() <= 3; + }) + ->map(static function (ValueKeeper $valueKeeper) { + return $valueKeeper->getValue(); + }) + ->toArray() + ; + self::assertEquals([0, 1, 2, 3], $result); + } + + /** + * @test + */ + public function checkSizeCutting() + { + $iterable = new StatePatternIterator(6); + $result = CollectionFactory::fromIterable($iterable) + ->stream() + ->filter(static function (ValueKeeper $valueKeeper) { + return $valueKeeper->getValue() > 0; + }) + ->limit(2) + ->map(static function (ValueKeeper $valueKeeper) { + return $valueKeeper->getValue(); + }) + ->toArray() + ; + self::assertEquals([1, 2], $result); + } + + /** + * @test + */ + public function checkRightSizeInStateIterator() + { + $iterable = new StatePatternIterator(3); + $size = CollectionFactory::fromIterable($iterable)->size(); + self::assertEquals(3, $size); + } + + /** + * @test + */ + public function checkEmptyIterator() + { + $iterable = new StatePatternIterator(0); + self::assertTrue(CollectionFactory::fromIterable($iterable)->isEmpty()); + } + + /** + * @test + */ + public function checkEachBehavior() + { + $iterable = new StatePatternIterator(6); + $i = 1; + CollectionFactory::fromIterable($iterable) + ->stream() + ->filter(static function (ValueKeeper $valueKeeper) { + return $valueKeeper->getValue() > 0; + }) + ->limit(4) + ->each(static function (ValueKeeper $valueKeeper) use (& $i) { + self::assertEquals($i++, $valueKeeper->getValue()); + }) + ; + } + + /** + * @test + */ + public function checkWalkingByStateIterator() + { + $iterable = new StatePatternIterator(6); + $i = 2; + $result = CollectionFactory::fromIterable($iterable) + ->stream() + ->filter(static function (ValueKeeper $keeper) { + return $keeper->getValue() > 1; + }) + ->walk(static function (ValueKeeper $keeper) use (& $i) { + self::assertTrue($keeper->getValue() === $i); + $i++; + }, 2) + ->map(static function (ValueKeeper $keeper) { + return $keeper->getValue(); + }) + ->toArray() + ; + self::assertEquals([2, 3, 4, 5], $result); + } + + /** + * @test + */ + public function checkWalkingWithStopping() + { + $iterable = new StatePatternIterator(6); + $i = 2; + $result = CollectionFactory::fromIterable($iterable) + ->stream() + ->walk(static function () use (& $i) { + if ($i <= 0) { + return false; + } + $i--; + return true; + }) + ->map(static function (ValueKeeper $keeper) { + return $keeper->getValue(); + }) + ->getCollection() + ; + self::assertEquals(6, $result->size()); + self::assertEquals(0, $i); + } + + /** + * @test + */ + public function withTwoElementsChecking() + { + $iterable = new StatePatternIterator(2); + $collection = CollectionFactory::fromIterable($iterable); + + self::assertInstanceOf(IteratorCollection::class, $collection); + self::assertInstanceOf(IteratorStream::class, $collection->stream()); + } + + /** + * @test + */ + public function checkReduceMethod() + { + $iterator = new StatePatternIterator(5); + $sumOfThree = CollectionFactory::fromIterable($iterator) + ->stream() + ->filter(static function (ValueKeeper $keeper) { + return $keeper->getValue() > 1; + }) + ->reduce(static function (ValueKeeper $keeper, $sum) { + return $sum + $keeper->getValue(); + }, 0) + ; + self::assertEquals(9, $sumOfThree); + } + + /** + * @test + */ + public function allMatchIteratorChecking() + { + $iterator = new StatePatternIterator(5); + $collection = CollectionFactory::fromIterable($iterator); + + $everythingIsInt = $collection + ->stream() + ->allMatch(static function (ValueKeeper $keeper) { + return is_int($keeper->getValue()); + }) + ; + + $greatThanTwoPredicate = static function (ValueKeeper $keeper) { + return $keeper->getValue() > 2; + }; + $everythingIsGreatThanTwo = $collection + ->stream() + ->allMatch($greatThanTwoPredicate) + ; + + $everythingIsGreatThanTwoWithFilter = $collection + ->stream() + ->filter($greatThanTwoPredicate) + ->allMatch($greatThanTwoPredicate) + ; + + self::assertTrue($everythingIsInt); + self::assertFalse($everythingIsGreatThanTwo); + self::assertTrue($everythingIsGreatThanTwoWithFilter); + } + + /** + * @test + */ + public function anyMatchIteratorChecking() + { + $greatThanTwoPredicate = static function (ValueKeeper $keeper) { + return $keeper->getValue() > 2; + }; + $greatThanTenPredicate = static function (ValueKeeper $keeper) { + return $keeper->getValue() > 10; + }; + + $iterator = new StatePatternIterator(5); + $collection = CollectionFactory::fromIterable($iterator); + + $hasElementsWithGreatThanWho = $collection + ->stream() + ->anyMatch($greatThanTwoPredicate) + ; + + $hasElementsWithGreatThanTen = $collection + ->stream() + ->anyMatch($greatThanTenPredicate) + ; + + $hasLessThanTwoFiltered = $collection + ->stream() + ->filter($greatThanTwoPredicate) + ->anyMatch(static function (ValueKeeper $keeper) { + return $keeper->getValue() <= 2; + }) + ; + + self::assertTrue($hasElementsWithGreatThanWho); + self::assertFalse($hasElementsWithGreatThanTen); + self::assertFalse($hasLessThanTwoFiltered); + } + + /** + * @test + */ + public function cunningMapChecking() + { + $iterator = new StatePatternIterator(3); + self::expectException(UnsupportedException::class); + CollectionFactory::fromIterable($iterator) + ->stream() + ->map(static function ($item) { + return $item; + }) + ->getCollection() + ; + } + + /** + * @test + */ + public function iterableStreamFlowChecking() + { + $greatThanTwoPredicate = static function (ValueKeeper $keeper) { + return $keeper->getValue() > 2; + }; + + $iterator = new StatePatternIterator(5); + + $array = CollectionFactory::fromIterable($iterator) + ->stream() + ->when(false) + ->filter($greatThanTwoPredicate) + ->always() + ->map(static function (ValueKeeper $keeper) { + return $keeper->getValue(); + }) + ->toArray() + ; + self::assertCount(5, $array); + + $stream = CollectionFactory::fromIterable($iterator) + ->stream() + ->when(true) + ->always() + ; + self::assertInstanceOf(IteratorStream::class, $stream); + } +} + diff --git a/tests/WS/Utils/Collections/Iterator/StatePatternIterator.php b/tests/WS/Utils/Collections/Iterator/StatePatternIterator.php new file mode 100644 index 0000000..c02f65c --- /dev/null +++ b/tests/WS/Utils/Collections/Iterator/StatePatternIterator.php @@ -0,0 +1,63 @@ +count = $count; + } + + public function getIterator() + { + return new class($this->count) implements Iterator, ValueKeeper { + private $current; + private $count; + + public function __construct(int $count) + { + $this->count = $count; + $this->rewind(); + } + + public function current() + { + return $this; + } + + public function next() + { + $this->current++; + } + + public function key() + { + return $this->current; + } + + public function valid(): bool + { + return $this->current < $this->count; + } + + public function rewind() + { + $this->current = 0; + } + + public function getValue() + { + return $this->current; + } + }; + } +} diff --git a/tests/WS/Utils/Collections/Iterator/ValueKeeper.php b/tests/WS/Utils/Collections/Iterator/ValueKeeper.php new file mode 100644 index 0000000..a2b8964 --- /dev/null +++ b/tests/WS/Utils/Collections/Iterator/ValueKeeper.php @@ -0,0 +1,8 @@ +assertEquals($expected, $actual); } - /** @noinspection PhpUnusedParameterInspection */ /** * @dataProvider firstLastElementCases * @test @@ -565,7 +560,7 @@ public function limitedWalkCheck(): void */ public function suspendedWalkCheck(): void { - CollectionFactory::numbers(10) + $collection = CollectionFactory::numbers(10) ->stream() ->walk(function ($i) { if ($i === 2) { @@ -575,9 +570,12 @@ public function suspendedWalkCheck(): void $this->fail('El this index > 2 should not be here'); } return null; - }); + }) + ->getCollection() + ; $this->assertTrue(true); + $this->assertEquals(10, $collection->size()); } /** From b7a6dde5f632e4a703547311538a487f843227c7 Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Mon, 20 Dec 2021 10:33:22 +0300 Subject: [PATCH 13/21] Code formatting. --- src/WS/Utils/Collections/IteratorStream.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WS/Utils/Collections/IteratorStream.php b/src/WS/Utils/Collections/IteratorStream.php index 065fe95..4c02115 100644 --- a/src/WS/Utils/Collections/IteratorStream.php +++ b/src/WS/Utils/Collections/IteratorStream.php @@ -54,7 +54,7 @@ public function walk(callable $consumer, ?int $limit = null): Stream if ($consumerRes === false) { break; } - if ($i +1 >= $iterationsCount) { + if ($i + 1 >= $iterationsCount) { break; } } @@ -72,7 +72,7 @@ public function filter(callable $predicate): Stream $iterator->rewind(); $i = 0; while ($iterator->valid()) { - if (!$this->isExcluded($i)){ + if (!$this->isExcluded($i)) { $item = $iterator->current(); !$predicate($item) && $this->exclude($i); } From e0eb808e0bb324855e29561dd5f082ac41957d08 Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Mon, 20 Dec 2021 10:37:40 +0300 Subject: [PATCH 14/21] Php 8.0. Increase package version. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5f2e011..6870327 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.1.2", + "version": "1.1.1", "authors": [ { "name": "Maxim Sokolovsky", From c9b355f11acd6d297a5d4753af6ed82edc3b543d Mon Sep 17 00:00:00 2001 From: Anton Lytkin Date: Mon, 20 Dec 2021 10:40:09 +0300 Subject: [PATCH 15/21] Php 8.0. Increase package version. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6870327..5f2e011 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.1.1", + "version": "1.1.2", "authors": [ { "name": "Maxim Sokolovsky", From 60a0af24c2258be8ada7e74d3838488797ac89a6 Mon Sep 17 00:00:00 2001 From: Maxim Sokolovsky Date: Wed, 22 Dec 2021 16:17:43 +0300 Subject: [PATCH 16/21] Fix. Iterator must have rewind method to used as state pattern. --- composer.json | 2 +- src/WS/Utils/Collections/CollectionFactory.php | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 5f2e011..983aa14 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.1.2", + "version": "1.1.3", "authors": [ { "name": "Maxim Sokolovsky", diff --git a/src/WS/Utils/Collections/CollectionFactory.php b/src/WS/Utils/Collections/CollectionFactory.php index a4fb7e5..794938c 100644 --- a/src/WS/Utils/Collections/CollectionFactory.php +++ b/src/WS/Utils/Collections/CollectionFactory.php @@ -101,13 +101,16 @@ public static function empty(): Collection private static function isStatePatternIterator(iterable $iterable): bool { + if (!is_object($iterable)) { + return false; + } $i = 2; $lastItem = null; foreach ($iterable as $item) { if ($i === 0) { break; } - if (is_object($item) && $item === $lastItem) { + if (is_object($item) && $item === $lastItem && method_exists($item, 'rewind')) { return true; } $lastItem = $item; From 30dd666e2906faa4c4b34d859c2b37cfe7f9a0a5 Mon Sep 17 00:00:00 2001 From: Maxim Sokolovsky Date: Wed, 22 Dec 2021 16:25:29 +0300 Subject: [PATCH 17/21] Fix. Iterator must have rewind method to used as state pattern. Second attempt --- composer.json | 2 +- src/WS/Utils/Collections/CollectionFactory.php | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 983aa14..255809b 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.1.3", + "version": "1.1.4", "authors": [ { "name": "Maxim Sokolovsky", diff --git a/src/WS/Utils/Collections/CollectionFactory.php b/src/WS/Utils/Collections/CollectionFactory.php index 794938c..bace3ff 100644 --- a/src/WS/Utils/Collections/CollectionFactory.php +++ b/src/WS/Utils/Collections/CollectionFactory.php @@ -76,11 +76,11 @@ public static function fromStrict(array $values): Collection */ public static function fromIterable(iterable $iterable): Collection { + if ($iterable instanceof IteratorAggregate) { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterable = $iterable->getIterator(); + } if (self::isStatePatternIterator($iterable)) { - if ($iterable instanceof IteratorAggregate) { - /** @noinspection PhpUnhandledExceptionInspection */ - $iterable = $iterable->getIterator(); - } if (!$iterable instanceof Iterator) { throw new UnsupportedException('Only Iterator interface can be applied to IteratorCollection'); } @@ -104,13 +104,16 @@ private static function isStatePatternIterator(iterable $iterable): bool if (!is_object($iterable)) { return false; } + if (!method_exists($iterable, 'rewind')) { + return false; + } $i = 2; $lastItem = null; foreach ($iterable as $item) { if ($i === 0) { break; } - if (is_object($item) && $item === $lastItem && method_exists($item, 'rewind')) { + if (is_object($item) && $item === $lastItem) { return true; } $lastItem = $item; From 418e3739b0e6e78bb7cd3247cc43d3f6f8f28c12 Mon Sep 17 00:00:00 2001 From: Maxim Sokolovsky Date: Wed, 22 Dec 2021 16:46:13 +0300 Subject: [PATCH 18/21] Who attempts of run rewind methods is not allowed for everything iterators. Fixed. --- composer.json | 2 +- .../Utils/Collections/CollectionFactory.php | 46 ++++++++----------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index 255809b..7cc4517 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.1.4", + "version": "1.1.5", "authors": [ { "name": "Maxim Sokolovsky", diff --git a/src/WS/Utils/Collections/CollectionFactory.php b/src/WS/Utils/Collections/CollectionFactory.php index bace3ff..de019ad 100644 --- a/src/WS/Utils/Collections/CollectionFactory.php +++ b/src/WS/Utils/Collections/CollectionFactory.php @@ -76,19 +76,20 @@ public static function fromStrict(array $values): Collection */ public static function fromIterable(iterable $iterable): Collection { - if ($iterable instanceof IteratorAggregate) { - /** @noinspection PhpUnhandledExceptionInspection */ - $iterable = $iterable->getIterator(); - } - if (self::isStatePatternIterator($iterable)) { - if (!$iterable instanceof Iterator) { - throw new UnsupportedException('Only Iterator interface can be applied to IteratorCollection'); - } - return new IteratorCollection($iterable); - } $list = ArrayList::of(); + $count = 0; + $lastItem = null; + foreach ($iterable as $item) { + if ($count <= 1) { + $isObject = is_object($item); + if ($item === $lastItem && $isObject) { + return self::createIterableCollection($iterable); + } + $lastItem = $item; + } $list->add($item); + $count++; } return $list; @@ -99,26 +100,15 @@ public static function empty(): Collection return ArrayList::of(); } - private static function isStatePatternIterator(iterable $iterable): bool + private static function createIterableCollection(iterable $iterable): IteratorCollection { - if (!is_object($iterable)) { - return false; - } - if (!method_exists($iterable, 'rewind')) { - return false; + if ($iterable instanceof IteratorAggregate) { + /** @noinspection PhpUnhandledExceptionInspection */ + $iterable = $iterable->getIterator(); } - $i = 2; - $lastItem = null; - foreach ($iterable as $item) { - if ($i === 0) { - break; - } - if (is_object($item) && $item === $lastItem) { - return true; - } - $lastItem = $item; - $i--; + if (!$iterable instanceof Iterator) { + throw new UnsupportedException('Only Iterator interface can be applied to IteratorCollection'); } - return false; + return new IteratorCollection($iterable); } } From 74c40d5110d6accd57306840d1872a5884412ab3 Mon Sep 17 00:00:00 2001 From: Maxim Sokolovsky Date: Sat, 5 Mar 2022 15:21:04 +0300 Subject: [PATCH 19/21] Fix chunk with tail elements --- .../Collections/Functions/Reorganizers.php | 25 ++++++------------- .../Collections/ReorganizersFunctionsTest.php | 17 +++++++++++++ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/WS/Utils/Collections/Functions/Reorganizers.php b/src/WS/Utils/Collections/Functions/Reorganizers.php index 38e622d..ae00f71 100644 --- a/src/WS/Utils/Collections/Functions/Reorganizers.php +++ b/src/WS/Utils/Collections/Functions/Reorganizers.php @@ -90,23 +90,14 @@ public static function random(int $count = 1): Closure public static function chunk(int $size): Closure { return static function (Collection $collection) use ($size): Collection { - $chunkCollection = self::collectionConstructor(); - $currentChunk = self::collectionConstructor(); - $pointer = $size; - $collection - ->stream() - ->each(static function ($el) use ($size, $chunkCollection, & $currentChunk, & $pointer) { - $pointer--; - $currentChunk->add($el); - - if ($pointer === 0) { - $chunkCollection->add($currentChunk); - $currentChunk = self::collectionConstructor(); - $pointer = $size; - } - }) - ; - return $chunkCollection; + $array = $collection->toArray(); + $chunkedArray = array_chunk($array, $size); + $result = self::collectionConstructor(); + foreach ($chunkedArray as $items) { + $result->add(self::collectionConstructor($items)); + }; + + return $result; }; } diff --git a/tests/WS/Utils/Collections/ReorganizersFunctionsTest.php b/tests/WS/Utils/Collections/ReorganizersFunctionsTest.php index f72f243..fe61557 100644 --- a/tests/WS/Utils/Collections/ReorganizersFunctionsTest.php +++ b/tests/WS/Utils/Collections/ReorganizersFunctionsTest.php @@ -33,6 +33,23 @@ public function chunking(): void $this->assertThat($chunkedCollection->stream()->findLast(), CollectionIsEqual::to([5, 6])); } + /** + * @test + * @return void + */ + public function chunkingWhitTail(): void + { + $collection = self::toCollection(1, 2, 3, 4, 5); + $chunkedCollection = $collection + ->stream() + ->reorganize(Reorganizers::chunk(2)) + ->getCollection() + ; + $this->assertEquals(3, $chunkedCollection->size()); + $this->assertThat($chunkedCollection->stream()->findFirst(), CollectionIsEqual::to([1, 2])); + $this->assertThat($chunkedCollection->stream()->findLast(), CollectionIsEqual::to([5])); + } + /** * @test */ From b7c9ac5de65e43c53ba5a7068dd06169ce900ee1 Mon Sep 17 00:00:00 2001 From: Maxim Sokolovsky Date: Sat, 5 Mar 2022 15:23:57 +0300 Subject: [PATCH 20/21] Increase version after fixing --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7cc4517..83580e3 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.1.5", + "version": "1.1.6", "authors": [ { "name": "Maxim Sokolovsky", From 0f2db144fd2da675111e26a2752062096dcc3cc7 Mon Sep 17 00:00:00 2001 From: "M.Sokolovsky" Date: Thu, 20 Oct 2022 17:43:53 +0300 Subject: [PATCH 21/21] Fix. Sorting with extraction function inside --- composer.json | 2 +- src/WS/Utils/Collections/SerialStream.php | 4 +++- tests/WS/Utils/Collections/SerialStreamTest.php | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 83580e3..a899383 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Collections library for php language", "minimum-stability": "dev", "license": "MIT", - "version": "1.1.6", + "version": "1.1.7", "authors": [ { "name": "Maxim Sokolovsky", diff --git a/src/WS/Utils/Collections/SerialStream.php b/src/WS/Utils/Collections/SerialStream.php index 7f272f9..2bc429a 100644 --- a/src/WS/Utils/Collections/SerialStream.php +++ b/src/WS/Utils/Collections/SerialStream.php @@ -137,8 +137,10 @@ public function sortBy(callable $extractor): Stream sort($values); $newList = $this->emptyList(); foreach ($values as $value) { - $els = $map[$value.''] ?? []; + $key = $value.''; + $els = $map[$key] ?? []; $newList->addAll($els); + unset($map[$key]); } $this->list = $newList; diff --git a/tests/WS/Utils/Collections/SerialStreamTest.php b/tests/WS/Utils/Collections/SerialStreamTest.php index e6f7784..340dfa4 100644 --- a/tests/WS/Utils/Collections/SerialStreamTest.php +++ b/tests/WS/Utils/Collections/SerialStreamTest.php @@ -480,6 +480,7 @@ public function sortingWithExtractor($input, $min, $max): void $this->assertEquals($min, $actualMin->getValue()); $this->assertEquals($max, $actualMax->getValue()); + $this->assertEquals(count($input), $sortedCollection->size()); } /**