Skip to content

Commit 53a6695

Browse files
authored
feat: add more problem and fix test issue (#56)
1 parent 94c5dfd commit 53a6695

File tree

14 files changed

+617
-233
lines changed

14 files changed

+617
-233
lines changed

.amazonq/rules/problem-creation.md

Lines changed: 218 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,224 @@ Required fields for `leetcode_py/cli/resources/leetcode/json/problems/{problem_n
4242
- `playground_assertion`: Use single quotes for string literals
4343
- Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues
4444

45-
**Reference the complete template example:**
46-
47-
See `leetcode_py/cli/resources/leetcode/examples/example.json5` for a comprehensive template with:
48-
49-
- All field definitions and variations
50-
- Comments explaining each field
51-
- Examples for different problem types (basic, tree, linked list, design, trie)
52-
- Proper JSON escaping rules for playground fields
53-
- Multiple solution class patterns
45+
**IMPORTANT: Create actual JSON files, not JSON5**
46+
47+
The template below uses JSON5 format with comments for documentation purposes only. When creating the actual `.json` file, you must:
48+
49+
1. **Remove all comments** (lines starting with `//`)
50+
2. **Use proper JSON syntax** with quoted property names
51+
3. **Save as `.json` file** (not `.json5`)
52+
53+
**Template with comments (JSON5 format for reference only):**
54+
55+
````json5
56+
{
57+
// ============================================================================
58+
// COMPREHENSIVE LEETCODE TEMPLATE EXAMPLE
59+
// ============================================================================
60+
// This example demonstrates ALL template patterns using valid_anagram as base
61+
// with comprehensive comments showing variations for different problem types.
62+
//
63+
// REFERENCE PROBLEMS (see .templates/leetcode/json/ for complete examples):
64+
// 1. valid_anagram - Basic: string parameters, boolean return
65+
// 2. invert_binary_tree - Tree: TreeNode imports/parameters
66+
// 3. merge_two_sorted_lists - LinkedList: ListNode imports/parameters
67+
// 4. lru_cache - Design: custom class, multiple methods, operations
68+
// 5. implement_trie_prefix_tree - Trie: DictTree inheritance
69+
// ============================================================================
70+
71+
// === PROBLEM IDENTIFICATION ===
72+
problem_name: "valid_anagram", // snake_case: used for directory/file names
73+
solution_class_name: "Solution", // "Solution" for basic problems
74+
// "LRUCache" for design problems
75+
// "Trie(DictTree[str])" for inheritance
76+
problem_number: "242", // LeetCode problem number as string
77+
problem_title: "Valid Anagram", // Exact title from LeetCode
78+
difficulty: "Easy", // Easy, Medium, Hard
79+
topics: "Hash Table, String, Sorting", // Comma-separated topics from LeetCode
80+
_tags: { list: ["grind-75"] }, // Optional: common problem set tags
81+
// Use _tags wrapper for cookiecutter lists
82+
83+
// === README CONTENT ===
84+
// IMPORTANT: Preserve rich HTML content from LeetCode including:
85+
// - Code snippets with backticks: `code`
86+
// - Bold text: **bold** or <strong>bold</strong>
87+
// - Italic text: *italic* or <em>italic</em>
88+
// - Images: ![Example](https://assets.leetcode.com/uploads/...)
89+
// - HTML formatting: <p>, <br>, <ul>, <li>, etc.
90+
// - Mathematical expressions and special characters
91+
readme_description: "Given two strings `s` and `t`, return `true` if `t` is an anagram of `s`, and `false` otherwise.",
92+
93+
_readme_examples: {
94+
// Use _readme_examples wrapper for cookiecutter lists
95+
list: [
96+
{ content: '```\nInput: s = "anagram", t = "nagaram"\nOutput: true\n```' },
97+
{ content: '```\nInput: s = "rat", t = "car"\nOutput: false\n```' },
98+
// For tree problems: Include images
99+
// { "content": "![Example 1](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg)\n\n```\nInput: root = [4,2,7,1,3,6,9]\nOutput: [4,7,2,9,6,3,1]\n```" }
100+
],
101+
},
102+
103+
readme_constraints: "- 1 <= s.length, t.length <= 5 * 10^4\n- s and t consist of lowercase English letters.",
104+
readme_additional: "**Follow up:** What if the inputs contain Unicode characters? How would you adapt your solution to such a case?",
105+
106+
// === HELPER FUNCTIONS ===
107+
// New template system uses helper functions for cleaner test organization
108+
helpers_imports: "", // Empty for basic problems
109+
// "from leetcode_py import TreeNode" for tree problems
110+
// "from leetcode_py import ListNode" for linked list problems
111+
helpers_content: "", // Additional helper content if needed
112+
helpers_run_name: "is_anagram", // Function name matching main method
113+
helpers_run_signature: "(solution_class: type, s: str, t: str)",
114+
// For tree: "(solution_class: type, root_list: list[int | None])"
115+
// For linked list: "(solution_class: type, list1_vals: list[int], list2_vals: list[int])"
116+
// For design: "(solution_class: type, operations: list[str], inputs: list[list[int]])"
117+
helpers_run_body: " implementation = solution_class()\n return implementation.is_anagram(s, t)",
118+
// For tree: " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.invert_tree(root)"
119+
// For design: " cache = None\n results: list[int | None] = []\n # ... operation loop ...\n return results, cache"
120+
helpers_assert_name: "is_anagram", // Function name matching main method
121+
helpers_assert_signature: "(result: bool, expected: bool) -> bool",
122+
// For tree: "(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool"
123+
// For design: "(result: list[int | None], expected: list[int | None]) -> bool"
124+
helpers_assert_body: " assert result == expected\n return True",
125+
// For tree: " expected = TreeNode[int].from_list(expected_list)\n assert result == expected\n return True"
126+
127+
// === SOLUTION TEMPLATE ===
128+
solution_imports: "", // Empty for basic problems
129+
// "from leetcode_py import TreeNode" for tree problems
130+
// "from leetcode_py import ListNode" for linked list problems
131+
// "from leetcode_py.data_structures import DictTree, RecursiveDict" for trie problems
132+
solution_contents: "", // Additional content before class definition
133+
solution_class_content: "", // Content inside class definition (usually empty)
134+
135+
// === TEST CONFIGURATION ===
136+
test_imports: "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_is_anagram, run_is_anagram\nfrom .solution import Solution",
137+
// For design: "from .solution import LRUCache" instead of Solution
138+
test_content: "", // Additional test content
139+
test_class_name: "ValidAnagram", // PascalCase: TestClassName for pytest class
140+
test_class_content: " def setup_method(self):\n self.solution = Solution()",
141+
// Empty for design problems: ""
142+
143+
// === SOLUTION METHODS ===
144+
_solution_methods: {
145+
// Use _solution_methods wrapper for cookiecutter lists
146+
list: [
147+
{
148+
name: "is_anagram", // snake_case method name
149+
signature: "(self, s: str, t: str) -> bool", // Full method signature with type hints
150+
// For tree: "(self, root: TreeNode[int] | None) -> TreeNode[int] | None"
151+
// For linked list: "(self, list1: ListNode[int] | None, list2: ListNode[int] | None) -> ListNode[int] | None"
152+
body: " # TODO: Implement is_anagram\n return False",
153+
// For design problems with __init__:
154+
// { "name": "__init__", "signature": "(self, capacity: int) -> None", "body": " # TODO: Initialize\n pass" }
155+
},
156+
],
157+
},
158+
159+
// === TEST HELPER METHODS ===
160+
_test_helper_methods: {
161+
// Use _test_helper_methods wrapper for cookiecutter lists
162+
list: [
163+
{ name: "setup_method", parameters: "", body: "self.solution = Solution()" },
164+
// Empty list for design problems: []
165+
],
166+
},
167+
168+
// === TEST METHODS ===
169+
_test_methods: {
170+
// Use _test_methods wrapper for cookiecutter lists
171+
list: [
172+
{
173+
name: "test_is_anagram", // test_{method_name}
174+
signature: "(self, s: str, t: str, expected: bool)", // Method signature with type hints
175+
parametrize: "s, t, expected", // pytest parametrize parameters
176+
// For tree: "root_list, expected_list"
177+
// For design: "operations, inputs, expected"
178+
test_cases: "[('anagram', 'nagaram', True), ('rat', 'car', False), ('listen', 'silent', True), ('hello', 'bello', False), ('', '', True), ('a', 'a', True), ('a', 'b', False), ('ab', 'ba', True), ('abc', 'bca', True), ('abc', 'def', False), ('aab', 'abb', False), ('aabbcc', 'abcabc', True), ('abcd', 'abcde', False), ('race', 'care', True), ('elbow', 'below', True), ('study', 'dusty', True), ('night', 'thing', True), ('stressed', 'desserts', True)]",
179+
// For tree: "[([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], [])]"
180+
// For design: "[(['LRUCache', 'put', 'get'], [[2], [1, 1], [1]], [None, None, 1])]"
181+
body: " result = run_is_anagram(Solution, s, t)\n assert_is_anagram(result, expected)",
182+
// For tree: " result = run_invert_tree(Solution, root_list)\n assert_invert_tree(result, expected_list)"
183+
// For design: " result, _ = run_lru_cache(LRUCache, operations, inputs)\n assert_lru_cache(result, expected)"
184+
},
185+
],
186+
},
187+
188+
// === PLAYGROUND NOTEBOOK ===
189+
// CRITICAL: Use single quotes for Python strings to avoid JSON escaping issues with Jupyter notebooks
190+
// Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues
191+
// ALWAYS use single quotes: s = 'hello', not s = "hello"
192+
playground_imports: "from helpers import run_is_anagram, assert_is_anagram\nfrom solution import Solution",
193+
// For tree: "from helpers import run_invert_tree, assert_invert_tree\nfrom solution import Solution\nfrom leetcode_py import TreeNode"
194+
// For design: "from helpers import run_lru_cache, assert_lru_cache\nfrom solution import LRUCache"
195+
playground_setup: "# Example test case\ns = 'anagram'\nt = 'nagaram'\nexpected = True",
196+
// For tree: "# Example test case\nroot_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\nexpected_list: list[int | None] = [4, 7, 2, 9, 6, 3, 1]"
197+
// For design: "# Example test case\noperations = ['LRUCache', 'put', 'get']\ninputs = [[2], [1, 1], [1]]\nexpected = [None, None, 1]"
198+
playground_run: "result = run_is_anagram(Solution, s, t)\nresult",
199+
// For tree: "result = run_invert_tree(Solution, root_list)\nresult"
200+
// For design: "result, cache = run_lru_cache(LRUCache, operations, inputs)\nprint(result)\ncache"
201+
playground_assert: "assert_is_anagram(result, expected)",
202+
// For tree: "assert_invert_tree(result, expected_list)"
203+
// For design: "assert_lru_cache(result, expected)"
204+
205+
// ============================================================================
206+
// PROBLEM TYPE VARIATIONS SUMMARY:
207+
// ============================================================================
208+
//
209+
// BASIC PROBLEMS (valid_anagram):
210+
// - solution_class_name: "Solution"
211+
// - solution_imports: ""
212+
// - Simple method signatures: "(self, s: str, t: str) -> bool"
213+
// - Basic test cases: string/number parameters
214+
// - Playground: single quotes for strings
215+
//
216+
// TREE PROBLEMS (invert_binary_tree):
217+
// - solution_class_name: "Solution"
218+
// - solution_imports: "from leetcode_py import TreeNode"
219+
// - Tree method signatures: "(self, root: TreeNode[int] | None) -> TreeNode[int] | None"
220+
// - Helper functions use TreeNode.from_list()
221+
// - Test cases: list representations of trees
222+
// - Playground: TreeNode imports and list conversions
223+
//
224+
// LINKED LIST PROBLEMS (merge_two_sorted_lists):
225+
// - solution_class_name: "Solution"
226+
// - solution_imports: "from leetcode_py import ListNode"
227+
// - List method signatures: "(self, list1: ListNode[int] | None, list2: ListNode[int] | None) -> ListNode[int] | None"
228+
// - Helper functions use ListNode.from_list()
229+
// - Test cases: list representations of linked lists
230+
// - Playground: ListNode imports and list conversions
231+
//
232+
// DESIGN PROBLEMS (lru_cache):
233+
// - solution_class_name: "LRUCache" (custom class name)
234+
// - Multiple methods including __init__
235+
// - Operations-based testing: operations, inputs, expected arrays
236+
// - Complex test body with operation loops
237+
// - Helper functions return (results, instance) for debugging
238+
// - Playground: print results, return instance
239+
// - test_class_content: "" (no setup_method)
240+
//
241+
// INHERITANCE PROBLEMS (implement_trie_prefix_tree):
242+
// - solution_class_name: "Trie(DictTree[str])" (with inheritance)
243+
// - solution_imports: "from leetcode_py.data_structures import DictTree, RecursiveDict"
244+
// - Custom class with inheritance from DictTree
245+
// - Operations-based testing like design problems
246+
// - Helper functions return (results, instance) for debugging
247+
//
248+
// MULTIPLE SOLUTIONS (invert_binary_tree, lru_cache):
249+
// - Add parametrize for solution classes in test files:
250+
// @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS])
251+
// @pytest.mark.parametrize("solution_class", [LRUCache, LRUCacheWithDoublyList])
252+
// - Update test method signature to include solution_class parameter
253+
// - Import all solution classes in test file
254+
// ============================================================================
255+
}
256+
````
257+
258+
**IMPORTANT: When creating the actual .json file, convert the above JSON5 to valid JSON by:**
259+
260+
1. **Remove all comments** (lines starting with `//`)
261+
2. **Keep all quoted property names** (already done above)
262+
3. **Save as `.json` file** (not `.json5`)
54263

55264
## Naming Conventions
56265

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PYTHON_VERSION = 3.13
2-
PROBLEM ?= design_add_and_search_words_data_structure
2+
PROBLEM ?= valid_sudoku
33
FORCE ?= 0
44
COMMA := ,
55

@@ -100,4 +100,4 @@ gen-all-problems:
100100
@echo "Deleting existing problems..."
101101
@rm -rf leetcode/*/
102102
@echo "Generating all problems..."
103-
poetry run lcpy gen -t grind-75 -o leetcode $(if $(filter 1,$(FORCE)),--force)
103+
poetry run lcpy gen --all -o leetcode $(if $(filter 1,$(FORCE)),--force)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Pacific Atlantic Water Flow
2+
3+
**Difficulty:** Medium
4+
**Topics:** Array, Depth-First Search, Breadth-First Search, Matrix
5+
**Tags:** grind
6+
7+
**LeetCode:** [Problem 417](https://leetcode.com/problems/pacific-atlantic-water-flow/description/)
8+
9+
## Problem Description
10+
11+
There is an `m x n` rectangular island that borders both the **Pacific Ocean** and **Atlantic Ocean**. The **Pacific Ocean** touches the island's left and top edges, and the **Atlantic Ocean** touches the island's right and bottom edges.
12+
13+
The island is partitioned into a grid of square cells. You are given an `m x n` integer matrix `heights` where `heights[r][c]` represents the **height above sea level** of the cell at coordinate `(r, c)`.
14+
15+
The island receives a lot of rain, and the rain water can flow to neighboring cells directly north, south, east, and west if the neighboring cell's height is **less than or equal to** the current cell's height. Water can flow from any cell adjacent to an ocean into the ocean.
16+
17+
Return _a **2D list** of grid coordinates_ `result` _where_ `result[i] = [ri, ci]` _denotes that rain water can flow from cell_ `(ri, ci)` _to **both** the Pacific and Atlantic oceans_.
18+
19+
## Examples
20+
21+
### Example 1:
22+
23+
![Example 1](https://assets.leetcode.com/uploads/2021/06/08/waterflow-grid.jpg)
24+
25+
```
26+
Input: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
27+
Output: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
28+
Explanation: The following cells can flow to the Pacific and Atlantic oceans, as shown below:
29+
[0,4]: [0,4] -> Pacific Ocean
30+
[0,4] -> Atlantic Ocean
31+
[1,3]: [1,3] -> [0,3] -> Pacific Ocean
32+
[1,3] -> [1,4] -> Atlantic Ocean
33+
[1,4]: [1,4] -> [1,3] -> [0,3] -> Pacific Ocean
34+
[1,4] -> Atlantic Ocean
35+
[2,2]: [2,2] -> [1,2] -> [0,2] -> Pacific Ocean
36+
[2,2] -> [2,3] -> [2,4] -> Atlantic Ocean
37+
[3,0]: [3,0] -> Pacific Ocean
38+
[3,0] -> [4,0] -> Atlantic Ocean
39+
[3,1]: [3,1] -> [3,0] -> Pacific Ocean
40+
[3,1] -> [4,1] -> Atlantic Ocean
41+
[4,0]: [4,0] -> Pacific Ocean
42+
[4,0] -> Atlantic Ocean
43+
Note that there are other possible paths for these cells to flow to the Pacific and Atlantic oceans.
44+
```
45+
46+
### Example 2:
47+
48+
```
49+
Input: heights = [[1]]
50+
Output: [[0,0]]
51+
Explanation: The water can flow from the only cell to the Pacific and Atlantic oceans.
52+
```
53+
54+
## Constraints
55+
56+
- `m == heights.length`
57+
- `n == heights[r].length`
58+
- `1 <= m, n <= 200`
59+
- `0 <= heights[r][c] <= 10^5`

leetcode/pacific_atlantic_water_flow/__init__.py

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
def run_pacific_atlantic(solution_class: type, heights: list[list[int]]):
2+
implementation = solution_class()
3+
return implementation.pacific_atlantic(heights)
4+
5+
6+
def assert_pacific_atlantic(result: list[list[int]], expected: list[list[int]]) -> bool:
7+
assert sorted(result) == sorted(expected)
8+
return True
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# ---
2+
# jupyter:
3+
# jupytext:
4+
# text_representation:
5+
# extension: .py
6+
# format_name: percent
7+
# format_version: '1.3'
8+
# jupytext_version: 1.17.3
9+
# kernelspec:
10+
# display_name: leetcode-py-py3.13
11+
# language: python
12+
# name: python3
13+
# ---
14+
15+
# %%
16+
from helpers import assert_pacific_atlantic, run_pacific_atlantic
17+
from solution import Solution
18+
19+
# %%
20+
# Example test case
21+
heights = [[1, 2, 2, 3, 5], [3, 2, 3, 4, 4], [2, 4, 5, 3, 1], [6, 7, 1, 4, 5], [5, 1, 1, 2, 4]]
22+
expected = [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]]
23+
24+
# %%
25+
result = run_pacific_atlantic(Solution, heights)
26+
result
27+
28+
# %%
29+
assert_pacific_atlantic(result, expected)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
class Solution:
2+
3+
# Time: O(m * n)
4+
# Space: O(m * n)
5+
def pacific_atlantic(self, heights: list[list[int]]) -> list[list[int]]:
6+
if not heights or not heights[0]:
7+
return []
8+
9+
m, n = len(heights), len(heights[0])
10+
pacific: set[tuple[int, int]] = set()
11+
atlantic: set[tuple[int, int]] = set()
12+
13+
def dfs(r: int, c: int, visited: set) -> None:
14+
visited.add((r, c))
15+
for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
16+
nr, nc = r + dr, c + dc
17+
if (
18+
0 <= nr < m
19+
and 0 <= nc < n
20+
and (nr, nc) not in visited
21+
and heights[nr][nc] >= heights[r][c]
22+
):
23+
dfs(nr, nc, visited)
24+
25+
# DFS from Pacific borders (top and left)
26+
for i in range(m):
27+
dfs(i, 0, pacific)
28+
for j in range(n):
29+
dfs(0, j, pacific)
30+
31+
# DFS from Atlantic borders (bottom and right)
32+
for i in range(m):
33+
dfs(i, n - 1, atlantic)
34+
for j in range(n):
35+
dfs(m - 1, j, atlantic)
36+
37+
return [[r, c] for r, c in pacific & atlantic]

0 commit comments

Comments
 (0)