|
| 1 | +# Function Composition - Right-to-Left Pipeline via reduceRight |
| 2 | + |
| 3 | +## 目次(Table of Contents) |
| 4 | + |
| 5 | +- [概要](#overview) |
| 6 | +- [アルゴリズム要点(TL;DR)](#tldr) |
| 7 | +- [図解](#figures) |
| 8 | +- [正しさのスケッチ](#correctness) |
| 9 | +- [計算量](#complexity) |
| 10 | +- [TypeScript 実装](#impl) |
| 11 | +- [TypeScript / V8 最適化ポイント](#cpython) |
| 12 | +- [エッジケースと検証観点](#edgecases) |
| 13 | +- [FAQ](#faq) |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +<h2 id="overview">概要</h2> |
| 18 | + |
| 19 | +**LeetCode 2629 – Function Composition** |
| 20 | + |
| 21 | +関数の配列 `[f1, f2, ..., fn]` を受け取り、**右から左** へ順に適用する合成関数 `fn` を返す。 |
| 22 | + |
| 23 | +``` |
| 24 | +compose([f, g, h])(x) = f(g(h(x))) |
| 25 | +``` |
| 26 | + |
| 27 | +| 要件 | 内容 | |
| 28 | +| ------ | ------------------------------------------------- | |
| 29 | +| 正当性 | 空配列のとき恒等関数 `x => x` を返す | |
| 30 | +| 安定性 | 各関数は副作用なし・純粋関数を前提 | |
| 31 | +| 制約 | `0 ≤ functions.length ≤ 1000`, `-1000 ≤ x ≤ 1000` | |
| 32 | + |
| 33 | +--- |
| 34 | + |
| 35 | +<h2 id="tldr">アルゴリズム要点(TL;DR)</h2> |
| 36 | + |
| 37 | +- **戦略**: `Array.prototype.reduceRight` で配列を右端から畳み込む。 |
| 38 | +- **データ構造**: 追加データ構造なし。クロージャが `functions` 参照を保持するのみ。 |
| 39 | +- **時間計算量**: 合成関数呼び出し時 O(n)(n = 関数配列の長さ) |
| 40 | +- **空間計算量**: O(1)(クロージャのポインタ1本のみ) |
| 41 | +- **空配列**: `reduceRight` の初期値 `x` がそのまま返るため、特別処理不要。 |
| 42 | +- **型安全性**: `type F = (x: number) => number` で入出力を厳密に束縛。 |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +<h2 id="figures">図解</h2> |
| 47 | + |
| 48 | +### フローチャート |
| 49 | + |
| 50 | +```mermaid |
| 51 | +flowchart TD |
| 52 | + Start[compose called with functions array] --> Empty{functions.length is 0?} |
| 53 | + Empty -- Yes --> Identity[Return identity x equals x] |
| 54 | + Empty -- No --> RetFn[Return closure fn x] |
| 55 | + RetFn --> Call[fn x is called] |
| 56 | + Call --> RR[reduceRight over functions] |
| 57 | + RR --> Step{More functions?} |
| 58 | + Step -- Yes --> Apply[Apply current fn to acc] |
| 59 | + Apply --> Step |
| 60 | + Step -- No --> Out[Return final acc] |
| 61 | +``` |
| 62 | + |
| 63 | +> `compose` は呼ばれた時点でクロージャを返すだけ(O(1))。 |
| 64 | +> 実際の計算は返された関数 `fn(x)` が呼ばれたときに行われる(O(n))。 |
| 65 | +
|
| 66 | +--- |
| 67 | + |
| 68 | +### データフロー図 |
| 69 | + |
| 70 | +```mermaid |
| 71 | +graph LR |
| 72 | + subgraph Input |
| 73 | + A[functions array] --> B[compose] |
| 74 | + X[x value] --> C[returned fn] |
| 75 | + end |
| 76 | + subgraph Execution |
| 77 | + B --> C |
| 78 | + C --> D[reduceRight] |
| 79 | + D --> E[h x] |
| 80 | + E --> F[g h x] |
| 81 | + F --> G[f g h x] |
| 82 | + end |
| 83 | + G --> H[Output number] |
| 84 | +``` |
| 85 | + |
| 86 | +> 配列の末尾から先頭に向かって関数が順に適用される。各ステップの出力が次のステップの入力となる。 |
| 87 | +
|
| 88 | +--- |
| 89 | + |
| 90 | +<h2 id="correctness">正しさのスケッチ</h2> |
| 91 | + |
| 92 | +**不変条件** |
| 93 | +`reduceRight` の各ステップで、`acc` は「現在のインデックスより右側の全関数を適用した結果」を保持する。 |
| 94 | + |
| 95 | +**基底条件** |
| 96 | + |
| 97 | +- 空配列:`reduceRight` はコールバックを一度も呼ばず、初期値 `x` を返す → 恒等関数として正しい。 |
| 98 | +- 要素1個:`reduceRight` は1回だけコールバックを呼び、`fn(x)` を返す。 |
| 99 | + |
| 100 | +**網羅性** |
| 101 | + |
| 102 | +- 全関数は `F = (x: number) => number` 型を満たすため、型レベルで入出力の整合性が保証される。 |
| 103 | +- `acc` の型は常に `number` であり、コンパイル時に検証される。 |
| 104 | + |
| 105 | +**終了性** |
| 106 | + |
| 107 | +- `reduceRight` は有限配列を走査するため必ず終了する。 |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +<h2 id="complexity">計算量</h2> |
| 112 | + |
| 113 | +| フェーズ | 時間計算量 | 空間計算量 | 備考 | |
| 114 | +| ---------------------- | ---------- | ---------- | ------------------ | |
| 115 | +| `compose` 呼び出し | O(1) | O(1) | クロージャ生成のみ | |
| 116 | +| 返された関数の呼び出し | O(n) | O(1) | n = 関数配列の長さ | |
| 117 | + |
| 118 | +> **in-place vs Pure**: |
| 119 | +> 本実装は Pure(副作用なし)。`functions` 配列を変更せず、クロージャは参照のみ保持する。 |
| 120 | +
|
| 121 | +--- |
| 122 | + |
| 123 | +<h2 id="impl">TypeScript 実装</h2> |
| 124 | + |
| 125 | +```typescript |
| 126 | +// LeetCode 2629 - Function Composition |
| 127 | +// TypeScript strict mode 対応 |
| 128 | + |
| 129 | +type F = (x: number) => number; |
| 130 | + |
| 131 | +/** |
| 132 | + * 関数配列の右から左への合成を返す。 |
| 133 | + * 空配列の場合は恒等関数を返す。 |
| 134 | + * |
| 135 | + * @param functions - 合成する関数の配列(右端から順に適用) |
| 136 | + * @returns 合成された関数 |
| 137 | + * @complexity Time: O(n) per call, Space: O(1) |
| 138 | + */ |
| 139 | +function compose(functions: readonly F[]): F { |
| 140 | + // reduceRight の初期値 x が空配列時の恒等関数を自然に実現する |
| 141 | + return function (x: number): number { |
| 142 | + return functions.reduceRight((acc: number, fn: F): number => fn(acc), x); |
| 143 | + }; |
| 144 | +} |
| 145 | + |
| 146 | +/** |
| 147 | + * const fn = compose([x => x + 1, x => 2 * x]); |
| 148 | + * fn(4); // 9 (2*4=8, 8+1=9) |
| 149 | + */ |
| 150 | +``` |
| 151 | + |
| 152 | +**主要ポイント** |
| 153 | + |
| 154 | +- `readonly F[]` で入力配列の不変性を型レベルで保証。 |
| 155 | +- コールバック引数 `acc: number`, `fn: F` に明示的型注釈を付与し、`strict` モード下での推論を確実化。 |
| 156 | +- 空配列の特別分岐が不要 → 分岐ゼロ・コードが簡潔。 |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +<h2 id="cpython">TypeScript / V8 最適化ポイント</h2> |
| 161 | + |
| 162 | +| 観点 | 内容 | |
| 163 | +| --------------------------- | ----------------------------------------------------------------------------------------- | |
| 164 | +| **クロージャコスト** | `compose` 呼び出しごとにクロージャを1つ生成するのみ。追加オブジェクトなし。 | |
| 165 | +| **`reduceRight` vs ループ** | V8 の組み込みメソッドは JIT 最適化されやすい。可読性も高く推奨。 | |
| 166 | +| **配列参照の共有** | クロージャは `functions` の参照を保持するのみ(コピーなし)。大配列でもメモリ効率が良い。 | |
| 167 | +| **型注釈の効果** | コンパイル時に型エラーを除去することで、実行時の型チェック分岐が不要になる。 | |
| 168 | +| **`readonly` 修飾子** | TypeScript コンパイラが配列の書き換えをコンパイル時にブロック。実行時オーバーヘッドなし。 | |
| 169 | + |
| 170 | +> `for` ループで書き換えても計算量は変わらないが、`reduceRight` の方が数学的意図が明確で保守性が高い。 |
| 171 | +
|
| 172 | +--- |
| 173 | + |
| 174 | +<h2 id="edgecases">エッジケースと検証観点</h2> |
| 175 | + |
| 176 | +| ケース | 入力例 | 期待出力 | 対応 | |
| 177 | +| ---------------- | ------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------ | |
| 178 | +| 空配列 | `functions = [], x = 42` | `42` | `reduceRight` の初期値がそのまま返る | |
| 179 | +| 関数1つ | `functions = [x => x * 2], x = 5` | `10` | コールバック1回だけ実行 | |
| 180 | +| 全関数が恒等関数 | `functions = [x => x, x => x], x = 7` | `7` | 変換なし | |
| 181 | +| 境界値 x = -1000 | `functions = [x => x + 1], x = -1000` | `-999` | 制約内で正常動作 | |
| 182 | +| 境界値 x = 1000 | `functions = [x => x - 1], x = 1000` | `999` | 制約内で正常動作 | |
| 183 | +| 関数1000個 | 全て `x => x + 1`, `x = 0` | `1000` | O(n) で完走 | |
| 184 | +| 非可換な合成順序 | `[x => x + 1, x => x * x], x = 3` | `10` (3²=9, 9+1=10) vs `[x => x * x, x => x + 1]` → `16` (3+1=4, 4²=16) | 右から左の順が正しく守られる | |
| 185 | + |
| 186 | +--- |
| 187 | + |
| 188 | +<h2 id="faq">FAQ</h2> |
| 189 | + |
| 190 | +**Q. なぜ `reduceRight` を使うのか? `reduce` ではだめなのか?** |
| 191 | +A. 数学の合成関数 `f∘g∘h` は右から左に評価される(`h` を先に適用)。`reduceRight` はその順序を自然に表現できる。`reduce` を使うと左から右の適用になり、問題の仕様と逆になる。 |
| 192 | + |
| 193 | +**Q. 空配列の場合、なぜ特別分岐が不要なのか?** |
| 194 | +A. `reduceRight(callback, initialValue)` は配列が空のとき、`callback` を一度も呼ばずに `initialValue` を返す。これが恒等関数 `x => x` の動作と一致する。 |
| 195 | + |
| 196 | +**Q. `functions` 配列の要素を変更するとどうなるか?** |
| 197 | +A. クロージャは参照を保持するため、外部から配列の要素を書き換えると合成結果も変わる。`readonly F[]` はコンパイル時にこの書き換えをブロックするが、実行時の完全な防御が必要な場合は `Object.freeze(functions)` を追加する。 |
| 198 | + |
| 199 | +**Q. ループ実装と `reduceRight` 実装でパフォーマンス差はあるか?** |
| 200 | +A. 制約(`functions.length ≤ 1000`)の範囲では計測上の差はほぼない。可読性・意図の明確さから `reduceRight` を推奨する。 |
| 201 | + |
| 202 | +**Q. TypeScript の `strict` モードで問題なく動作するか?** |
| 203 | +A. はい。`reduceRight` のコールバック引数に明示的型注釈 `(acc: number, fn: F): number` を付与しているため、`strict` / `noImplicitAny` 下でもコンパイルエラーは発生しない。 |
0 commit comments