From d1f6deb47d90a57acd1774f3f51315ef941a946c Mon Sep 17 00:00:00 2001 From: Alexey Kopytko Date: Tue, 13 Jun 2023 11:51:40 +0900 Subject: [PATCH] Add skipWhile() (#130) Fixes #129 --- README.md | 1 + src/Standard.php | 29 +++++++++++ tests/Helper/RunningVarianceTest.php | 8 +-- tests/SkipWhileTest.php | 73 ++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 tests/SkipWhileTest.php diff --git a/README.md b/README.md index 36131e6..61fd732 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ All entry points always return an instance of the pipeline. | `unpack()` | Unpacks arrays into arguments for a callback. Flattens inputs if no callback provided. | | | `chunk()` | Chunks the pipeline into arrays of specified length. | `array_chunk` | | `filter()` | Removes elements unless a callback returns true. Removes falsey values if no callback provided. | `array_filter`, `Where` | +| `skipWhile()` | Skips elements while the predicate returns true, and keeps everything after the predicate return false just once. | | | `slice()` | Extracts a slice from the inputs. Keys are not discarded intentionally. Suppors negative values for both arguments. | `array_slice` | | `fold()` | Reduces input values to a single value. Defaults to summation. Requires an initial value. | `array_reduce`, `Aggregate`, `Sum` | | `reduce()` | Alias to `fold()` with a reversed order of arguments. | `array_reduce` | diff --git a/src/Standard.php b/src/Standard.php index 486cad1..c9d3a6a 100644 --- a/src/Standard.php +++ b/src/Standard.php @@ -446,6 +446,35 @@ private static function resolvePredicate(?callable $func): callable return $func; } + /** + * Skips elements while the predicate returns true, and keeps everything after the predicate return false just once. + * + * @param callable $predicate a callback returning boolean value + */ + public function skipWhile(callable $predicate): self + { + // No-op: an empty array or null. + if ($this->empty()) { + return $this; + } + + $predicate = self::resolvePredicate($predicate); + + $this->filter(static function ($value) use ($predicate): bool { + static $done = false; + + if ($predicate($value) && !$done) { + return false; + } + + $done = true; + + return true; + }); + + return $this; + } + /** * Reduces input values to a single value. Defaults to summation. This is a terminal operation. * diff --git a/tests/Helper/RunningVarianceTest.php b/tests/Helper/RunningVarianceTest.php index 42fa946..6aff20a 100644 --- a/tests/Helper/RunningVarianceTest.php +++ b/tests/Helper/RunningVarianceTest.php @@ -242,11 +242,7 @@ public function testMullerTransform(int $count, float $mean, float $sigma): void $numbers ), $sigma / 10); } catch (Throwable $e) { - if ($mean > 1E10) { - $this->markTestSkipped($e->getMessage()); - } - - throw $e; + $this->assertGreaterThan(1E10, $mean, "Naive standard deviation calculation failed where it should not: {$e->getMessage()}"); } } @@ -261,7 +257,7 @@ public function testMullerTransform(int $count, float $mean, float $sigma): void private static function getRandomNumbers(float $mean, float $sigma): iterable { $two_pi = 2 * M_PI; - $epsilon = 1E-6; // Arbitrary number + $epsilon = PHP_FLOAT_EPSILON; while (true) { do { diff --git a/tests/SkipWhileTest.php b/tests/SkipWhileTest.php new file mode 100644 index 0000000..6f6a500 --- /dev/null +++ b/tests/SkipWhileTest.php @@ -0,0 +1,73 @@ + + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare(strict_types=1); + +namespace Tests\Pipeline; + +use PHPUnit\Framework\TestCase; +use Pipeline\Standard; +use function Pipeline\map; +use function Pipeline\take; + +/** + * @covers \Pipeline\Standard + * + * @internal + */ +final class SkipWhileTest extends TestCase +{ + public function testSkipEmpty(): void + { + $pipeline = new Standard(); + + $result = $pipeline + ->skipWhile(fn ($number) => 1 === $number) + ->toArray(); + + $this->assertSame([], $result); + } + + public function testSkipNever(): void + { + $result = take([2]) + ->skipWhile(fn ($number) => 1 === $number) + ->toArray(); + + $this->assertSame([2], $result); + } + + public function testSkipWhileOnce(): void + { + $result = take([1, 1, 1, 2, 3, 4, 1, 2, 3]) + ->skipWhile(fn ($number) => 1 === $number) + ->toArray(); + + $this->assertSame([2, 3, 4, 1, 2, 3], $result); + } + + public function testSkipWhileTwice() + { + $result = map(fn () => yield from [1, 1, 1, 2, 2, 3, 4, 5, 1, 2]) + ->skipWhile(fn ($number) => 1 === $number) + ->skipWhile(fn ($number) => 2 === $number) + ->filter(fn ($number) => 1 === $number % 2) + ->toArray(); + + $this->assertSame([3, 5, 1], $result); + } +}