Skip to content

Commit cf5e33b

Browse files
committed
feat(JavaScript): add solution for 2631. Group By (Claude 3.7 Sonnet)
1 parent 4c2a99e commit cf5e33b

5 files changed

Lines changed: 3983 additions & 4 deletions

File tree

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "a0aa6c59",
6+
"metadata": {},
7+
"source": [
8+
"## 1. 問題の分析\n",
9+
"\n",
10+
"**競技プログラミング視点**\n",
11+
"- 配列を1回走査するだけで分類できるため、O(n)が理論的下限\n",
12+
"- ハッシュマップ(オブジェクト)によるO(1)キーアクセスで最適化\n",
13+
"\n",
14+
"**業務開発視点**\n",
15+
"- `Array.prototype`拡張はグローバルな副作用を持つため、型定義側でインターフェース宣言を行い型安全性を担保\n",
16+
"- `Record<string, T[]>`で戻り値を明確に型付け\n",
17+
"\n",
18+
"**TypeScript特有の考慮点**\n",
19+
"- `this`は`Array.prototype`拡張内では実行時配列を指すが、型は`any[]`扱い → ジェネリクスで安全化\n",
20+
"- `interface Array<T>`のマージ宣言(Declaration Merging)でプロトタイプ拡張を型レベルに反映\n",
21+
"\n",
22+
"---\n",
23+
"\n",
24+
"## 2. アルゴリズムアプローチ比較\n",
25+
"\n",
26+
"| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n",
27+
"|---|---|---|---|---|---|---|\n",
28+
"| `for`ループ + オブジェクト | O(n) | O(n) | 低 | 高 | 高 | **最適解** |\n",
29+
"| `reduce` | O(n) | O(n) | 低 | 高 | 高 | 関数型スタイル |\n",
30+
"| `sort` + 走査 | O(n log n) | O(n) | 中 | 中 | 低 | 不要なソートコスト |\n",
31+
"\n",
32+
"---\n",
33+
"\n",
34+
"## 3. 選択したアルゴリズムと理由\n",
35+
"\n",
36+
"- **選択**: `reduce` によるアキュムレータパターン\n",
37+
"- **理由**:\n",
38+
" - O(n)の単一走査で完結、追加メモリはO(n)(結果格納分のみ)\n",
39+
" - `reduce`はイミュータブル志向で副作用がなく純粋関数的\n",
40+
" - TypeScriptの型推論と相性が良く、アキュムレータの型を`Record<string, T[]>`で明示可能\n",
41+
"\n",
42+
"---\n",
43+
"\n",
44+
"## 4. 実装コード\n",
45+
"\n",
46+
"```typescript\n",
47+
"// Analyze Complexity\n",
48+
"// Runtime 98 ms\n",
49+
"// Beats 80.23%\n",
50+
"// Memory 79.27 MB\n",
51+
"// Beats 40.00%\n",
52+
"interface Array<T> {\n",
53+
" groupBy(fn: (item: T) => string): Record<string, T[]>;\n",
54+
"}\n",
55+
"\n",
56+
"Array.prototype.groupBy = function<T>(\n",
57+
" this: T[],\n",
58+
" fn: (item: T) => string\n",
59+
"): Record<string, T[]> {\n",
60+
" return this.reduce<Record<string, T[]>>((acc, item) => {\n",
61+
" const key = fn(item);\n",
62+
" // キーが未存在なら空配列で初期化、存在すれば既存配列を再利用\n",
63+
" (acc[key] ??= []).push(item);\n",
64+
" return acc;\n",
65+
" }, Object.create(null)); // プロトタイプ汚染を防ぐためObject.create(null)\n",
66+
"};\n",
67+
"\n",
68+
"/**\n",
69+
" * [1,2,3].groupBy(String) // {\"1\":[1],\"2\":[2],\"3\":[3]}\n",
70+
" */\n",
71+
"```\n",
72+
"\n",
73+
"---\n",
74+
"\n",
75+
"### 実装の重要ポイント解説\n",
76+
"\n",
77+
"**① `this: T[]` による明示的 `this` 型バインド**\n",
78+
"```typescript\n",
79+
"// NG: thisがany扱いになりTypeScriptの恩恵を受けられない\n",
80+
"Array.prototype.groupBy = function(fn) { ... }\n",
81+
"\n",
82+
"// OK: thisをT[]と明示することでジェネリクスが機能する\n",
83+
"Array.prototype.groupBy = function<T>(this: T[], fn) { ... }\n",
84+
"```\n",
85+
"\n",
86+
"**② `??=`(Nullish coalescing assignment)による簡潔な初期化**\n",
87+
"```typescript\n",
88+
"// 従来の書き方(冗長)\n",
89+
"if (!acc[key]) acc[key] = [];\n",
90+
"acc[key].push(item);\n",
91+
"\n",
92+
"// ??= を使った簡潔な書き方(ES2021+, Node.js v22対応)\n",
93+
"(acc[key] ??= []).push(item);\n",
94+
"```\n",
95+
"\n",
96+
"**③ `Object.create(null)` によるプロトタイプ汚染防止**\n",
97+
"```typescript\n",
98+
"// NG: {}はObject.prototypeを継承するため\n",
99+
"// \"constructor\", \"toString\"等のキーと衝突リスクがある\n",
100+
"const acc = {};\n",
101+
"\n",
102+
"// OK: プロトタイプチェーンを持たない純粋なマップとして機能\n",
103+
"const acc = Object.create(null);\n",
104+
"```\n",
105+
"\n",
106+
"## 現状分析\n",
107+
"\n",
108+
"| 指標 | 現状 | 問題点 |\n",
109+
"|---|---|---|\n",
110+
"| Runtime 98ms | Beats 80.23% | まだ改善余地あり |\n",
111+
"| Memory 79.27MB | **Beats 40.00%** | **ここがボトルネック** |\n",
112+
"\n",
113+
"メモリがボトルネックの主因は **`reduce`のコールバック関数が反復ごとにスタックフレームを生成する**ことです。\n",
114+
"\n",
115+
"---\n",
116+
"\n",
117+
"## ボトルネックの詳細\n",
118+
"\n",
119+
"```\n",
120+
"reduce の内部動作(メモリ観点)\n",
121+
"─────────────────────────────────────────\n",
122+
"item[0] → fn呼び出し → スタックフレーム生成 → pop\n",
123+
"item[1] → fn呼び出し → スタックフレーム生成 → pop\n",
124+
"item[2] → fn呼び出し → スタックフレーム生成 → pop\n",
125+
"...× n回繰り返す ← n=10^5 のとき顕著にメモリ圧迫\n",
126+
"```\n",
127+
"\n",
128+
"```\n",
129+
"for ループの内部動作(メモリ観点)\n",
130+
"─────────────────────────────────────────\n",
131+
"スタックフレームは1つだけ確保 → ループ内で使い回す\n",
132+
"```\n",
133+
"\n",
134+
"---\n",
135+
"\n",
136+
"## 改善コード\n",
137+
"\n",
138+
"```typescript\n",
139+
"// Analyze Complexity\n",
140+
"// Runtime 101 ms\n",
141+
"// Beats 70.34%\n",
142+
"// Memory 74.68 MB\n",
143+
"// Beats 90.11%\n",
144+
"interface Array<T> {\n",
145+
" groupBy(fn: (item: T) => string): Record<string, T[]>;\n",
146+
"}\n",
147+
"\n",
148+
"Array.prototype.groupBy = function<T>(\n",
149+
" this: T[],\n",
150+
" fn: (item: T) => string\n",
151+
"): Record<string, T[]> {\n",
152+
" // ① reduceをforループに変更 → スタックフレームの反復生成を排除\n",
153+
" const result: Record<string, T[]> = Object.create(null);\n",
154+
" const len = this.length; // ② length キャッシュ → プロパティ参照コスト削減\n",
155+
"\n",
156+
" for (let i = 0; i < len; i++) {\n",
157+
" const item = this[i]; // ③ 一時変数でthis参照を1回に抑制\n",
158+
" const key = fn(item);\n",
159+
"\n",
160+
" // ④ in演算子で存在確認 → ??=より軽量(型変換コストなし)\n",
161+
" if (key in result) {\n",
162+
" result[key].push(item);\n",
163+
" } else {\n",
164+
" result[key] = [item];\n",
165+
" }\n",
166+
" }\n",
167+
"\n",
168+
" return result;\n",
169+
"};\n",
170+
"```\n",
171+
"\n",
172+
"---\n",
173+
"\n",
174+
"## 改善ポイント詳解\n",
175+
"\n",
176+
"**① `reduce` → `for`ループ**\n",
177+
"```typescript\n",
178+
"// Before: n回のコールバック呼び出し = n個のスタックフレーム生成・破棄\n",
179+
"this.reduce<Record<string, T[]>>((acc, item) => { ... }, Object.create(null));\n",
180+
"\n",
181+
"// After: 1つのスタックフレームを使い回す\n",
182+
"for (let i = 0; i < len; i++) { ... }\n",
183+
"```\n",
184+
"\n",
185+
"**② `length`キャッシュ**\n",
186+
"```typescript\n",
187+
"// Before: ループ毎にthis.lengthプロパティを参照(微小だが積み重なる)\n",
188+
"for (let i = 0; i < this.length; i++)\n",
189+
"\n",
190+
"// After: 変数参照のみ(プロパティルックアップ不要)\n",
191+
"const len = this.length;\n",
192+
"for (let i = 0; i < len; i++)\n",
193+
"```\n",
194+
"\n",
195+
"**③ `??=` → `in` 演算子**\n",
196+
"```typescript\n",
197+
"// Before: ??= は内部でnullish判定 + 代入の2ステップ\n",
198+
"(acc[key] ??= []).push(item);\n",
199+
"\n",
200+
"// After: プロパティ存在確認のみ(型変換コストゼロ)\n",
201+
"if (key in result) {\n",
202+
" result[key].push(item);\n",
203+
"} else {\n",
204+
" result[key] = [item]; // 初回のみ配列生成\n",
205+
"}\n",
206+
"```\n",
207+
"\n",
208+
"---\n",
209+
"\n",
210+
"## 期待される改善効果\n",
211+
"\n",
212+
"| 指標 | Before | After(予測) |\n",
213+
"|---|---|---|\n",
214+
"| Runtime | 98ms / Beats 80% | ~70ms / Beats ~90%+ |\n",
215+
"| Memory | 79.27MB / Beats 40% | ~74MB / Beats ~70%+ |\n",
216+
"| 主な改善理由 | — | スタックフレーム削減・プロパティ参照最小化 |\n",
217+
"\n",
218+
"> **補足**: LeetCodeのメモリ計測はV8エンジンのGCタイミングに依存するため、実行ごとに若干ブレます。複数回提出して中央値で評価することを推奨します。"
219+
]
220+
}
221+
],
222+
"metadata": {
223+
"kernelspec": {
224+
"display_name": "TypeScript",
225+
"language": "typescript",
226+
"name": "typescript"
227+
},
228+
"language_info": {
229+
"file_extension": ".ts",
230+
"mimetype": "text/typescript",
231+
"name": "typescript",
232+
"version": "5.9.3"
233+
}
234+
},
235+
"nbformat": 4,
236+
"nbformat_minor": 5
237+
}

0 commit comments

Comments
 (0)