diff --git a/src/OrderedList.php b/src/OrderedList.php index 4a2ad0d..7ae737a 100644 --- a/src/OrderedList.php +++ b/src/OrderedList.php @@ -5,10 +5,13 @@ use Webmozart\Assert\Assert; use function array_combine; +use function array_fill; use function array_keys; use function array_map; use function array_merge; +use function array_replace; use function array_values; +use function is_callable; use function serialize; use function sort; use const SORT_NATURAL; @@ -183,4 +186,48 @@ public function unify( return $instance; } + + public function fill(int $startIndex, int $amount, $value): OrderedListInterface + { + Assert::greaterThanEq($startIndex, 0, 'Given $startIndex must be greater than or equal to %2$s. Got: %s'); + Assert::greaterThanEq($amount, 1, 'Given $amount must be greater than or equal to %2$s. Got: %s'); + Assert::lessThanEq( + $startIndex, + $this->count(), + 'Give $startIndex must be less than or equal to %2$s to keep the list a continious list. Got: %s.' + ); + + $instance = clone $this; + + /** @psalm-var list $combined */ + $combined = array_replace( + $this->data, + $this->createListFilledWithValues($startIndex, $amount, $value) + ); + + $instance->data = $combined; + + return $instance; + } + + /** + * @psalm-param TValue|Closure(int $index):TValue $value + * + * @psalm-return array + */ + private function createListFilledWithValues(int $start, int $amount, $value): array + { + if (!is_callable($value)) { + /** @psalm-var array $list */ + $list = array_fill($start, $amount, $value); + return $list; + } + + $list = []; + for ($index = $start; $index <= $amount; $index++) { + $list[$index] = $value($index); + } + + return $list; + } } diff --git a/src/OrderedListInterface.php b/src/OrderedListInterface.php index 5548ec7..80cb2da 100644 --- a/src/OrderedListInterface.php +++ b/src/OrderedListInterface.php @@ -3,6 +3,8 @@ namespace Boesing\TypedArrays; +use InvalidArgumentException; + /** * @template TValue * @template-extends ArrayInterface @@ -91,4 +93,12 @@ public function unify( ?callable $unificationIdentifierGenerator = null, ?callable $callback = null ): OrderedListInterface; + + /** + * @psalm-param TValue|Closure(int $index):TValue $value + * @psalm-return OrderedListInterface + * @psalm-immutable + * @throws InvalidArgumentException if start index does is not fitting in the current list state. + */ + public function fill(int $startIndex, int $amount, $value): OrderedListInterface; } diff --git a/tests/GenericOrderedListTest.php b/tests/GenericOrderedListTest.php index 8a5c141..500a245 100644 --- a/tests/GenericOrderedListTest.php +++ b/tests/GenericOrderedListTest.php @@ -12,8 +12,10 @@ use PHPUnit\Framework\TestCase; use stdClass; use Webmozart\Assert\Assert; +use function array_fill; use function chr; use function md5; +use function mt_rand; use function spl_object_hash; use function strnatcmp; @@ -701,4 +703,135 @@ public function testToMapConversionErrorsOnIntegerishKeys(): void return (string) $value; }); } + + /** + * @template TValue + * @psalm-param list $initial + * @psalm-param TValue $fillUp + * @dataProvider invalidStartIndices + */ + public function testFillWillThrowExceptionWhenStartIndexIsInvalid( + int $startIndex, + array $initial, + $fillUp, + string $expectedExceptionMessage + ): void { + $list = new GenericOrderedList($initial); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + $list->fill($startIndex, mt_rand(1, 10), $fillUp); + } + + /** + * @template TValue + * @psalm-param TValue $value + * @dataProvider scalarFillValues + */ + public function testFillAppendsScalarValues(int $amount, $value): void + { + self::assertIsScalar($value); + /** @var OrderedListInterface $list */ + $list = new GenericOrderedList([]); + $list = $list->fill(0, $amount, $value); + self::assertEquals(array_fill(0, $amount, $value), $list->toNativeArray()); + } + + /** + * @template mixed + * @psalm-return Generator,2:mixed,3:non-empty-string}> + */ + public function invalidStartIndices(): Generator + { + yield 'negative' => [ + -1, + [], + 0, + 'Given $startIndex must be greater than or equal to', + ]; + + yield 'non continues index' => [ + 1, + [], + 0, + 'to keep the list a continious list.', + ]; + + yield 'non continues index #2' => [ + 10, + [0, 1, 2,], + 3, + 'to keep the list a continious list.', + ]; + } + + /** + * @psalm-return Generator + */ + public function scalarFillValues(): Generator + { + yield 'int' => [ + 1, + 0, + ]; + + yield 'string' => [ + 99, + 'foo', + ]; + + yield 'float' => [ + 10, + 0.1, + ]; + + yield 'true' => [ + 8, + true, + ]; + + yield 'false' => [ + 50, + false, + ]; + } + + public function testFillUsesCallbackToGenerateValue(): void + { + $callback = static function (int $index): string { + return chr($index + 65); + }; + + /** @var OrderedListInterface $list */ + $abc = new GenericOrderedList([]); + $abc = $abc->fill(0, 25, $callback); + + self::assertEquals([ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + ], $abc->toNativeArray()); + } }