|
| 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