From 158c82bccbde5081c6fcf54b7658875cca5e58d1 Mon Sep 17 00:00:00 2001 From: kakiuchi-shigenao Date: Mon, 4 Aug 2025 18:16:38 +0900 Subject: [PATCH] =?UTF-8?q?ArrayList,=20Map=20=E3=81=AB=20filterAs=20?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=82=92=E8=BF=BD=E5=8A=A0=EF=BC=88=E3=81=A4?= =?UTF-8?q?=E3=81=84=E3=81=A7=E3=81=AB=20ArrayList=20=E3=81=AB=20values=20?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=82=82=E5=AE=9F=E8=A3=85=EF=BC=89=20Fixes?= =?UTF-8?q?=20#52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Collection/ArrayList.php | 25 ++++ src/Collection/List/IArrayList.php | 15 +++ src/Collection/Map.php | 21 +++ src/Collection/Map/IMap.php | 9 ++ tests/Unit/Collection/ArrayListTest.php | 108 +++++++++++----- tests/Unit/Collection/MapTest.php | 163 ++++++++++++++---------- 6 files changed, 236 insertions(+), 105 deletions(-) diff --git a/src/Collection/ArrayList.php b/src/Collection/ArrayList.php index 8ef59dd..e5f223a 100644 --- a/src/Collection/ArrayList.php +++ b/src/Collection/ArrayList.php @@ -368,6 +368,31 @@ final public function filter(Closure $closure): self return new self(array_filter($this->elements, $closure, ARRAY_FILTER_USE_BOTH)); } + /** + * @template TFilterValue of TValue + * @param class-string $innerClass + * @return self + */ + #[Override] + final public function filterAs(string $innerClass): self + { + // @phpstan-ignore-next-line + return new self(array_filter( + $this->elements, + static fn ($value) => $value instanceof $innerClass, + ARRAY_FILTER_USE_BOTH, + )); + } + + /** + * @return self + */ + #[Override] + final public function values(): self + { + return new self(array_values($this->elements)); + } + #[Override] final public function filterStrict(Closure $closure): static { diff --git a/src/Collection/List/IArrayList.php b/src/Collection/List/IArrayList.php index 28d8fea..9c62dd4 100644 --- a/src/Collection/List/IArrayList.php +++ b/src/Collection/List/IArrayList.php @@ -146,6 +146,21 @@ public function mapStrict(Closure $closure): static; */ public function filter(Closure $closure): self; + /** + * 与えられたクラスのインスタンスのみを含むコレクションを作成する。 + * + * @template TFilterValue of TValue + * @param class-string $innerClass + * @return self + */ + public function filterAs(string $innerClass): self; + + /** + * キーが連続した整数にリセットされた新しいコレクションを作成する。 + * @return self + */ + public function values(): self; + /** * 与えられた真理判定に合格するすべての要素のコレクションを作成する。 * (strict version - 正確な型を保持) diff --git a/src/Collection/Map.php b/src/Collection/Map.php index 47ce378..6135dec 100644 --- a/src/Collection/Map.php +++ b/src/Collection/Map.php @@ -83,6 +83,7 @@ final public function offsetSet($offset, $value): void } /** + * @return TValue * @throws OutOfBoundsException */ #[Override] @@ -433,6 +434,26 @@ final public function filter(Closure $closure): static return new static($elements); } + /** + * @template TFilterValue of TValue + * @param class-string $innerClass + * @return static + */ + #[Override] + final public function filterAs(string $innerClass): static + { + /** @var array> */ + $elements = []; + + foreach ($this->elements as $index => $pair) { + if ($pair->value instanceof $innerClass) { + $elements[$index] = Pair::of($pair->key, $pair->value); + } + } + + return new static($elements); // @phpstan-ignore-line + } + #[Override] final public function reject(Closure $closure): static { diff --git a/src/Collection/Map/IMap.php b/src/Collection/Map/IMap.php index 953a21c..f8fcd13 100644 --- a/src/Collection/Map/IMap.php +++ b/src/Collection/Map/IMap.php @@ -155,6 +155,15 @@ public function mapStrict(Closure $closure): static; */ public function filter(Closure $closure): static; + /** + * 与えられたクラスのインスタンスのみを含むコレクションを作成する。 + * + * @template TFilterValue of TValue + * @param class-string $innerClass + * @return static + */ + public function filterAs(string $innerClass): static; + /** * 与えられた真理判定に合格しないすべての要素のコレクションを作成する。 * @param Closure(TValue,TKey): bool $closure diff --git a/tests/Unit/Collection/ArrayListTest.php b/tests/Unit/Collection/ArrayListTest.php index 8da63fc..64725a2 100644 --- a/tests/Unit/Collection/ArrayListTest.php +++ b/tests/Unit/Collection/ArrayListTest.php @@ -22,22 +22,35 @@ final class ArrayListTest extends TestCase { /** - * @return array}> + * @param array $elements */ - public static function 様々な要素のコレクションを提供(): array + #[Test] + #[DataProvider('様々な要素のコレクションを提供')] + public function from静的メソッドでインスタンスが作成できる(array $elements): void { - return [ - 'プリミティブ値の配列' => [[1, 2, 3, 4, 5]], - '文字列の配列' => [['apple', 'banana', 'cherry']], - '空の配列' => [[]], - '混合型の配列' => [[1, 'string', true, 3.14]], - ]; + $collection = ArrayList::from($elements); + + $this->assertInstanceOf(ArrayList::class, $collection); + $this->assertEquals($elements, $collection->toArray()); + } + + /** + * @param array $elements + */ + #[Test] + #[DataProvider('provide独自クラスのコレクションが作成できるCases')] + public function 独自クラスのコレクションが作成できる(array $elements): void + { + $collection = ArrayList::from($elements); + + $this->assertInstanceOf(ArrayList::class, $collection); + $this->assertEquals($elements, $collection->toArray()); } /** * @return array}> */ - public static function 独自クラスを含むコレクションを提供(): array + public static function provide独自クラスのコレクションが作成できるCases(): iterable { return [ 'StringValue配列' => [[ @@ -63,32 +76,6 @@ public static function 独自クラスを含むコレクションを提供(): ar ]; } - /** - * @param array $elements - */ - #[Test] - #[DataProvider('様々な要素のコレクションを提供')] - public function from静的メソッドでインスタンスが作成できる(array $elements): void - { - $collection = ArrayList::from($elements); - - $this->assertInstanceOf(ArrayList::class, $collection); - $this->assertEquals($elements, $collection->toArray()); - } - - /** - * @param array $elements - */ - #[Test] - #[DataProvider('独自クラスを含むコレクションを提供')] - public function 独自クラスのコレクションが作成できる(array $elements): void - { - $collection = ArrayList::from($elements); - - $this->assertInstanceOf(ArrayList::class, $collection); - $this->assertEquals($elements, $collection->toArray()); - } - #[Test] public function empty静的メソッドで空のコレクションが作成できる(): void { @@ -137,6 +124,19 @@ public function tryFrom静的メソッドで有効な配列から成功結果が $this->assertEquals($elements, $collection->toArray()); } + /** + * @return array}> + */ + public static function 様々な要素のコレクションを提供(): iterable + { + return [ + 'プリミティブ値の配列' => [[1, 2, 3, 4, 5]], + '文字列の配列' => [['apple', 'banana', 'cherry']], + '空の配列' => [[]], + '混合型の配列' => [[1, 'string', true, 3.14]], + ]; + } + #[Test] public function first関数で先頭要素が取得できる(): void { @@ -539,4 +539,42 @@ public function flatMap関数で各要素を変換して平坦化できる(): vo // 元のコレクションは変更されない(イミュータブル) $this->assertEquals([ArrayList::from([1, 2]), ArrayList::from([3, 4]), ArrayList::from([5, 6])], $collection5->toArray()); } + + #[Test] + public function filterAs関数で特定のクラスのインスタンスのみを含むコレクションが取得できる(): void + { + $collection = ArrayList::from([ + StringValue::from('apple'), + IntegerValue::from(10), + StringValue::from('banana'), + DecimalValue::from(new Number('2.5')), + ]); + + $filtered = $collection + ->filterAs(StringValue::class) + ->values(); + + // @phpstan-ignore-next-line + $this->assertContainsOnlyInstancesOf(StringValue::class, $filtered); + + $this->assertCount(2, $filtered); + $this->assertEquals('apple', $filtered[0]->value); + $this->assertEquals('banana', $filtered[1]->value); + } + + #[Test] + public function values関数でキーが連続した整数にリセットされた新しいコレクションが取得できる(): void + { + $collection = ArrayList::from([10, 20, 30]); + + $filteredCollection = $collection->filter(static fn ($x) => $x >= 20); + $this->assertCount(2, $filteredCollection); + $this->assertEquals($filteredCollection[1], 20); + $this->assertEquals($filteredCollection[2], 30); + + $valuesCollection = $filteredCollection->values(); + $this->assertCount(2, $valuesCollection); + $this->assertEquals($valuesCollection[0], 20); + $this->assertEquals($valuesCollection[1], 30); + } } diff --git a/tests/Unit/Collection/MapTest.php b/tests/Unit/Collection/MapTest.php index 240f564..14c3445 100644 --- a/tests/Unit/Collection/MapTest.php +++ b/tests/Unit/Collection/MapTest.php @@ -26,76 +26,6 @@ #[CoversClass(Map::class)] final class MapTest extends TestCase { - /** - * @return array}> - */ - public static function 様々な要素を持つPairの配列を提供(): array - { - return [ - 'プリミティブ値のPair' => [[ - Pair::of('key1', 1), - Pair::of('key2', 2), - Pair::of('key3', 3), - ]], - '文字列値のPair' => [[ - Pair::of('name1', 'Alice'), - Pair::of('name2', 'Bob'), - Pair::of('name3', 'Charlie'), - ]], - '数値キーのPair' => [[ - Pair::of(1, 'value1'), - Pair::of(2, 'value2'), - Pair::of(3, 'value3'), - ]], - '混合型のPair' => [[ - Pair::of('key1', 1), - Pair::of('key2', 'string'), - Pair::of('key3', true), - Pair::of('key4', 3.14), - ]], - '空の配列' => [[]], - ]; - } - - /** - * @return array}> - */ - public static function 独自クラスを含むPairの配列を提供(): array - { - return [ - 'StringValueキーのPair' => [[ - Pair::of(StringValue::from('key1'), 'value1'), - Pair::of(StringValue::from('key2'), 'value2'), - Pair::of(StringValue::from('key3'), 'value3'), - ]], - 'IntegerValueキーのPair' => [[ - Pair::of(IntegerValue::from(1), 'value1'), - Pair::of(IntegerValue::from(2), 'value2'), - Pair::of(IntegerValue::from(3), 'value3'), - ]], - 'StringValue値のPair' => [[ - Pair::of('key1', StringValue::from('value1')), - Pair::of('key2', StringValue::from('value2')), - Pair::of('key3', StringValue::from('value3')), - ]], - 'IntegerValue値のPair' => [[ - Pair::of('key1', IntegerValue::from(1)), - Pair::of('key2', IntegerValue::from(2)), - Pair::of('key3', IntegerValue::from(3)), - ]], - 'DecimalValue値のPair' => [[ - Pair::of('key1', DecimalValue::from(new Number('1.5'))), - Pair::of('key2', DecimalValue::from(new Number('2.5'))), - Pair::of('key3', DecimalValue::from(new Number('3.5'))), - ]], - '混合ValueObjectのPair' => [[ - Pair::of(StringValue::from('key1'), IntegerValue::from(1)), - Pair::of(IntegerValue::from(2), StringValue::from('value2')), - Pair::of(StringValue::from('key3'), DecimalValue::from(new Number('3.5'))), - ]], - ]; - } - /** * @param Pair[] $pairs */ @@ -199,6 +129,37 @@ public function tryFrom静的メソッドで有効なPair配列から成功結 $this->assertEquals($expected, $collection->toArray()); } + /** + * @return array}> + */ + public static function 様々な要素を持つPairの配列を提供(): iterable + { + return [ + 'プリミティブ値のPair' => [[ + Pair::of('key1', 1), + Pair::of('key2', 2), + Pair::of('key3', 3), + ]], + '文字列値のPair' => [[ + Pair::of('name1', 'Alice'), + Pair::of('name2', 'Bob'), + Pair::of('name3', 'Charlie'), + ]], + '数値キーのPair' => [[ + Pair::of(1, 'value1'), + Pair::of(2, 'value2'), + Pair::of(3, 'value3'), + ]], + '混合型のPair' => [[ + Pair::of('key1', 1), + Pair::of('key2', 'string'), + Pair::of('key3', true), + Pair::of('key4', 3.14), + ]], + '空の配列' => [[]], + ]; + } + /** * @param Pair[] $pairs */ @@ -227,6 +188,45 @@ public function tryFrom静的メソッドで独自クラスを含むPair配列 $this->assertEquals($expected, $collection->toArray()); } + /** + * @return array}> + */ + public static function 独自クラスを含むPairの配列を提供(): iterable + { + return [ + 'StringValueキーのPair' => [[ + Pair::of(StringValue::from('key1'), 'value1'), + Pair::of(StringValue::from('key2'), 'value2'), + Pair::of(StringValue::from('key3'), 'value3'), + ]], + 'IntegerValueキーのPair' => [[ + Pair::of(IntegerValue::from(1), 'value1'), + Pair::of(IntegerValue::from(2), 'value2'), + Pair::of(IntegerValue::from(3), 'value3'), + ]], + 'StringValue値のPair' => [[ + Pair::of('key1', StringValue::from('value1')), + Pair::of('key2', StringValue::from('value2')), + Pair::of('key3', StringValue::from('value3')), + ]], + 'IntegerValue値のPair' => [[ + Pair::of('key1', IntegerValue::from(1)), + Pair::of('key2', IntegerValue::from(2)), + Pair::of('key3', IntegerValue::from(3)), + ]], + 'DecimalValue値のPair' => [[ + Pair::of('key1', DecimalValue::from(new Number('1.5'))), + Pair::of('key2', DecimalValue::from(new Number('2.5'))), + Pair::of('key3', DecimalValue::from(new Number('3.5'))), + ]], + '混合ValueObjectのPair' => [[ + Pair::of(StringValue::from('key1'), IntegerValue::from(1)), + Pair::of(IntegerValue::from(2), StringValue::from('value2')), + Pair::of(StringValue::from('key3'), DecimalValue::from(new Number('3.5'))), + ]], + ]; + } + #[Test] public function put関数でキーと値を追加したコレクションが取得できる(): void { @@ -640,4 +640,27 @@ public function remove関数で指定したキーを削除したコレクショ // 元のコレクションは変更されない(イミュータブル) $this->assertEquals(['a' => 1, 'b' => 2, 'c' => 3], $collection->toArray()); } + + #[Test] + public function filterAs関数で特定のクラスのインスタンスのみを含むコレクションが取得できる(): void + { + $collection = Map::make([ + 'a' => StringValue::from('Hello'), + 'b' => IntegerValue::from(42), + 'c' => StringValue::from('World'), + ]); + + // StringValueのインスタンスのみをフィルタリング + $filteredAsString = $collection->filterAs(StringValue::class); + $this->assertContainsOnlyInstancesOf(StringValue::class, $filteredAsString); // @phpstan-ignore-line + $this->assertCount(2, $filteredAsString); + $this->assertEquals('Hello', $filteredAsString['a']->value); + $this->assertEquals('World', $filteredAsString['c']->value); + + // IntegerValueのインスタンスのみをフィルタリング + $filteredAsInt = $collection->filterAs(IntegerValue::class); + $this->assertContainsOnlyInstancesOf(IntegerValue::class, $filteredAsInt); // @phpstan-ignore-line + $this->assertCount(1, $filteredAsInt); + $this->assertEquals(42, $filteredAsInt['b']->value); + } }