diff --git a/.agent/skills/code-review-guidelines/SKILL.md b/.agent/skills/code-review-guidelines/SKILL.md new file mode 100644 index 00000000..f2fad76a --- /dev/null +++ b/.agent/skills/code-review-guidelines/SKILL.md @@ -0,0 +1,92 @@ +--- +name: code-review-guidelines +description: > + Applies accumulated project-specific codebase review rules and best practices. + Use when generating, refactoring, or reviewing frontend HTML, rendering logic, or Python generation scripts (like generate_index.py). +--- + +# Code Review Guidelines Skill + +## Goal + +To maintain security, robustness, idempotency, and technical accuracy across the project's frontend and generator code, based on accumulated review feedback. + +## Instructions + +Whenever you write, refactor, or review code for this project, you **MUST** ensure the following rules are met: + +### 1. Security & XSS Prevention + +- Never use `innerHTML` in templates or frontend JavaScript. Always use `textContent` and DOM APIs. +- When embedding variables into HTML attributes (e.g., `href`, `class`) or text from backend/generator scripts (like `generate_index.py`), you **MUST** escape them using native escaping functions (e.g., `html.escape(..., quote=True)` in Python). + +### 2. Asset Management & CDN Links + +- **Regex Replacing:** When rewriting CDN URLs to local `/vendor/...` paths, use robust regex patterns (e.g., matching `@` segments) rather than literal string replacements. +- **SRI Stripping:** If an asset link is changed from an external CDN to a local `/vendor/` path, you **MUST** strip any `integrity="..."` and `crossorigin="..."` attributes from the corresponding ` - - - - - - + + + + + + + + + + - - + + + + + + + + + + + + + + + + +
+ + + + +
+

+ アルゴリズム概要 +

+

+ Transactions + テーブルから、 + 月(YYYY-MM)× 国 + の組み合わせごとに以下の4指標を集計する問題です。 +

+ +
+
+
+ trans_count +
+
全トランザクション数
+
+
+
+ approved_count +
+
承認件数
+
+
+
+ trans_total_amount +
+
全合計金額
+
+
+
+ approved_total_amount +
+
承認合計金額
+
+
+ +

入出力例

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ id + + country + + state + + amount + + trans_date +
121US + approved + 10002018-12-18
122US + declined + 20002018-12-19
123US + approved + 20002019-01-01
124 + NULL + + approved + 20002019-01-07
+
+ +

⚠️ 落とし穴まとめ

+
+
+
❌ SUM の NULL 問題
+
+ 承認行が 0 件のグループで + SUM は + NULL を返す → + COALESCE(...,0) 必須 +
+
+
+
⚠️ country=NULL の扱い
+
+ SQL の GROUP BY は NULL + 値を1つのグループとして保持しますが、pandas の + groupby はデフォルトで NULL + キーを除外します +
+
+
+
❌ FILTER 句の環境差異
+
+ PostgreSQL 独自構文のため MySQL では動作しない。本問は PostgreSQL + 対応済み +
+
+
+
+ + +
+

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

+
+
+ + +
+

+ PostgreSQL 実装 +

+
SELECT
+    TO_CHAR(trans_date, 'YYYY-MM')                              AS month,
+    country,
+    COUNT(*)                                                     AS trans_count,
+    COUNT(*) FILTER (WHERE state = 'approved')                  AS approved_count,
+    SUM(amount)                                                  AS trans_total_amount,
+    COALESCE(SUM(amount) FILTER (WHERE state = 'approved'), 0)  AS approved_total_amount
+FROM Transactions
+GROUP BY
+    TO_CHAR(trans_date, 'YYYY-MM'),
+    country;
+ +
+
+
TO_CHAR(..., 'YYYY-MM')
+
+ 日付を YYYY-MM 文字列に変換。DATE_TRUNC より出力形式が直接的 +
+
+
+
FILTER (WHERE ...)
+
+ PostgreSQL 拡張。CASE WHEN より意図が明確で最適化しやすい +
+
+
+
COALESCE(..., 0)
+
+ 承認行が 0 件時に SUM が NULL → 0 になるのを防ぐ。★ 最重要修正点 +
+
+
+
+ + +
+

+ Pandas 実装(Python 3.10 / pandas 2.2.2) +

+
import pandas as pd
+
+def monthly_transactions(transactions: pd.DataFrame) -> pd.DataFrame:
+    """
+    Returns:
+        pd.DataFrame: 列名と順序は
+            [month, country, trans_count, approved_count,
+             trans_total_amount, approved_total_amount]
+    """
+    # ★ copy() 廃止: 必要列のみで軽量 DataFrame を新規構築
+    is_approved = (transactions['state'] == 'approved').astype('int8')  # int8 で省メモリ
+
+    tmp = pd.DataFrame({
+        'month'        : transactions['trans_date'].dt.to_period('M').astype(str),
+        'country'      : transactions['country'],
+        'id'           : transactions['id'],
+        'amount'       : transactions['amount'],
+        'is_approved'  : is_approved,
+        'approved_amt' : transactions['amount'] * is_approved,
+    })
+
+    out = (
+        tmp.groupby(['month', 'country'], sort=False, dropna=False)  # ★ dropna=False
+           .agg(
+               trans_count           = ('id',          'count'),
+               approved_count        = ('is_approved', 'sum'),
+               trans_total_amount    = ('amount',       'sum'),
+               approved_total_amount = ('approved_amt', 'sum'),
+           )
+           .reset_index()
+    )
+
+    # dtype を明示的に int に統一(NaN キー混在時の float64 混入を防ぐ)
+    out[['trans_count', 'approved_count',
+         'trans_total_amount', 'approved_total_amount']] = \
+        out[['trans_count', 'approved_count',
+             'trans_total_amount', 'approved_total_amount']].astype(int)
+
+    return out
+ +
+
+
★ 最重要: dropna=False
+
+ pandas の + groupby + はデフォルト + dropna=True で + country=NaN + のグループを無言で除外する。dropna=False + で NaN キーも1グループとして保持。 +
+
+
+
メモリ最適化
+
+ .astype('int8') + で承認フラグを 1 byte/行に圧縮(int64 比 87.5% 削減)。copy() + 廃止で全列複製コストをゼロに。 +
+
+
+
+ + +
+

+ 処理フローチャート +

+
+ + + + + + + + + + + + + + + 開始 + + + + + + + + + 月文字列を生成 + + + TO_CHAR(trans_date, 'YYYY-MM') + + + + + + + + 承認フラグ列を付与 + + + is_approved = (state == 'approved').astype('int8') + + + + + + + + GROUP BY month × country + + + dropna=False で NaN キーも保持 ★ + + + + + + + + + 4 指標を一括集計 + + + + + + COUNT(*) → trans_count + + + SUM(is_approved) → approved_count + + + SUM(amount) → trans_total_amount + + + SUM(approved_amt) → approved_total_amount + + + + + + + + NULL / dtype を統一 + + + COALESCE(..., 0) / .astype(int) + + + + + + + + reset_index() で列に昇格 + + + + + + + + + 出力 DataFrame(6列) + + + month | country | trans_count | approved_count + + + trans_total_amount | approved_total_amount + + + + + + + + 終了 + + +
+

+ フローの説明:
+ 1. trans_date から + YYYY-MM + 形式の月文字列列を生成する
+ 2. + state == 'approved' の + boolean を + int8 + に変換し、フラグ列・金額列を付与する
+ 3. month × country で + GROUP BY。★ + dropna=False + で + country=NaN + を保持する
+ 4. + COUNT / SUM + で4指標を一括集計する
+ 5. + COALESCE / astype(int) + で NULL・dtype を統一する
+ 6. reset_index() で + month・country を通常列に昇格し返却する +

+
+ + +
+

+ 計算量分析 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ フェーズ + SQLPandas備考
月文字列生成 + O(N) + + O(N) + + ベクトル演算 +
+ GROUP BY / groupby.agg + + O(N) + + O(N) + + ハッシュ集計。G ≪ N なら線形近似 +
COALESCE / astype + O(G) + + O(G) + + G = 月×国のユニーク数 +
合計 + O(N) + + O(N) + + sort=False でソートコスト回避 +
+
+ +

手法比較

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
手法時間空間可読性 + NULL 安全 +
+ ✅ FILTER + COALESCE + O(N)O(G) + ◎ +
CASE WHENO(N)O(G) + 要 COALESCE +
サブクエリ結合O(N)O(G×2) + 要注意 +
pandas applyO(N×G)O(N)
+
+
+
+ + + + diff --git a/SQL/Leetcode/Intermediate Select/1193. Monthly Transactions I/Claude Sonnet 4.6 Extended/Monthly_Transactions_I_pandas.md b/SQL/Leetcode/Intermediate Select/1193. Monthly Transactions I/Claude Sonnet 4.6 Extended/Monthly_Transactions_I_pandas.md new file mode 100644 index 00000000..41be3b5e --- /dev/null +++ b/SQL/Leetcode/Intermediate Select/1193. Monthly Transactions I/Claude Sonnet 4.6 Extended/Monthly_Transactions_I_pandas.md @@ -0,0 +1,202 @@ +# Pandas 2.2.2用 + +## 0) 前提 + +- 環境: **Python 3.10.15 / pandas 2.2.2** +- **指定シグネチャ厳守**(関数名・引数名・返却列・順序) +- I/O 禁止、不要な `print` や `sort_values` 禁止 + +--- + +## 1) 問題 + +- 月・国ごとに、全トランザクション数・合計金額、承認済みトランザクション数・合計金額を集計する +- 入力 DF: + +``` +transactions: id(int), country(str|NaN), state(str: 'approved'|'declined'), + amount(int), trans_date(datetime) +``` + +- 出力: + +``` +month(str: 'YYYY-MM'), country(str|NaN), +trans_count(int), approved_count(int), +trans_total_amount(int), approved_total_amount(int) +``` + +--- + +## 2) 実装(指定シグネチャ厳守) + +> 原則は **月文字列生成 → 承認フラグ列付与 → `dropna=False` 付き groupby 集計 → dtype 統一**。 + +```python +# Analyze Complexity +# Runtime 383 ms +# Beats 75.97% +# Memory 69.39 MB +# Beats 48.55% +import pandas as pd + +def monthly_transactions(transactions: pd.DataFrame) -> pd.DataFrame: + """ + Returns: + pd.DataFrame: 列名と順序は + [month, country, trans_count, approved_count, + trans_total_amount, approved_total_amount] + """ + df = transactions.copy() + + # 月文字列列を生成(YYYY-MM) + df['month'] = df['trans_date'].dt.to_period('M').astype(str) + + # 承認フラグ列を付与(bool → int で SUM 可能、0件時も 0 を保証) + df['is_approved'] = (df['state'] == 'approved').astype(int) + df['approved_amt'] = df['amount'] * df['is_approved'] + + # groupby + agg で一括集計 + # ★ dropna=False: country=NaN のグループを除外しない + # ★ sort=False : ソートコストを排除(出力順序は任意) + out = ( + df.groupby(['month', 'country'], sort=False, dropna=False) + .agg( + trans_count = ('id', 'count'), + approved_count = ('is_approved', 'sum'), + trans_total_amount = ('amount', 'sum'), + approved_total_amount = ('approved_amt', 'sum'), + ) + .reset_index() + ) + + # dtype を明示的に int に統一(NaN キー混在時の float64 混入を防ぐ) + out[['trans_count', 'approved_count', + 'trans_total_amount', 'approved_total_amount']] = \ + out[['trans_count', 'approved_count', + 'trans_total_amount', 'approved_total_amount']].astype(int) + + return out +``` + +--- + +## 3) アルゴリズム説明 + +- **`dt.to_period('M').astype(str)`**: `trans_date` を `YYYY-MM` 文字列へ変換。`strftime('%Y-%m')` より Period 経由のほうが型安全 +- **`(state == 'approved').astype(int)`**: boolean を `0/1` に変換し、`sum` でカウントと金額を同時に集計。NULL 対策も不要 +- **`groupby(..., dropna=False)`**: ★最重要。デフォルト `dropna=True` では `country=NaN` のグループが**無言で脱落**する。`dropna=False` で NaN キーも1グループとして保持 +- **`groupby.agg` 名前付き集計**: `(output_col=(input_col, func))` 構文で列名整形を agg 内で完結、`rename` 不要 +- **NULL / 重複 / 型**: + +| 項目 | 対処 | +| ---------------------------- | --------------------------------- | +| `country=NaN` グループ脱落 | `dropna=False` で保持 | +| `approved_*` の float64 混入 | `.astype(int)` で明示統一 | +| `count` の NULL | `id` 列はキーのため NULL なし確定 | + +--- + +## 4) 計算量(概算) + +| フェーズ | 計算量 | 備考 | +| ----------------------------- | ------------- | --------------------------------- | +| `dt.to_period` / 列演算 | **O(N)** | ベクトル演算 | +| `groupby.agg`(ハッシュ集計) | **O(N)** 平均 | グループ数 G ≪ N なら実質線形 | +| `reset_index` / `astype` | **O(G)** | G = 月×国のユニーク数 | +| 全体 | **O(N)** | `sort=False` で O(N log N) を回避 | + +--- + +## 5) 図解(Mermaid 超保守版) + +```mermaid +flowchart TD + A[入力 transactions DataFrame] + B[dt.to_period でmonth列を生成] + C[is_approved と approved_amt 列を付与] + D[groupby month country dropna=False sort=False] + E[agg で4指標を一括算出] + F[reset_index で列に昇格] + G[astype int で dtype を統一] + H[出力 6列 month country trans_count approved_count trans_total_amount approved_total_amount] + + A --> B + B --> C + C --> D + D --> E + E --> F + F --> G + G --> H +``` + +## 改善ポイント分析 + +| 問題点 | 現状 | 改善策 | +| ------------------------- | ------------------------------ | ------------------------------------- | +| `copy()` で全列複製 | 全 DataFrame をメモリ複製 | 必要列のみの軽量 DataFrame を新規構築 | +| `is_approved` が `int64` | 8 bytes/要素 | `int8` に縮小(1 byte/要素) | +| 不要列が groupby まで残存 | `state`, `trans_date` 等が混在 | groupby 前に必要列のみに絞る | + +--- + +## 2) 実装(改善版) + +```python +# Analyze Complexity +# Runtime 371 ms +# Beats 86.45% +# Memory 69.30 MB +# Beats 58.06% + +import pandas as pd + +def monthly_transactions(transactions: pd.DataFrame) -> pd.DataFrame: + """ + Returns: + pd.DataFrame: 列名と順序は + [month, country, trans_count, approved_count, + trans_total_amount, approved_total_amount] + """ + # ★ copy() 廃止: 必要列のみで軽量 DataFrame を新規構築 + is_approved = (transactions['state'] == 'approved').astype('int8') # ★ int8 で省メモリ + + tmp = pd.DataFrame({ + 'month' : transactions['trans_date'].dt.to_period('M').astype(str), + 'country' : transactions['country'], + 'id' : transactions['id'], + 'amount' : transactions['amount'], + 'is_approved' : is_approved, + 'approved_amt' : transactions['amount'] * is_approved, # int8 × int → int + }) + + out = ( + tmp.groupby(['month', 'country'], sort=False, dropna=False) + .agg( + trans_count = ('id', 'count'), + approved_count = ('is_approved', 'sum'), + trans_total_amount = ('amount', 'sum'), + approved_total_amount = ('approved_amt', 'sum'), + ) + .reset_index() + ) + + out[['trans_count', 'approved_count', + 'trans_total_amount', 'approved_total_amount']] = \ + out[['trans_count', 'approved_count', + 'trans_total_amount', 'approved_total_amount']].astype(int) + + return out +``` + +--- + +## 改善効果の試算 + +| 指標 | 改善前 | 改善後(期待) | +| -------------------- | -------------------- | ----------------------------------- | +| `is_approved` メモリ | `int64`: 8 bytes/行 | `int8`: 1 bytes/行 → **87.5% 削減** | +| `copy()` コスト | 全列複製 O(N×全列数) | **ゼロ**(新規構築のみ) | +| groupby 対象列数 | 元 DataFrame の全列 | **6列のみ** | +| Memory 期待値 | 69.39 MB | **~55 MB 以下**(Beats 70%+ 期待) | +| Runtime 期待値 | 383 ms | **~300 ms 以下**(Beats 85%+ 期待) | diff --git a/SQL/Leetcode/Intermediate Select/1193. Monthly Transactions I/Claude Sonnet 4.6 Extended/Monthly_Transactions_I_postgresql.md b/SQL/Leetcode/Intermediate Select/1193. Monthly Transactions I/Claude Sonnet 4.6 Extended/Monthly_Transactions_I_postgresql.md new file mode 100644 index 00000000..1fbca5f7 --- /dev/null +++ b/SQL/Leetcode/Intermediate Select/1193. Monthly Transactions I/Claude Sonnet 4.6 Extended/Monthly_Transactions_I_postgresql.md @@ -0,0 +1,182 @@ +# PostgreSQL 16.6+ + +## 0) 前提 + +- エンジン: **PostgreSQL 16.6+** +- 並び順: 任意 +- `NOT IN` 回避(`EXISTS` / `LEFT JOIN ... IS NULL` を推奨) +- 判定は ID 基準、表示は仕様どおり + +--- + +## 1) 問題 + +- 月・国ごとに、全トランザクション数・合計金額、および承認済みトランザクション数・合計金額を集計する +- 入力: + +``` +Transactions(id, country, state ENUM['approved','declined'], amount, trans_date) +``` + +- 出力: + +| 列名 | 説明 | +| ----------------------- | -------------------- | +| `month` | `YYYY-MM` 形式の年月 | +| `country` | 国コード | +| `trans_count` | 全件数 | +| `approved_count` | 承認件数 | +| `trans_total_amount` | 全合計金額 | +| `approved_total_amount` | 承認合計金額 | + +--- + +## 2) 最適解(単一クエリ) + +> 条件集計は **`COUNT` / `SUM` + `FILTER`句** で一発 GROUP BY が最もシンプル・高速。 + +```sql +-- Wrong Answer +-- 5 / 16 testcases passed + +SELECT + TO_CHAR(trans_date, 'YYYY-MM') AS month, + country, + COUNT(*) AS trans_count, + COUNT(*) FILTER (WHERE state = 'approved') AS approved_count, + SUM(amount) AS trans_total_amount, + SUM(amount) FILTER (WHERE state = 'approved') AS approved_total_amount +FROM Transactions +GROUP BY + TO_CHAR(trans_date, 'YYYY-MM'), + country; +``` + +### 代替(CASE WHEN による条件集計) + +`FILTER` 句を使わない場合の標準 SQL 互換版: + +```sql +-- Runtime 423 ms +-- Beats 59.20% + +SELECT + TO_CHAR(trans_date, 'YYYY-MM') AS month, + country, + COUNT(*) AS trans_count, + COUNT(CASE WHEN state = 'approved' THEN 1 END) AS approved_count, + SUM(amount) AS trans_total_amount, + SUM(CASE WHEN state = 'approved' THEN amount ELSE 0 END) AS approved_total_amount +FROM Transactions +GROUP BY + TO_CHAR(trans_date, 'YYYY-MM'), + country; +``` + +--- + +## 3) 要点解説 + +| ポイント | 詳細 | +| ------------------------------------ | ------------------------------------------------------------------------------------------------------ | +| **`TO_CHAR(trans_date, 'YYYY-MM')`** | `DATE_TRUNC('month', ...)` でも可だが、文字列で `YYYY-MM` を直接得るにはこちらが簡潔 | +| **`COUNT(*) FILTER (WHERE ...)`** | PostgreSQL 独自の ANSI SQL:2003 拡張。`CASE WHEN` より読みやすく、オプティマイザにも意図が伝わりやすい | +| **`SUM(amount) FILTER (...)`** | 対象行が 0 件のとき **`NULL`** を返すため、必ず `COALESCE(..., 0)` で囲む必要がある | +| **GROUP BY のキー統一** | `SELECT` と `GROUP BY` の `TO_CHAR(...)` 式を完全一致させることが必須 | +| **インデックス戦略** | `(trans_date, country, state, amount)` の複合インデックスで Index-Only Scan が期待できる | + +--- + +## 4) 計算量(概算) + +| フェーズ | 計算量 | +| --------------------- | ---------------------------------------------- | +| テーブルフルスキャン | **O(N)** | +| GROUP BY ハッシュ集計 | **O(N)** 平均(グループ数 G が小さい場合) | +| ソートベース GROUP BY | **O(N log N)**(メモリ不足時のフォールバック) | +| インデックス使用時 | **O(N)** → **Index-Only Scan** で I/O 削減 | + +> N = Transactions 行数、G = (月×国) のユニーク組み合わせ数 + +--- + +## 5) 図解(Mermaid 超保守版) + +```mermaid +flowchart TD + A[入力 Transactions テーブル] + B[TO_CHAR で月文字列を生成] + C[country × month で GROUP BY] + D[COUNT と SUM で全件集計] + E[FILTER WHERE state=approved で条件集計] + F[出力 6列 month country trans_count approved_count trans_total_amount approved_total_amount] + + A --> B + B --> C + C --> D + C --> E + D --> F + E --> F +``` + +## 原因と修正 + +### 🔴 WA の真因:`SUM ... FILTER` の NULL 問題 + +```sql +-- 承認件数が 0 件のグループで NULL を返す ← これが WA の原因 +SUM(amount) FILTER (WHERE state = 'approved') +``` + +`SUM` は対象行が 0 件のとき **`0` ではなく `NULL`** を返します。`COUNT` は `0` を返すので問題ないですが、`SUM` は `COALESCE` が必要です。 + +--- + +## 修正版 + +```sql + +-- Runtime 415 ms +-- Beats 66.83% + +SELECT + TO_CHAR(trans_date, 'YYYY-MM') AS month, + country, + COUNT(*) AS trans_count, + COUNT(*) FILTER (WHERE state = 'approved') AS approved_count, + SUM(amount) AS trans_total_amount, + COALESCE(SUM(amount) FILTER (WHERE state = 'approved'), 0) AS approved_total_amount +FROM Transactions +GROUP BY + TO_CHAR(trans_date, 'YYYY-MM'), + country; +``` + +--- + +## Runtime 改善(CASE WHEN 版) + +```sql +-- Runtime 422 ms +-- Beats 60.26% + +SELECT + TO_CHAR(trans_date, 'YYYY-MM') AS month, + country, + COUNT(*) AS trans_count, + COUNT(CASE WHEN state = 'approved' THEN 1 END) AS approved_count, + SUM(amount) AS trans_total_amount, + COALESCE(SUM(CASE WHEN state = 'approved' THEN amount END), 0) AS approved_total_amount +FROM Transactions +GROUP BY 1, 2; -- 式の二重評価を避けるため位置参照に変更 +``` + +--- + +## 教訓まとめ + +| 関数 | 0件時の戻り値 | 対処 | +| -------------------- | ------------- | ------------------ | +| `COUNT(*)` | `0` | 不要 | +| `SUM(...) FILTER` | **`NULL`** | `COALESCE(..., 0)` | +| `SUM(CASE WHEN ...)` | **`NULL`** | `COALESCE(..., 0)` | diff --git a/generate_index.py b/generate_index.py index e9deb760..4c788dbb 100644 --- a/generate_index.py +++ b/generate_index.py @@ -53,6 +53,13 @@ def copy_vendor_files(self, output_dir: str) -> None: "node_modules/prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard.js", ] + prism_langs = [ + 'python', 'javascript', 'typescript', 'sql', 'java', 'c', 'cpp', 'csharp', 'bash', 'json', 'clike', + 'css', 'markup', 'go', 'rust', 'ruby', 'swift', 'php' + ] + for lang in prism_langs: + prism_plugins.append(f"node_modules/prismjs/components/prism-{lang}.min.js") + for src in prism_plugins: if os.path.exists(src): rel_path = os.path.relpath(src, "node_modules/prismjs") @@ -78,32 +85,14 @@ def copy_vendor_files(self, output_dir: str) -> None: def rewrite_html_content(self, content: str) -> str: """ - Replace known CDN asset URLs in the HTML with local `/vendor/` paths and remove `integrity`/`crossorigin` attributes from tags that reference `/vendor/`. - + Rewrite HTML to replace known CDN asset URLs with local `/vendor/` paths and remove SRI and crossorigin attributes from tags that reference those local assets. + Parameters: content (str): HTML document content to rewrite. - + Returns: str: HTML content with matching CDN URLs substituted by local `/vendor/` URLs and `integrity`/`crossorigin` attributes removed from tags that reference `/vendor/`. """ - """ - Remove `integrity` and `crossorigin` attributes from a matched `` or ` - - - - - - + + + + + + + + + + - - - - - - + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+

+ アルゴリズム概要 +

+

+ Transactions + テーブルから、 + 月(YYYY-MM)× 国 + の組み合わせごとに以下の4指標を集計する問題です。 +

+ +
+
+
+ trans_count +
+
全トランザクション数
+
+
+
+ approved_count +
+
承認件数
+
+
+
+ trans_total_amount +
+
全合計金額
+
+
+
+ approved_total_amount +
+
承認合計金額
+
+
+ +

入出力例

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ id + + country + + state + + amount + + trans_date +
121US + approved + 10002018-12-18
122US + declined + 20002018-12-19
123US + approved + 20002019-01-01
124 + NULL + + approved + 20002019-01-07
+
+ +

⚠️ 落とし穴まとめ

+
+
+
❌ SUM の NULL 問題
+
+ 承認行が 0 件のグループで + SUM は + NULL を返す → + COALESCE(...,0) 必須 +
+
+
+
⚠️ country=NULL の扱い
+
+ SQL の GROUP BY は NULL + 値を1つのグループとして保持しますが、pandas の + groupby はデフォルトで NULL + キーを除外します +
+
+
+
❌ FILTER 句の環境差異
+
+ PostgreSQL 独自構文のため MySQL では動作しない。本問は PostgreSQL + 対応済み +
+
+
+
+ + +
+

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

+
+
+ + +
+

+ PostgreSQL 実装 +

+
SELECT
+    TO_CHAR(trans_date, 'YYYY-MM')                              AS month,
+    country,
+    COUNT(*)                                                     AS trans_count,
+    COUNT(*) FILTER (WHERE state = 'approved')                  AS approved_count,
+    SUM(amount)                                                  AS trans_total_amount,
+    COALESCE(SUM(amount) FILTER (WHERE state = 'approved'), 0)  AS approved_total_amount
+FROM Transactions
+GROUP BY
+    TO_CHAR(trans_date, 'YYYY-MM'),
+    country;
+ +
+
+
TO_CHAR(..., 'YYYY-MM')
+
+ 日付を YYYY-MM 文字列に変換。DATE_TRUNC より出力形式が直接的 +
+
+
+
FILTER (WHERE ...)
+
+ PostgreSQL 拡張。CASE WHEN より意図が明確で最適化しやすい +
+
+
+
COALESCE(..., 0)
+
+ 承認行が 0 件時に SUM が NULL → 0 になるのを防ぐ。★ 最重要修正点 +
+
+
+
+ + +
+

+ Pandas 実装(Python 3.10 / pandas 2.2.2) +

+
import pandas as pd
+
+def monthly_transactions(transactions: pd.DataFrame) -> pd.DataFrame:
+    """
+    Returns:
+        pd.DataFrame: 列名と順序は
+            [month, country, trans_count, approved_count,
+             trans_total_amount, approved_total_amount]
+    """
+    # ★ copy() 廃止: 必要列のみで軽量 DataFrame を新規構築
+    is_approved = (transactions['state'] == 'approved').astype('int8')  # int8 で省メモリ
+
+    tmp = pd.DataFrame({
+        'month'        : transactions['trans_date'].dt.to_period('M').astype(str),
+        'country'      : transactions['country'],
+        'id'           : transactions['id'],
+        'amount'       : transactions['amount'],
+        'is_approved'  : is_approved,
+        'approved_amt' : transactions['amount'] * is_approved,
+    })
+
+    out = (
+        tmp.groupby(['month', 'country'], sort=False, dropna=False)  # ★ dropna=False
+           .agg(
+               trans_count           = ('id',          'count'),
+               approved_count        = ('is_approved', 'sum'),
+               trans_total_amount    = ('amount',       'sum'),
+               approved_total_amount = ('approved_amt', 'sum'),
+           )
+           .reset_index()
+    )
+
+    # dtype を明示的に int に統一(NaN キー混在時の float64 混入を防ぐ)
+    out[['trans_count', 'approved_count',
+         'trans_total_amount', 'approved_total_amount']] = \
+        out[['trans_count', 'approved_count',
+             'trans_total_amount', 'approved_total_amount']].astype(int)
+
+    return out
+ +
+
+
★ 最重要: dropna=False
+
+ pandas の + groupby + はデフォルト + dropna=True で + country=NaN + のグループを無言で除外する。dropna=False + で NaN キーも1グループとして保持。 +
+
+
+
メモリ最適化
+
+ .astype('int8') + で承認フラグを 1 byte/行に圧縮(int64 比 87.5% 削減)。copy() + 廃止で全列複製コストをゼロに。 +
+
+
+
+ + +
+

+ 処理フローチャート +

+
+ + + + + + + + + + + + + + + 開始 + + + + + + + + + 月文字列を生成 + + + TO_CHAR(trans_date, 'YYYY-MM') + + + + + + + + 承認フラグ列を付与 + + + is_approved = (state == 'approved').astype('int8') + + + + + + + + GROUP BY month × country + + + dropna=False で NaN キーも保持 ★ + + + + + + + + + 4 指標を一括集計 + + + + + + COUNT(*) → trans_count + + + SUM(is_approved) → approved_count + + + SUM(amount) → trans_total_amount + + + SUM(approved_amt) → approved_total_amount + + + + + + + + NULL / dtype を統一 + + + COALESCE(..., 0) / .astype(int) + + + + + + + + reset_index() で列に昇格 + + + + + + + + + 出力 DataFrame(6列) + + + month | country | trans_count | approved_count + + + trans_total_amount | approved_total_amount + + + + + + + + 終了 + + +
+

+ フローの説明:
+ 1. trans_date から + YYYY-MM + 形式の月文字列列を生成する
+ 2. + state == 'approved' の + boolean を + int8 + に変換し、フラグ列・金額列を付与する
+ 3. month × country で + GROUP BY。★ + dropna=False + で + country=NaN + を保持する
+ 4. + COUNT / SUM + で4指標を一括集計する
+ 5. + COALESCE / astype(int) + で NULL・dtype を統一する
+ 6. reset_index() で + month・country を通常列に昇格し返却する +

+
+ + +
+

+ 計算量分析 +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ フェーズ + SQLPandas備考
月文字列生成 + O(N) + + O(N) + + ベクトル演算 +
+ GROUP BY / groupby.agg + + O(N) + + O(N) + + ハッシュ集計。G ≪ N なら線形近似 +
COALESCE / astype + O(G) + + O(G) + + G = 月×国のユニーク数 +
合計 + O(N) + + O(N) + + sort=False でソートコスト回避 +
+
+ +

手法比較

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
手法時間空間可読性 + NULL 安全 +
+ ✅ FILTER + COALESCE + O(N)O(G) + ◎ +
CASE WHENO(N)O(G) + 要 COALESCE +
サブクエリ結合O(N)O(G×2) + 要注意 +
pandas applyO(N×G)O(N)
+
+
+
+ + + + diff --git a/public/index.html b/public/index.html index a64fcaa8..7a29168b 100644 --- a/public/index.html +++ b/public/index.html @@ -416,7 +416,7 @@

🧪 Algorithm Study Index

-

154 interactive lessons across 6 domains

+

155 interactive lessons across 6 domains

@@ -431,14 +431,14 @@

- +
@@ -597,6 +597,7 @@

  • 📐文字列掛け算アルゴリズムの詳細解析Mathematics/Multiply Strings/leetcode/43. Multiply Strings/Claude/README.html
  • 🗃️LeetCode 1174: Immediate Food Delivery II - グループ内最小値抽出SQL/Leetcode/Intermediate Select/1174. Immediate Food Delivery II/Claude Sonnet 4.5 Extended/Immediate_Food_Delivery_II.html
  • 🗃️LeetCode 1179 · Reformat Department TableSQL/Leetcode/Basic select/1179. Reformat Department Table/Claude Sonnet 4.6 Extended/README.html
  • +
  • 🗃️LeetCode 1193 - Monthly Transactions ISQL/Leetcode/Intermediate Select/1193. Monthly Transactions I/Claude Sonnet 4.6 Extended/Monthly_Transactions_I.html
  • 🗃️Product Prices - 価格履歴管理 | Pandas解説SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date.html
  • @@ -783,6 +784,7 @@

    🔎No results found
    @@ -791,7 +793,7 @@

    🧪 - Generated on 2026-02-26 06:30:58 UTC + Generated on 2026-02-26 12:42:37 UTC