diff --git a/CLAUDE.md b/CLAUDE.md index b3b7bac1..c50bb0ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,6 +91,11 @@ python generate_index.py # public/index.html 再生成 - `file://` URLはブロックされる → `python -m http.server 8765 --directory public` でローカルサーバー起動 - `ruff` / `black` はグローバル未インストールの場合あり → `python -c "import py_compile; ..."` でシンタックスチェック代替 +### 定性・定量評価のガイドライン + +- 実装手法(例: `in` 演算子と `??=` 演算子など)を比較する際、「Aの方がBよりも確実に軽量/高速である」といった絶対的なパフォーマンスの断言は**避ける**こと。 +- その代わり、セマンティクスの違い(例: `Object.create(null)` 等)を解説するか、V8エンジンのインラインキャッシュ(IC)などによって「実行環境に依存してパフォーマンスが逆転・変化する」といった中立的な補足を必ず含めること。 + ## SVGフローチャートガイドライン `.agent/workflows/svg_flowchart_guidelines.md` に詳細あり。主要ポイント: diff --git a/Gemini.md b/Gemini.md index 6dfb68b9..3bfb96b5 100644 --- a/Gemini.md +++ b/Gemini.md @@ -60,3 +60,8 @@ This file provides guidance to Gemini (or other Google AI assistants) when worki - `file://` URLでのアクセスはブロックされる前提で設計されています。 - テスト等で確認が必要な場合は `python -m http.server 8765 --directory public` でローカルサーバーを起動してアクセスしてください。 + +### 5. パフォーマンス等の絶対的評価の回避 + +- `in` 演算子と `??=` 演算子のような特定の構文において、「Aの方がBよりも確実に軽量/高速である」といった絶対的なパフォーマンスの主張・断言は**避けてください**。 +- 代わりに、セマンティクスの違い(例: `Object.create(null)` によるプロトタイプなしオブジェクトに対する純粋な存在確認)を説明するか、V8エンジンの最適化(インラインキャッシュ/IC等)によって「実行環境や状況に依存してパフォーマンスが逆転・変化する」といった中立的な補足を必ず含めてください。 diff --git a/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/Group_By_TS.ipynb b/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/Group_By_TS.ipynb new file mode 100644 index 00000000..db96fee2 --- /dev/null +++ b/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/Group_By_TS.ipynb @@ -0,0 +1,233 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a0aa6c59", + "metadata": {}, + "source": [ + "## 1. 問題の分析\n", + "\n", + "**競技プログラミング視点**\n", + "- 配列を1回走査するだけで分類できるため、O(n)が理論的下限\n", + "- ハッシュマップ(オブジェクト)によるO(1)キーアクセスで最適化\n", + "\n", + "**業務開発視点**\n", + "- `Array.prototype`拡張はグローバルな副作用を持つため、型定義側でインターフェース宣言を行い型安全性を担保\n", + "- `Record`で戻り値を明確に型付け\n", + "\n", + "**TypeScript特有の考慮点**\n", + "- `this`は`Array.prototype`拡張内では実行時配列を指すが、型は`any[]`扱い → ジェネリクスで安全化\n", + "- `interface Array`のマージ宣言(Declaration Merging)でプロトタイプ拡張を型レベルに反映\n", + "\n", + "---\n", + "\n", + "## 2. アルゴリズムアプローチ比較\n", + "\n", + "| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n", + "|---|---|---|---|---|---|---|\n", + "| `for`ループ + オブジェクト | O(n) | O(n) | 低 | 高 | 高 | **最適解** |\n", + "| `reduce` | O(n) | O(n) | 低 | 高 | 高 | 関数型スタイル |\n", + "| `sort` + 走査 | O(n log n) | O(n) | 中 | 中 | 低 | 不要なソートコスト |\n", + "\n", + "---\n", + "\n", + "## 3. 選択したアルゴリズムと理由\n", + "\n", + "- **選択**: `reduce` によるアキュムレータパターン\n", + "- **理由**:\n", + " - O(n)の単一走査で完結、追加メモリはO(n)(結果格納分のみ)\n", + " - `reduce`はイミュータブル志向で副作用がなく純粋関数的\n", + " - TypeScriptの型推論と相性が良く、アキュムレータの型を`Record`で明示可能\n", + "\n", + "---\n", + "\n", + "## 4. 実装コード\n", + "\n", + "```typescript\n", + "// Analyze Complexity\n", + "// 実測: Runtime 98 ms / Memory 79.27 MB\n", + "// Beats 80.23% / 40.00%\n", + "interface Array {\n", + " groupBy(fn: (item: T) => string): Record;\n", + "}\n", + "\n", + "Array.prototype.groupBy = function(\n", + " this: T[],\n", + " fn: (item: T) => string\n", + "): Record {\n", + " return this.reduce>((acc, item) => {\n", + " const key = fn(item);\n", + " // キーが未存在なら空配列で初期化、存在すれば既存配列を再利用\n", + " (acc[key] ??= []).push(item);\n", + " return acc;\n", + " }, Object.create(null)); // プロトタイプ汚染を防ぐためObject.create(null)\n", + "};\n", + "\n", + "/**\n", + " * [1,2,3].groupBy(String) // {\"1\":[1],\"2\":[2],\"3\":[3]}\n", + " */\n", + "```\n", + "\n", + "---\n", + "\n", + "### 実装の重要ポイント解説\n", + "\n", + "**① `this: T[]` による明示的 `this` 型バインド**\n", + "```typescript\n", + "// NG: thisがany扱いになりTypeScriptの恩恵を受けられない\n", + "Array.prototype.groupBy = function(fn) { ... }\n", + "\n", + "// OK: thisをT[]と明示することでジェネリクスが機能する\n", + "Array.prototype.groupBy = function(this: T[], fn) { ... }\n", + "```\n", + "\n", + "**② `??=`(Nullish coalescing assignment)による簡潔な初期化**\n", + "```typescript\n", + "// 従来の書き方(冗長)\n", + "if (!acc[key]) acc[key] = [];\n", + "acc[key].push(item);\n", + "\n", + "// ??= を使った簡潔な書き方(ES2021+, Node.js v22対応)\n", + "(acc[key] ??= []).push(item);\n", + "```\n", + "\n", + "**③ `Object.create(null)` によるプロトタイプ汚染防止**\n", + "```typescript\n", + "// NG: {}はObject.prototypeを継承するため\n", + "// \"constructor\", \"toString\"等のキーと衝突リスクがある\n", + "const acc = {};\n", + "\n", + "// OK: プロトタイプチェーンを持たない純粋なマップとして機能\n", + "const acc = Object.create(null);\n", + "```\n", + "\n", + "## 現状分析\n", + "\n", + "| 指標 | 現状 | 問題点 |\n", + "|---|---|---|\n", + "| Runtime 98ms | Beats 80.23% | まだ改善余地あり |\n", + "| Memory 79.27MB | **Beats 40.00%** | **ここがボトルネック** |\n", + "\n", + "メモリがボトルネックの主因は **`reduce`のコールバック関数が反復ごとにスタックフレームを生成する**ことです。\n", + "\n", + "---\n", + "\n", + "## ボトルネックの詳細\n", + "\n", + "```\n", + "reduce の内部動作(メモリ観点)\n", + "─────────────────────────────────────────\n", + "item[0] → fn呼び出し → スタックフレーム生成 → pop\n", + "item[1] → fn呼び出し → スタックフレーム生成 → pop\n", + "item[2] → fn呼び出し → スタックフレーム生成 → pop\n", + "...× n回繰り返す ← n=10^5 のとき顕著にメモリ圧迫\n", + "```\n", + "\n", + "```\n", + "for ループの内部動作(メモリ観点)\n", + "─────────────────────────────────────────\n", + "スタックフレームは1つだけ確保 → ループ内で使い回す\n", + "```\n", + "\n", + "---\n", + "\n", + "## 改善コード\n", + "\n", + "```typescript\n", + "// Analyze Complexity\n", + "// 実測: Runtime 101 ms / Memory 74.68 MB\n", + "// Beats 70.34% / 90.11%\n", + "interface Array {\n", + " groupBy(fn: (item: T) => string): Record;\n", + "}\n", + "\n", + "Array.prototype.groupBy = function(\n", + " this: T[],\n", + " fn: (item: T) => string\n", + "): Record {\n", + " // ① reduceをforループに変更 → スタックフレームの反復生成を排除\n", + " const result: Record = Object.create(null);\n", + " const len = this.length; // ② length キャッシュ → プロパティ参照コスト削減\n", + "\n", + " for (let i = 0; i < len; i++) {\n", + " const item = this[i]; // ③ 一時変数でthis参照を1回に抑制\n", + " const key = fn(item);\n", + "\n", + " // ④ Object.create(null) に対するキー存在確認のセマンティクス\n", + " if (key in result) {\n", + " result[key].push(item);\n", + " } else {\n", + " result[key] = [item];\n", + " }\n", + " }\n", + "\n", + " return result;\n", + "};\n", + "```\n", + "\n", + "---\n", + "\n", + "## 改善ポイント詳解\n", + "\n", + "**① `reduce` → `for`ループ**\n", + "```typescript\n", + "// Before: n回のコールバック呼び出し = n個のスタックフレーム生成・破棄\n", + "this.reduce>((acc, item) => { ... }, Object.create(null));\n", + "\n", + "// After: 1つのスタックフレームを使い回す\n", + "for (let i = 0; i < len; i++) { ... }\n", + "```\n", + "\n", + "**② `length`キャッシュ**\n", + "```typescript\n", + "// Before: ループ毎にthis.lengthプロパティを参照(微小だが積み重なる)\n", + "for (let i = 0; i < this.length; i++)\n", + "\n", + "// After: 変数参照のみ(プロパティルックアップ不要)\n", + "const len = this.length;\n", + "for (let i = 0; i < len; i++)\n", + "```\n", + "\n", + "**③ `??=` と `in` 演算子**\n", + "```typescript\n", + "// パターンA: ??= を用いた記述(V8環境でIC最適化が効きやすい)\n", + "(acc[key] ??= []).push(item);\n", + "\n", + "// パターンB: in 演算子による存在確認(本実装での採用)\n", + "if (key in result) {\n", + " result[key].push(item);\n", + "} else {\n", + " result[key] = [item]; // 初回のみ配列生成\n", + "}\n", + "```\n", + "\n", + "---\n", + "\n", + "## 期待される改善効果\n", + "\n", + "| 指標 | Before(実測) | After(実測) |\n", + "|---|---|---|\n", + "| Runtime | 98 ms (Beats 80.23%) | 101 ms (Beats 70.34%) |\n", + "| Memory | 79.27 MB (Beats 40.00%) | 74.68 MB (Beats 90.11%) |\n", + "| 主な影響 | — | GCの影響によりRuntime微増・Memory大幅改善 |\n", + "\n", + "> **補足**: LeetCodeのメモリ計測はV8エンジンのGCタイミングに依存するため、実行ごとに若干ブレます。複数回提出して中央値で評価することを推奨します。" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "TypeScript", + "language": "typescript", + "name": "typescript" + }, + "language_info": { + "file_extension": ".ts", + "mimetype": "text/typescript", + "name": "typescript", + "version": "5.9.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README.md b/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README.md new file mode 100644 index 00000000..07dd3658 --- /dev/null +++ b/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README.md @@ -0,0 +1,281 @@ +# Group By - Prototype Extension for Grouped Array + +--- + +## 目次(Table of Contents) + +- [概要](#overview) +- [アルゴリズム要点(TL;DR)](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [計算量](#complexity) +- [TypeScript 実装](#impl) +- [V8 / Node.js 最適化ポイント](#cpython) +- [エッジケースと検証観点](#edgecases) +- [FAQ](#faq) + +--- + +

概要

+ +**LeetCode 2631 – Group By** + +`Array.prototype` に `groupBy(fn)` メソッドを追加する問題。 +コールバック関数 `fn` を各要素に適用し、返却されたキー文字列ごとに元の要素を分類した `Record` を返す。 + +### 要件 + +| 項目 | 内容 | +| ------ | ----------------------------------------------------------------------------- | +| 入力 | 任意の型の配列 `T[]`、コールバック `fn: (item: T) => string` | +| 出力 | `Record`(キーは `fn` の返り値、値はそのキーを返した要素の配列) | +| 順序 | 各グループ内の順序は元の配列と同順 | +| キー順 | 任意(規定なし) | +| 制約 | `0 <= array.length <= 10^5`、`fn` は必ず string を返す | + +--- + +

アルゴリズム要点(TL;DR)

+ +- **戦略**: `for` ループで配列を1回走査し、キー存在確認(`in` 演算子)→ push でグルーピング +- **データ構造**: `Object.create(null)` による純粋なハッシュマップ(プロトタイプ汚染なし) +- **時間計算量**: O(n) — 全要素を1回走査 +- **空間計算量**: O(n) — 結果オブジェクトに全要素を格納 +- **最適化ポイント**: `reduce` よりも `for` ループがスタックフレームを節約、`length` キャッシュでプロパティ参照コスト削減、キーの存在確認におけるセマンティクスの違い(`Object.create(null)` と `in` 演算子) +- **プロトタイプ拡張**: `interface Array` の Declaration Merging で型安全性を維持 + +--- + +

図解

+ +### フローチャート + +```mermaid +flowchart TD + Start[Start groupBy fn] --> LenCheck{length > 0} + LenCheck -- No --> RetEmpty[Return empty object] + LenCheck -- Yes --> Init[Init result via Object.create null] + Init --> LoopStart[i = 0] + LoopStart --> Cond{i < len} + Cond -- No --> Return[Return result] + Cond -- Yes --> GetItem[item = this i] + GetItem --> CallFn[key = fn item] + CallFn --> KeyExists{key in result} + KeyExists -- Yes --> Push[result key .push item] + KeyExists -- No --> NewArr[result key = new array with item] + Push --> Inc[i plus plus] + NewArr --> Inc + Inc --> Cond +``` + +> `fn` をコールしてキーを取得し、`in` 演算子でキーの存在を確認。存在すれば `push`、なければ新規配列で初期化。全要素を O(n) で走査して返す。 + +--- + +### データフロー図 + +```mermaid +graph LR + subgraph Input + A[Array T items] --> B[fn callback] + end + subgraph Core_Loop + B --> C[key string] + C --> D{key exists in result} + D -- Yes --> E[push to existing array] + D -- No --> F[create new array] + E --> G[result Record] + F --> G + end + subgraph Output + G --> H[Grouped Record string T array] + end +``` + +> 入力配列の各要素を `fn` でキーに変換し、結果オブジェクトへ順次グルーピングするデータフロー。 + +--- + +

正しさのスケッチ

+ +### 不変条件 + +- ループ実行中、`result` には「走査済み要素 `this[0..i-1]` を `fn` でグルーピングした状態」が保持される +- `push` はリファレンスをコピーするのみで元の配列を変更しない(浅いコピー) + +### 網羅性 + +- 全要素 `i in [0, len)` を走査するため、未分類の要素は存在しない +- `fn` が同一キーを返す要素は必ず同一グループにまとめられる + +### 基底条件 + +- `length === 0` のとき `for` ループは1度も実行されず、空オブジェクトを返す + +### 終了性 + +- `i` は毎イテレーション必ず `++` されるため無限ループは発生しない +- `len` は事前にキャッシュされており、ループ中に変化しない + +--- + +

計算量

+ +| 観点 | 計算量 | 備考 | +| ---------- | -------- | ------------------------------------------------------ | +| 時間計算量 | **O(n)** | 全要素を1回走査。`fn` の計算量が O(1) であることが前提 | +| 空間計算量 | **O(n)** | 全要素への参照を結果オブジェクトに格納 | + +### `reduce` vs `for` ループ 比較 + +| 項目 | `reduce` | `for` ループ | +| -------------------------- | -------------- | --------------------------- | +| スタックフレーム | n 回生成・破棄 | 1つを使い回す | +| 関数呼び出しオーバーヘッド | あり | なし | +| メモリ効率 | やや劣る | **優れる** | +| 可読性 | 高い(関数型) | 高い | +| LeetCode Memory | Beats 40.00% | **Beats 90.11%** (実測改善) | + +--- + +

TypeScript 実装

+ +```typescript +// Declaration Merging: Array インターフェースに groupBy を追加 +interface Array { + groupBy(fn: (item: T) => string): Record; +} + +Array.prototype.groupBy = function (this: T[], fn: (item: T) => string): Record { + // プロトタイプ汚染防止: Object.create(null) で純粋なハッシュマップを生成 + const result: Record = Object.create(null); + + // length をキャッシュしてプロパティ参照コストを削減 + const len = this.length; + + for (let i = 0; i < len; i++) { + const item = this[i]; // this参照を1回に抑制 + const key = fn(item); // コールバックでキーを生成 + + // in演算子はhasOwnPropertyより軽量(型変換コストなし) + if (key in result) { + result[key].push(item); // キー存在: 既存配列へ追加 + } else { + result[key] = [item]; // キー不在: 新規配列を生成 + } + } + + return result; +}; + +/** + * [1,2,3].groupBy(String) // {"1":[1],"2":[2],"3":[3]} + */ +``` + +--- + +

V8 / Node.js 最適化ポイント

+ +### 1. `reduce` → `for` ループへの変更 + +```typescript +// Before: n回のコールバック呼び出し = n個のスタックフレーム生成・破棄 +this.reduce>((acc, item) => { + (acc[fn(item)] ??= []).push(item); + return acc; +}, Object.create(null)); + +// After: 1スタックフレームを全要素で使い回す → メモリ効率 ↑ +for (let i = 0; i < len; i++) { ... } +``` + +### 2. `length` プロパティのキャッシュ + +```typescript +// Before: ループ毎に this.length プロパティを参照 +for (let i = 0; i < this.length; i++) + +// After: 変数参照のみ(V8の最適化後も明示キャッシュが効果的) +const len = this.length; +for (let i = 0; i < len; i++) +``` + +### 3. `??=` と `in` 演算子 + +```typescript +// パターンA: ??= を用いた簡潔な記述 +(acc[key] ??= []).push(item); + +// パターンB: in 演算子による存在確認(本実装での採用) +if (key in result) { + result[key].push(item); +} else { + result[key] = [item]; +} +``` + +- `Object.create(null)` で作成されたプロトタイプを持たないオブジェクトに対しては、キーの存在確認としてセマンティクス上 `in` 演算子が適しています。 +- ただし V8 などの JS エンジンでは、インラインキャッシュ(IC)の最適化が働きやすいため、実環境上のマイクロベンチマークでは `??=` を用いた方が高速に動作するケースも多く見られます。パフォーマンスの優劣については、絶対指標ではなく実行環境や最適化状況に依存します。 + +### 4. `Object.create(null)` によるプロトタイプ汚染防止 + +```typescript +// NG: "constructor", "toString", "__proto__" 等と衝突リスク +const result = {}; + +// OK: プロトタイプチェーンなし = 純粋なキーバリューストア +const result = Object.create(null); +``` + +### 5. `this[i]` での直接インデックスアクセス + +- `for...of` はイテレータプロトコルを経由するためオーバーヘッドがある +- インデックスアクセス `this[i]` は V8 の配列最適化(Hidden Class・要素種別 FAST_SMI_ELEMENTS など)と相性が良い + +--- + +

エッジケースと検証観点

+ +| ケース | 入力例 | 期待動作 | +| --------------------- | ------------------------------------------ | -------------------------------------- | +| 空配列 | `[].groupBy(fn)` | `{}` を返す(ループ不実行) | +| 全要素が同じキー | `[1,2,3].groupBy(() => "a")` | `{"a":[1,2,3]}` | +| 全要素がユニークキー | `[1,2,3].groupBy(String)` | `{"1":[1],"2":[2],"3":[3]}` | +| キーに特殊文字 | `fn` が `"__proto__"` を返す | `Object.create(null)` により安全に格納 | +| ネスト配列の要素 | `[[1,2],[1,3]].groupBy(l => String(l[0]))` | `{"1":[[1,2],[1,3]]}` | +| 最大サイズ | `length = 10^5` | O(n) で正常完了 | +| `fn` が同じ参照を返す | 文字列は値比較のため問題なし | 正常動作 | +| オブジェクト要素 | `[{id:"1"},{id:"1"},{id:"2"}]` | 参照がそのまま配列に格納 | + +--- + +

FAQ

+ +**Q1. なぜ `{}` ではなく `Object.create(null)` を使うのか?** + +`{}` は `Object.prototype` を継承するため、`"constructor"` や `"toString"` などのキーを使うと既存プロパティと衝突する可能性がある。`Object.create(null)` はプロトタイプチェーンを持たない純粋なハッシュマップを生成するため安全。 + +--- + +**Q2. `Array.prototype` を拡張することの問題点は?** + +グローバルな副作用があり、他のライブラリとの競合リスクがある。プロダクションコードでは `Map` や純粋関数 `groupBy(arr, fn)` の利用を推奨。LeetCode の本問題は学習目的でこのパターンを要求している。 + +--- + +**Q3. TypeScript の Declaration Merging とは?** + +同名の `interface` を複数箇所で宣言すると TypeScript がマージしてくれる機能。`interface Array` にメソッドを追加することで、`Array.prototype` への実行時拡張を型レベルでも認識させられる。 + +--- + +**Q4. `for...of` ではなくインデックスループを使う理由は?** + +`for...of` はイテレータプロトコル(`Symbol.iterator`)を経由するため、インデックス直接アクセスより若干オーバーヘッドがある。`10^5` 要素規模ではその差が Runtime に現れることがある。 + +--- + +**Q5. メモリ使用量をさらに減らす方法はあるか?** + +本問題は結果に全要素への参照を格納するため O(n) は理論的下限。V8 の GC タイミングの影響が大きく、同一コードでも実行ごとに Memory の測定値がブレる。複数回提出して中央値で評価することを推奨する。 diff --git a/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README_react.html b/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README_react.html new file mode 100644 index 00000000..8550f6cc --- /dev/null +++ b/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README_react.html @@ -0,0 +1,1733 @@ + + + + + + LeetCode 2631 – Group By | Prototype Extension + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ アルゴリズム概要 +

+ +
+
+
O(n)
+
時間計算量
+
+
+
O(n)
+
空間計算量
+
+
+
0 〜 10⁵
+
配列サイズ
+
+
+
string
+
fnの戻り値型
+
+
+ +
+
+

問題の要約

+

+ Array.prototype + に + groupBy(fn) + メソッドを追加する。 コールバック + fn(item) → string + を各要素に適用し、 同じキーを返した要素を同じ配列にまとめた + Record<string, T[]> + を返す。 +

+
+

+ 入出力例 +

+
+[{id:"1"},{id:"1"},{id:"2"}]
+  .groupBy(item => item.id)
+
+→ {
+    "1": [{id:"1"}, {id:"1"}],
+    "2": [{id:"2"}]
+  }
+
+
+ +
+

最適化のポイント

+
    +
  • + + reduce → for ループ:スタックフレームを n 回生成しない +
  • +
  • + + length キャッシュ:毎ループの this.length 参照を排除 +
  • +
  • + + in 演算子 vs ??=:キーの存在確認におけるセマンティクスの違い(Object.create(null)との併用) +
  • +
  • + + Object.create(null):プロトタイプ汚染を防ぐ純粋ハッシュマップ +
  • +
+
+
+
+ + +
+

+ ステップバイステップ解説 +

+
+
+ + +
+

+ 最適化 Before / After 比較 +

+
+
+ + +
+

+ TypeScript 実装(最適化版) +

+
// Declaration Merging: Array<T> に groupBy を追加
+interface Array<T> {
+    groupBy(fn: (item: T) => string): Record<string, T[]>;
+}
+
+Array.prototype.groupBy = function<T>(
+    this: T[],
+    fn: (item: T) => string
+): Record<string, T[]> {
+    // ① Object.create(null) でプロトタイプ汚染を防ぐ純粋ハッシュマップを生成
+    const result: Record<string, T[]> = Object.create(null);
+
+    // ② length をキャッシュしてプロパティ参照コストを削減
+    const len = this.length;
+
+    for (let i = 0; i < len; i++) {
+        const item = this[i];   // ③ this 参照を1回に抑制
+        const key = fn(item);   // コールバックでキーを生成
+
+        // ④ in 演算子: プロトタイプなしオブジェクトのキー存在確認
+        if (key in result) {
+            result[key].push(item);  // キー存在: 既存配列へ追加
+        } else {
+            result[key] = [item];    // キー不在: 新規配列を生成
+        }
+    }
+
+    return result;
+};
+
+/**
+ * 使用例
+ * [1,2,3].groupBy(String)
+ * // => {"1":[1], "2":[2], "3":[3]}
+ *
+ * [6,7,1,2].groupBy(n => String(n > 5))
+ * // => {"true":[6,7], "false":[1,2]}
+ */
+
+ + +
+

+ 処理フローチャート +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + groupBy(fn) 開始 + + + + + + + + + 初期化 + + + + result=Object.create(null) / i=0 / len=this.length + + + + + + + + + i < len ? + + + + + + + + No (ループ終了) + + + + + + Yes + + + + + + 要素とキーを取得 + + + + item = this[i] / key = fn(item) + + + + + + + + + key in result ? + + + + + + + + + Yes + + + + + + + + + No + + + + + + result[key] = [item] + + + + + + result[key].push(item) + + + + + + + + + + + + i++ + + + + + + + + + + + ループバック + + + + + + result を返却 + + + Record<string, T[]> + + + + + + + + + 終了 + + +
+

+ フローの説明:
+ 1. + Object.create(null) + でプロトタイプなしの純粋ハッシュマップを初期化し、len + をキャッシュ
+ 2. + i < len + の間ループ(スタックフレームは1つ)。No → 右バイパス(赤)で返却へスキップ
+ 3. + fn(item) でキーを生成し + in + 演算子で存在確認。Yes → push / + No → 新規配列
+ 4. 両分岐が合流し + i++ 後に紫矢印でループ先頭へ戻る
+ 5. 全要素処理後に + result を返却して終了 +

+
+ + +
+

+ 計算量分析 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 観点 + 計算量 + 詳細 +
時間計算量 + O(n) + + 全要素を1回走査。fn が O(1) であることが前提 +
空間計算量 + O(n) + + 全要素への参照を result に格納(理論的下限) +
ハッシュマップ参照 + O(1) amortized + + V8 エンジンのハッシュテーブル実装による +
push 操作 + O(1) amortized + + 配列の動的拡張(倍増アルゴリズム)による +
+
+ +
+
+

reduce

+

+ n 回のコールバック生成
スタックフレーム × n +

+

Memory Beats ~40%

+
+
+
+ 推奨 +
+

for ループ

+

+ スタックフレーム 1 つ
length キャッシュ有効 +

+

Memory Beats ~70%+

+
+
+

for...of

+

+ Iterator プロトコル経由
Symbol.iterator オーバーヘッド +

+

中間的なパフォーマンス

+
+
+
+ + + + + +
+ LeetCode 2631 – Group By / TypeScript 解説ページ / Node.js v22.14.0 ESM +
+ + diff --git a/generate_index.py b/generate_index.py index 4c788dbb..1521a261 100644 --- a/generate_index.py +++ b/generate_index.py @@ -231,7 +231,7 @@ def generate_index(self) -> None: } total_count = sum(len(v) for v in structure.values()) domain_count = len(sorted_categories) - current_time = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") + current_time = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d") # HTML Template — "Refined Lab" デザイン html_template = """ diff --git a/public/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README_react.html b/public/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README_react.html new file mode 100644 index 00000000..34ca6dbc --- /dev/null +++ b/public/JavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README_react.html @@ -0,0 +1,1733 @@ + + + + + + LeetCode 2631 – Group By | Prototype Extension + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ アルゴリズム概要 +

+ +
+
+
O(n)
+
時間計算量
+
+
+
O(n)
+
空間計算量
+
+
+
0 〜 10⁵
+
配列サイズ
+
+
+
string
+
fnの戻り値型
+
+
+ +
+
+

問題の要約

+

+ Array.prototype + に + groupBy(fn) + メソッドを追加する。 コールバック + fn(item) → string + を各要素に適用し、 同じキーを返した要素を同じ配列にまとめた + Record<string, T[]> + を返す。 +

+
+

+ 入出力例 +

+
+[{id:"1"},{id:"1"},{id:"2"}]
+  .groupBy(item => item.id)
+
+→ {
+    "1": [{id:"1"}, {id:"1"}],
+    "2": [{id:"2"}]
+  }
+
+
+ +
+

最適化のポイント

+
    +
  • + + reduce → for ループ:スタックフレームを n 回生成しない +
  • +
  • + + length キャッシュ:毎ループの this.length 参照を排除 +
  • +
  • + + in 演算子 vs ??=:キーの存在確認におけるセマンティクスの違い(Object.create(null)との併用) +
  • +
  • + + Object.create(null):プロトタイプ汚染を防ぐ純粋ハッシュマップ +
  • +
+
+
+
+ + +
+

+ ステップバイステップ解説 +

+
+
+ + +
+

+ 最適化 Before / After 比較 +

+
+
+ + +
+

+ TypeScript 実装(最適化版) +

+
// Declaration Merging: Array<T> に groupBy を追加
+interface Array<T> {
+    groupBy(fn: (item: T) => string): Record<string, T[]>;
+}
+
+Array.prototype.groupBy = function<T>(
+    this: T[],
+    fn: (item: T) => string
+): Record<string, T[]> {
+    // ① Object.create(null) でプロトタイプ汚染を防ぐ純粋ハッシュマップを生成
+    const result: Record<string, T[]> = Object.create(null);
+
+    // ② length をキャッシュしてプロパティ参照コストを削減
+    const len = this.length;
+
+    for (let i = 0; i < len; i++) {
+        const item = this[i];   // ③ this 参照を1回に抑制
+        const key = fn(item);   // コールバックでキーを生成
+
+        // ④ in 演算子: プロトタイプなしオブジェクトのキー存在確認
+        if (key in result) {
+            result[key].push(item);  // キー存在: 既存配列へ追加
+        } else {
+            result[key] = [item];    // キー不在: 新規配列を生成
+        }
+    }
+
+    return result;
+};
+
+/**
+ * 使用例
+ * [1,2,3].groupBy(String)
+ * // => {"1":[1], "2":[2], "3":[3]}
+ *
+ * [6,7,1,2].groupBy(n => String(n > 5))
+ * // => {"true":[6,7], "false":[1,2]}
+ */
+
+ + +
+

+ 処理フローチャート +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + groupBy(fn) 開始 + + + + + + + + + 初期化 + + + + result=Object.create(null) / i=0 / len=this.length + + + + + + + + + i < len ? + + + + + + + + No (ループ終了) + + + + + + Yes + + + + + + 要素とキーを取得 + + + + item = this[i] / key = fn(item) + + + + + + + + + key in result ? + + + + + + + + + Yes + + + + + + + + + No + + + + + + result[key] = [item] + + + + + + result[key].push(item) + + + + + + + + + + + + i++ + + + + + + + + + + + ループバック + + + + + + result を返却 + + + Record<string, T[]> + + + + + + + + + 終了 + + +
+

+ フローの説明:
+ 1. + Object.create(null) + でプロトタイプなしの純粋ハッシュマップを初期化し、len + をキャッシュ
+ 2. + i < len + の間ループ(スタックフレームは1つ)。No → 右バイパス(赤)で返却へスキップ
+ 3. + fn(item) でキーを生成し + in + 演算子で存在確認。Yes → push / + No → 新規配列
+ 4. 両分岐が合流し + i++ 後に紫矢印でループ先頭へ戻る
+ 5. 全要素処理後に + result を返却して終了 +

+
+ + +
+

+ 計算量分析 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 観点 + 計算量 + 詳細 +
時間計算量 + O(n) + + 全要素を1回走査。fn が O(1) であることが前提 +
空間計算量 + O(n) + + 全要素への参照を result に格納(理論的下限) +
ハッシュマップ参照 + O(1) amortized + + V8 エンジンのハッシュテーブル実装による +
push 操作 + O(1) amortized + + 配列の動的拡張(倍増アルゴリズム)による +
+
+ +
+
+

reduce

+

+ n 回のコールバック生成
スタックフレーム × n +

+

Memory Beats ~40%

+
+
+
+ 推奨 +
+

for ループ

+

+ スタックフレーム 1 つ
length キャッシュ有効 +

+

Memory Beats ~70%+

+
+
+

for...of

+

+ Iterator プロトコル経由
Symbol.iterator オーバーヘッド +

+

中間的なパフォーマンス

+
+
+
+ + + + + +
+ LeetCode 2631 – Group By / TypeScript 解説ページ / Node.js v22.14.0 ESM +
+ + diff --git a/public/index.html b/public/index.html index 81ed2514..dd07e2f6 100644 --- a/public/index.html +++ b/public/index.html @@ -416,7 +416,7 @@

🧪 Algorithm Study Index

-

156 interactive lessons across 6 domains

+

157 interactive lessons across 6 domains

@@ -431,12 +431,12 @@

- + @@ -577,6 +577,7 @@

  • 📜LeetCode 2625 - Flatten Deeply Nested Array | 再帰的配列平坦化JavaScript/2625. Flatten Deeply Nested Array/Claude Code Sonnet 4.5 extended/README_react.html
  • 📜LeetCode 2629 - Function CompositionJavaScript/2629. Function Composition/Claude Code Sonnet 4.6 extended/README_react.html
  • 📜LeetCode 2630 - Memoize II | ネストMap トライ構造JavaScript/2630. Memoize II/Claude Code Sonnet 4.6 extended/README_react.html
  • +
  • 📜LeetCode 2631 – Group By | Prototype ExtensionJavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README_react.html
  • 📜LeetCode: Snail Traversal - 蛇行パターンで1D→2D配列変換JavaScript/2624. Snail Traversal/Claude Code Sonnet 4.5/README_react.html
  • 📜Sleep - 非同期スリープ関数の実装JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README_react.html
  • 📜Time Limited Cache - 有効期限付きキャッシュ | LeetCode解説JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README_react.html
  • @@ -755,6 +756,7 @@

  • 📜LeetCode 2625 - Flatten Deeply Nested Array | 再帰的配列平坦化JavaScript/2625. Flatten Deeply Nested Array/Claude Code Sonnet 4.5 extended/README_react.html
  • 📜LeetCode 2629 - Function CompositionJavaScript/2629. Function Composition/Claude Code Sonnet 4.6 extended/README_react.html
  • 📜LeetCode 2630 - Memoize II | ネストMap トライ構造JavaScript/2630. Memoize II/Claude Code Sonnet 4.6 extended/README_react.html
  • +
  • 📜LeetCode 2631 – Group By | Prototype ExtensionJavaScript/2631. Group By/Claude Code Sonnet 4.6 extended/README_react.html
  • 📜LeetCode: Snail Traversal - 蛇行パターンで1D→2D配列変換JavaScript/2624. Snail Traversal/Claude Code Sonnet 4.5/README_react.html
  • 📜Sleep - 非同期スリープ関数の実装JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README_react.html
  • 📜Time Limited Cache - 有効期限付きキャッシュ | LeetCode解説JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README_react.html
  • @@ -795,7 +797,7 @@

    🧪 - Generated on 2026-02-27 05:05:08 UTC + Generated on 2026-02-28