From 9a58ce215000e4265df0f7e633d8d1bb4ad1a8b7 Mon Sep 17 00:00:00 2001 From: kakiuchi-shigenao Date: Thu, 28 Aug 2025 15:53:16 +0900 Subject: [PATCH 1/2] =?UTF-8?q?ArrayList=20=E3=81=AB=20mapToDictionary=20?= =?UTF-8?q?=E3=81=A8=20mapToGroups=20=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=99=E3=82=8B=20Fixes=20#59?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Collection/ArrayList.php | 46 ++++++++++++++++++++++++++++++ src/Collection/List/IArrayList.php | 26 +++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/Collection/ArrayList.php b/src/Collection/ArrayList.php index 857bdeb..75323a6 100644 --- a/src/Collection/ArrayList.php +++ b/src/Collection/ArrayList.php @@ -496,6 +496,52 @@ final public function reduce(Closure $closure, $initial = null) return $carry; } + /** + * @template TMapToDictionaryKey of array-key + * @template TMapToDictionaryValue + * + * @param Closure(TValue, int): array $closure + * @return array> + */ + #[Override] + final public function mapToDictionary(Closure $closure): array + { + $dictionary = []; + + foreach ($this->elements as $key => $item) { + $pair = $closure($item, $key); + + /** @var TMapToDictionaryKey */ + $key = key($pair); + + /** @var TMapToDictionaryValue */ + $value = reset($pair); + + if (! isset($dictionary[$key])) { + $dictionary[$key] = []; + } + + $dictionary[$key][] = $value; + } + + return $dictionary; + } + + /** + * @template TMapToGroupsKey of array-key + * @template TMapToGroupsValue + * + * @param Closure(TValue, int): array $closure + * @return array> + */ + #[Override] + final public function mapToGroups(Closure $closure): array + { + $groups = $this->mapToDictionary($closure); + + return array_map(static fn ($items) => new static($items), $groups); + } + #[Override] final public function contains($key): bool { diff --git a/src/Collection/List/IArrayList.php b/src/Collection/List/IArrayList.php index b1bff3a..4c93354 100644 --- a/src/Collection/List/IArrayList.php +++ b/src/Collection/List/IArrayList.php @@ -198,6 +198,32 @@ public function unique(?Closure $closure = null): static; */ public function reduce(Closure $closure, $initial = null); + /** + * Run a dictionary map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapToDictionaryKey of array-key + * @template TMapToDictionaryValue + * + * @param Closure(TValue, int): array $closure + * @return array> + */ + public function mapToDictionary(Closure $closure): array; + + /** + * Run a grouping map over the items. + * + * The callback should return an associative array with a single key/value pair. + * + * @template TMapToGroupsKey of array-key + * @template TMapToGroupsValue + * + * @param Closure(TValue, int): array $closure + * @return array> + */ + public function mapToGroups(Closure $closure): array; + /** * コレクションに指定した要素が含まれているかどうかを判定する * @param (Closure(TValue,int): bool)|TValue $key From be6cae069a23bad04a30aaab31b46c1c2a9c446e Mon Sep 17 00:00:00 2001 From: kakiuchi-shigenao Date: Thu, 28 Aug 2025 16:20:58 +0900 Subject: [PATCH 2/2] =?UTF-8?q?ArrayList=20=E3=81=AB=20mapToDictionary=20?= =?UTF-8?q?=E3=81=A8=20mapToGroups=20=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Unit/Collection/ArrayListTest.php | 145 ++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/tests/Unit/Collection/ArrayListTest.php b/tests/Unit/Collection/ArrayListTest.php index ff2bd6f..0cebc45 100644 --- a/tests/Unit/Collection/ArrayListTest.php +++ b/tests/Unit/Collection/ArrayListTest.php @@ -620,4 +620,149 @@ public function values関数でキーが連続した整数にリセットされ $this->assertEquals($valuesCollection[0], 20); $this->assertEquals($valuesCollection[1], 30); } + + #[Test] + public function mapToDictionary関数で要素をキーごとにグループ化できる(): void + { + // 基本的なケース:文字列の長さでグループ化 + $collection = ArrayList::from(['one', 'two', 'three', 'four', 'five']); + $dictionary = $collection->mapToDictionary(static fn ($value) => [mb_strlen($value) => $value]); + + $this->assertArrayHasKey(3, $dictionary); + $this->assertArrayHasKey(4, $dictionary); + $this->assertArrayHasKey(5, $dictionary); + $this->assertEquals(['one', 'two'], $dictionary[3]); + $this->assertEquals(['four', 'five'], $dictionary[4]); + $this->assertEquals(['three'], $dictionary[5]); + + // 数値の偶数・奇数でグループ化 + $numbers = ArrayList::from([1, 2, 3, 4, 5, 6]); + $evenOddDict = $numbers->mapToDictionary(static fn ($value) => [$value % 2 === 0 ? 'even' : 'odd' => $value]); + + $this->assertArrayHasKey('odd', $evenOddDict); + $this->assertArrayHasKey('even', $evenOddDict); + $this->assertEquals([1, 3, 5], $evenOddDict['odd']); + $this->assertEquals([2, 4, 6], $evenOddDict['even']); + + // インデックスを使用した場合 + $indexed = ArrayList::from(['a', 'b', 'c']); + $indexedDict = $indexed->mapToDictionary(static fn ($value, $index) => [$index % 2 => $value]); + + $this->assertEquals(['a', 'c'], $indexedDict[0]); + $this->assertEquals(['b'], $indexedDict[1]); + } + + #[Test] + public function mapToDictionary関数で空のコレクションを処理できる(): void + { + $empty = ArrayList::empty(); + $dictionary = $empty->mapToDictionary(static fn ($value) => ['key' => $value]); + + $this->assertEmpty($dictionary); + } + + #[Test] + public function mapToDictionary関数でValueObjectを使用できる(): void + { + $collection = ArrayList::from([ + StringValue::from('apple'), + StringValue::from('banana'), + StringValue::from('avocado'), + StringValue::from('blueberry'), + ]); + + // 最初の文字でグループ化 + $dictionary = $collection->mapToDictionary( + static fn (StringValue $value) => [mb_substr($value->value, 0, 1) => $value] + ); + + $this->assertCount(2, $dictionary); + $this->assertArrayHasKey('a', $dictionary); + $this->assertArrayHasKey('b', $dictionary); + + $this->assertCount(2, $dictionary['a']); + $this->assertEquals('apple', $dictionary['a'][0]->value); + $this->assertEquals('avocado', $dictionary['a'][1]->value); + + $this->assertCount(2, $dictionary['b']); + $this->assertEquals('banana', $dictionary['b'][0]->value); + $this->assertEquals('blueberry', $dictionary['b'][1]->value); + } + + #[Test] + public function mapToGroups関数で要素をキーごとにArrayListのグループにできる(): void + { + // 基本的なケース:文字列の長さでグループ化 + $collection = ArrayList::from(['one', 'two', 'three', 'four', 'five']); + $groups = $collection->mapToGroups(static fn ($value) => [mb_strlen($value) => $value]); + + $this->assertArrayHasKey(3, $groups); + $this->assertArrayHasKey(4, $groups); + $this->assertArrayHasKey(5, $groups); + + // 各グループがArrayListインスタンスであることを確認 + $this->assertInstanceOf(ArrayList::class, $groups[3]); + $this->assertInstanceOf(ArrayList::class, $groups[4]); + $this->assertInstanceOf(ArrayList::class, $groups[5]); + + // グループの内容を確認 + $this->assertEquals(['one', 'two'], $groups[3]->toArray()); + $this->assertEquals(['four', 'five'], $groups[4]->toArray()); + $this->assertEquals(['three'], $groups[5]->toArray()); + + // 数値の偶数・奇数でグループ化 + $numbers = ArrayList::from([1, 2, 3, 4, 5, 6]); + $evenOddGroups = $numbers->mapToGroups(static fn ($value) => [$value % 2 === 0 ? 'even' : 'odd' => $value]); + + $this->assertInstanceOf(ArrayList::class, $evenOddGroups['odd']); + $this->assertInstanceOf(ArrayList::class, $evenOddGroups['even']); + $this->assertEquals([1, 3, 5], $evenOddGroups['odd']->toArray()); + $this->assertEquals([2, 4, 6], $evenOddGroups['even']->toArray()); + } + + #[Test] + public function mapToGroups関数で空のコレクションを処理できる(): void + { + $empty = ArrayList::empty(); + $groups = $empty->mapToGroups(static fn ($value) => ['key' => $value]); + + $this->assertEmpty($groups); + } + + #[Test] + public function mapToGroups関数でインデックスを使用できる(): void + { + $collection = ArrayList::from(['a', 'b', 'c', 'd']); + $groups = $collection->mapToGroups(static fn ($value, $index) => [$index % 2 === 0 ? 'even_index' : 'odd_index' => $value]); + + $this->assertInstanceOf(ArrayList::class, $groups['even_index']); + $this->assertInstanceOf(ArrayList::class, $groups['odd_index']); + + $this->assertEquals(['a', 'c'], $groups['even_index']->toArray()); + $this->assertEquals(['b', 'd'], $groups['odd_index']->toArray()); + } + + #[Test] + public function mapToGroups関数で作成されたグループはArrayListの全機能を使用できる(): void + { + $collection = ArrayList::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + $groups = $collection->mapToGroups(static fn ($value) => [$value % 3 => $value]); + + // 余り0のグループ + $this->assertInstanceOf(ArrayList::class, $groups[0]); + $remainder0 = $groups[0]; + $this->assertEquals([3, 6, 9], $remainder0->toArray()); + + // グループに対してArrayListのメソッドが使用できる + $doubled = $remainder0->map(static fn ($v) => $v * 2); + $this->assertEquals([6, 12, 18], $doubled->toArray()); + + $sum = $remainder0->reduce(static fn ($carry, $v) => $carry + $v, 0); + $this->assertEquals(18, $sum); + + // 余り1のグループ + $remainder1 = $groups[1]; + $filtered = $remainder1->filter(static fn ($v) => $v > 5); + $this->assertEquals([7, 10], $filtered->values()->toArray()); + } }