Skip to content

feat: flat_map と group_map を追加する - #86

Open
Javakky wants to merge 2 commits into
masterfrom
javakky/flat-map-group-map
Open

feat: flat_map と group_map を追加する#86
Javakky wants to merge 2 commits into
masterfrom
javakky/flat-map-group-map

Conversation

@Javakky

@Javakky Javakky commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

概要(What / Why)

callback の戻り配列を 1 段だけ平坦化する flat_map と、group_by しつつ各要素を変換する group_map を追加します。

変更点

  • src/flat_map.php — LIST は連番へ正規化、ASSOC は callback 戻りのキーを採用 (衝突は後勝ち)
  • src/group_map.phpgroup_by とキーの扱いを揃え、各要素を $transform で変換
  • tests/FlatMapTest.php (12 ケース) / tests/GroupMapTest.php (8 ケース)
  • composer.jsonautoload.files に 2 件追加

動作確認

  • 手動:
    • flat_map([1, 2], fn ($v) => [$v, $v * 10])[1, 10, 2, 20]
    • group_map(['alice' => 20, 'bob' => 17], fn ($age) => $age >= 20 ? 'adult' : 'minor', fn ($age) => $age * 12)['adult' => ['alice' => 240], 'minor' => ['bob' => 204]]
  • 自動:
    • vendor/bin/php-cs-fixer fix --dry-run --diff / vendor/bin/phpstan analyse -c phpstan.neon / vendor/bin/phpunit tests
    • 結果:OK (117 tests, 122 assertions) / PHPStan level 10 [OK] No errors / CS Fixer Fixed 0 of 33 files

補足(任意)

flat_map の平坦化は 1 段だけ

戻り配列の中の配列は展開しません (phpdoc に明記、テストで固定)。

MODE_ASSOC は外側キーを捨てる

入力側のキーではなく callback 戻り配列のキーを採用し、衝突は後勝ちです。テストでは値が後勝ちになることに加えてキーの位置が初出のまま残ることまで固定しています (PHP の上書きはキー位置を移動しないため)。

list 入力に MODE_ASSOC を明示すると callback 戻りの内側キーが全部 0 になって 1 件に潰れます。仕様からは正しいものの驚きやすい組み合わせなので、これもテストで固定しました。

@template R of array-key が解決されることを確認済み

callback が list を返す呼び出しで R が推論されず array<never, E> に落ちる懸念がありましたが、\PHPStan\dumpType() で 7 パターン確認したところ 1 件も出ませんでした (list 戻りのケースは array<0, 1|2> と解決され、実行時結果 [0 => 2] を正しく含みます)。

group_mapMode を取りません

group_by と同じく入力形状でキーの扱いを決めます (list 入力なら各グループは list、assoc 入力なら元のキーを保持)。

Closes #57
Closes #20

🤖 Generated with Claude Code


追記: group_mapMode を追加しました

新規関数には Mode を持たせる方針に合わせました。当初は「group_by が取らないから」という理由で省いていましたが、それは既存関数の制約であって新規関数が従う理由になりません。

function group_map(array $input, callable $classifier, callable $transform, Mode $mode = Mode::MODE_AUTO): array
  • MODE_LIST: 各グループが完全な list (入力が assoc でもキーを捨てる)
  • MODE_ASSOC: 各グループが元のキーを維持 (list 入力でも元の整数添字を維持し、添字を詰めない)
  • MODE_AUTO: array_is_list() 判定で group_by と同じ挙動 = 既存テストはすべてそのまま通ります

実装は array_is_list($input) の直接呼び出しをやめて Mode::check_mode($mode, $input) に置き換え、@phpstan-return を 4 分岐の条件型にしました。

OK (126 tests, 133 assertions) / PHPStan level 10 [OK] No errors

Note

group_by 自体は Mode を持たないままなので、group_map(..., MODE_LIST) に相当する挙動が group_by にはありません。既存関数への追随は別途相談させてください。

Javakky added 2 commits July 27, 2026 16:16
各要素をコールバックで配列に変換し、順序を保ったまま 1 段だけ平坦化する関数を追加する。

Closes #57
分類キーごとにグループ化しつつ、各要素を変換して格納する関数を追加する。

Closes #20
@Javakky
Javakky force-pushed the javakky/flat-map-group-map branch from 688b6b6 to 602bad8 Compare July 27, 2026 07:17
Javakky added a commit that referenced this pull request Jul 27, 2026
出力キーを指す名前が G (group_by) と R of array-key (map_keys) に分裂していた。 さらに R は
reduce と chunk_by では「集約結果」「比較用の中間値」の意味で使われていて、 bound の有無で 2 つの
役割を持っていた。

関数型言語の R は変換関数が返す「値」を指す (Kotlin map(transform: (T) -> R): List<R> /
Java Function<T, R> / Scala map[B])。 map_keys が R of array-key を使っていたのは「callback の
戻り値」の意味で R を選び、 それが結果的にキーになるという用法で、 キー側の名前として使っていた
わけではない。 R は値側に限定し、 出力キーには I を与える。

  K  入力のキー型
  V  入力の値型
  I  出力のキー型
  E  出力の値型 (配列の要素)
  R  配列でない戻り値 (reduce の集約結果、 chunk_by の比較値)

reduce.php と chunk_by.php の R は触らない。

今やる理由: count_by / associate / group_map / flat_map はまだ master に無く open PR (#81 / #86)
側にある。 それらがマージされる前に規約を確定させるのが最も安い (後からだと 11 本すべてに追随
コミットが必要になる)。

I という文字自体に PHP の用例は無い。 出力キーに名前を与えている実例は 3 つで、 C# LINQ の
ToDictionary<TSource, TKey, TElement> は TKey を selector の戻り値として再定義するが phollection は
入力キーに K を使うため衝突する。 Haskell の mapKeys :: (k1 -> k2) -> Map k1 a -> Map k2 a は
入力キーの改名が 33 箇所に及ぶ。 Ramda の groupBy<T, K extends string> は出力キー専用の型パラメータを
立てていて、 独立した名前を与えるという発想はこれと同じ。 Swift / Rust / Python / lodash / fp-ts は
キーを変換する標準 API が無く概念自体が存在しない。

Refs #76
Javakky added a commit that referenced this pull request Jul 27, 2026
出力キーを指す名前が G (group_by) と R of array-key (map_keys) に分裂していた。 さらに R は
reduce と chunk_by では「集約結果」「比較用の中間値」の意味で使われていて、 bound の有無で 2 つの
役割を持っていた。

関数型言語の R は変換関数が返す「値」を指す (Kotlin map(transform: (T) -> R): List<R> /
Java Function<T, R> / Scala map[B])。 map_keys が R of array-key を使っていたのは「callback の
戻り値」の意味で R を選び、 それが結果的にキーになるという用法で、 キー側の名前として使っていた
わけではない。 R は値側に限定し、 出力キーには I を与える。

  K  入力のキー型
  V  入力の値型
  I  出力のキー型
  E  出力の値型 (配列の要素)
  R  配列でない戻り値 (reduce の集約結果、 chunk_by の比較値)

reduce.php と chunk_by.php の R は触らない。

今やる理由: count_by / associate / group_map / flat_map はまだ master に無く open PR (#81 / #86)
側にある。 それらがマージされる前に規約を確定させるのが最も安い (後からだと 11 本すべてに追随
コミットが必要になる)。

I という文字自体に PHP の用例は無い。 出力キーに名前を与えている実例は 3 つで、 C# LINQ の
ToDictionary<TSource, TKey, TElement> は TKey を selector の戻り値として再定義するが phollection は
入力キーに K を使うため衝突する。 Haskell の mapKeys :: (k1 -> k2) -> Map k1 a -> Map k2 a は
入力キーの改名が 33 箇所に及ぶ。 Ramda の groupBy<T, K extends string> は出力キー専用の型パラメータを
立てていて、 独立した名前を与えるという発想はこれと同じ。 Swift / Rust / Python / lodash / fp-ts は
キーを変換する標準 API が無く概念自体が存在しない。

Refs #76
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: flat_map を追加する feat: group_map の実装

1 participant