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 @@ + + +
+ + ++ groupby + idxmax による O(N) 最新価格抽出 +
+ +
+ 問題: 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 |
++------------+-------+
+ groupby('product_id')['change_date'].idxmax()
+ で各製品の最新変更日のインデックスを取得
+ map() で高速結合
+ fillna(10)
+ で価格変更履歴がない製品にデフォルト値を設定
+ O(N)
+ - Nは全レコード数
+ O(M)
+ - Mはユニーク製品数
+ idxmax() によるインデックスベースの抽出で、ソート不要
+ map() は
+ merge() より高速(単一キー時)
+ 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
+
+ フローの説明:
+ 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) + | ++ メモリ消費大 + | +