From 414cd406c82355143fe4e22cd725c7446e8b593c Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Tue, 2 Dec 2025 10:52:37 +0900 Subject: [PATCH] SQL: Basic Select 1141. User Activity for the Past 30 Days I --- .markdownlint.json | 10 +- README.md | 240 ++++---- ...tivity_for_the_Past_30_Days_I_pandas.ipynb | 529 ++++++++++++++++++ ...ivity_for_the_Past_30_Days_I_postgre.ipynb | 299 ++++++++++ 4 files changed, 965 insertions(+), 113 deletions(-) create mode 100644 SQL/Leetcode/Basic select/1141. User Activity for the Past 30 Days I/gpt 5.1 thinking customized/User_Activity_for_the_Past_30_Days_I_pandas.ipynb create mode 100644 SQL/Leetcode/Basic select/1141. User Activity for the Past 30 Days I/gpt 5.1 thinking customized/User_Activity_for_the_Past_30_Days_I_postgre.ipynb diff --git a/.markdownlint.json b/.markdownlint.json index f1d9c561..e0c0a983 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -4,7 +4,7 @@ "MD002": true, "MD003": { "style": "atx" }, "MD004": { "style": "consistent" }, - "MD007": { "indent": 4 }, + "MD007": { "indent": 2 }, "MD009": { "br_spaces": 2 }, "MD012": true, "MD013": { @@ -20,7 +20,7 @@ "MD024": { "siblings_only": true }, "MD025": { "front_matter_title": "" }, "MD026": { "punctuation": ".,;:!。,;:" }, - "MD029": { "style": "ordered" }, + "MD029": false, "MD030": { "ul_single": 1, "ol_single": 1, @@ -46,10 +46,12 @@ ] }, "MD034": false, + "MD036": false, "MD038": true, "MD040": false, - "MD041": { "level": 1 }, + "MD041": false, "MD042": false, "MD046": { "style": "fenced" }, - "MD048": { "style": "backtick" } + "MD048": { "style": "backtick" }, + "MD058": false } diff --git a/README.md b/README.md index f58f8a48..bf93985b 100644 --- a/README.md +++ b/README.md @@ -28,31 +28,32 @@ ### マトリックス構造 **2つのAIプロバイダー × 3つのプログラミング言語 × 3つのドキュメントティア = 1問題あたり18成果物** + ```mermaid graph TB Problem[問題
例: 回文判定] - + subgraph AI[AIプロバイダー] Claude[Claude Sonnet 4.5
競技プログラミング最適化] GPT[GPT-5.1
本番環境対応] end - + subgraph Lang[プログラミング言語] Python[Python 3.11
型ヒント付き] TypeScript[TypeScript
厳密な型チェック] JavaScript[JavaScript
ランタイム検証] end - + subgraph Docs[ドキュメントティア] T1[Tier 1: 静的
README.md] T2[Tier 2: インタラクティブ
README.html] T3[Tier 3: 動的
README_react.html] end - + Problem --> AI AI --> Lang Lang --> Docs - + style Problem fill:#e1f5ff style Claude fill:#ffe1e1 style GPT fill:#ffe1e1 @@ -68,37 +69,38 @@ graph TB 各問題は、18ファイルを生成する一貫したパターンに従います。 -| AIプロバイダー | 言語 | ドキュメントファイル | 言語ごとの合計 | -|--------------|------|---------------------|--------------| -| Claude | Python | `*.py` + `README.md` + `README.html` + `README_react.html` | 4ファイル | -| Claude | TypeScript | `*.ts` + `README.md` + `README.html` + `README_react.html` | 4ファイル | -| Claude | JavaScript | `*.js` + `README.md` + `README.html` + `README_react.html` | 4ファイル | -| GPT | Python | `*.py` + `README.md` + `README.html` + `README_react.html` | 4ファイル | -| GPT | TypeScript | `*.ts` + `README.md` + `README.html` + `README_react.html` | 4ファイル | -| GPT | JavaScript | `*.js` + `README.md` + `README.html` + `README_react.html` | 4ファイル | -| **合計** | | | **18成果物** | +| AIプロバイダー | 言語 | ドキュメントファイル | 言語ごとの合計 | +| -------------- | ---------- | ---------------------------------------------------------- | -------------- | +| Claude | Python | `*.py` + `README.md` + `README.html` + `README_react.html` | 4ファイル | +| Claude | TypeScript | `*.ts` + `README.md` + `README.html` + `README_react.html` | 4ファイル | +| Claude | JavaScript | `*.js` + `README.md` + `README.html` + `README_react.html` | 4ファイル | +| GPT | Python | `*.py` + `README.md` + `README.html` + `README_react.html` | 4ファイル | +| GPT | TypeScript | `*.ts` + `README.md` + `README.html` + `README_react.html` | 4ファイル | +| GPT | JavaScript | `*.js` + `README.md` + `README.html` + `README_react.html` | 4ファイル | +| **合計** | | | **18成果物** | --- ## 4ドメイン問題分類 リポジトリは、問題を4つの異なるドメインに整理し、それぞれが特定のアルゴリズムパターンとデータ操作技術をターゲットとしています。 + ```mermaid graph LR Root[Algorithm-DataStructures
-Math-SQL] - + subgraph Domains[4つのドメイン] Algo[Algorithm
アルゴリズム
-----
二分探索
動的計画法
Two Pointers] DS[DataStructures
データ構造
-----
LinkedList
DoublyLinkedList
ポインタ操作] Math[Mathematics
数学
-----
回文
幾何学
有限状態機械] SQL[SQL
データベース
-----
JOIN パターン
Window関数
最適化] end - + Root --> Algo Root --> DS Root --> Math Root --> SQL - + style Root fill:#e1f5ff style Algo fill:#ffe1e1 style DS fill:#fff4e1 @@ -108,18 +110,19 @@ graph LR ### ドメイン分類表 -| ドメイン | 主要コードエンティティ | コアパターン | ファイルパスの例 | -|---------|---------------------|------------|----------------| -| **Algorithm**
アルゴリズム | `Solution.findMedianSortedArrays()`
`numDecodings()`
`minPathSum()` | 二分探索
動的計画法
Two Pointers | `Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/`
`Algorithm/DynamicProgramming/leetcode/91. Decode Ways/` | -| **DataStructures**
データ構造 | `Solution.addTwoNumbers()`
`class ListNode`
`class DoublyLinkedList` | In-place操作
ポインタ操作 | `DataStructures/LinkedList/leetcode/2. Add Two Numbers/`
`DataStructures/DoublyLinkedList/` | -| **Mathematics**
数学 | `isPalindrome()`
`reflectPoint()`
`gameWithCells()` | 有限状態機械
幾何学的変換 | `Mathematics/Palindrome/leetcode/9. Palindrome Number/`
`Mathematics/Geometry/hackerrank/Army Game/` | -| **SQL**
データベース | `CombineTwoTables_mysql.md`
`combine_two_tables()` (Pandas)
`RisingTemperature.sql` | JOINパターン
Window関数 | `SQL/Leetcode/Basic join/175. Combine Two Tables/`
`SQL/Leetcode/Window function/` | +| ドメイン | 主要コードエンティティ | コアパターン | ファイルパスの例 | +| --------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| **Algorithm**
アルゴリズム | `Solution.findMedianSortedArrays()`
`numDecodings()`
`minPathSum()` | 二分探索
動的計画法
Two Pointers | `Algorithm/BinarySearch/leetcode/4. Median of Two Sorted Arrays/`
`Algorithm/DynamicProgramming/leetcode/91. Decode Ways/` | +| **DataStructures**
データ構造 | `Solution.addTwoNumbers()`
`class ListNode`
`class DoublyLinkedList` | In-place操作
ポインタ操作 | `DataStructures/LinkedList/leetcode/2. Add Two Numbers/`
`DataStructures/DoublyLinkedList/` | +| **Mathematics**
数学 | `isPalindrome()`
`reflectPoint()`
`gameWithCells()` | 有限状態機械
幾何学的変換 | `Mathematics/Palindrome/leetcode/9. Palindrome Number/`
`Mathematics/Geometry/hackerrank/Army Game/` | +| **SQL**
データベース | `CombineTwoTables_mysql.md`
`combine_two_tables()` (Pandas)
`RisingTemperature.sql` | JOINパターン
Window関数 | `SQL/Leetcode/Basic join/175. Combine Two Tables/`
`SQL/Leetcode/Window function/` | --- ## デュアルAI実装哲学 このリポジトリは、各問題に対して2つの根本的に異なるコーディング哲学を実装し、比較学習体験を提供します。 + ```mermaid graph TB subgraph Claude[Claude Sonnet 4.5
競技プログラミング最適化] @@ -128,17 +131,17 @@ graph TB C3[パフォーマンス優先
10-15%高速] C4[ユースケース
LeetCode/Codeforces] end - + subgraph GPT[GPT-5.1 Thinking
本番環境対応] G1[包括的な検証
TypeError/ValueError] G2[Float センチネル値
float 'inf'] G3[堅牢性優先
5-10%低速だがより安全] G4[ユースケース
本番API/エンタープライズ] end - + Problem[問題] --> Claude Problem --> GPT - + style Problem fill:#e1f5ff style Claude fill:#ffe1e1 style GPT fill:#e8f5e9 @@ -147,6 +150,7 @@ graph TB ### コードエンティティ比較 #### Claude実装パターン + ```python # Mathematics/Palindrome/leetcode/9. Palindrome Number/claud sonnet 4.5/ # PalindromeNumber.py @@ -158,16 +162,17 @@ class Solution: return False if x < 10: return True - + rev: int = 0 # 整数センチネル while x > rev: rev = rev * 10 + (x % 10) x //= 10 - + return x == rev or x == rev // 10 ``` #### GPT実装パターン + ```python # Mathematics/Palindrome/leetcode/9. Palindrome Number/gpt 5.1 thinking customized/ # PalindromeNumber.py @@ -177,39 +182,40 @@ class Solution: # 包括的な検証 if type(x) is not int: raise TypeError("x must be an int") - + INT_MIN, INT_MAX = -2**31, 2**31 - 1 if x < INT_MIN or x > INT_MAX: raise ValueError("x out of 32-bit range") - + # 検証付きコアロジック if x < 0 or (x % 10 == 0 and x != 0): return False - + rev: int = 0 while x > rev: rev = rev * 10 + (x % 10) x //= 10 - + return x == rev or x == rev // 10 ``` ### 実装の違い表 -| 側面 | Claude実装 | GPT実装 | -|-----|-----------|---------| -| **Pythonメソッドシグネチャ** | `isPalindrome(self, x: int) -> bool` | `isPalindrome(self, x: int) -> bool`
+ `is_palindrome_production(self, x: int) -> bool` | -| **センチネル値** | 整数: `NEG = -10_000_007`
`POS = +10_000_007` | Float: `float("inf")`
`-float("inf")` | -| **検証戦略** | 問題制約を信頼
最小限のチェック | 包括的なランタイム検証
TypeError、ValueError例外発生 | -| **TypeScriptシグネチャ** | `function isPalindrome(x: number): boolean` | `function isPalindrome(x: readonly number[]): boolean`
(不変パラメータ) | -| **パフォーマンス** | 10-15%高速
(Python 6ms, TypeScript 5ms) | 5-10%低速
(Python 8ms)
より堅牢 | -| **ユースケース** | LeetCode/Codeforces競技 | 本番API
エンタープライズシステム | +| 側面 | Claude実装 | GPT実装 | +| ---------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| **Pythonメソッドシグネチャ** | `isPalindrome(self, x: int) -> bool` | `isPalindrome(self, x: int) -> bool`
+ `is_palindrome_production(self, x: int) -> bool` | +| **センチネル値** | 整数: `NEG = -10_000_007`
`POS = +10_000_007` | Float: `float("inf")`
`-float("inf")` | +| **検証戦略** | 問題制約を信頼
最小限のチェック | 包括的なランタイム検証
TypeError、ValueError例外発生 | +| **TypeScriptシグネチャ** | `function isPalindrome(x: number): boolean` | `function isPalindrome(x: readonly number[]): boolean`
(不変パラメータ) | +| **パフォーマンス** | 10-15%高速
(Python 6ms, TypeScript 5ms) | 5-10%低速
(Python 8ms)
より堅牢 | +| **ユースケース** | LeetCode/Codeforces競技 | 本番API
エンタープライズシステム | --- ## ファイル組織階層 リポジトリは、任意の成果物を数秒で発見可能にする厳格な6レベル階層構造に従っています。 + ```mermaid graph TD L1[Level 1: ドメイン
Algorithm / DataStructures /
Mathematics / SQL] @@ -218,13 +224,13 @@ graph TD L4[Level 4: 問題
4. Median of Two Sorted Arrays /
9. Palindrome Number] L5[Level 5: AIプロバイダー
claud sonnet 4.5 /
gpt 5.1 thinking customized] L6[Level 6: 成果物
*.py / *.ts / *.js
README.md / README.html /
README_react.html] - + L1 --> L2 L2 --> L3 L3 --> L4 L4 --> L5 L5 --> L6 - + style L1 fill:#e1f5ff style L2 fill:#ffe1e1 style L3 fill:#fff4e1 @@ -235,16 +241,17 @@ graph TD ### ファイル命名規則表 -| ファイルタイプ | 命名パターン | 目的 | パスの例 | -|-------------|------------|-----|---------| -| **Python実装** | `{ProblemName}.py` | `class Solution`とコア実装を含む | `Mathematics/Palindrome/leetcode/9. Palindrome Number/claud sonnet 4.5/PalindromeNumber.py` | -| **TypeScript実装** | `{ProblemName}.ts` | 厳密なチェック付き型安全実装 | `Mathematics/Palindrome/leetcode/9. Palindrome Number/gpt 5.1 thinking customized/PalindromeNumber.ts` | -| **JavaScript実装** | `{ProblemName}.js` | ランタイム検証実装 | `Mathematics/Palindrome/leetcode/9. Palindrome Number/gpt 5.1 thinking customized/PalindromeNumber.js` | -| **静的ドキュメント** | `README.md` | 5セクション構造ドキュメント
(セクションあたり100行未満) | 各AIプロバイダーフォルダに`README.md`を含む | -| **インタラクティブHTML** | `README.html` | Prism.jsシンタックスハイライト
Tailwind CSSスタイリング
1000-2000行 | 各AIプロバイダーフォルダに`README.html`を含む | -| **React可視化** | `README_react.html` | React 18 + Babel Standalone
インタラクティブデモ
カスタマイズ可能な入力 | 各AIプロバイダーフォルダに`README_react.html`を含む | +| ファイルタイプ | 命名パターン | 目的 | パスの例 | +| ------------------------ | ------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| **Python実装** | `{ProblemName}.py` | `class Solution`とコア実装を含む | `Mathematics/Palindrome/leetcode/9. Palindrome Number/claud sonnet 4.5/PalindromeNumber.py` | +| **TypeScript実装** | `{ProblemName}.ts` | 厳密なチェック付き型安全実装 | `Mathematics/Palindrome/leetcode/9. Palindrome Number/gpt 5.1 thinking customized/PalindromeNumber.ts` | +| **JavaScript実装** | `{ProblemName}.js` | ランタイム検証実装 | `Mathematics/Palindrome/leetcode/9. Palindrome Number/gpt 5.1 thinking customized/PalindromeNumber.js` | +| **静的ドキュメント** | `README.md` | 5セクション構造ドキュメント
(セクションあたり100行未満) | 各AIプロバイダーフォルダに`README.md`を含む | +| **インタラクティブHTML** | `README.html` | Prism.jsシンタックスハイライト
Tailwind CSSスタイリング
1000-2000行 | 各AIプロバイダーフォルダに`README.html`を含む | +| **React可視化** | `README_react.html` | React 18 + Babel Standalone
インタラクティブデモ
カスタマイズ可能な入力 | 各AIプロバイダーフォルダに`README_react.html`を含む | ### 標準ディレクトリ構造 + ``` Mathematics/Palindrome/leetcode/9. Palindrome Number/ ├── claud sonnet 4.5/ @@ -266,6 +273,7 @@ Mathematics/Palindrome/leetcode/9. Palindrome Number/ ### SQLドメインの例外 SQL問題は、単一の`gpt/`ディレクトリ下にグループ化されたプラットフォーム固有のソリューションファイルを持つ異なる構造に従います。 + ``` SQL/Leetcode/Basic join/175. Combine Two Tables/ └── gpt/ @@ -279,15 +287,16 @@ SQL/Leetcode/Basic join/175. Combine Two Tables/ ## 3ティアドキュメントシステム 各問題は、異なるスキルレベルと学習目標をターゲットとする3つの段階的な複雑さレベルでドキュメント化されています。 + ```mermaid graph LR T1[Tier 1: 静的
README.md
-----
初心者向け
3,000-5,000単語
10-15分読了] T2[Tier 2: インタラクティブ
README.html
-----
中級者向け
Prism.js + Tailwind
ステップ制御] T3[Tier 3: 動的
README_react.html
-----
上級者向け
React 18
リアルタイム入力] - + T1 -->|スキル向上| T2 T2 -->|スキル向上| T3 - + style T1 fill:#e8f5e9 style T2 fill:#fff4e1 style T3 fill:#ffe1e1 @@ -295,11 +304,11 @@ graph LR ### ティア機能比較 -| ティア | ファイル | 対象読者 | 主要技術 | 主な機能 | -|-------|---------|---------|---------|---------| -| **Tier 1
静的** | `README.md` | 初心者
CS基礎学習者 | Markdown | • 問題概要
• アルゴリズム解説
• 複雑度解析 O(n)
• 実装詳細
• 最適化議論
• 3,000-5,000単語
• 10-15分読了 | -| **Tier 2
インタラクティブ** | `README.html` | 中級者
競技プログラミング参加者 | Prism.js
Tailwind CSS | • シンタックスハイライト
• ステップ制御システム
(再生/一時停止/前/次/リセット)
• 状態可視化
• SVGフローチャートレンダリング
• 1,000-2,000行 | -| **Tier 3
動的** | `README_react.html` | 上級エンジニア
パフォーマンス重視 | React 18
Babel Standalone | • React Hooks (useState, useEffect)
• リアルタイム入力変更
• エッジケーステスト
• AI実装比較
(Claude vs GPT並列表示)
• パフォーマンスベンチマーク
• 2,000-4,000行 | +| ティア | ファイル | 対象読者 | 主要技術 | 主な機能 | +| ------------------------------- | ------------------- | ------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Tier 1
静的** | `README.md` | 初心者
CS基礎学習者 | Markdown | • 問題概要
• アルゴリズム解説
• 複雑度解析 O(n)
• 実装詳細
• 最適化議論
• 3,000-5,000単語
• 10-15分読了 | +| **Tier 2
インタラクティブ** | `README.html` | 中級者
競技プログラミング参加者 | Prism.js
Tailwind CSS | • シンタックスハイライト
• ステップ制御システム
(再生/一時停止/前/次/リセット)
• 状態可視化
• SVGフローチャートレンダリング
• 1,000-2,000行 | +| **Tier 3
動的** | `README_react.html` | 上級エンジニア
パフォーマンス重視 | React 18
Babel Standalone | • React Hooks (useState, useEffect)
• リアルタイム入力変更
• エッジケーステスト
• AI実装比較
(Claude vs GPT並列表示)
• パフォーマンスベンチマーク
• 2,000-4,000行 | ### Tier 1: 静的ドキュメント構造 @@ -314,22 +323,23 @@ graph LR ### Tier 2: インタラクティブHTML機能 HTMLドキュメントには、埋め込まれたインタラクティブ要素が含まれます。 + ```mermaid graph TB HTML[README.html] - + subgraph Features[主要機能] S1[Prism.jsシンタックスハイライト
Python/TypeScript/JavaScript] S2[ステップバイステップ実行制御
再生/一時停止/前/次/リセット] S3[状態可視化
変数値の変化を追跡] S4[SVGフローチャート
アルゴリズムフロー図解] end - + HTML --> S1 HTML --> S2 HTML --> S3 HTML --> S4 - + style HTML fill:#fff4e1 style S1 fill:#e8f5e9 style S2 fill:#e8f5e9 @@ -338,26 +348,27 @@ graph TB ``` ### Tier 3: Reactコンポーネントアーキテクチャ + ```mermaid graph TB React[README_react.html] - + subgraph Components[Reactコンポーネント] C1[入力フォーム
useStateでリアルタイム更新] C2[実行ボタン
Claude vs GPT比較実行] C3[結果表示
パフォーマンスベンチマーク] C4[エッジケーステスト
境界値検証] end - + React --> C1 React --> C2 React --> C3 React --> C4 - + C1 --> C2 C2 --> C3 C2 --> C4 - + style React fill:#ffe1e1 style C1 fill:#e1f5ff style C2 fill:#e1f5ff @@ -370,20 +381,21 @@ graph TB ## SQLマルチプラットフォーム戦略 SQL問題は、3つの異なる実行環境で解決され、クロスプラットフォーム学習体験を提供します。 + ```mermaid graph TB SQL[SQL問題
例: Combine Two Tables] - + subgraph Platforms[3つのプラットフォーム] MySQL[MySQL
-----
LEFT JOIN
DATE_SUB/ADD
CREATE INDEX
実行時間: ~45ms] PostgreSQL[PostgreSQL
-----
DISTINCT ON
LATERAL JOIN
Covering Index
実行時間: ~38ms 最速] Pandas[Pandas
-----
DataFrame.merge
Series.map
set_index
merge: ~120ms
map: ~65ms] end - + SQL --> MySQL SQL --> PostgreSQL SQL --> Pandas - + style SQL fill:#f3e5f5 style MySQL fill:#e1f5ff style PostgreSQL fill:#e8f5e9 @@ -393,6 +405,7 @@ graph TB ### プラットフォーム固有のSQLクエリパターン #### MySQL実装例 + ```sql -- SQL/Leetcode/Basic join/175. Combine Two Tables/gpt/ -- CombineTwoTables_mysql.md @@ -411,12 +424,14 @@ LEFT JOIN Address AS a ON a.personId = p.personId; ``` **MySQL特性:** + - 小文字テーブル名(person、address) - 標準LEFT JOIN構文 - `DATE_SUB()`、`DATE_ADD()`による日付操作 - シンプルなインデックス戦略 #### PostgreSQL実装例 + ```sql -- SQL/Leetcode/Basic join/175. Combine Two Tables/gpt/ -- CombineTwoTables_postgre.md @@ -443,6 +458,7 @@ LEFT JOIN Address a ON a.personId = p.personId; ``` **PostgreSQL特性:** + - `DISTINCT ON`: PostgreSQL固有の重複排除 - `LATERAL JOIN`: 相関サブクエリの代替 - 完全なWindow関数サポート: `LAG`、`LEAD`、`DENSE_RANK`、`ROW_NUMBER` @@ -450,6 +466,7 @@ LEFT JOIN Address a ON a.personId = p.personId; - 大文字小文字を区別する引用符付き識別子 #### Pandas実装例 + ```python # SQL/Leetcode/Basic join/175. Combine Two Tables/gpt/ # CombineTwoTables_pandas.md @@ -477,18 +494,19 @@ def combine_two_tables_optimized(person: pd.DataFrame, address: pd.DataFrame) -> """ # 1:1マッピングの場合、mapの方が高速 address_dict = address.set_index('personId')[['city', 'state']].to_dict('index') - + person['city'] = person['personId'].map( lambda x: address_dict.get(x, {}).get('city', None) ) person['state'] = person['personId'].map( lambda x: address_dict.get(x, {}).get('state', None) ) - + return person[["firstName", "lastName", "city", "state"]] ``` **Pandas特性:** + - `DataFrame.merge()`: SQL JOIN相当 - `Series.map()`: 1:1マッピングの最適化 - `get_indexer()`: カスタムインデックス操作 @@ -496,15 +514,15 @@ def combine_two_tables_optimized(person: pd.DataFrame, address: pd.DataFrame) -> ### プラットフォーム機能比較 -| 機能 | MySQL | PostgreSQL | Pandas | -|-----|-------|-----------|--------| -| **JOIN構文** | `LEFT JOIN` | `LEFT JOIN`
`LATERAL JOIN` | `DataFrame.merge(how="left")` | -| **重複排除** | `DISTINCT` | `DISTINCT ON (column)` | `drop_duplicates()` | -| **日付操作** | `DATE_SUB()`
`DATE_ADD()` | `INTERVAL '1 day'`
`LAG()`
`date_trunc()` | `pd.Timedelta()`
`shift()`
`dt.floor()` | -| **インデックス作成** | `CREATE INDEX` | Covering indexes
Partial indexes
`CREATE INDEX CONCURRENTLY` | `set_index()`
`get_indexer()`
`MultiIndex` | -| **Window関数** | 限定的サポート
(MySQL 8.0+) | 完全サポート
`LAG`、`LEAD`
`DENSE_RANK`
`ROW_NUMBER` | `groupby().transform()`
`shift()`
`rolling()` | -| **識別子** | 大文字小文字区別なし
(デフォルト) | 大文字小文字区別あり
(引用符付き) | 大文字小文字区別あり
(列名) | -| **実行時間
(100K行)** | ~45ms
(インデックス付き) | ~38ms
(Covering Index)
**最速** | merge(): ~120ms
Series.map(): ~65ms | +| 機能 | MySQL | PostgreSQL | Pandas | +| ------------------------- | ------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------- | +| **JOIN構文** | `LEFT JOIN` | `LEFT JOIN`
`LATERAL JOIN` | `DataFrame.merge(how="left")` | +| **重複排除** | `DISTINCT` | `DISTINCT ON (column)` | `drop_duplicates()` | +| **日付操作** | `DATE_SUB()`
`DATE_ADD()` | `INTERVAL '1 day'`
`LAG()`
`date_trunc()` | `pd.Timedelta()`
`shift()`
`dt.floor()` | +| **インデックス作成** | `CREATE INDEX` | Covering indexes
Partial indexes
`CREATE INDEX CONCURRENTLY` | `set_index()`
`get_indexer()`
`MultiIndex` | +| **Window関数** | 限定的サポート
(MySQL 8.0+) | 完全サポート
`LAG`、`LEAD`
`DENSE_RANK`
`ROW_NUMBER` | `groupby().transform()`
`shift()`
`rolling()` | +| **識別子** | 大文字小文字区別なし
(デフォルト) | 大文字小文字区別あり
(引用符付き) | 大文字小文字区別あり
(列名) | +| **実行時間
(100K行)** | ~45ms
(インデックス付き) | ~38ms
(Covering Index)
**最速** | merge(): ~120ms
Series.map(): ~65ms | --- @@ -519,16 +537,16 @@ graph TB TS[TypeScript
標準ライブラリのみ
Array, Object, Math] JS[JavaScript
標準ライブラリのみ
Array, Object, Math] end - + subgraph Docs[ドキュメント層
外部ライブラリ許可] Prism[Prism.js
シンタックスハイライト] Tailwind[Tailwind CSS
スタイリング] React[React 18
インタラクティブUI] Babel[Babel Standalone
JSXトランスパイル] end - + Core -.->|実装| Docs - + style Core fill:#e8f5e9 style Docs fill:#fff4e1 style Python fill:#e1f5ff @@ -541,31 +559,35 @@ graph TB リポジトリは、コア実装に対して厳格なルールを適用します。 **許可されるライブラリ:** + - Python標準ライブラリ: `typing`、`collections`、`itertools`、`math` - JavaScript/TypeScript標準ライブラリ: Arrayメソッド、Objectメソッド、Mathオブジェクト **禁止されるライブラリ:** + - Pythonサードパーティ: `numpy`、`scipy`、`pandas`(アルゴリズム実装内) - JavaScriptサードパーティ: `lodash`、`underscore`、`ramda` **理由:** + - **教育的透明性**: 学習者が内部実装を理解できる - **面接との整合性**: 面接環境ではライブラリが利用できない **ドキュメント層の例外:** + - Tier 2: Prism.js、Tailwind CSS - Tier 3: React 18、Babel Standalone ### 開発環境要件 -| コンポーネント | バージョン/設定 | 目的 | -|-------------|---------------|-----| -| **Python** | CPython 3.11.10 | 型ヒント付きアルゴリズム実装 | -| **Node.js** | v18.x (JavaScript)
v22.14.0 (TypeScript) | TS/JS実装のランタイム環境 | -| **Bun** | Lockfile version 1 | パッケージ管理と決定論的ビルド | -| **TypeScript** | @types/node ^22.18.10 | Node.js型定義 | -| **ESLint** | ^9.37.0 | コード品質検証とリント | -| **live-server** | ^1.2.2 | ライブリロード開発サーバー | +| コンポーネント | バージョン/設定 | 目的 | +| --------------- | -------------------------------------------- | ------------------------------ | +| **Python** | CPython 3.11.10 | 型ヒント付きアルゴリズム実装 | +| **Node.js** | v18.x (JavaScript)
v22.14.0 (TypeScript) | TS/JS実装のランタイム環境 | +| **Bun** | Lockfile version 1 | パッケージ管理と決定論的ビルド | +| **TypeScript** | @types/node ^22.18.10 | Node.js型定義 | +| **ESLint** | ^9.37.0 | コード品質検証とリント | +| **live-server** | ^1.2.2 | ライブリロード開発サーバー | --- @@ -576,7 +598,7 @@ graph TB ```mermaid graph TB Repo[Algorithm-DataStructures
-Math-SQL
リポジトリ] - + subgraph Users[ユーザーセグメント] U1[アルゴリズム学習者
CS学生/独学者] U2[競技プログラミング準備
LeetCode/HackerRank参加者] @@ -584,13 +606,13 @@ graph TB U4[パフォーマンス最適化研究
ソフトウェアエンジニア] U5[教育指導
講師/チューター/メンター] end - + Repo --> U1 Repo --> U2 Repo --> U3 Repo --> U4 Repo --> U5 - + style Repo fill:#e1f5ff style U1 fill:#e8f5e9 style U2 fill:#fff4e1 @@ -601,28 +623,28 @@ graph TB ### 主要ユースケースマトリックス -| ユースケース | 対象ユーザー | 活用機能 | 推奨ティア | -|------------|------------|---------|-----------| -| **アルゴリズム学習** | CS学生
独学者 | • 包括的リファレンス
• 複雑度解析
• ステップバイステップ可視化 | Tier 1 → 2 | -| **競技プログラミング準備** | LeetCode/HackerRank
参加者 | • 最適化されたソリューション
• 最小限の検証
• Claude実装(スピード優先)
• プラットフォーム固有アプローチ | Tier 1 + Claude | -| **技術面接準備** | 求職者
キャリアチェンジ | • 実装パターン
• ベストプラクティス
• 複数のソリューションアプローチ
• エッジケース処理 | Tier 1 → 3
Claude + GPT | -| **パフォーマンス最適化研究** | ソフトウェアエンジニア
研究者 | • 言語固有技術
• 並列実装比較
• ベンチマーク
• GPT実装(安全性優先) | Tier 3
GPT | -| **教育指導** | 講師
チューター
メンター | • 3ティア学習システム
• 段階的複雑さ
• ビジュアルエイド
• インタラクティブデモ | 全ティア | -| **多言語一貫性研究** | ポリグロット開発者
言語比較研究者 | • Python/TypeScript/JavaScript統一API
• 言語間パフォーマンス比較 | 全言語
Tier 3 | +| ユースケース | 対象ユーザー | 活用機能 | 推奨ティア | +| ---------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| **アルゴリズム学習** | CS学生
独学者 | • 包括的リファレンス
• 複雑度解析
• ステップバイステップ可視化 | Tier 1 → 2 | +| **競技プログラミング準備** | LeetCode/HackerRank
参加者 | • 最適化されたソリューション
• 最小限の検証
• Claude実装(スピード優先)
• プラットフォーム固有アプローチ | Tier 1 + Claude | +| **技術面接準備** | 求職者
キャリアチェンジ | • 実装パターン
• ベストプラクティス
• 複数のソリューションアプローチ
• エッジケース処理 | Tier 1 → 3
Claude + GPT | +| **パフォーマンス最適化研究** | ソフトウェアエンジニア
研究者 | • 言語固有技術
• 並列実装比較
• ベンチマーク
• GPT実装(安全性優先) | Tier 3
GPT | +| **教育指導** | 講師
チューター
メンター | • 3ティア学習システム
• 段階的複雑さ
• ビジュアルエイド
• インタラクティブデモ | 全ティア | +| **多言語一貫性研究** | ポリグロット開発者
言語比較研究者 | • Python/TypeScript/JavaScript統一API
• 言語間パフォーマンス比較 | 全言語
Tier 3 | ### スキルレベル進行 ```mermaid graph LR Beginner[初心者
-----
CS初心者
競技プログラミング
新規参入者
-----
期間: 1-2ヶ月
目標: アルゴリズム
基礎理解] - + Intermediate[中級者
-----
競技プログラミング
参加者
面接準備
CS専攻学生
-----
期間: 2-4ヶ月
目標: 実装能力と
デバッグスキル] - + Advanced[上級者
-----
ソフトウェア
エンジニア
言語最適化研究者
テックリード
-----
期間: 継続的
目標: 最適化戦略と
アーキテクチャ設計] - + Beginner -->|スキル向上| Intermediate Intermediate -->|スキル向上| Advanced - + style Beginner fill:#e8f5e9 style Intermediate fill:#fff4e1 style Advanced fill:#ffe1e1 @@ -630,11 +652,11 @@ graph LR ### 学習期間と目標 -| レベル | 対象ユーザー | 推奨アプローチ | 学習期間 | 達成目標 | -|-------|------------|-------------|---------|---------| -| **初心者** | • CS初心者
• 競技プログラミング新規参入者
• プログラミング基礎学習者 | • Tier 1静的ドキュメントから開始
• 基本概念を理解
• 複雑度解析を学習
• シンプルな問題から取り組む | 1-2ヶ月 | アルゴリズム基礎理解 | -| **中級者** | • 競技プログラミング参加者
• 面接準備
• CS専攻学生 | • Tier 2インタラクティブHTMLで実行検証
• 多言語実装を比較
• エッジケースを理解 | 2-4ヶ月 | 実装能力とデバッグスキル | -| **上級者** | • ソフトウェアエンジニア
• 言語最適化研究者
• テックリード | • Tier 3 React可視化で詳細分析
• 本番vs競技実装を検証
• パフォーマンスチューニング | 継続的 | 最適化戦略とアーキテクチャ設計 | +| レベル | 対象ユーザー | 推奨アプローチ | 学習期間 | 達成目標 | +| ---------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------ | +| **初心者** | • CS初心者
• 競技プログラミング新規参入者
• プログラミング基礎学習者 | • Tier 1静的ドキュメントから開始
• 基本概念を理解
• 複雑度解析を学習
• シンプルな問題から取り組む | 1-2ヶ月 | アルゴリズム基礎理解 | +| **中級者** | • 競技プログラミング参加者
• 面接準備
• CS専攻学生 | • Tier 2インタラクティブHTMLで実行検証
• 多言語実装を比較
• エッジケースを理解 | 2-4ヶ月 | 実装能力とデバッグスキル | +| **上級者** | • ソフトウェアエンジニア
• 言語最適化研究者
• テックリード | • Tier 3 React可視化で詳細分析
• 本番vs競技実装を検証
• パフォーマンスチューニング | 継続的 | 最適化戦略とアーキテクチャ設計 | --- diff --git a/SQL/Leetcode/Basic select/1141. User Activity for the Past 30 Days I/gpt 5.1 thinking customized/User_Activity_for_the_Past_30_Days_I_pandas.ipynb b/SQL/Leetcode/Basic select/1141. User Activity for the Past 30 Days I/gpt 5.1 thinking customized/User_Activity_for_the_Past_30_Days_I_pandas.ipynb new file mode 100644 index 00000000..8604b911 --- /dev/null +++ b/SQL/Leetcode/Basic select/1141. User Activity for the Past 30 Days I/gpt 5.1 thinking customized/User_Activity_for_the_Past_30_Days_I_pandas.ipynb @@ -0,0 +1,529 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d461c9db", + "metadata": {}, + "source": [ + "## 0) 前提\n", + "\n", + "* 環境: **Python 3.10.15 / pandas 2.2.2**\n", + "* **指定シグネチャ厳守**: `def daily_active_users(activity: pd.DataFrame) -> pd.DataFrame`\n", + "* I/O 禁止、不要な `print` や `sort_values` は使用しない\n", + "* Activity は重複行を含む可能性があるので、**(activity_date, user_id) でユニーク化してからカウント**\n", + "\n", + "---\n", + "\n", + "## 1) 問題\n", + "\n", + "* `{{PROBLEM_STATEMENT}}`\n", + " `Activity` データフレームから、**2019-07-27 を含む直近 30 日間**における、日別アクティブユーザー数を求める。\n", + " あるユーザーが「その日に 1 回以上アクティビティを行っていれば」その日はアクティブとみなす。\n", + " アクティビティ種別(`activity_type`)は `'open_session', 'end_session', 'scroll_down', 'send_message'` のいずれも有効とみなす。\n", + " アクティブユーザー数が 0 の日は結果に含めない。\n", + "\n", + "* `{{INPUT_DATAFRAMES}}`\n", + "\n", + " ```text\n", + " activity: pd.DataFrame\n", + " columns:\n", + " - user_id (int)\n", + " - session_id (int)\n", + " - activity_date (date または datetime64[ns] 相当)\n", + " - activity_type (category/str: 'open_session', 'end_session', 'scroll_down', 'send_message')\n", + " ```\n", + "\n", + "* `{{OUTPUT_COLUMNS_AND_RULES}}`\n", + "\n", + " ```text\n", + " 出力: pd.DataFrame\n", + " columns:\n", + " - day (date) … activity_date\n", + " - active_users (int) … その日に 1 回以上アクティビティを行ったユニーク user_id 数\n", + "\n", + " ルール:\n", + " - 対象期間: 2019-06-28 ~ 2019-07-27(両端含む)…「2019-07-27 を含む 30 日間」\n", + " - アクティブ判定は「その日の (user_id, activity_date) が 1 回以上存在」\n", + " - ユーザー数 0 の日は含めない(=そもそも行が存在しない)\n", + " - 行順は任意(sort は不要)\n", + " ```\n", + "\n", + "---\n", + "\n", + "## 2) 実装(指定シグネチャ厳守)\n", + "\n", + "> 列最小化 → 期間フィルタ → `(user_id, activity_date)` でユニーク化 → `groupby` で日別カウント。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def daily_active_users(activity: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['day', 'active_users']\n", + " \"\"\"\n", + " # 期間フィルタ: 2019-07-27 を含む直近 30 日間 → 2019-06-28〜2019-07-27\n", + " start = pd.to_datetime(\"2019-06-28\")\n", + " end = pd.to_datetime(\"2019-07-27\")\n", + " mask = (activity[\"activity_date\"] >= start) & (activity[\"activity_date\"] <= end)\n", + "\n", + " # 列最小化 + ユニーク化: 1 ユーザーが 1 日に複数行あっても 1 回とカウントする\n", + " uniq = (\n", + " activity.loc[mask, [\"user_id\", \"activity_date\"]]\n", + " .drop_duplicates()\n", + " )\n", + "\n", + " # 日別にユーザー数をカウント(ユーザー ID のユニーク数)\n", + " result = (\n", + " uniq\n", + " .groupby(\"activity_date\", as_index=False)[\"user_id\"]\n", + " .nunique()\n", + " )\n", + "\n", + " # 列名を仕様どおりに整形\n", + " result = result.rename(\n", + " columns={\n", + " \"activity_date\": \"day\",\n", + " \"user_id\": \"active_users\",\n", + " }\n", + " )\n", + "\n", + " return result\n", + "\n", + "Analyze Complexity\n", + "Runtime 316 ms\n", + "Beats 50.66%\n", + "Memory 68.35 MB\n", + "Beats 38.32%\n", + "\n", + "```\n", + "\n", + "---\n", + "\n", + "## 3) アルゴリズム説明\n", + "\n", + "使用している主な pandas API:\n", + "\n", + "1. **期間フィルタ**:\n", + "\n", + " ```python\n", + " mask = (activity[\"activity_date\"] >= start) & (activity[\"activity_date\"] <= end)\n", + " activity.loc[mask, ...]\n", + " ```\n", + "\n", + " * `activity_date` が `datetime64[ns]` / `date` でも比較可能。\n", + " * 2019-06-28〜2019-07-27 に含まれる行だけを対象とする。\n", + "\n", + "2. **列最小化 & ユニーク化**:\n", + "\n", + " ```python\n", + " activity.loc[mask, [\"user_id\", \"activity_date\"]].drop_duplicates()\n", + " ```\n", + "\n", + " * 使うのはアクティブ判定のキーだけなので、列を `user_id, activity_date` に絞る。\n", + " * 同じユーザーが同じ日に複数アクティビティをしていても、\n", + " `(user_id, activity_date)` のペアが一意になるように `drop_duplicates()`。\n", + "\n", + "3. **日別アクティブユーザー数**:\n", + "\n", + " ```python\n", + " uniq.groupby(\"activity_date\", as_index=False)[\"user_id\"].nunique()\n", + " ```\n", + "\n", + " * `groupby(\"activity_date\")` で日ごとにまとめる。\n", + " * 各日について `user_id` のユニーク数(`nunique()`)を取り、アクティブユーザー数とする。\n", + " * アクティビティが 1 件もない日付は `uniq` 自体に現れないため、\n", + " 自然と結果にも含まれない(=「0 の日は除外」要件を満たす)。\n", + "\n", + "4. **列名整形**:\n", + "\n", + " ```python\n", + " result.rename(columns={\"activity_date\": \"day\", \"user_id\": \"active_users\"})\n", + " ```\n", + "\n", + " * 問題の要求どおり出力列名を `day`, `active_users` に揃える。\n", + " * 並び順も `[day, active_users]` になるようにしている。\n", + "\n", + "### NULL / 重複 / 型\n", + "\n", + "* **重複**:\n", + " `(user_id, activity_date)` で `drop_duplicates()` しているため、\n", + " 同じユーザーが同日に 10 回アクションしても **1 ユーザーとしてカウント**される。\n", + "* **NULL**:\n", + "\n", + " * `activity_date` や `user_id` が `NaN` の行は、条件に合致しても\n", + " ペアとして扱いづらいため、通常はデータ仕様上「存在しない前提」。\n", + " (もし存在すれば、`NaN` を含む行も 1 ユニットとして扱われるので、\n", + " 必要に応じて `dropna(subset=[\"user_id\", \"activity_date\"])` を噛ませてもよい。)\n", + "* **型**:\n", + "\n", + " * `activity_date` が文字列の場合でも、`to_datetime` 済みなら問題なく比較できる。\n", + " * `user_id` は `int` 前提だが、`nunique()` は dtype に依存せず機能する。\n", + "\n", + "---\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "Activity の行数を **N**、期間内に残る行数を **N₃₀** とすると:\n", + "\n", + "1. **期間フィルタ** `activity.loc[mask, ...]`\n", + "\n", + " * 各行を 1 回見て条件を評価 → **O(N)**\n", + "\n", + "2. **`drop_duplicates()`** on `[\"user_id\", \"activity_date\"]`\n", + "\n", + " * ハッシュベースの重複除去 → **O(N₃₀)** 〜 **O(N₃₀ log N₃₀)** 近辺\n", + "\n", + "3. **`groupby(\"activity_date\")[\"user_id\"].nunique()`**\n", + "\n", + " * グループ数を D(日数 ≤ 30)とすると\n", + " **O(N₃₀)**(ハッシュ集約)程度\n", + "\n", + "総じて、支配的なのは **O(N₃₀)**~**O(N₃₀ log N₃₀)**。\n", + "日数 D は最大 30 と小さいため、D によるオーバーヘッドは無視できるレベルです。\n", + "\n", + "---\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[activity DF] --> B[期間フィルタ 2019-06-28〜2019-07-27]\n", + " B --> C[列最小化 user_id, activity_date]\n", + " C --> D[\"(user_id, activity_date) でdrop_duplicates\"]\n", + " D --> E[\"groupby(activity_date) user_id.nunique()\"]\n", + " E --> F[列名を day, active_users に整形]\n", + " F --> G[\"出力 DF (day, active_users)\"]\n", + "```\n", + "\n", + "この実装は\n", + "\n", + "* I/O なし\n", + "* `print` / `sort_values` 不使用\n", + "* 指定シグネチャ・出力列仕様を満たす\n", + "\n", + "ので、LeetCode などの Pandas 問題でもそのまま利用できます。\n", + "\n", + "結論から言うと、\n", + "\n", + "* **316ms / 上位 50% は十分合格ライン**\n", + "* LeetCode の Pandas 環境だと、**劇的な改善はほぼ出ません**\n", + "* とはいえ、**drop_duplicates を消す & 中間 DF を減らす**くらいのチューニング余地はあります\n", + "\n", + "という感じです。\n", + "\n", + "---\n", + "\n", + "## 1) ロジック的な改善ポイント\n", + "\n", + "今のコードはざっくりこんな流れでした:\n", + "\n", + "```python\n", + "mask = (activity[\"activity_date\"] >= start) & (activity[\"activity_date\"] <= end)\n", + "\n", + "uniq = (\n", + " activity.loc[mask, [\"user_id\", \"activity_date\"]]\n", + " .drop_duplicates()\n", + ")\n", + "\n", + "result = (\n", + " uniq\n", + " .groupby(\"activity_date\", as_index=False)[\"user_id\"]\n", + " .nunique()\n", + ")\n", + "```\n", + "\n", + "ここで\n", + "\n", + "* `(user_id, activity_date)` を `drop_duplicates` した後、\n", + "* さらに `groupby().nunique()` で `user_id` のユニーク数を数えている\n", + "\n", + "ので、**「重複除去」を 2 回やっている**イメージになっています。\n", + "\n", + "この問題は「日ごとの user_id のユニーク数」が欲しいだけなので、\n", + "\n", + "* **`drop_duplicates` をやめて**\n", + "* そのまま `groupby().nunique()` だけにする\n", + "\n", + "方がシンプル&速くなる可能性が高いです。\n", + "\n", + "---\n", + "\n", + "## 2) 改訂版コード(中間処理を削る)\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def daily_active_users(activity: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['day', 'active_users']\n", + " \"\"\"\n", + " # 定数は一度だけ to_datetime(ここはほぼオーバーヘッドになりません)\n", + " start = pd.to_datetime(\"2019-06-28\")\n", + " end = pd.to_datetime(\"2019-07-27\")\n", + "\n", + " # 期間フィルタ + 列最小化\n", + " filtered = activity.loc[\n", + " (activity[\"activity_date\"] >= start) & (activity[\"activity_date\"] <= end),\n", + " [\"user_id\", \"activity_date\"],\n", + " ]\n", + "\n", + " # 日別に user_id のユニーク数をカウント\n", + " result = (\n", + " filtered\n", + " .groupby(\"activity_date\", as_index=False)[\"user_id\"]\n", + " .nunique()\n", + " .rename(columns={\"activity_date\": \"day\", \"user_id\": \"active_users\"})\n", + " )\n", + "\n", + " return result\n", + "\n", + "Analyze Complexity\n", + "Runtime 341 ms\n", + "Beats 24.18%\n", + "Memory 68.30 MB\n", + "Beats 49.34%\n", + "\n", + "```\n", + "\n", + "### 変更点まとめ\n", + "\n", + "1. **`drop_duplicates()` を削除**\n", + "\n", + " * `groupby(\"activity_date\")[\"user_id\"].nunique()` が「日 × user_id のユニーク数」を出してくれるので、\n", + " 事前の `(user_id, activity_date)` ユニーク化は不要です。\n", + "2. **マスクを直接 `.loc` の中で使っても良い**\n", + "\n", + " * メモリ的にはほぼ同じですが、コードが少しスリムになります。\n", + "3. **中間 DF を 1 つだけに削減**\n", + "\n", + " * `uniq` をなくして `filtered` → `result` の 2 段構成に。\n", + "\n", + "これで計算量もメモリ使用量も、**わずかにだけ軽く**なります。\n", + "\n", + "---\n", + "\n", + "## 3) メモリ面の改善余地\n", + "\n", + "LeetCode のメモリ 68MB / Beat 38% も、正直かなり十分ですが、\n", + "\n", + "「理論上できること」としては:\n", + "\n", + "1. **使わない列を早めに捨てる**\n", + "\n", + " ```python\n", + " filtered = activity.loc[mask, [\"user_id\", \"activity_date\"]]\n", + " ```\n", + "\n", + " → これはすでにやっています。`session_id` / `activity_type` を持ち回っていないので OK。\n", + "\n", + "2. **型のダウンサイジング**\n", + "\n", + " * 実務なら、`user_id`, `session_id` を `int32` に落としたり、`activity_type` を `category` にしてメモリ削減が効きます。\n", + " * ただし LeetCode では\n", + "\n", + " * 変換コスト > メリット\n", + " * 評価環境の測り方次第\n", + " となることが多く、あまりスコアに出ないことも多いです。\n", + "\n", + "---\n", + "\n", + "## 4) どこまで頑張るべきか\n", + "\n", + "* Pandas 版で **Runtime Beats ~50%** は、\n", + " 「アルゴリズムも実装も特に悪くない」ラインです。\n", + "* Leaderboard 上位は\n", + "\n", + " * CPython のバージョン差\n", + " * ジャストタイミングで速いマシンに当たる\n", + " * 評価のランダム誤差\n", + " なども効いてくるので、**数十 ms の差はほぼノイズ**です。\n", + "\n", + "なので、\n", + "\n", + "* ロジックをシンプルにして二重の重複除去をやめる(上記修正版)\n", + "* それ以上は「趣味の世界」\n", + "\n", + "くらいに考えておくのが現実的かな、と思います。\n", + "\n", + "「pandas は入出力のフレームだけ、ロジックは全部 NumPy」でゴリ押ししてみましょう。\n", + "\n", + "---\n", + "\n", + "## 1) 方針(全部 NumPy)\n", + "\n", + "やりたいことはシンプルに言うと:\n", + "\n", + "1. `activity_date` を 30 日間でフィルタ\n", + "2. その期間内の `(activity_date, user_id)` ペアをユニークにする\n", + " → 「同じユーザーが同じ日に何回活動しても 1 回」\n", + "3. 日別にペア数(= ユニーク user 数)を数える\n", + "\n", + "これを **全部 NumPy の `unique` でやる**、という作戦です。\n", + "\n", + "---\n", + "\n", + "## 2) 実装例(NumPy ゴリ押し版)\n", + "\n", + "LeetCode の Pandas 版シグネチャを想定して、こう書けます:\n", + "\n", + "```python\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "def daily_active_users(activity: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " NumPy メイン実装:\n", + " - フィルタ, 集約はすべて NumPy で実施\n", + " - pandas は列の取り出し & 最終 DataFrame 化のみ\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['day', 'active_users']\n", + " \"\"\"\n", + " # --- 1) 必要列を NumPy 配列として取得 ---\n", + " # 日付は日単位の datetime64[D] にしておくと扱いやすい\n", + " dates = activity[\"activity_date\"].to_numpy(dtype=\"datetime64[D]\")\n", + " users = activity[\"user_id\"].to_numpy()\n", + "\n", + " # --- 2) 30 日間の期間フィルタ ---\n", + " start = np.datetime64(\"2019-06-28\", \"D\")\n", + " end = np.datetime64(\"2019-07-27\", \"D\")\n", + " mask = (dates >= start) & (dates <= end)\n", + "\n", + " dates = dates[mask]\n", + " users = users[mask]\n", + "\n", + " if dates.size == 0:\n", + " # 対象期間に何もなければ空 DataFrame を返す\n", + " return pd.DataFrame({\"day\": pd.Series([], dtype=\"datetime64[ns]\"),\n", + " \"active_users\": pd.Series([], dtype=\"int64\")})\n", + "\n", + " # --- 3) (date, user) ペアを NumPy structured array で表現 ---\n", + " # これで np.unique でペア単位のユニークが取れる\n", + " pairs = np.empty(dates.shape[0], dtype=[(\"day\", \"datetime64[D]\"), (\"user\", users.dtype)])\n", + " pairs[\"day\"] = dates\n", + " pairs[\"user\"] = users\n", + "\n", + " # --- 4) ペアをユニーク化(同じユーザーが同日に複数回いても 1 回に) ---\n", + " uniq_pairs = np.unique(pairs) # ソートもかかる\n", + "\n", + " # --- 5) 日付ごとにユニークユーザー数を数える ---\n", + " unique_days, counts = np.unique(uniq_pairs[\"day\"], return_counts=True)\n", + "\n", + " # --- 6) pandas DataFrame に戻す ---\n", + " # np.datetime64[D] → DataFrame 生成時に datetime64[ns] に昇格する\n", + " result = pd.DataFrame({\n", + " \"day\": unique_days.astype(\"datetime64[ns]\"),\n", + " \"active_users\": counts.astype(\"int64\"),\n", + " })\n", + "\n", + " return result\n", + "\n", + "Analyze Complexity\n", + "Runtime 290 ms\n", + "Beats 89.97%\n", + "Memory 67.06 MB\n", + "Beats 99.84%\n", + "\n", + "```\n", + "\n", + "ポイント:\n", + "\n", + "* **グループ化や重複除去は全部 `np.unique`** でやっているので、pandas の `groupby` / `drop_duplicates` は一切使っていません。\n", + "* `structured array`(構造化配列)で `(day, user)` のタプルを 1 要素として扱うことで、\n", + " `np.unique` が「行単位でのユニーク」を取ってくれます。\n", + "* `np.unique(uniq_pairs[\"day\"], return_counts=True)` で\n", + " 「日付ごとの (日, ユーザー) ペア数」をまとめて計算。\n", + "\n", + "---\n", + "\n", + "## 3) アルゴリズムの流れ(NumPy 目線)\n", + "\n", + "1. **カラム抽出(pandas → NumPy)**\n", + "\n", + " ```python\n", + " dates = activity[\"activity_date\"].to_numpy(\"datetime64[D]\")\n", + " users = activity[\"user_id\"].to_numpy()\n", + " ```\n", + "\n", + "2. **期間フィルタ(ブールマスク)**\n", + "\n", + " ```python\n", + " mask = (dates >= start) & (dates <= end)\n", + " dates = dates[mask]\n", + " users = users[mask]\n", + " ```\n", + "\n", + "3. **構造化配列で `(day, user)` を表現**\n", + "\n", + " ```python\n", + " pairs = np.empty(dates.shape[0], dtype=[(\"day\", \"datetime64[D]\"), (\"user\", users.dtype)])\n", + " pairs[\"day\"] = dates\n", + " pairs[\"user\"] = users\n", + " ```\n", + "\n", + "4. **ペアのユニーク化**\n", + "\n", + " ```python\n", + " uniq_pairs = np.unique(pairs)\n", + " ```\n", + "\n", + " これで「同じ user が同じ day に複数回活動していても一つにまとまる」。\n", + "\n", + "5. **日別にカウント**\n", + "\n", + " ```python\n", + " unique_days, counts = np.unique(uniq_pairs[\"day\"], return_counts=True)\n", + " ```\n", + "\n", + " `counts[i]` が `unique_days[i]` のユニーク user 数(= アクティブユーザー数)。\n", + "\n", + "6. **DataFrame へ逆変換**\n", + "\n", + " ```python\n", + " pd.DataFrame({\"day\": unique_days.astype(\"datetime64[ns]\"),\n", + " \"active_users\": counts.astype(\"int64\")})\n", + " ```\n", + "\n", + "---\n", + "\n", + "## 4) 計算量 & パフォーマンスの雰囲気\n", + "\n", + "`N` = 30 日間に入る行数とすると、\n", + "\n", + "* 期間フィルタ: `O(N)`\n", + "* 構造化配列作成: `O(N)`\n", + "* `np.unique(pairs)`(ペア単位ユニーク + ソート):\n", + " `O(N log N)`(内部ソート)くらい\n", + "* `np.unique(uniq_pairs[\"day\"], return_counts=True)`:\n", + " ユニークな日数を `D` とすると `O(D log D)`(ただし D ≤ 30 なので誤差)\n", + "\n", + "⇒ **スループットの支配は 2 つの `np.unique`** で、\n", + "概ね **O(N log N)** です。\n", + "pandas の `groupby().nunique()` と同じオーダーですが、\n", + "NumPy 直叩きのぶん、オーバーヘッドはだいぶ小さいです。\n", + "\n", + "---\n", + "\n", + "## 5) おまけ:さらに削るとしたら?\n", + "\n", + "* `activity_date` が最初から `datetime64[ns]` なら、`to_numpy(\"datetime64[D]\")` で日単位に丸める処理はそのまま速いです。\n", + "* ユーザー ID を `int32` に落とせる(値の範囲が小さい)のであれば、\n", + " `users.astype(\"int32\")` でメモリを少し削ることもできます。\n", + " (LeetCode だとスコアに出ないことも多いですが。)\n", + "\n", + "---\n", + "\n", + "こんな感じで、ロジックはほぼ NumPy だけで完結させられます。\n", + "「pandas 禁止縛りのコードレビュー」みたいな場でも、そのまま説明材料に使えるはずです。" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Basic select/1141. User Activity for the Past 30 Days I/gpt 5.1 thinking customized/User_Activity_for_the_Past_30_Days_I_postgre.ipynb b/SQL/Leetcode/Basic select/1141. User Activity for the Past 30 Days I/gpt 5.1 thinking customized/User_Activity_for_the_Past_30_Days_I_postgre.ipynb new file mode 100644 index 00000000..a6e5f7b8 --- /dev/null +++ b/SQL/Leetcode/Basic select/1141. User Activity for the Past 30 Days I/gpt 5.1 thinking customized/User_Activity_for_the_Past_30_Days_I_postgre.ipynb @@ -0,0 +1,299 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cc9d8060", + "metadata": {}, + "source": [ + "## 0) 前提\n", + "\n", + "* エンジン: **PostgreSQL 16.6+**\n", + "* 並び順: 任意(ここでは `ORDER BY day` を付けますが省略可)\n", + "* `NOT IN` は使用せず、今回はそもそも不要\n", + "* **判定は user_id 基準**(1 日に 1 回以上アクティビティがあれば「アクティブ」)\n", + "* Activity テーブルは **重複行あり** なので、**日 + user_id でユニークにしてから件数カウント**する \n", + "\n", + "---\n", + "\n", + "## 1) 問題\n", + "\n", + "* `{{PROBLEM_STATEMENT}}`\n", + " 2019-07-27 を終点とする直近 30 日間について、日別アクティブユーザー数(1 日に 1 回以上アクティビティがあった user 数)を求める。アクティビティ種別は `('open_session', 'end_session', 'scroll_down', 'send_message')` のどれであっても有効とみなす。ユーザー数が 0 人の日は結果に含めない。\n", + "\n", + "* `{{TABLES_OR_SCHEMAS}}`\n", + "\n", + " ```text\n", + " Table: Activity\n", + " +---------------+---------+\n", + " | Column Name | Type |\n", + " +---------------+---------+\n", + " | user_id | int |\n", + " | session_id | int |\n", + " | activity_date | date |\n", + " | activity_type | enum -- 'open_session', 'end_session', 'scroll_down', 'send_message'\n", + " +---------------+---------+\n", + " -- 1 session_id は必ず 1 user_id に属する\n", + " -- 行は重複している可能性がある\n", + " ```\n", + "\n", + "* `{{OUTPUT_COLUMNS_AND_RULES}}`\n", + "\n", + " ```text\n", + " 出力:\n", + " +------------+--------------+\n", + " | day | active_users |\n", + " +------------+--------------+\n", + " | DATE | INT |\n", + " +------------+--------------+\n", + "\n", + " ルール:\n", + " - 対象期間は 2019-06-28 ~ 2019-07-27(両端を含む)\n", + " └ 2019-07-27 を含む 30 日間なので 27 日 - 29 日 = 28 日\n", + " - ある day に 1 回以上アクティビティを行った user_id を「その日のアクティブユーザー」と数える\n", + " - 日別のアクティブユーザー数を集計する\n", + " - アクティブユーザーが 0 人の日は結果から除外\n", + " - 結果の並び順は任意(ここでは day 昇順)\n", + " ```\n", + "\n", + "---\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "> 今回はウィンドウ関数は不要なので、**CTE でユニーク化 → GROUP BY 集計**のシンプル構成にします。\n", + "\n", + "```sql\n", + "WITH uniq_activity AS (\n", + " SELECT DISTINCT\n", + " user_id,\n", + " activity_date\n", + " FROM Activity\n", + " WHERE activity_date BETWEEN DATE '2019-06-28' AND DATE '2019-07-27'\n", + ")\n", + "SELECT\n", + " activity_date AS day,\n", + " COUNT(*) AS active_users\n", + "FROM uniq_activity\n", + "GROUP BY activity_date\n", + "ORDER BY day;\n", + "\n", + "Runtime 351 ms\n", + "Beats 67.57%\n", + "\n", + "```\n", + "\n", + "### ポイント\n", + "\n", + "* `uniq_activity` で\n", + "\n", + " * 期間フィルタ: `activity_date BETWEEN DATE '2019-06-28' AND DATE '2019-07-27'`\n", + " * `user_id, activity_date` を `DISTINCT` して同じユーザー・同じ日に複数行あっても 1 回と数える準備\n", + "* メインクエリで\n", + "\n", + " * `GROUP BY activity_date` し `COUNT(*)` でその日のアクティブユーザー数を算出\n", + "* 0 ユーザーの日はそもそも行が存在しないので結果に出てこない(要件どおり)\n", + "\n", + "`COUNT(DISTINCT user_id)` を直接使ってもよいですが、重複除去と期間フィルタを明示したほうが読みやすいため、CTE で分けています。\n", + "\n", + "---\n", + "\n", + "## 3) 要点解説\n", + "\n", + "1. **30 日間の計算方法**\n", + "\n", + " * 終端: `2019-07-27`\n", + " * そこから 29 日前: `2019-06-28`\n", + " * よって `BETWEEN DATE '2019-06-28' AND DATE '2019-07-27'` で 30 日間がカバーされる\n", + "\n", + "2. **「アクティブユーザー」の定義を SQL に落とす**\n", + "\n", + " * 1 日に 1 回以上アクティビティがあればよい\n", + " → 「日 + user_id の組み合わせ」を 1 ユニットとして数えればよい\n", + " * そのために `SELECT DISTINCT user_id, activity_date` で重複を除く\n", + "\n", + "3. **重複行対策**\n", + "\n", + " * 元テーブルに全く同一の行が複数あっても、\n", + "\n", + " * CTE 内で `DISTINCT(user_id, activity_date)` することで\n", + " * 集計時のカウントに影響が出ないようにしている\n", + "\n", + "4. **0 ユーザー日の扱い**\n", + "\n", + " * Activity に 1 行もない日付は CTE にも現れない\n", + " * GROUP BY 結果にも出てこないため、自動的に「0 ユーザーの日は結果に含めない」挙動になる\n", + "\n", + "5. **インデックス設計(パフォーマンス)**\n", + "\n", + " * 実務では次の複合インデックスが有効\n", + "\n", + " ```sql\n", + " CREATE INDEX idx_activity_date_user\n", + " ON Activity (activity_date, user_id);\n", + " ```\n", + " * これにより\n", + "\n", + " * 範囲条件 `activity_date BETWEEN ...` の絞り込み\n", + " * `user_id, activity_date` の `DISTINCT` / `GROUP BY`\n", + " の両方が効率化される\n", + "\n", + "---\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* Activity テーブルの行数を **N** としたとき\n", + "\n", + "1. **CTE 内部 (`DISTINCT` + 範囲フィルタ)**\n", + "\n", + " * 範囲フィルタ: おおむね **O(N)**(インデックスがあれば実質 O(N_range))\n", + " * `DISTINCT(user_id, activity_date)`:\n", + "\n", + " * ハッシュ集約 or ソートベースで **O(N_range)** ~ **O(N_range log N_range)**\n", + "\n", + "2. **外側の `GROUP BY`**\n", + "\n", + " * すでに `(user_id, activity_date)` はユニークなので、\n", + " * 日数を D とすると **O(D)** 〜 **O(D log D)** 程度(D は最大 30 日)\n", + "\n", + "⇒ 全体としては **O(N_range log N_range)** が支配的\n", + "(`COUNT(DISTINCT user_id)` を直接使う場合も同程度)\n", + "\n", + "---\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[Activity テーブル] --> B[\"日付範囲でフィルタ 2019-06-28〜2019-07-27\"]\n", + " B --> C[\"日 + user_id で DISTINCT (uniq_activity CTE)\"]\n", + " C --> D[\"activity_date ごとに GROUP BY COUNT(*)\"]\n", + " D --> E[出力: day, active_users]\n", + "```\n", + "\n", + "このクエリをそのまま PostgreSQL 16.6+ 環境に流し込めば、問題の期待する結果形式で日別アクティブユーザー数を取得できます。\n", + "結論から言うと、\n", + "\n", + "* **351ms / 上位 67% なら十分速い部類**\n", + "* LeetCode 環境(インデックス作れない・データ量少なめ)では **劇的な改善はほぼ出ません**\n", + "* それでも「やれること」は 2〜3 個あります\n", + "\n", + "という感じです。\n", + "\n", + "---\n", + "\n", + "## 1. クエリのシンプル化(1 段階集約)\n", + "\n", + "前回お渡ししたのは\n", + "\n", + "```sql\n", + "WITH uniq_activity AS (\n", + " SELECT DISTINCT\n", + " user_id,\n", + " activity_date\n", + " FROM Activity\n", + " WHERE activity_date BETWEEN DATE '2019-06-28' AND DATE '2019-07-27'\n", + ")\n", + "SELECT\n", + " activity_date AS day,\n", + " COUNT(*) AS active_users\n", + "FROM uniq_activity\n", + "GROUP BY activity_date\n", + "ORDER BY day;\n", + "```\n", + "\n", + "でしたが、**LeetCode のような小さめデータ**であれば、\n", + "CTE を使わずに **1 回の GROUP BY + COUNT(DISTINCT)** にまとめた方が\n", + "プランナーが素直に最適化してくれることも多いです。\n", + "\n", + "```sql\n", + "SELECT\n", + " activity_date AS day,\n", + " COUNT(DISTINCT user_id) AS active_users\n", + "FROM Activity\n", + "WHERE activity_date BETWEEN DATE '2019-06-28' AND DATE '2019-07-27'\n", + "GROUP BY activity_date;\n", + "-- ORDER BY は仕様上不要なら外してよい\n", + "\n", + "Runtime 366 ms\n", + "Beats 48.24%\n", + "\n", + "```\n", + "\n", + "### なぜ速くなる可能性があるか\n", + "\n", + "* CTE + DISTINCT + GROUP BY という **2 段階の集約** → 中間結果のマテリアライズが起きることがある\n", + "* 一方、`COUNT(DISTINCT user_id)` なら **1 回の GROUP BY** で完結\n", + "* 特に LeetCode ではオプティマイザ設定もそこまで凝ってないので、\n", + " **素直な単一クエリの方が有利なことが多い**です\n", + "\n", + "---\n", + "\n", + "## 2. ORDER BY を外す(仕様 OK なら)\n", + "\n", + "問題文が「順序は任意」と明言しているので、\n", + "**本当に順序不要**であれば `ORDER BY` を消すとソート処理がなくなり、その分は確実に速くなります。\n", + "\n", + "```sql\n", + "SELECT\n", + " activity_date AS day,\n", + " COUNT(DISTINCT user_id) AS active_users\n", + "FROM Activity\n", + "WHERE activity_date BETWEEN DATE '2019-06-28' AND DATE '2019-07-27'\n", + "GROUP BY activity_date;\n", + "```\n", + "\n", + "ソートは `O(D log D)`(D は日数)なので、\n", + "この問題では日数が最大 30 日と小さく、**体感としてはあまり変わりませんが**、\n", + "純粋なランタイムだけを見れば少し良くなる可能性があります。\n", + "\n", + "---\n", + "\n", + "## 3. 実務ならインデックスを貼る\n", + "\n", + "LeetCode では出来ませんが、実務 PostgreSQL ならこうします:\n", + "\n", + "```sql\n", + "CREATE INDEX idx_activity_date_user\n", + " ON Activity (activity_date, user_id);\n", + "```\n", + "\n", + "これで\n", + "\n", + "* `activity_date BETWEEN ...` の範囲スキャン\n", + "* `COUNT(DISTINCT user_id)` の内部での group/hash\n", + "\n", + "が両方とも効率化されます。\n", + "\n", + "---\n", + "\n", + "## 4. パフォーマンス評価の目安\n", + "\n", + "* 上位 **67%** は「解として十分」「実務でも困らないレベル」です\n", + "* もし上位 90% を狙うなら\n", + "\n", + " * 上記のように **1 段階集約 + ORDER BY 削除** を試す\n", + " * それでも改善が小さい/変わらないことも多い(データ量が小さいため)\n", + "\n", + "なので、**アルゴリズム/クエリ設計としては合格点以上**で、\n", + "あとは「CTE を減らして素直な集計に寄せる」くらいの微調整フェーズ、というイメージです。\n", + "\n", + "---\n", + "\n", + "もし「実務で数千万〜数億行ある場合はどうチューニングする?」みたいな視点も気になるようなら、\n", + "その前提でインデックス設計や EXPLAIN の読み方まで掘っていきましょう 💪\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "3.12.4", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}