diff --git a/.amazonq/rules/problem-creation.md b/.amazonq/rules/problem-creation.md index 9249475..8159741 100644 --- a/.amazonq/rules/problem-creation.md +++ b/.amazonq/rules/problem-creation.md @@ -4,7 +4,7 @@ When user requests a problem by **number** or **name/slug**, the assistant will: -1. **Scrape** problem data using `lcpy scrape` +1. **Scrape** problem data using `poetry run lcpy scrape` 2. **Transform** data into proper JSON template format 3. **CRITICAL: Include images** - Extract image URLs from scraped data and add to readme_examples with format: `![Example N](image_url)\n\n` before code blocks - Check scraped data for image URLs in the `raw_content` field @@ -23,10 +23,10 @@ When user requests a problem by **number** or **name/slug**, the assistant will: ```bash # Fetch by number -lcpy scrape -n 1 +poetry run lcpy scrape -n 1 # Fetch by slug -lcpy scrape -s "two-sum" +poetry run lcpy scrape -s "two-sum" ``` ## JSON Template Format diff --git a/.amazonq/rules/test-quality-assurance.md b/.amazonq/rules/test-quality-assurance.md index dd68f27..cff96ad 100644 --- a/.amazonq/rules/test-quality-assurance.md +++ b/.amazonq/rules/test-quality-assurance.md @@ -59,7 +59,7 @@ mv .cache/leetcode/{problem_name} leetcode/{problem_name} ```bash # Generate enhanced problem -lcpy gen -s {problem_name} -o leetcode --force +poetry run lcpy gen -s {problem_name} -o leetcode --force # Test specific problem make p-test PROBLEM={problem_name} @@ -80,7 +80,7 @@ poetry run python -m leetcode_py.tools.check_test_cases --threshold=10 --max=non # Check with custom threshold poetry run python -m leetcode_py.tools.check_test_cases --threshold=12 -# Generate from JSON template (uses lcpy internally) +# Generate from JSON template (uses poetry run lcpy internally) make p-gen PROBLEM={problem_name} FORCE=1 ``` diff --git a/Makefile b/Makefile index 83be5a8..7ce4dcd 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= daily_temperatures +PROBLEM ?= house_robber FORCE ?= 0 COMMA := , diff --git a/README.md b/README.md index bf5c4a9..e4c610a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# LeetCode Practice Repository ๐Ÿš€ +# LeetCode Practice Environment Generator ๐Ÿš€ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=wisarootl_leetcode-py&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=wisarootl_leetcode-py) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=wisarootl_leetcode-py&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=wisarootl_leetcode-py) @@ -7,11 +7,11 @@ [![tests](https://img.shields.io/github/actions/workflow/status/wisarootl/leetcode-py/ci-test.yml?branch=main&label=tests&logo=github)](https://github.com/wisarootl/zerv/actions/workflows/ci-test.yml) [![release](https://img.shields.io/github/actions/workflow/status/wisarootl/leetcode-py/cd.yml?branch=main&label=release&logo=github)](https://github.com/wisarootl/zerv/actions/workflows/cd.yml) -A modern Python LeetCode practice environment that goes beyond basic problem solving. Features automated problem generation from LeetCode URLs, beautiful data structure visualizations (TreeNode, ListNode, GraphNode), and comprehensive testing with 12+ test cases per problem. Built with professional development practices including CI/CD, type hints, and quality gates. +A Python package to generate professional LeetCode practice environments. Features automated problem generation from LeetCode URLs, beautiful data structure visualizations (TreeNode, ListNode, GraphNode), and comprehensive testing with 10+ test cases per problem. Built with professional development practices including CI/CD, type hints, and quality gates. **What makes this different:** -- ๐Ÿค– **LLM-Assisted Workflow**: Generate new problems instantly with AI assistance +- ๐Ÿค– **[LLM-Assisted Workflow](#llm-assisted-problem-creation)**: Generate new problems instantly with AI assistance - ๐ŸŽจ **Visual Debugging**: Interactive tree/graph rendering with Graphviz and anytree - ๐Ÿงช **Production Testing**: Comprehensive test suites with edge cases and reproducibility verification - ๐Ÿš€ **Modern Python**: PEP 585/604 type hints, Poetry, and professional tooling @@ -21,14 +21,20 @@ A modern Python LeetCode practice environment that goes beyond basic problem sol **Current**: All 75 problems from [Grind 75](https://www.techinterviewhandbook.org/grind75/) - the most essential coding interview questions curated by the creator of Blind 75. -**Future**: Planned expansion to all 169 Grind problems for comprehensive interview preparation. +**Future**: Planned expansion to all free Grind problems for comprehensive interview preparation. ## ๐Ÿš€ Quick Start -### CLI Installation (Recommended) +### System Requirements + +- **Python 3.13+** - Modern Python runtime with latest type system features +- **Poetry** - Dependency management and packaging +- **Make** - Build automation (development workflows) +- **Git** - Version control system +- **Graphviz** - Graph visualization library (for data structure rendering) ```bash -# Install globally via pip +# Install the package pip install leetcode-py # Generate problems anywhere @@ -36,31 +42,26 @@ lcpy gen -n 1 # Generate Two Sum lcpy gen -t grind-75 # Generate all Grind 75 problems lcpy list -t grind-75 # List available problems lcpy scrape -n 1 # Fetch problem data + +# Start practicing +cd leetcode/two_sum +python -m pytest test_solution.py # Run tests +# Edit solution.py, then rerun tests ``` -### Development Setup +### Example ```bash -# Clone and setup for development -git clone https://github.com/wisarootl/leetcode-py.git -cd leetcode-py -poetry install - -# Start with existing Grind 75 problems -make gen-all-problems # Regenerates all problems with TODO placeholders +lcpy gen --problem-tag grind-75 --output leetcode # Generate all Grind 75 problems +``` -# Practice a specific problem -make p-test PROBLEM=two_sum -# Edit leetcode/two_sum/solution.py, then rerun tests +![Problem Generation](docs/images/problems-generation.png) -# Run all tests -make test -``` +_Bulk generation output showing "Generated problem:" messages for all 75 Grind problems_ -## ๐Ÿ“‹ Prerequisites +![Problem Generation 2](docs/images/problems-generation-2.png) -- Python 3.13+ -- Poetry, Make, Git, Graphviz +_Generated folder structure showing all 75 problem directories after command execution_ ## ๐Ÿ“ Problem Structure @@ -70,42 +71,77 @@ Each problem follows a consistent, production-ready template: leetcode/two_sum/ โ”œโ”€โ”€ README.md # Problem description with examples and constraints โ”œโ”€โ”€ solution.py # Implementation with type hints and TODO placeholder -โ”œโ”€โ”€ test_solution.py # Comprehensive parametrized tests (12+ test cases) +โ”œโ”€โ”€ test_solution.py # Comprehensive parametrized tests (10+ test cases) โ”œโ”€โ”€ helpers.py # Test helper functions โ”œโ”€โ”€ playground.py # Interactive debugging environment (converted from .ipynb) โ””โ”€โ”€ __init__.py # Package marker ``` +![README Example](docs/images/readme-example.png) + +_README format that mirrors LeetCode's problem description layout_ + +![Solution Boilerplate](docs/images/solution-boilerplate.png) + +_Solution boilerplate with type hints and TODO placeholder_ + +![Test Example](docs/images/test-example.png) + +_Comprehensive parametrized tests with 10+ test cases - executable and debuggable in local development environment_ + +![Test Logging](docs/images/logs-in-test-solution.png) + +_Beautiful colorful test output with loguru integration for enhanced debugging and test result visualization_ + ## โœจ Key Features ### Production-Grade Development Environment - **Modern Python**: PEP 585/604 type hints, snake_case conventions - **Comprehensive Linting**: black, isort, ruff, mypy with nbqa for notebooks -- **High Test Coverage**: 12+ test cases per problem including edge cases +- **High Test Coverage**: 10+ test cases per problem including edge cases - **Beautiful Logging**: loguru integration for enhanced test debugging - **CI/CD Pipeline**: Automated testing, security scanning, and quality gates ### Enhanced Data Structure Visualization -- **TreeNode**: Beautiful tree rendering with anytree and Graphviz -- **ListNode**: Clean arrow-based visualization (`1 -> 2 -> 3`) -- **Interactive Debugging**: Multi-cell playground environment +Professional-grade visualization for debugging complex data structures with dual rendering modes: + +- **TreeNode**: Beautiful tree rendering with anytree and Graphviz integration +- **ListNode**: Clean arrow-based visualization with cycle detection +- **GraphNode**: Interactive graph rendering for adjacency list problems +- **DictTree**: Box-drawing character trees perfect for Trie implementations + +#### Jupyter Notebook Integration (HTML Rendering) -![Tree Visualization](https://raw.githubusercontent.com/wisarootl/leetcode-py/main/docs/images/tree-viz.png) -_Beautiful tree rendering with anytree and Graphviz_ +![Tree Visualization](docs/images/tree-viz.png) -![LinkedList Visualization](https://raw.githubusercontent.com/wisarootl/leetcode-py/main/docs/images/linkedlist-viz.png) -_Clean arrow-based list visualization_ +_Interactive tree visualization using Graphviz SVG rendering in Jupyter notebooks_ + +![LinkedList Visualization](docs/images/linkedlist-viz.png) + +_Professional linked list visualization with Graphviz in Jupyter environment_ + +#### Terminal/Console Output (String Rendering) + +![Tree String Visualization](docs/images/tree-str-viz.png) + +_Clean ASCII tree rendering using anytree for terminal debugging and logging_ + +![LinkedList String Visualization](docs/images/linkedlist-str-viz.png) + +_Simple arrow-based list representation for console output and test debugging_ ### Flexible Notebook Support -- **Template Generation**: Creates Jupyter notebooks (`.ipynb`) by default -- **Repository State**: This repo uses Python files (`.py`) for better version control -- **User Choice**: Use `make nb-to-py` to convert notebooks to Python files, or keep as `.ipynb` for interactive development +- **Template Generation**: Creates Jupyter notebooks (`.ipynb`) by default with rich data structure rendering +- **User Choice**: Use `jupytext` to convert notebooks to Python files, or keep as `.ipynb` for interactive exploration +- **Repository State**: This repo converts them to Python files (`.py`) for better version control +- **Dual Rendering**: Automatic HTML visualization in notebooks, clean string output in terminals -![Notebook Example](https://raw.githubusercontent.com/wisarootl/leetcode-py/main/docs/images/notebook-example.png) -_Interactive multi-cell playground for each problem_ +![Notebook Example](docs/images/notebook-example.png) + +_Interactive multi-cell playground with rich data structure visualization for each problem_ ## ๐Ÿ”„ Usage Patterns @@ -131,31 +167,38 @@ lcpy list -d Medium # Filter by difficulty lcpy scrape -n 1 > two_sum.json # Save problem data ``` -### Development Workflow +## ๐Ÿ› ๏ธ Development Setup -For repository development and customization: +For working within this repository to generate additional LeetCode problems using LLM assistance: ```bash -# Regenerate all 75 problems with fresh TODO placeholders -make gen-all-problems +# Clone repository for development +git clone https://github.com/wisarootl/leetcode-py.git +cd leetcode-py +poetry install + +# Generate problems from JSON templates +make p-gen PROBLEM=problem_name +make p-test PROBLEM=problem_name -# Work through problems systematically -make p-test PROBLEM=two_sum -make p-test PROBLEM=valid_palindrome -make p-test PROBLEM=merge_two_sorted_lists +# Regenerate all existing problems +make gen-all-problems ``` ### LLM-Assisted Problem Creation -If you need more problems beyond Grind 75, use an LLM assistant in your IDE (Cursor, GitHub Copilot Chat, Amazon Q, etc.): +To extend the problem collection beyond the current catalog, leverage an LLM assistant within your IDE (Cursor, GitHub Copilot Chat, Amazon Q, etc.). + +๐Ÿ“– **[Complete LLM-Assisted Problem Creation Guide](docs/llm-assisted-problem-creation.md)** - Comprehensive guide with screenshots and detailed workflow. + +**Quick Start:** ```bash -# Example commands to give your LLM assistant: -"Create LeetCode problem 146 (LRU Cache)" -"Add problem 'Word Ladder' by number 127" -"Generate problem 'Serialize and Deserialize Binary Tree'" +# Problem generation commands: +"Add problem 198. House Robber" +"Add problem 198. House Robber. tag: grind" -# For test enhancement (when you need more comprehensive test coverage): +# Test enhancement commands: "Enhance test cases for two_sum problem" "Fix test reproducibility for binary_tree_inorder_traversal" ``` @@ -201,6 +244,8 @@ poetry run python -m leetcode_py.tools.check_test_cases --threshold=10 ### CLI Commands (Global) +๐Ÿ“– **[Complete CLI Usage Guide](docs/cli-usage.md)** - Detailed documentation with all options and examples. + ```bash # Generate problems lcpy gen -n 1 # Single problem by number diff --git a/docs/images/generated-solution.png b/docs/images/generated-solution.png new file mode 100644 index 0000000..40ac8ef Binary files /dev/null and b/docs/images/generated-solution.png differ diff --git a/docs/images/generated-test.png b/docs/images/generated-test.png new file mode 100644 index 0000000..c41b14b Binary files /dev/null and b/docs/images/generated-test.png differ diff --git a/docs/images/linkedlist-str-viz.png b/docs/images/linkedlist-str-viz.png new file mode 100644 index 0000000..dd3e8f3 Binary files /dev/null and b/docs/images/linkedlist-str-viz.png differ diff --git a/docs/images/logs-in-test-solution.png b/docs/images/logs-in-test-solution.png new file mode 100644 index 0000000..b2820b1 Binary files /dev/null and b/docs/images/logs-in-test-solution.png differ diff --git a/docs/images/problems-are-generated.png b/docs/images/problems-are-generated.png new file mode 100644 index 0000000..f5c037a Binary files /dev/null and b/docs/images/problems-are-generated.png differ diff --git a/docs/images/problems-generation-2.png b/docs/images/problems-generation-2.png new file mode 100644 index 0000000..8ad4028 Binary files /dev/null and b/docs/images/problems-generation-2.png differ diff --git a/docs/images/problems-generation.png b/docs/images/problems-generation.png new file mode 100644 index 0000000..d27af25 Binary files /dev/null and b/docs/images/problems-generation.png differ diff --git a/docs/images/prompt-with-context.png b/docs/images/prompt-with-context.png new file mode 100644 index 0000000..788ec66 Binary files /dev/null and b/docs/images/prompt-with-context.png differ diff --git a/docs/images/readme-example.png b/docs/images/readme-example.png new file mode 100644 index 0000000..4708beb Binary files /dev/null and b/docs/images/readme-example.png differ diff --git a/docs/images/solution-boilerplate.png b/docs/images/solution-boilerplate.png new file mode 100644 index 0000000..83ca9b8 Binary files /dev/null and b/docs/images/solution-boilerplate.png differ diff --git a/docs/images/test-example.png b/docs/images/test-example.png new file mode 100644 index 0000000..56df636 Binary files /dev/null and b/docs/images/test-example.png differ diff --git a/docs/images/tree-str-viz.png b/docs/images/tree-str-viz.png new file mode 100644 index 0000000..3318e30 Binary files /dev/null and b/docs/images/tree-str-viz.png differ diff --git a/docs/llm-assisted-problem-creation.md b/docs/llm-assisted-problem-creation.md new file mode 100644 index 0000000..03bb253 --- /dev/null +++ b/docs/llm-assisted-problem-creation.md @@ -0,0 +1,144 @@ +# LLM-Assisted Problem Creation Guide + +This guide demonstrates how to leverage Large Language Models (LLMs) like Amazon Q, GitHub Copilot Chat, or Cursor to automatically generate new LeetCode problems for your practice environment. + +## Overview + +The LLM-assisted workflow enables you to add new problems to your collection with a simple natural language command. The AI assistant handles the entire process from scraping problem data to generating the complete problem structure with comprehensive test cases. + +## Prerequisites + +### Required LLM Context + +For optimal results, include these rule files in your LLM context: + +- [`.amazonq/rules/problem-creation.md`](../.amazonq/rules/problem-creation.md) - Complete problem generation workflow +- [`.amazonq/rules/test-quality-assurance.md`](../.amazonq/rules/test-quality-assurance.md) - Test enhancement and reproducibility verification +- [`.amazonq/rules/development-rules.md`](../.amazonq/rules/development-rules.md) - Code standards and testing patterns + +### Setup Your IDE + +Configure your IDE with an LLM assistant: + +- **Amazon Q**: Install the Amazon Q plugin +- **GitHub Copilot**: Enable Copilot Chat +- **Cursor**: Built-in AI assistant +- **Other**: Any IDE with LLM integration + +## Quick Start + +### Basic Problem Addition + +Simply ask your LLM assistant to add a problem: + +![Prompt with Context](images/prompt-with-context.png) + +_Example prompt showing how to request a new problem with the LLM assistant_ + +```bash +# Simple commands that work: +"Add problem 198. House Robber" +"Add problem 198. House Robber. tag: grind" +"Create problem 70. Climbing Stairs with grind-75 tag" +``` + +### What Happens Automatically + +The LLM assistant will execute the complete workflow: + +1. **Scrape** problem data from LeetCode +2. **Transform** data into proper JSON template format (including images) +3. **Create** JSON file in `leetcode_py/cli/resources/leetcode/json/problems/{problem_name}.json` +4. **Update** `leetcode_py/cli/resources/leetcode/json/tags.json5` with specified tags +5. **Generate** complete problem structure in `leetcode/{problem_name}/` +6. **Verify** with linting checks (iterates from step 3 until all pass) + +![Problems Are Generated](images/problems-are-generated.png) + +_Source control view showing all files created and modified during the problem generation process_ + +## Generated Problem Structure + +### Solution Template + +The assistant generates a clean solution template with proper type hints: + +![Generated Solution](images/generated-solution.png) + +_Generated solution.py file with TODO placeholder and proper method signature_ + +### Comprehensive Test Suite + +Each problem includes 10+ test cases covering edge cases (note: generated test cases may need verification for correctness): + +![Generated Test](images/generated-test.png) + +_Generated test_solution.py with parametrized tests and comprehensive test cases_ + +## Test Enhancement Workflow + +### Enhancing Existing Problems + +Improve test coverage for existing problems: + +```bash +"Enhance test cases for two_sum problem" +"Add more edge cases to binary_tree_inorder_traversal" +"Fix test reproducibility for valid_palindrome" +``` + +### Quality Assurance + +The assistant can identify problems needing more test cases and verify test case correctness and reproducibility: + +```bash +"Check which problems need more test cases" +"Find problems with less than 12 test cases" +"Verify test case correctness for house_robber" +"Fix test reproducibility for binary_tree_inorder_traversal" +``` + +## Best Practices + +### Effective Prompts + +**Good prompts:** + +- "Add problem 198. House Robber with grind tag" +- "Create problem 70. Climbing Stairs for grind-75" +- "Enhance test cases for two_sum problem" + +**Avoid:** + +- Vague requests without problem numbers +- Requests for non-existent problems + +## Troubleshooting + +### Common Issues + +**Template errors:** + +- Assistant will automatically fix JSON template issues +- Re-runs generation until linting passes +- If JSON template fails after many iterations, ask agent to review the example template carefully as mentioned in the rules + +**Test failures:** + +- Assistant verifies test cases against expected outputs +- Fixes incorrect expected values +- Use test QA workflow for comprehensive test enhancement and reproducibility verification + +## Integration with Development Workflow + +### CI/CD Compatibility + +Generated problems integrate seamlessly with: + +- **Test Reproducibility** - CI automatically verifies problems can be regenerated consistently; just implement your solution and CI handles the rest + +## Conclusion + +LLM-assisted problem creation transforms the tedious process of adding new problems into a simple natural language interaction. The assistant handles all the complexity while ensuring professional code quality and comprehensive test coverage. + +Start practicing with your new problems immediately - the assistant takes care of everything else! diff --git a/leetcode/house_robber/README.md b/leetcode/house_robber/README.md new file mode 100644 index 0000000..1486e41 --- /dev/null +++ b/leetcode/house_robber/README.md @@ -0,0 +1,38 @@ +# House Robber + +**Difficulty:** Medium +**Topics:** Array, Dynamic Programming +**Tags:** grind + +**LeetCode:** [Problem 198](https://leetcode.com/problems/house-robber/description/) + +## Problem Description + +You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security systems connected and **it will automatically contact the police if two adjacent houses were broken into on the same night**. + +Given an integer array `nums` representing the amount of money of each house, return \*the maximum amount of money you can rob tonight **without alerting the police\***. + +## Examples + +### Example 1: + +``` +Input: nums = [1,2,3,1] +Output: 4 +Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3). +Total amount you can rob = 1 + 3 = 4. +``` + +### Example 2: + +``` +Input: nums = [2,7,9,3,1] +Output: 12 +Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1). +Total amount you can rob = 2 + 9 + 1 = 12. +``` + +## Constraints + +- `1 <= nums.length <= 100` +- `0 <= nums[i] <= 400` diff --git a/leetcode/house_robber/__init__.py b/leetcode/house_robber/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/house_robber/helpers.py b/leetcode/house_robber/helpers.py new file mode 100644 index 0000000..0107488 --- /dev/null +++ b/leetcode/house_robber/helpers.py @@ -0,0 +1,8 @@ +def run_rob(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.rob(nums) + + +def assert_rob(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/house_robber/playground.py b/leetcode/house_robber/playground.py new file mode 100644 index 0000000..92a889a --- /dev/null +++ b/leetcode/house_robber/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_rob, run_rob +from solution import Solution + +# %% +# Example test case +nums = [2, 7, 9, 3, 1] +expected = 12 + +# %% +result = run_rob(Solution, nums) +result + +# %% +assert_rob(result, expected) diff --git a/leetcode/house_robber/solution.py b/leetcode/house_robber/solution.py new file mode 100644 index 0000000..92567ea --- /dev/null +++ b/leetcode/house_robber/solution.py @@ -0,0 +1,21 @@ +class Solution: + """ + Houses: [2, 7, 9, 3, 1] + Can't rob adjacent houses! + For each house: max(skip, rob) = max(prev1, prev2 + current) + + Step by step: + i=0: prev2=0, prev1=0, num=2 โ†’ max(0, 0+2) = 2 + i=1: prev2=0, prev1=2, num=7 โ†’ max(2, 0+7) = 7 + i=2: prev2=2, prev1=7, num=9 โ†’ max(7, 2+9) = 11 + i=3: prev2=7, prev1=11, num=3 โ†’ max(11, 7+3) = 11 + i=4: prev2=11, prev1=11, num=1 โ†’ max(11, 11+1) = 12 + """ + + # Time: O(n) + # Space: O(1) + def rob(self, nums: list[int]) -> int: + prev2 = prev1 = 0 # prev2: max 2 ago, prev1: max 1 ago + for num in nums: + prev2, prev1 = prev1, max(prev1, prev2 + num) + return prev1 diff --git a/leetcode/house_robber/test_solution.py b/leetcode/house_robber/test_solution.py new file mode 100644 index 0000000..636fe3e --- /dev/null +++ b/leetcode/house_robber/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_rob, run_rob +from .solution import Solution + + +class TestHouseRobber: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([1, 2, 3, 1], 4), + ([2, 7, 9, 3, 1], 12), + ([1], 1), + ([2, 1], 2), + ([5, 1, 3, 9], 14), + ([2, 7, 9, 3, 1, 5, 8], 20), + ([0, 0, 0], 0), + ([5], 5), + ([1, 2], 2), + ([2, 1, 1, 2], 4), + ([5, 5, 10, 100, 10, 5], 110), + ([100, 1, 1, 100], 200), + ([1, 3, 1, 3, 100], 103), + ([400, 0, 400], 800), + ([1, 2, 3, 4, 5], 9), + ], + ) + def test_rob(self, nums: list[int], expected: int): + result = run_rob(Solution, nums) + assert_rob(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/house_robber.json b/leetcode_py/cli/resources/leetcode/json/problems/house_robber.json new file mode 100644 index 0000000..3f27e7c --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/house_robber.json @@ -0,0 +1,73 @@ +{ + "problem_name": "house_robber", + "solution_class_name": "Solution", + "problem_number": "198", + "problem_title": "House Robber", + "difficulty": "Medium", + "topics": "Array, Dynamic Programming", + "_tags": { "list": ["grind"] }, + + "readme_description": "You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security systems connected and **it will automatically contact the police if two adjacent houses were broken into on the same night**.\n\nGiven an integer array `nums` representing the amount of money of each house, return *the maximum amount of money you can rob tonight **without alerting the police***.", + + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [1,2,3,1]\nOutput: 4\nExplanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).\nTotal amount you can rob = 1 + 3 = 4.\n```" + }, + { + "content": "```\nInput: nums = [2,7,9,3,1]\nOutput: 12\nExplanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).\nTotal amount you can rob = 2 + 9 + 1 = 12.\n```" + } + ] + }, + + "readme_constraints": "- `1 <= nums.length <= 100`\n- `0 <= nums[i] <= 400`", + + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "rob", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.rob(nums)", + "helpers_assert_name": "rob", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_rob, run_rob\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "HouseRobber", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + + "_solution_methods": { + "list": [ + { + "name": "rob", + "signature": "(self, nums: list[int]) -> int", + "body": " # TODO: Implement rob\n return 0" + } + ] + }, + + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + + "_test_methods": { + "list": [ + { + "name": "test_rob", + "signature": "(self, nums: list[int], expected: int)", + "parametrize": "nums, expected", + "test_cases": "[([1, 2, 3, 1], 4), ([2, 7, 9, 3, 1], 12), ([1], 1), ([2, 1], 2), ([5, 1, 3, 9], 14), ([2, 7, 9, 3, 1, 5, 8], 20), ([0, 0, 0], 0), ([5], 5), ([1, 2], 2), ([2, 1, 1, 2], 4), ([5, 5, 10, 100, 10, 5], 110), ([100, 1, 1, 100], 200), ([1, 3, 1, 3, 100], 103), ([400, 0, 400], 800), ([1, 2, 3, 4, 5], 9)]", + "body": " result = run_rob(Solution, nums)\n assert_rob(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_rob, assert_rob\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [2, 7, 9, 3, 1]\nexpected = 12", + "playground_run": "result = run_rob(Solution, nums)\nresult", + "playground_assert": "assert_rob(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/tags.json5 b/leetcode_py/cli/resources/leetcode/json/tags.json5 index f09def5..229fb89 100644 --- a/leetcode_py/cli/resources/leetcode/json/tags.json5 +++ b/leetcode_py/cli/resources/leetcode/json/tags.json5 @@ -78,7 +78,7 @@ "zero_one_matrix", ], - grind: [{ tag: "grind-75" }, "daily_temperatures"], + grind: [{ tag: "grind-75" }, "daily_temperatures", "house_robber"], // Test tag for development and testing test: ["binary_search", "two_sum", "valid_palindrome"], diff --git a/leetcode_py/cli/utils/problem_finder.py b/leetcode_py/cli/utils/problem_finder.py index 9a12ba2..74de861 100644 --- a/leetcode_py/cli/utils/problem_finder.py +++ b/leetcode_py/cli/utils/problem_finder.py @@ -13,7 +13,19 @@ def find_problems_by_tag(tag: str) -> list[str]: try: with open(tags_file) as f: tags_data = json5.load(f) - return tags_data.get(tag, []) + + problems = [] + tag_items = tags_data.get(tag, []) + + for item in tag_items: + if isinstance(item, dict) and "tag" in item: + # Resolve tag reference + referenced_problems = find_problems_by_tag(item["tag"]) + problems.extend(referenced_problems) + elif isinstance(item, str): + problems.append(item) + + return problems except (ValueError, OSError, KeyError): return [] @@ -59,19 +71,15 @@ def _add_problem_to_tag_map( problem_tags_map[problem_name].append(tag_name) -def _process_tag_reference( - tags_data: dict, item: dict, tag_name: str, problem_tags_map: dict[str, list[str]] -) -> None: - for problem_name in tags_data.get(item["tag"], []): - if isinstance(problem_name, str): - _add_problem_to_tag_map(problem_tags_map, problem_name, tag_name) +def _process_tag_reference(item: dict, tag_name: str, problem_tags_map: dict[str, list[str]]) -> None: + referenced_problems = find_problems_by_tag(item["tag"]) + for problem_name in referenced_problems: + _add_problem_to_tag_map(problem_tags_map, problem_name, tag_name) -def _process_tag_item( - tags_data: dict, item: str | dict, tag_name: str, problem_tags_map: dict[str, list[str]] -) -> None: +def _process_tag_item(item: str | dict, tag_name: str, problem_tags_map: dict[str, list[str]]) -> None: if isinstance(item, dict) and "tag" in item: - _process_tag_reference(tags_data, item, tag_name, problem_tags_map) + _process_tag_reference(item, tag_name, problem_tags_map) elif isinstance(item, str): _add_problem_to_tag_map(problem_tags_map, item, tag_name) @@ -87,7 +95,7 @@ def _build_problem_tags_cache() -> dict[str, list[str]]: for tag_name, problems in tags_data.items(): if isinstance(problems, list): for item in problems: - _process_tag_item(tags_data, item, tag_name, problem_tags_map) + _process_tag_item(item, tag_name, problem_tags_map) return problem_tags_map except (ValueError, OSError, KeyError): diff --git a/poetry.lock b/poetry.lock index b6650a4..362093c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -403,12 +403,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "base", "dev"] +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", base = "sys_platform == \"win32\"", dev = "sys_platform == \"win32\""} +markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\"", dev = "sys_platform == \"win32\""} [[package]] name = "comm" @@ -990,7 +990,7 @@ version = "0.7.3" description = "Python logging made (stupidly) simple" optional = false python-versions = "<4.0,>=3.5" -groups = ["base"] +groups = ["main"] files = [ {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, @@ -2277,7 +2277,7 @@ version = "1.2.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" -groups = ["base"] +groups = ["main"] markers = "sys_platform == \"win32\"" files = [ {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, @@ -2290,4 +2290,4 @@ dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "36c5064361f5582ade3b260efc37e6c576cf1fd3d97f2608b1b7d333d19caee7" +content-hash = "6c3bd1d31e56dea9fd4566ca17a9861668562919945749b4dce8c9e266a37c5f" diff --git a/pyproject.toml b/pyproject.toml index 1a07d33..22e60a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,13 +34,11 @@ black = "^25.1.0" cookiecutter = "^2.6.0" graphviz = "^0.21" json5 = "^0.12.1" +loguru = "^0.7.3" requests = "^2.32.5" rich = "^14.1.0" typer = "^0.17.0" -[tool.poetry.group.base.dependencies] -loguru = "^0.7.3" - [tool.poetry.group.dev.dependencies] ipykernel = "^6.30.1" isort = "^6.0.1" diff --git a/tests/test_problem_finder.py b/tests/test_problem_finder.py index 1d0aac6..fadf48b 100644 --- a/tests/test_problem_finder.py +++ b/tests/test_problem_finder.py @@ -12,6 +12,7 @@ def test_build_problem_tags_cache_with_real_tags(): assert "two_sum" in result assert "grind-75" in result["two_sum"] assert "grind" in result["two_sum"] + assert "test" in result["two_sum"] def test_get_tags_for_problem(): @@ -23,3 +24,4 @@ def test_get_tags_for_problem(): tags = get_tags_for_problem("two_sum") assert "grind-75" in tags assert "grind" in tags + assert "test" in tags