diff --git a/SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date.html b/SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date.html new file mode 100644 index 00000000..d87f0a54 --- /dev/null +++ b/SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date.html @@ -0,0 +1,2668 @@ + + + + + + Product Prices - 価格履歴管理 | Pandas解説 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+

+ アルゴリズム概要 +

+ +
+

+ 問題: Products + テーブルには製品の価格変更履歴が記録されています。 すべての製品は初期価格 10 + でスタートし、change_date + に新しい価格 + new_price + に変更されます。 + 2019-08-16 時点での全製品の価格を求めてください。 +

+ +
+

入力例

+
Products table:
++------------+-----------+-------------+
+| product_id | new_price | change_date |
++------------+-----------+-------------+
+| 1          | 20        | 2019-08-14  |
+| 2          | 50        | 2019-08-14  |
+| 1          | 30        | 2019-08-15  |
+| 1          | 35        | 2019-08-16  |
+| 2          | 65        | 2019-08-17  |
+| 3          | 20        | 2019-08-18  |
++------------+-----------+-------------+
+
+ +
+

出力例

+
+------------+-------+
+| product_id | price |
++------------+-------+
+| 1          | 35    |
+| 2          | 50    |
+| 3          | 10    |
++------------+-------+
+
+ +
+

解法の戦略

+
    +
  • + ステップ1: 対象日 + (2019-08-16) 以前のデータのみにフィルタ +
  • +
  • + ステップ2: + groupby('product_id')['change_date'].idxmax() + で各製品の最新変更日のインデックスを取得 +
  • +
  • + ステップ3: + 全製品リストを生成(重複削除) +
  • +
  • + ステップ4: + map() で高速結合 +
  • +
  • + ステップ5: + fillna(10) + で価格変更履歴がない製品にデフォルト値を設定 +
  • +
+
+ +
+

主要ポイント

+
    +
  • + 時間計算量: + O(N) + - Nは全レコード数 +
  • +
  • + 空間計算量: + O(M) + - Mはユニーク製品数 +
  • +
  • + 最適化手法: + idxmax() によるインデックスベースの抽出で、ソート不要 +
  • +
  • + 高速結合: map() は + merge() より高速(単一キー時) +
  • +
+
+
+
+ + +
+

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

+
+
+ + +
+

+ Python実装 +

+
import pandas as pd
+
+def price_at_given_date(products: pd.DataFrame) -> pd.DataFrame:
+    """
+    2019-08-16時点での全製品の価格を算出
+
+    Parameters
+    ----------
+    products : pd.DataFrame
+        Columns: product_id, new_price, change_date
+
+    Returns
+    -------
+    pd.DataFrame
+        Columns: product_id, price
+    """
+
+    # --- 対象日以前のデータのみ抽出
+    target_date = '2019-08-16'
+    before_target = products[products['change_date'] <= target_date]
+
+    # --- 各製品の最新価格を取得(groupby + idxmax)
+    if not before_target.empty:
+        latest_idx = before_target.groupby('product_id')['change_date'].idxmax()
+        latest_prices = before_target.loc[latest_idx, ['product_id', 'new_price']]
+    else:
+        latest_prices = pd.DataFrame(columns=['product_id', 'new_price'])
+
+    # --- 全製品リストを生成
+    all_products = products[['product_id']].drop_duplicates()
+
+    # --- 軽量結合(map優先)
+    price_mapper = latest_prices.set_index('product_id')['new_price']
+
+    out = pd.DataFrame({
+        'product_id': all_products['product_id'],
+        'price': all_products['product_id'].map(price_mapper).fillna(10).astype(int)
+    })
+
+    return out
+
+
+# テストデータ
+products = pd.DataFrame({
+    'product_id': [1, 2, 1, 1, 2, 3],
+    'new_price': [20, 50, 30, 35, 65, 20],
+    'change_date': pd.to_datetime([
+        '2019-08-14', '2019-08-14', '2019-08-15',
+        '2019-08-16', '2019-08-17', '2019-08-18'
+    ])
+})
+
+result = price_at_given_date(products)
+print(result)
+
+# 出力:
+#    product_id  price
+# 0           1     35
+# 1           2     50
+# 2           3     10
+
+ + +
+

+ フローチャート +

+
+ + + + + + + + + + + + + + + + + 開始 + + + + + + 入力読み込み + + + products DataFrame + + + + + + + 対象日フィルタ + + + change_date + + + <= 2019-08-16 + + + + + + + データあり? + + + empty check + + + + + + はい + + + + + + いいえ + + + + + + + groupby + idxmax + + + 各製品の最新日付 + + + インデックスを取得 + + + latest_idx + + + + + + loc で行抽出 + + + latest_prices + + + + + + + 空DataFrame + + + 作成 + + + latest_prices + + + = empty + + + + + + 全製品リスト生成 + + + drop_duplicates() + + + + + + + + + + + + + + + map 結合 + + + set_index + map + + + + + + + fillna(10) + + + デフォルト価格設定 + + + + + + + 終了 + + + +
+ +

+ フローの説明:
+ 1. 入力読み込み: products DataFrame + を受け取る
+ 2. 対象日フィルタ: change_date <= + 2019-08-16 の条件でフィルタ
+ 3. データ存在確認: + フィルタ後のデータが空でないかチェック
+ 4a. はい: groupby + idxmax + で各製品の最新日付のインデックスを取得 → loc で行抽出
+ 4b. いいえ: 空の latest_prices DataFrame + を作成
+ 5. 全製品リスト生成: 元データから + product_id をユニーク化
+ 6. map結合: set_index で辞書化し、map() + で高速マッピング
+ 7. fillna(10): + 価格変更履歴がない製品にデフォルト値 10 を設定
+ 8. 終了: 結果を返却 +

+
+ + +
+

+ 計算量分析 +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 処理 + + 計算量 + + 備考 +
+ フィルタ + + O(N) + + ブール索引で全行をスキャン +
+ groupby + idxmax + + O(N) + + ハッシュテーブル構築 + 各グループで最大値探索 +
+ loc 抽出 + + O(M) + + M = ユニーク製品数、インデックスベースで高速 +
+ drop_duplicates + + O(N) + + ハッシュセットで重複削除 +
+ map + + O(M) + + 辞書ルックアップ、merge より高速 +
+ 合計 + + O(N) + + N = 全レコード数 +
+
+ +
+

代替手法との比較

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 手法 + + 時間 + + 空間 + + メリット +
+ 本実装(idxmax) + + O(N) + + O(M) + + ソート不要、最速 +
+ sort + first() + + O(N log N) + + O(N) + + 直感的だが遅い +
+ merge ベース + + O(N) + + O(N) + + メモリ消費大 +
+
+ +
+

最適化のポイント

+
    +
  • + idxmax() の優位性: + ソートせずに各グループの最大値インデックスを取得できるため、O(N log N) + を回避 +
  • +
  • + map() の高速性: + 単一キーの結合では merge() より高速。辞書ルックアップ O(1) を利用 +
  • +
  • + メモリ効率: + 中間DataFrameは最小限の列のみ保持。latest_prices は M 行のみ +
  • +
  • + スケーラビリティ: + 製品数が増えても線形時間で処理可能 +
  • +
+
+
+
+ + + + + + + diff --git a/SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date_pandas.md b/SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date_pandas.md new file mode 100644 index 00000000..bd3e34c0 --- /dev/null +++ b/SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date_pandas.md @@ -0,0 +1,235 @@ +# Pandas 2.2.2 用(Notebook安定版) + +## ⭐ 0) 前提 + +- 環境: **Python 3.10.15 / pandas 2.2.2** +- 指定シグネチャ厳守 +- IO禁止、print禁止、不要sort禁止 + +--- + +## ⭐ 1) 問題 + +- **2019-08-16 時点での全製品の価格を求める** + - 初期価格は全製品 10 + - Products には価格変更履歴が記録 + +- 入力 DF: `products` (product_id, new_price, change_date) +- 出力: `product_id, price` + +--- + +## ⭐ 2) 実装(指定シグネチャ厳守) + +### 🎯 Pandas最適処理順 + +``` +対象日フィルタ +↓ +groupby + idxmax で最新抽出 +↓ +全製品リスト生成 +↓ +map結合 + fillna(10) +``` + +### 💎 最適実装 + +```python +import pandas as pd + +def price_at_given_date(products: pd.DataFrame) -> pd.DataFrame: + + # --- 対象日以前のデータのみ抽出 + target_date = '2019-08-16' + before_target = products[products['change_date'] <= target_date] + + # --- 各製品の最新価格を取得(groupby + idxmax) + if not before_target.empty: + latest_idx = before_target.groupby('product_id')['change_date'].idxmax() + latest_prices = before_target.loc[latest_idx, ['product_id', 'new_price']] + else: + latest_prices = pd.DataFrame(columns=['product_id', 'new_price']) + + # --- 全製品リストを生成 + all_products = products[['product_id']].drop_duplicates() + + # --- 軽量結合(map優先) + price_mapper = latest_prices.set_index('product_id')['new_price'] + + out = pd.DataFrame({ + 'product_id': all_products['product_id'], + 'price': all_products['product_id'].map(price_mapper).fillna(10).astype(int) + }) + + return out +``` + +--- + +## ⭐ 3) アルゴリズム説明 + +### 使用API + +- **`groupby('product_id')['change_date'].idxmax()`**: 各製品の最新日付の行インデックスを取得 +- **`map()`**: 単一キー結合の最速手段 +- **`fillna(10)`**: デフォルト値設定 + +### 処理フロー + +1. **日付フィルタ**: `change_date <= '2019-08-16'` +2. **最新抽出**: `idxmax()`で各製品の最新変更日 +3. **全製品**: ユニークリスト作成 +4. **結合**: `map()`で高速マッピング + +--- + +## ⭐ 4) 計算量 + +| 処理 | 計算量 | 備考 | +| ---------------- | -------- | ------------------ | +| フィルタ | **O(N)** | ブール索引 | +| groupby + idxmax | **O(N)** | ハッシュテーブル | +| map | **O(M)** | M = ユニーク製品数 | +| **合計** | **O(N)** | N = 全レコード数 | + +--- + +## ⭐ 5) 図解 + +### 📊 処理フロー図 + +```mermaid +flowchart TD + A[Products DataFrame] + B[Filter: change_date <= 2019-08-16] + C[GroupBy product_id + idxmax] + D[Extract latest prices] + E[Get all unique products] + F[Map prices] + G[Fill missing with 10] + H[Output: product_id, price] + + A --> B + B --> C + C --> D + A --> E + D --> F + E --> F + F --> G + G --> H +``` + +--- + +## 📝 実行例 + +```python +# テストデータ +products = pd.DataFrame({ + 'product_id': [1, 2, 1, 1, 2, 3], + 'new_price': [20, 50, 30, 35, 65, 20], + 'change_date': pd.to_datetime([ + '2019-08-14', '2019-08-14', '2019-08-15', + '2019-08-16', '2019-08-17', '2019-08-18' + ]) +}) + +result = price_at_given_date(products) +print(result) +``` + +**出力**: + +``` + product_id price +0 1 35 +1 2 50 +2 3 10 +``` + +## 📁 ファイル構成 + +``` +project/ +├── solution.md # ドキュメント本体(下記参照) +└── price_solution.py # 実行用Pythonコード(関数のみ) +``` + +--- + +## 🖥️ VSCodeでの使い方 + +### 1️⃣ ファイル保存 + +- 上記を `solution.md` として保存 + +### 2️⃣ プレビュー表示 + +- **方法A**: `Ctrl+Shift+V` (Windows) / `Cmd+Shift+V` (Mac) +- **方法B**: 右クリック → "Open Preview" +- **方法C**: コマンドパレット (`Ctrl+Shift+P`) → "Markdown: Open Preview" + +### 3️⃣ Mermaid図の表示 + +VSCodeでMermaidを表示するには拡張機能が必要: + +``` +拡張機能: Markdown Preview Mermaid Support +ID: bierner.markdown-mermaid +``` + +**インストール手順**: + +1. VSCode左サイドバーの拡張機能アイコンをクリック +2. "Markdown Preview Mermaid" で検索 +3. インストール +4. Markdownプレビューを再読み込み + +--- + +## 📊 セクション分類表 + +| セクション | 形式 | 記法 | +| -------------- | ------------------- | ---------------------- | +| 見出し・説明文 | **Markdown** | そのまま記述 | +| 実装コード | **コードブロック** | ` ```python ` | +| テスト実行例 | **コードブロック** | ` ```python ` | +| 出力結果 | **コードブロック** | ` ``` ` (言語指定なし) | +| フローチャート | **Mermaidブロック** | ` ```mermaid ` | +| 表 | **Markdown** | `\| 列1 \| 列2 \|` | +| 箇条書き | **Markdown** | `*` or `-` or `1.` | + +--- + +## 🚀 別ファイルで実行する場合 + +### price_solution.py + +```python +# Analyze Complexity +# Runtime 311 ms +# Beats 85.54% +# Memory 68.21 MB +# Beats 81.26% + +import pandas as pd + +def price_at_given_date(products: pd.DataFrame) -> pd.DataFrame: + target_date = '2019-08-16' + before_target = products[products['change_date'] <= target_date] + + if not before_target.empty: + latest_idx = before_target.groupby('product_id')['change_date'].idxmax() + latest_prices = before_target.loc[latest_idx, ['product_id', 'new_price']] + else: + latest_prices = pd.DataFrame(columns=['product_id', 'new_price']) + + all_products = products[['product_id']].drop_duplicates() + price_mapper = latest_prices.set_index('product_id')['new_price'] + + return pd.DataFrame({ + 'product_id': all_products['product_id'], + 'price': all_products['product_id'].map(price_mapper).fillna(10).astype(int) + }) +``` diff --git a/SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date_postgreSQL.md b/SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date_postgreSQL.md new file mode 100644 index 00000000..f7faa167 --- /dev/null +++ b/SQL/Leetcode/Intermediate Join/1164. Product Price at a Given Date/Claude Sonnet 4.5 Extended/Product_Price_at_a_Given_Date_postgreSQL.md @@ -0,0 +1,146 @@ +# PostgreSQL 16.6+ + +## 0) 前提 + +- エンジン: **PostgreSQL 16.6+** +- 並び順: 任意 +- `NOT IN` 回避(`EXISTS` / `LEFT JOIN ... IS NULL` を推奨) +- 判定は ID 基準、表示は仕様どおり + +## 1) 問題 + +- **2019-08-16 時点での全製品の価格を求める** + - 初期価格は全製品 10 + - `Products` テーブルには価格変更履歴が記録されている + - 対象日以前に価格変更があれば最新価格を、なければ初期価格 10 を返す +- 入力: `Products (product_id, new_price, change_date)` +- 出力: `product_id, price` ※ 2019-08-16 時点の価格 + +## 2) 最適解(単一クエリ) + +> PostgreSQL の **`DISTINCT ON`** で各製品の最新価格を一発抽出。`COALESCE` でデフォルト価格をカバー。 + +```sql +WITH latest_prices AS ( + SELECT DISTINCT ON (product_id) + product_id, + new_price + FROM Products + WHERE change_date <= '2019-08-16' + ORDER BY product_id, change_date DESC +), +all_products AS ( + SELECT DISTINCT product_id + FROM Products +) +SELECT + a.product_id, + COALESCE(l.new_price, 10) AS price +FROM all_products a +LEFT JOIN latest_prices l + ON a.product_id = l.product_id; +``` + +### 代替(CTE + ウィンドウ関数版) + +```sql +WITH ranked_prices AS ( + SELECT + product_id, + new_price, + ROW_NUMBER() OVER ( + PARTITION BY product_id + ORDER BY change_date DESC + ) AS rn + FROM Products + WHERE change_date <= '2019-08-16' +), +all_products AS ( + SELECT DISTINCT product_id + FROM Products +) +SELECT + a.product_id, + COALESCE(r.new_price, 10) AS price +FROM all_products a +LEFT JOIN ranked_prices r + ON a.product_id = r.product_id AND r.rn = 1; +``` + +### 最もコンパクトな実装 + +```sql +SELECT + product_id, + COALESCE( + (SELECT new_price + FROM Products p2 + WHERE p2.product_id = p1.product_id + AND p2.change_date <= '2019-08-16' + ORDER BY p2.change_date DESC + LIMIT 1), + 10 + ) AS price +FROM (SELECT DISTINCT product_id FROM Products) p1; +``` + +## 3) 要点解説 + +- **`DISTINCT ON (product_id)`**: PostgreSQL 固有の強力な構文。各グループの先頭行のみを抽出 + - `ORDER BY product_id, change_date DESC` で製品ごとに最新日付順にソート + - 各製品の最初の行(= 最新の価格変更)のみが残る + +- **`LEFT JOIN` + `COALESCE`**: + - 全製品リストと最新価格を外部結合 + - 価格変更履歴がない製品は `NULL` → `COALESCE` で 10 に変換 + +- **`WHERE change_date <= '2019-08-16'`**: 対象日以前の変更のみを考慮 + - この条件がないと未来の価格変更も含まれてしまう + +- **ROW_NUMBER 版**: 標準 SQL で他 RDBMS にも移植しやすい + - `rn = 1` で各製品の最新価格のみフィルタ + +## 4) 計算量(概算) + +- **`DISTINCT ON` 版**: + - ソート: **O(n log n)** ※ n = 対象日以前のレコード数 + - 重複除去: **O(n)** + - JOIN: **O(m)** ※ m = ユニーク製品数 + - 合計: **O(n log n)** + +- **ウィンドウ関数版**: + - ウィンドウ処理: **O(n log n)** + - フィルタ + JOIN: **O(n + m)** + - 合計: **O(n log n)** + +- **スカラーサブクエリ版**: + - 外側の製品数 m に対し、各製品で内部ソート + - ワーストケース: **O(m × n/m × log(n/m)) ≈ O(n log n)** + - インデックス `(product_id, change_date DESC)` があれば **O(m)** に近づく + +## 5) 図解(Mermaid 超保守版) + +```mermaid +flowchart TD + A[Products テーブル] + B[条件: change_date <= 2019-08-16] + C[製品ごとに最新価格を抽出
DISTINCT ON or ROW_NUMBER] + D[全製品リストを生成
DISTINCT product_id] + E[LEFT JOIN
製品リスト ← 最新価格] + F[COALESCE で NULL を 10 に変換] + G[出力: product_id, price] + + A --> B + B --> C + A --> D + C --> E + D --> E + E --> F + F --> G +``` + +**実行例の解説**: + +- product_id = 1: 8/14→20, 8/15→30, **8/16→35** ✓ +- product_id = 2: 8/14→50, (8/17→65 は対象外) → **50** ✓ +- product_id = 3: (8/18→20 は対象外) → **デフォルト 10** ✓