Skip to content

Commit 0e979af

Browse files
committed
Add Function Composition solution files
1 parent f1cfce4 commit 0e979af

3 files changed

Lines changed: 1599 additions & 0 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "a7571108",
6+
"metadata": {},
7+
"source": [
8+
"## 1. 問題の分析\n",
9+
"\n",
10+
"**競技プログラミング視点**\n",
11+
"- 配列を右から左へ1パスするだけで解決可能。`reduceRight` が最適。\n",
12+
"- 追加メモリ不要(クロージャで `x` を畳み込む)。\n",
13+
"\n",
14+
"**業務開発視点**\n",
15+
"- 空配列 → 恒等関数という仕様を型安全に表現できる。\n",
16+
"- `readonly` 修飾子で入力配列の不変性を保証。\n",
17+
"\n",
18+
"**TypeScript特有の考慮点**\n",
19+
"- `F = (x: number) => number` という型エイリアスがすでに与えられているため、ジェネリクス不要。\n",
20+
"- `reduceRight` の型推論が自然に効く。\n",
21+
"\n",
22+
"---\n",
23+
"\n",
24+
"## 2. アルゴリズムアプローチ比較\n",
25+
"\n",
26+
"| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n",
27+
"|---|---|---|---|---|---|---|\n",
28+
"| `reduceRight` | O(n) | O(1) | 低 | 高 | 高 | 最もシンプル |\n",
29+
"| `for` ループ(右→左) | O(n) | O(1) | 低 | 高 | 中 | 命令的 |\n",
30+
"| 再帰 | O(n) | O(n) | 中 | 高 | 中 | スタックオーバーフローリスク |\n",
31+
"\n",
32+
"---\n",
33+
"\n",
34+
"## 3. 選択したアルゴリズムと理由\n",
35+
"\n",
36+
"- **選択**: `reduceRight`\n",
37+
"- **理由**:\n",
38+
" - 数学的な「右から左への関数合成」を宣言的に表現でき、可読性・意図の明確さが最高。\n",
39+
" - O(n) / O(1) で計算量も最適。\n",
40+
" - TypeScriptの型推論が自然に効き、型注釈の追加が不要。\n",
41+
"\n",
42+
"---\n",
43+
"\n",
44+
"## 4. 実装コード\n",
45+
"\n",
46+
"```typescript\n",
47+
"// Analyze Complexity\n",
48+
"// Runtime 56 ms\n",
49+
"// Beats 55.84%\n",
50+
"// Memory 56.88 MB\n",
51+
"// Beats 46.50%\n",
52+
"type F = (x: number) => number;\n",
53+
"\n",
54+
"/**\n",
55+
" * 関数配列の右から左への合成を返す\n",
56+
" * @param functions - 合成する関数の配列\n",
57+
" * @returns 合成された関数。空配列の場合は恒等関数\n",
58+
" * @complexity Time: O(n), Space: O(1)\n",
59+
" */\n",
60+
"function compose(functions: readonly F[]): F {\n",
61+
" return function (x: number): number {\n",
62+
" return functions.reduceRight(\n",
63+
" (acc: number, fn: F): number => fn(acc),\n",
64+
" x\n",
65+
" );\n",
66+
" };\n",
67+
"}\n",
68+
"\n",
69+
"/**\n",
70+
" * const fn = compose([x => x + 1, x => 2 * x])\n",
71+
" * fn(4) // 9\n",
72+
" */\n",
73+
"```\n",
74+
"\n",
75+
"**動作確認(コメントで検証):**\n",
76+
"```typescript\n",
77+
"// Example 1: [x+1, x*x, 2*x], x=4\n",
78+
"// 2*4=8 → 8*8=64 → 64+1=65 ✓\n",
79+
"\n",
80+
"// Example 2: [10x, 10x, 10x], x=1\n",
81+
"// 10 → 100 → 1000 ✓\n",
82+
"\n",
83+
"// Example 3: [], x=42\n",
84+
"// reduceRight の初期値 42 がそのまま返る → 42 ✓\n",
85+
"```\n",
86+
"\n",
87+
"**ポイント:**\n",
88+
"- `functions` を `readonly F[]` とすることで入力配列の不変性を型レベルで保証。\n",
89+
"- `reduceRight` の初期値 `x` が空配列時の恒等関数の役割を自然に担うため、空配列の特別処理が不要。\n",
90+
"- コールバック内の引数 `acc`・`fn` に型注釈を付与し、strict mode 下でも型推論が確実に機能。"
91+
]
92+
}
93+
],
94+
"metadata": {
95+
"kernelspec": {
96+
"display_name": "TypeScript",
97+
"language": "typescript",
98+
"name": "typescript"
99+
},
100+
"language_info": {
101+
"file_extension": ".ts",
102+
"mimetype": "text/typescript",
103+
"name": "typescript",
104+
"version": "5.3.3"
105+
}
106+
},
107+
"nbformat": 4,
108+
"nbformat_minor": 5
109+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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

Comments
 (0)