diff --git a/src/Collection/ArrayList.php b/src/Collection/ArrayList.php index e5f223a..8b11844 100644 --- a/src/Collection/ArrayList.php +++ b/src/Collection/ArrayList.php @@ -15,6 +15,7 @@ use WizDevelop\PhpValueObject\Collection\Base\CollectionBase; use WizDevelop\PhpValueObject\Collection\Base\CollectionDefault; use WizDevelop\PhpValueObject\Collection\Base\CountableDefault; +use WizDevelop\PhpValueObject\Collection\Base\ICollection; use WizDevelop\PhpValueObject\Collection\Exception\CollectionNotFoundException; use WizDevelop\PhpValueObject\Collection\Exception\MultipleCollectionsFoundException; use WizDevelop\PhpValueObject\Collection\List\IArrayList; @@ -344,18 +345,72 @@ final public function mapStrict(Closure $closure): static */ #[Override] final public function flatMap(Closure $closure): self + { + return $this->map($closure)->collapse(); // @phpstan-ignore-line + } + + /** + * @return self + */ + #[Override] + final public function flatten(int $depth = PHP_INT_MAX): self + { + $elements = $this->elements; + $flattened = self::flattenInner($elements, $depth); + + return new self($flattened); // @phpstan-ignore-line + } + + /** + * 多次元配列を単一レベルに平坦化する。 + * + * @param iterable $array + * @return array + */ + private static function flattenInner(iterable $array, int $depth = PHP_INT_MAX): array { $result = []; - foreach ($this->elements as $index => $item) { - $mapped = $closure($item, $index); + foreach ($array as $item) { + $item = $item instanceof ICollection ? $item->toArray() : $item; - foreach ($mapped as $subItem) { - $result[] = $subItem; + if (! is_array($item)) { + $result[] = $item; + } else { + $values = $depth === 1 + ? array_values($item) + : self::flattenInner($item, $depth - 1); + + foreach ($values as $value) { + $result[] = $value; + } + } + } + + return $result; + } + + /** + * 配列の配列を 1 つの配列に折りたたむ。 + * + * @return self + */ + private function collapse(): self + { + $elements = $this->elements; + $results = []; + + foreach ($elements as $values) { + if ($values instanceof ICollection) { + $values = $values->toArray(); + } elseif (! is_array($values)) { + continue; } + + $results[] = $values; } - return new self($result); + return new self(array_values(array_merge([], ...$results))); } /** diff --git a/src/Collection/List/IArrayList.php b/src/Collection/List/IArrayList.php index 9c62dd4..b1bff3a 100644 --- a/src/Collection/List/IArrayList.php +++ b/src/Collection/List/IArrayList.php @@ -133,6 +133,13 @@ public function map(Closure $closure): self; */ public function flatMap(Closure $closure): self; + /** + * 多次元配列を単一レベルに平坦化する。 + * + * @return self + */ + public function flatten(int $depth = PHP_INT_MAX): self; + /** * @param Closure(TValue,int): TValue $closure * @return static diff --git a/tests/Unit/Collection/ArrayListTest.php b/tests/Unit/Collection/ArrayListTest.php index 64725a2..ff2bd6f 100644 --- a/tests/Unit/Collection/ArrayListTest.php +++ b/tests/Unit/Collection/ArrayListTest.php @@ -540,6 +540,49 @@ public function flatMap関数で各要素を変換して平坦化できる(): vo $this->assertEquals([ArrayList::from([1, 2]), ArrayList::from([3, 4]), ArrayList::from([5, 6])], $collection5->toArray()); } + #[Test] + public function flatMap関数で空配列を含む場合も正しく平坦化できる(): void + { + $collection = ArrayList::from([1, 2, 3]); + + // 空配列を返す場合 + $flatMapped = $collection->flatMap(static fn ($value) => $value % 2 === 0 ? [] : [$value * 2]); + + $this->assertInstanceOf(ArrayList::class, $flatMapped); + $this->assertEquals([2, 6], $flatMapped->toArray()); + + // 元のコレクションは変更されない(イミュータブル) + $this->assertEquals([1, 2, 3], $collection->toArray()); + } + + #[Test] + public function flatten関数でネストされたコレクションを平坦化できる(): void + { + $collection = ArrayList::from([[1, 2], [3, 4, [5, 6, [7]]]]); + + $flattened = $collection->flatten(); + + $this->assertInstanceOf(ArrayList::class, $flattened); + $this->assertEquals([1, 2, 3, 4, 5, 6, 7], $flattened->toArray()); + + // 元のコレクションは変更されない(イミュータブル) + $this->assertEquals([[1, 2], [3, 4, [5, 6, [7]]]], $collection->toArray()); + } + + #[Test] + public function flatten関数で空のコレクションも正しく平坦化できる(): void + { + $collection = ArrayList::from([[], [1, 2], [], [3, 4]]); + + $flattened = $collection->flatten(); + + $this->assertInstanceOf(ArrayList::class, $flattened); + $this->assertEquals([1, 2, 3, 4], $flattened->toArray()); + + // 元のコレクションは変更されない(イミュータブル) + $this->assertEquals([[], [1, 2], [], [3, 4]], $collection->toArray()); + } + #[Test] public function filterAs関数で特定のクラスのインスタンスのみを含むコレクションが取得できる(): void {