Skip to content

Commit 8711fe6

Browse files
authored
feat: generalize cookiecutter (#15)
1 parent 531caad commit 8711fe6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1387
-1133
lines changed
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# Plan: Improve Cookiecutter Template for LeetCode Problems
2+
3+
## Overview
4+
5+
Improve the cookiecutter template to be more general and cover all LeetCode question types, including special cases like LRU Cache that use custom class names and multiple methods.
6+
7+
## Current Issues Identified
8+
9+
1. **Hard-coded class name**: Template assumes `Solution` class but some problems use custom classes (e.g., `LRUCache`)
10+
2. **Single method assumption**: Template assumes one method but some problems have multiple methods (`__init__`, `get`, `put`)
11+
3. **Complex test setup**: Current template doesn't handle operation-based testing for design problems
12+
4. **Import handling**: Need better solution import structure
13+
5. **Template parameter explosion**: Too many derived parameters that could be simplified
14+
15+
## Target Structure
16+
17+
```
18+
.templates/leetcode/
19+
├── {{cookiecutter.problem_name}}/
20+
│ ├── __init__.py
21+
│ ├── solution.py
22+
│ ├── tests.py
23+
│ ├── README.md
24+
│ └── playground.ipynb
25+
└── cookiecutter.json
26+
```
27+
28+
## Phase 1: Analyze Problem Types
29+
30+
### 1.1 Universal Template Variables
31+
32+
- `solution_imports`: Required imports for solution.py (empty, TreeNode, ListNode, etc.)
33+
- `test_imports`: All import lines for tests.py (pytest, loguru, TreeNode, test_utils, solution class)
34+
- `solution_class_name`: Dynamic class name (Solution, LRUCache, etc.)
35+
- `readme_description`: Problem description for README (supports HTML including images, preserves code snippets like `x`, `y`, `some_params`)
36+
- `readme_examples`: Examples for README (supports HTML including images, typically uses code blocks for input/output)
37+
- `readme_constraints`: Constraints for README
38+
- `readme_additional`: Additional README sections
39+
- `solution_methods`: List of methods with parameters/return types
40+
- `test_methods`: List of test methods with parametrize and body
41+
- `test_helper_methods`: List of test helper methods (setup_method, teardown_method, utility methods, etc.)
42+
43+
## Phase 2: Create New cookiecutter.json
44+
45+
### 2.1 Complete Template Variables Example
46+
47+
````json
48+
{
49+
"problem_name": "two_sum",
50+
"solution_class_name": "Solution",
51+
"problem_number": "1",
52+
"problem_title": "Two Sum",
53+
"difficulty": "Easy",
54+
"topics": "Array, Hash Table",
55+
"tags": ["grind-75"],
56+
57+
"readme_description": "Given an array of integers `nums` and an integer `target`, return indices of the two numbers such that they add up to `target`.\n\n<img src=\"example1.png\" alt=\"Array Example\" width=\"300\">\n\nYou may assume that each input would have exactly one solution, and you may not use the same element twice.",
58+
"readme_examples": [
59+
{
60+
"content": "```\nInput: nums = [2,7,11,15], target = 9\nOutput: [0,1]\n```\n**Explanation:** Because nums[0] + nums[1] == 9, we return [0, 1]."
61+
},
62+
{
63+
"content": "<img src=\"tree_example.png\" alt=\"Binary Tree\" width=\"200\">\n\n```\nInput: root = [4,2,7,1,3,6,9]\nOutput: [4,7,2,9,6,3,1]\n```\n**Explanation:** The tree is inverted as shown in the image above."
64+
}
65+
],
66+
"readme_constraints": "- 2 <= nums.length <= 10^4\n- -10^9 <= nums[i] <= 10^9\n- -10^9 <= target <= 10^9\n- Only one valid answer exists.",
67+
"readme_additional": "",
68+
69+
"solution_imports": "",
70+
"solution_methods": [
71+
{
72+
"name": "two_sum",
73+
"parameters": "nums: list[int], target: int",
74+
"return_type": "list[int]",
75+
"dummy_return": "[]"
76+
}
77+
],
78+
79+
"test_imports": "import pytest\nfrom loguru import logger\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
80+
"test_helper_methods": [
81+
{
82+
"name": "setup_method",
83+
"parameters": "",
84+
"body": "self.solution = Solution()"
85+
}
86+
],
87+
"test_methods": [
88+
{
89+
"name": "test_two_sum",
90+
"parametrize": "nums, target, expected",
91+
"test_cases": "[([2, 7, 11, 15], 9, [0, 1]), ([3, 2, 4], 6, [1, 2])]",
92+
"body": "result = self.solution.two_sum(nums, target)\nassert result == expected"
93+
}
94+
],
95+
96+
"playground_imports": "from solution import Solution",
97+
"playground_test_case": "# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9\nexpected = [0, 1]",
98+
"playground_execution": "result = Solution().two_sum(nums, target)\nresult",
99+
"playground_assertion": "assert result == expected"
100+
}
101+
````
102+
103+
## Phase 3: Update Template Files
104+
105+
### 3.1 solution.py Template
106+
107+
```python
108+
{{cookiecutter.solution_imports}}
109+
110+
class {{cookiecutter.solution_class_name}}:
111+
{% for method in cookiecutter.solution_methods %}
112+
# Time: O(?)
113+
# Space: O(?)
114+
def {{method.name}}(self, {{method.parameters}}) -> {{method.return_type}}:
115+
# TODO: Implement {{method.name}}
116+
return {{method.dummy_return}}
117+
118+
{% endfor %}
119+
```
120+
121+
### 3.2 tests.py Template
122+
123+
```python
124+
{{cookiecutter.test_imports}}
125+
126+
class Test{{cookiecutter.solution_class_name}}:
127+
{% for method in cookiecutter.test_helper_methods %}
128+
def {{method.name}}(self{% if method.parameters %}, {{method.parameters}}{% endif %}):
129+
{{method.body}}
130+
131+
{% endfor %}
132+
133+
{% for method in cookiecutter.test_methods %}
134+
@pytest.mark.parametrize("{{method.parametrize}}", {{method.test_cases}})
135+
@logged_test
136+
def {{method.name}}(self, {{method.parametrize}}):
137+
{{method.body}}
138+
139+
{% endfor %}
140+
```
141+
142+
### 3.3 README.md Template
143+
144+
```markdown
145+
# {{cookiecutter.problem_title}}
146+
147+
**Difficulty:** {{cookiecutter.difficulty}}
148+
**Topics:** {{cookiecutter.topics}}
149+
**Tags:** {{cookiecutter.tags | join(', ')}}
150+
{% if cookiecutter.problem_number %}
151+
**LeetCode:** [Problem {{cookiecutter.problem_number}}](https://leetcode.com/problems/{{cookiecutter.problem_name.replace('_', "-")}}/description/)
152+
{% endif %}
153+
154+
## Problem Description
155+
156+
{{cookiecutter.readme_description}}
157+
158+
## Examples
159+
160+
{% for example in cookiecutter.readme_examples %}
161+
162+
### Example {{loop.index}}:
163+
164+
{{example.content}}
165+
{% endfor %}
166+
167+
## Constraints
168+
169+
{{cookiecutter.readme_constraints}}
170+
171+
{% if cookiecutter.readme_additional %}
172+
{{cookiecutter.readme_additional}}
173+
{% endif %}
174+
```
175+
176+
### 3.4 playground.ipynb Template
177+
178+
```json
179+
{
180+
"cells": [
181+
{
182+
"cell_type": "code",
183+
"id": "imports",
184+
"source": ["{{cookiecutter.playground_imports}}"]
185+
},
186+
{
187+
"cell_type": "code",
188+
"id": "setup",
189+
"source": ["{{cookiecutter.playground_test_case}}"]
190+
},
191+
{
192+
"cell_type": "code",
193+
"id": "execute",
194+
"source": ["{{cookiecutter.playground_execution}}"]
195+
},
196+
{
197+
"cell_type": "code",
198+
"id": "test",
199+
"source": ["{{cookiecutter.playground_assertion}}"]
200+
}
201+
]
202+
}
203+
```
204+
205+
## Phase 4: Simplify Parameter Generation
206+
207+
### 4.1 Reduce Template Complexity
208+
209+
- Remove derived parameters like `param_names`, `input_description`, etc.
210+
- Generate these dynamically in the template using Jinja2 filters
211+
- Focus on core data: methods, test_cases, problem metadata
212+
213+
### 4.2 Smart Defaults
214+
215+
- Auto-generate method names from problem names
216+
- Auto-generate class names from problem names
217+
- Default to "basic" problem type unless specified
218+
219+
## Phase 5: Testing & Validation
220+
221+
### 5.1 Test with Existing Problems
222+
223+
- Generate all current problems using new template
224+
- Compare output with existing generated files
225+
- Ensure no regression in functionality
226+
227+
### 5.2 Test Special Cases
228+
229+
- LRU Cache (design problem)
230+
- Tree problems (TreeNode imports)
231+
- Linked list problems (ListNode imports)
232+
- Matrix problems (2D arrays)
233+
234+
## Phase 6: Integration
235+
236+
### 6.1 Update Generation Scripts
237+
238+
- Modify `gen.py` to work with new template structure
239+
- Update Makefile commands
240+
- Ensure backward compatibility with existing JSON files
241+
242+
### 6.2 Documentation
243+
244+
- Update problem creation guide
245+
- Create examples for each problem type
246+
- Document new template variables
247+
248+
## Success Criteria
249+
250+
1. ✅ Single cookiecutter template handles all problem types
251+
2. ✅ Reduced template complexity (fewer derived parameters)
252+
3. ✅ Support for design problems with multiple methods
253+
4. ✅ Proper imports for tree/linked list problems
254+
5. ✅ Clean, maintainable template structure
255+
6. ✅ All existing problems can be regenerated without issues
256+
7. ✅ New problem types can be easily added
257+
258+
## Implementation Order
259+
260+
1. Create new `cookiecutter.json` structure
261+
2. Update template files with conditional logic
262+
3. Test with basic problems (Two Sum)
263+
4. Test with design problems (LRU Cache)
264+
5. Test with tree/linked list problems
265+
6. Validate all existing problems
266+
7. Update generation scripts and documentation

0 commit comments

Comments
 (0)