From 68521e3cf13531b9cf9774230b7b46b43afca7da Mon Sep 17 00:00:00 2001 From: Wisaroot <66859294+wisarootl@users.noreply.github.com> Date: Wed, 17 Sep 2025 09:17:57 +0700 Subject: [PATCH 01/43] docs: improve docs (#49) - update README.md, CONTRIBUTING.md, docs/cli-usage.md, docs/llm-assisted-problem-creation.md --- CONTRIBUTING.md | 97 ++++++++++++++++++++------- README.md | 74 ++++++++++---------- docs/cli-usage.md | 33 +++++++-- docs/llm-assisted-problem-creation.md | 22 +++--- 4 files changed, 149 insertions(+), 77 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1d27ba..78d51bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,50 +6,97 @@ Thank you for your interest in contributing! This repository welcomes contributi ### 1. Add New Problems -Use an LLM assistant (Cursor, GitHub Copilot Chat, Amazon Q) with the rule files: +For adding new LeetCode problems, please refer to the comprehensive guide: -- Include `.amazonq/rules/problem-creation.md` in your LLM context -- Ask: "Create LeetCode problem [number] ([name])" +๐Ÿ“– **[LLM-Assisted Problem Creation Guide](docs/llm-assisted-problem-creation.md)** -### 2. Enhance Test Cases +This document provides detailed instructions for using LLM assistants to generate new problems with proper templates, test cases, and documentation. -- Include `.amazonq/rules/test-quality-assurance.md` in your LLM context -- Ask: "Enhance test cases for [problem_name] problem" +**Acceptance Criteria:** -### 3. Improve Helper Classes +- All GitHub Actions CI checks must pass (includes linting, testing, security scanning, reproducibility verification, and minimum 10 test cases per problem) +- Proper type hints and code formatting +- Complete the solution (documentation is auto-generated) -- Add new data structure helpers in `leetcode_py/data_structures/` -- Follow existing patterns with generic types and visualization support +### 2. Other Contributions -### 4. Bug Fixes & Improvements +All other contributions are welcome! This includes: -- Fix issues in existing problems -- Improve documentation -- Enhance CI/CD workflows +- Bug fixes and improvements +- Documentation enhancements +- Helper class improvements +- CI/CD workflow enhancements +- Test case enhancements +- New data structure visualizations + +**For small changes:** Feel free to open a pull request directly. + +**For larger changes:** Please open an issue for discussion first. + +I'm also open to feedback and suggestions for improving the project! ## Development Setup +### Prerequisites + +- **Python 3.10+** - Modern Python runtime +- **Poetry** - Dependency management ([install guide](https://python-poetry.org/docs/#installation)) +- **Make** - Build automation (usually pre-installed on Unix systems) +- **Git** - Version control +- **Graphviz** - Graph visualization ([install guide](https://graphviz.org/download/)) + +## Development Workflow + +### 1. Fork and Setup + ```bash -git clone https://github.com/wisarootl/leetcode-py.git +# Fork the repository on GitHub, then clone your fork +git clone https://github.com/YOUR_USERNAME/leetcode-py.git cd leetcode-py poetry install + +# Add upstream remote +git remote add upstream https://github.com/wisarootl/leetcode-py.git + +# Verify setup make test +make lint ``` -## Code Standards +### 2. Create Feature Branch + +```bash +git checkout -b your-feature-name +``` -- Follow [PEP 8](https://peps.python.org/pep-0008/) Python style guide -- Use modern type hints per [PEP 585](https://peps.python.org/pep-0585/)/[PEP 604](https://peps.python.org/pep-0604/): `list[str]`, `dict[str, int]`, `Type | None` -- Automated linting enforced by CI (black, isort, ruff, mypy) -- Minimum 12 test cases per problem +### 3. Make Changes and Test -## Pull Request Process +```bash +# Test specific problem +make p-test PROBLEM=problem_name + +# Test all +make test + +# Lint your changes +make lint + +# Generate/regenerate problems (if needed) +make p-gen PROBLEM=problem_name +``` + +### 4. Submit Pull Request + +```bash +# Commit and push to your fork +git add . +git commit -m "feat: your descriptive commit message" +git push origin your-feature-name + +# Then create a pull request on GitHub from your fork to the main repository +``` -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Run `make lint` and `make test` -5. Submit a pull request with clear description +**Ensure all GitHub Actions CI checks pass before requesting review.** ## Questions? diff --git a/README.md b/README.md index d64ae6f..58fb42b 100644 --- a/README.md +++ b/README.md @@ -4,44 +4,56 @@ [![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) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=wisarootl_leetcode-py&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=wisarootl_leetcode-py) [![codecov](https://codecov.io/gh/wisarootl/leetcode-py/graph/badge.svg?token=TI97VUIA4Z)](https://codecov.io/gh/wisarootl/leetcode-py) -[![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) +[![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/leetcode-py/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/leetcode-py/actions/workflows/cd.yml) +[![downloads](https://static.pepy.tech/personalized-badge/leetcode-py-sdk?period=total&units=international_system&left_color=grey&right_color=brightgreen&left_text=pypi%20downloads)](https://pepy.tech/projects/leetcode-py-sdk) +[![python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-brightgreen?logo=python)](https://pypi.org/project/leetcode-py-sdk/) 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. +## Table of Contents + +- [What's Included](#whats-included) +- [Quick Start](#quick-start) +- [Problem Structure](#problem-structure) +- [Key Features](#key-features) +- [Usage Patterns](#usage-patterns) +- [Development Setup](#development-setup) +- [Helper Classes](#helper-classes) +- [Commands](#commands) +- [Architecture](#architecture) +- [Quality Metrics](#quality-metrics) + **What makes this different:** -- ๐Ÿค– **[LLM-Assisted Workflow](https://github.com/wisarootl/leetcode-py#llm-assisted-problem-creation)**: Generate new problems instantly with AI assistance +- ๐Ÿค– **[LLM-Assisted Workflow](https://github.com/wisarootl/leetcode-py/blob/main/docs/llm-assisted-problem-creation.md)**: 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 - ๐Ÿ“Š **Quality Assurance**: 95%+ test coverage, security scanning, automated linting +- โšก **[Powerful CLI](https://github.com/wisarootl/leetcode-py/blob/main/docs/cli-usage.md)**: Generate problems anywhere with `lcpy` command -## ๐ŸŽฏ What's Included +## ๐ŸŽฏ What's Included **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 free Grind problems for comprehensive interview preparation. +**Future**: Planned expansion to all free Grind problems for comprehensive interview preparation. [Contributions welcome!](CONTRIBUTING.md) -## ๐Ÿš€ Quick Start +## ๐Ÿš€ Quick Start ### 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) +- **Python 3.10+** - Python runtime +- **Graphviz** - Graph visualization library ([install guide](https://graphviz.org/download/)) ```bash # Install the package -pip install leetcode-py +pip install leetcode-py-sdk # Generate problems anywhere 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 @@ -49,7 +61,7 @@ python -m pytest test_solution.py # Run tests # Edit solution.py, then rerun tests ``` -### Example +### Bulk Generation Example ```bash lcpy gen --problem-tag grind-75 --output leetcode # Generate all Grind 75 problems @@ -63,7 +75,7 @@ _Bulk generation output showing "Generated problem:" messages for all 75 Grind p _Generated folder structure showing all 75 problem directories after command execution_ -## ๐Ÿ“ Problem Structure +## ๐Ÿ“ Problem Structure Each problem follows a consistent, production-ready template: @@ -93,7 +105,7 @@ _Comprehensive parametrized tests with 10+ test cases - executable and debuggabl _Beautiful colorful test output with loguru integration for enhanced debugging and test result visualization_ -## โœจ Key Features +## โœจ Key Features ### Production-Grade Development Environment @@ -143,33 +155,23 @@ _Simple arrow-based list representation for console output and test debugging_ _Interactive multi-cell playground with rich data structure visualization for each problem_ -## ๐Ÿ”„ Usage Patterns +## ๐Ÿ”„ Usage Patterns ### CLI Usage (Global Installation) -Perfect for quick problem generation anywhere: - -```bash -# Generate specific problems -lcpy gen -n 1 -n 125 -n 206 # Multiple problems by number -lcpy gen -s two-sum -s valid-palindrome # Multiple problems by slug - -# Bulk generation -lcpy gen -t grind-75 # All Grind 75 problems -lcpy gen -t grind-75 -d Easy # Only Easy problems from Grind 75 +Perfect for quick problem generation anywhere. See the ๐Ÿ“– **[Complete CLI Usage Guide](https://github.com/wisarootl/leetcode-py/blob/main/docs/cli-usage.md)** for detailed documentation with all options and examples. -# Explore available problems -lcpy list # All problems -lcpy list -t grind-75 # Filter by tag -lcpy list -d Medium # Filter by difficulty +## ๐Ÿ› ๏ธ Development Setup -# Fetch problem data -lcpy scrape -n 1 > two_sum.json # Save problem data -``` +For working within this repository to generate additional LeetCode problems using LLM assistance: -## ๐Ÿ› ๏ธ Development Setup +### Development Requirements -For working within this repository to generate additional LeetCode problems using LLM assistance: +- **Python 3.10+** - 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 ([install guide](https://graphviz.org/download/)) ```bash # Clone repository for development diff --git a/docs/cli-usage.md b/docs/cli-usage.md index 811beb5..ca91ff3 100644 --- a/docs/cli-usage.md +++ b/docs/cli-usage.md @@ -65,9 +65,23 @@ lcpy gen -s two-sum # Multiple problems by slug lcpy gen -s two-sum -s valid-palindrome -# All problems with specific tag +# All problems with specific tag (bulk generation) lcpy gen -t grind-75 +# Example: Generate all Grind 75 problems +lcpy gen --problem-tag grind-75 --output leetcode +``` + +![Problem Generation](https://raw.githubusercontent.com/wisarootl/leetcode-py/main/docs/images/problems-generation.png) + +_Bulk generation output showing "Generated problem:" messages for all 75 Grind problems_ + +![Problem Generation 2](https://raw.githubusercontent.com/wisarootl/leetcode-py/main/docs/images/problems-generation-2.png) + +_Generated folder structure showing all 75 problem directories after command execution_ + +```bash + # Filter by difficulty lcpy gen -t grind-75 -d Easy @@ -151,15 +165,24 @@ problem_name/ โ””โ”€โ”€ __init__.py # Package marker ``` +![README Example](https://raw.githubusercontent.com/wisarootl/leetcode-py/main/docs/images/readme-example.png) + +_README format that mirrors LeetCode's problem description layout_ + +![Solution Boilerplate](https://raw.githubusercontent.com/wisarootl/leetcode-py/main/docs/images/solution-boilerplate.png) + +_Solution boilerplate with type hints and TODO placeholder_ + +![Test Example](https://raw.githubusercontent.com/wisarootl/leetcode-py/main/docs/images/test-example.png) + +_Comprehensive parametrized tests with 10+ test cases - executable and debuggable in local development environment_ + ## Tags Available tags for bulk operations: - `grind-75` - Essential 75 coding interview problems -- `blind-75` - Original Blind 75 problems -- `neetcode-150` - NeetCode 150 problems -- `top-interview` - Top interview questions -- `easy`, `medium`, `hard` - Difficulty-based tags +- `grind` - Original Blind 75 problems ## Output Directory diff --git a/docs/llm-assisted-problem-creation.md b/docs/llm-assisted-problem-creation.md index f4d770f..bb97d3b 100644 --- a/docs/llm-assisted-problem-creation.md +++ b/docs/llm-assisted-problem-creation.md @@ -75,27 +75,25 @@ Each problem includes 10+ test cases covering edge cases (note: generated test c _Generated test_solution.py with parametrized tests and comprehensive test cases_ -## Test Enhancement Workflow +## Test Enhancement & Verification -### Enhancing Existing Problems +### Enhancing and Verifying Test Cases -Improve test coverage for existing problems: +Improve test coverage and verify correctness for existing or newly generated problems: + +**1. Run the tests:** ```bash -"Enhance test cases for two_sum problem" -"Add more edge cases to binary_tree_inorder_traversal" -"Fix test reproducibility for valid_palindrome" +make p-test PROBLEM={problem_name} ``` -### Quality Assurance - -The assistant can identify problems needing more test cases and verify test case correctness and reproducibility: +**2. Ask LLM to enhance or verify:** ```bash +"Enhance test cases for {problem_name} problem" +"Fix test reproducibility for {problem_name}" "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 @@ -107,11 +105,13 @@ The assistant can identify problems needing more test cases and verify test case - "Add problem 198. House Robber with grind tag" - "Create problem 70. Climbing Stairs for grind-75" - "Enhance test cases for two_sum problem" +- "Verify and fix test cases for binary_search problem" **Avoid:** - Vague requests without problem numbers - Requests for non-existent problems +- Assuming generated test cases are always correct ## Troubleshooting From e46cc42c8c9b30c2b10b163dc4dc97497c4b8978 Mon Sep 17 00:00:00 2001 From: Wisaroot <66859294+wisarootl@users.noreply.github.com> Date: Thu, 18 Sep 2025 17:27:02 +0700 Subject: [PATCH 02/43] feat: add gas_station (#50) --- Makefile | 2 +- leetcode/gas_station/README.md | 53 ++++++++++++ leetcode/gas_station/__init__.py | 0 leetcode/gas_station/helpers.py | 8 ++ leetcode/gas_station/playground.py | 30 +++++++ leetcode/gas_station/solution.py | 37 ++++++++ leetcode/gas_station/test_solution.py | 33 +++++++ .../leetcode/json/problems/gas_station.json | 62 ++++++++++++++ .../cli/resources/leetcode/json/tags.json5 | 2 +- poetry.lock | 85 ++++++++++--------- 10 files changed, 269 insertions(+), 43 deletions(-) create mode 100644 leetcode/gas_station/README.md create mode 100644 leetcode/gas_station/__init__.py create mode 100644 leetcode/gas_station/helpers.py create mode 100644 leetcode/gas_station/playground.py create mode 100644 leetcode/gas_station/solution.py create mode 100644 leetcode/gas_station/test_solution.py create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/gas_station.json diff --git a/Makefile b/Makefile index 7ce4dcd..9a0b7ca 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= house_robber +PROBLEM ?= gas_station FORCE ?= 0 COMMA := , diff --git a/leetcode/gas_station/README.md b/leetcode/gas_station/README.md new file mode 100644 index 0000000..ac00986 --- /dev/null +++ b/leetcode/gas_station/README.md @@ -0,0 +1,53 @@ +# Gas Station + +**Difficulty:** Medium +**Topics:** Array, Greedy +**Tags:** grind + +**LeetCode:** [Problem 134](https://leetcode.com/problems/gas-station/description/) + +## Problem Description + +There are `n` gas stations along a circular route, where the amount of gas at the `ith` station is `gas[i]`. + +You have a car with an unlimited gas tank and it costs `cost[i]` of gas to travel from the `ith` station to its next `(i + 1)th` station. You begin the journey with an empty tank at one of the gas stations. + +Given two integer arrays `gas` and `cost`, return _the starting gas station's index if you can travel around the circuit once in the clockwise direction, otherwise return_ `-1`. If there exists a solution, it is **guaranteed** to be **unique**. + +## Examples + +### Example 1: + +``` +Input: gas = [1,2,3,4,5], cost = [3,4,5,1,2] +Output: 3 +Explanation: +Start at station 3 (index 3) and fill up with 4 unit of gas. Your tank = 0 + 4 = 4 +Travel to station 4. Your tank = 4 - 1 + 5 = 8 +Travel to station 0. Your tank = 8 - 2 + 1 = 7 +Travel to station 1. Your tank = 7 - 3 + 2 = 6 +Travel to station 2. Your tank = 6 - 4 + 3 = 5 +Travel to station 3. The cost is 5. Your gas is just enough to travel back to station 3. +Therefore, return 3 as the starting index. +``` + +### Example 2: + +``` +Input: gas = [2,3,4], cost = [3,4,3] +Output: -1 +Explanation: +You can't start at station 0 or 1, as there is not enough gas to travel to the next station. +Let's start at station 2 and fill up with 4 unit of gas. Your tank = 0 + 4 = 4 +Travel to station 0. Your tank = 4 - 3 + 2 = 3 +Travel to station 1. Your tank = 3 - 3 + 3 = 3 +You cannot travel back to station 2, as it requires 4 unit of gas but you only have 3. +Therefore, you can't travel around the circuit once no matter where you start. +``` + +## Constraints + +- `n == gas.length == cost.length` +- `1 <= n <= 10^5` +- `0 <= gas[i], cost[i] <= 10^4` +- The input is generated such that the answer is unique. diff --git a/leetcode/gas_station/__init__.py b/leetcode/gas_station/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/gas_station/helpers.py b/leetcode/gas_station/helpers.py new file mode 100644 index 0000000..404e374 --- /dev/null +++ b/leetcode/gas_station/helpers.py @@ -0,0 +1,8 @@ +def run_can_complete_circuit(solution_class: type, gas: list[int], cost: list[int]): + implementation = solution_class() + return implementation.can_complete_circuit(gas, cost) + + +def assert_can_complete_circuit(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/gas_station/playground.py b/leetcode/gas_station/playground.py new file mode 100644 index 0000000..10b8e6e --- /dev/null +++ b/leetcode/gas_station/playground.py @@ -0,0 +1,30 @@ +# --- +# 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_can_complete_circuit, run_can_complete_circuit +from solution import Solution + +# %% +# Example test case +gas = [1, 2, 3, 4, 5] +cost = [3, 4, 5, 1, 2] +expected = 3 + +# %% +result = run_can_complete_circuit(Solution, gas, cost) +result + +# %% +assert_can_complete_circuit(result, expected) diff --git a/leetcode/gas_station/solution.py b/leetcode/gas_station/solution.py new file mode 100644 index 0000000..69dc64a --- /dev/null +++ b/leetcode/gas_station/solution.py @@ -0,0 +1,37 @@ +class Solution: + """ + Gas Station Circuit - Greedy Approach + + Visual Example: gas=[1,2,3,4,5], cost=[3,4,5,1,2] + + Station: 0 1 2 3 4 + โ”Œโ”€โ” โ”Œโ”€โ” โ”Œโ”€โ” โ”Œโ”€โ” โ”Œโ”€โ” + Gas: โ”‚1โ”‚ โ”‚2โ”‚ โ”‚3โ”‚ โ”‚4โ”‚ โ”‚5โ”‚ + โ””โ”€โ”˜ โ””โ”€โ”˜ โ””โ”€โ”˜ โ””โ”€โ”˜ โ””โ”€โ”˜ + Cost: 3 4 5 1 2 + โ†“ โ†“ โ†“ โ†“ โ†“ + Net: -2 -2 -2 +3 +3 + + Algorithm trace: + i=0: tank=0+(-2)=-2 < 0 โ†’ reset tank=0, start=1 + i=1: tank=0+(-2)=-2 < 0 โ†’ reset tank=0, start=2 + i=2: tank=0+(-2)=-2 < 0 โ†’ reset tank=0, start=3 + i=3: tank=0+(+3)=+3 โ‰ฅ 0 โ†’ continue + i=4: tank=3+(+3)=+6 โ‰ฅ 0 โ†’ return start=3 + + Key insight: If total_gas โ‰ฅ total_cost, greedy start position works! + """ + + # Time: O(n) + # Space: O(1) + def can_complete_circuit(self, gas: list[int], cost: list[int]) -> int: + if sum(gas) < sum(cost): + return -1 + + tank = start = 0 + for i in range(len(gas)): + tank += gas[i] - cost[i] + if tank < 0: + tank = 0 + start = i + 1 + return start diff --git a/leetcode/gas_station/test_solution.py b/leetcode/gas_station/test_solution.py new file mode 100644 index 0000000..011b0d0 --- /dev/null +++ b/leetcode/gas_station/test_solution.py @@ -0,0 +1,33 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_can_complete_circuit, run_can_complete_circuit +from .solution import Solution + + +class TestGasStation: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "gas, cost, expected", + [ + ([1, 2, 3, 4, 5], [3, 4, 5, 1, 2], 3), + ([2, 3, 4], [3, 4, 3], -1), + ([1, 2], [2, 1], 1), + ([5], [4], 0), + ([2], [2], 0), + ([1, 2, 3], [3, 3, 3], -1), + ([3, 1, 1], [1, 2, 2], 0), + ([5, 1, 2, 3, 4], [4, 4, 1, 5, 1], 4), + ([1, 2, 3, 4, 5, 5, 70], [2, 3, 4, 3, 9, 6, 2], 6), + ([4, 5, 2, 6, 5, 3], [3, 2, 7, 3, 2, 9], -1), + ([6, 1, 4, 3, 5], [3, 8, 2, 4, 2], 2), + ([2, 3, 4, 5], [3, 4, 5, 6], -1), + ], + ) + def test_can_complete_circuit(self, gas: list[int], cost: list[int], expected: int): + result = run_can_complete_circuit(Solution, gas, cost) + assert_can_complete_circuit(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/gas_station.json b/leetcode_py/cli/resources/leetcode/json/problems/gas_station.json new file mode 100644 index 0000000..db27dc9 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/gas_station.json @@ -0,0 +1,62 @@ +{ + "problem_name": "gas_station", + "solution_class_name": "Solution", + "problem_number": "134", + "problem_title": "Gas Station", + "difficulty": "Medium", + "topics": "Array, Greedy", + "readme_description": "There are `n` gas stations along a circular route, where the amount of gas at the `ith` station is `gas[i]`.\n\nYou have a car with an unlimited gas tank and it costs `cost[i]` of gas to travel from the `ith` station to its next `(i + 1)th` station. You begin the journey with an empty tank at one of the gas stations.\n\nGiven two integer arrays `gas` and `cost`, return *the starting gas station's index if you can travel around the circuit once in the clockwise direction, otherwise return* `-1`. If there exists a solution, it is **guaranteed** to be **unique**.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: gas = [1,2,3,4,5], cost = [3,4,5,1,2]\nOutput: 3\nExplanation:\nStart at station 3 (index 3) and fill up with 4 unit of gas. Your tank = 0 + 4 = 4\nTravel to station 4. Your tank = 4 - 1 + 5 = 8\nTravel to station 0. Your tank = 8 - 2 + 1 = 7\nTravel to station 1. Your tank = 7 - 3 + 2 = 6\nTravel to station 2. Your tank = 6 - 4 + 3 = 5\nTravel to station 3. The cost is 5. Your gas is just enough to travel back to station 3.\nTherefore, return 3 as the starting index.\n```" + }, + { + "content": "```\nInput: gas = [2,3,4], cost = [3,4,3]\nOutput: -1\nExplanation:\nYou can't start at station 0 or 1, as there is not enough gas to travel to the next station.\nLet's start at station 2 and fill up with 4 unit of gas. Your tank = 0 + 4 = 4\nTravel to station 0. Your tank = 4 - 3 + 2 = 3\nTravel to station 1. Your tank = 3 - 3 + 3 = 3\nYou cannot travel back to station 2, as it requires 4 unit of gas but you only have 3.\nTherefore, you can't travel around the circuit once no matter where you start.\n```" + } + ] + }, + "readme_constraints": "- `n == gas.length == cost.length`\n- `1 <= n <= 10^5`\n- `0 <= gas[i], cost[i] <= 10^4`\n- The input is generated such that the answer is unique.", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "can_complete_circuit", + "helpers_run_signature": "(solution_class: type, gas: list[int], cost: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.can_complete_circuit(gas, cost)", + "helpers_assert_name": "can_complete_circuit", + "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_can_complete_circuit, run_can_complete_circuit\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "GasStation", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "can_complete_circuit", + "signature": "(self, gas: list[int], cost: list[int]) -> int", + "body": " # TODO: Implement can_complete_circuit\n return -1" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_can_complete_circuit", + "signature": "(self, gas: list[int], cost: list[int], expected: int)", + "parametrize": "gas, cost, expected", + "test_cases": "[([1,2,3,4,5], [3,4,5,1,2], 3), ([2,3,4], [3,4,3], -1), ([1,2], [2,1], 1), ([5], [4], 0), ([2], [2], 0), ([1,2,3], [3,3,3], -1), ([3,1,1], [1,2,2], 0), ([5,1,2,3,4], [4,4,1,5,1], 4), ([1,2,3,4,5,5,70], [2,3,4,3,9,6,2], 6), ([4,5,2,6,5,3], [3,2,7,3,2,9], -1), ([6,1,4,3,5], [3,8,2,4,2], 2), ([2,3,4,5], [3,4,5,6], -1)]", + "body": " result = run_can_complete_circuit(Solution, gas, cost)\n assert_can_complete_circuit(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_can_complete_circuit, assert_can_complete_circuit\nfrom solution import Solution", + "playground_setup": "# Example test case\ngas = [1,2,3,4,5]\ncost = [3,4,5,1,2]\nexpected = 3", + "playground_run": "result = run_can_complete_circuit(Solution, gas, cost)\nresult", + "playground_assert": "assert_can_complete_circuit(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/tags.json5 b/leetcode_py/cli/resources/leetcode/json/tags.json5 index 229fb89..2b9f498 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", "house_robber"], + grind: [{ tag: "grind-75" }, "daily_temperatures", "gas_station", "house_robber"], // Test tag for development and testing test: ["binary_search", "two_sum", "valid_palindrome"], diff --git a/poetry.lock b/poetry.lock index 362093c..5b08393 100644 --- a/poetry.lock +++ b/poetry.lock @@ -553,38 +553,42 @@ toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "debugpy" -version = "1.8.16" +version = "1.8.17" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "debugpy-1.8.16-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:2a3958fb9c2f40ed8ea48a0d34895b461de57a1f9862e7478716c35d76f56c65"}, - {file = "debugpy-1.8.16-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ca7314042e8a614cc2574cd71f6ccd7e13a9708ce3c6d8436959eae56f2378"}, - {file = "debugpy-1.8.16-cp310-cp310-win32.whl", hash = "sha256:8624a6111dc312ed8c363347a0b59c5acc6210d897e41a7c069de3c53235c9a6"}, - {file = "debugpy-1.8.16-cp310-cp310-win_amd64.whl", hash = "sha256:fee6db83ea5c978baf042440cfe29695e1a5d48a30147abf4c3be87513609817"}, - {file = "debugpy-1.8.16-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:67371b28b79a6a12bcc027d94a06158f2fde223e35b5c4e0783b6f9d3b39274a"}, - {file = "debugpy-1.8.16-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2abae6dd02523bec2dee16bd6b0781cccb53fd4995e5c71cc659b5f45581898"}, - {file = "debugpy-1.8.16-cp311-cp311-win32.whl", hash = "sha256:f8340a3ac2ed4f5da59e064aa92e39edd52729a88fbde7bbaa54e08249a04493"}, - {file = "debugpy-1.8.16-cp311-cp311-win_amd64.whl", hash = "sha256:70f5fcd6d4d0c150a878d2aa37391c52de788c3dc680b97bdb5e529cb80df87a"}, - {file = "debugpy-1.8.16-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:b202e2843e32e80b3b584bcebfe0e65e0392920dc70df11b2bfe1afcb7a085e4"}, - {file = "debugpy-1.8.16-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64473c4a306ba11a99fe0bb14622ba4fbd943eb004847d9b69b107bde45aa9ea"}, - {file = "debugpy-1.8.16-cp312-cp312-win32.whl", hash = "sha256:833a61ed446426e38b0dd8be3e9d45ae285d424f5bf6cd5b2b559c8f12305508"}, - {file = "debugpy-1.8.16-cp312-cp312-win_amd64.whl", hash = "sha256:75f204684581e9ef3dc2f67687c3c8c183fde2d6675ab131d94084baf8084121"}, - {file = "debugpy-1.8.16-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:85df3adb1de5258dca910ae0bb185e48c98801ec15018a263a92bb06be1c8787"}, - {file = "debugpy-1.8.16-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee89e948bc236a5c43c4214ac62d28b29388453f5fd328d739035e205365f0b"}, - {file = "debugpy-1.8.16-cp313-cp313-win32.whl", hash = "sha256:cf358066650439847ec5ff3dae1da98b5461ea5da0173d93d5e10f477c94609a"}, - {file = "debugpy-1.8.16-cp313-cp313-win_amd64.whl", hash = "sha256:b5aea1083f6f50023e8509399d7dc6535a351cc9f2e8827d1e093175e4d9fa4c"}, - {file = "debugpy-1.8.16-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:2801329c38f77c47976d341d18040a9ac09d0c71bf2c8b484ad27c74f83dc36f"}, - {file = "debugpy-1.8.16-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687c7ab47948697c03b8f81424aa6dc3f923e6ebab1294732df1ca9773cc67bc"}, - {file = "debugpy-1.8.16-cp38-cp38-win32.whl", hash = "sha256:a2ba6fc5d7c4bc84bcae6c5f8edf5988146e55ae654b1bb36fecee9e5e77e9e2"}, - {file = "debugpy-1.8.16-cp38-cp38-win_amd64.whl", hash = "sha256:d58c48d8dbbbf48a3a3a638714a2d16de537b0dace1e3432b8e92c57d43707f8"}, - {file = "debugpy-1.8.16-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:135ccd2b1161bade72a7a099c9208811c137a150839e970aeaf121c2467debe8"}, - {file = "debugpy-1.8.16-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:211238306331a9089e253fd997213bc4a4c65f949271057d6695953254095376"}, - {file = "debugpy-1.8.16-cp39-cp39-win32.whl", hash = "sha256:88eb9ffdfb59bf63835d146c183d6dba1f722b3ae2a5f4b9fc03e925b3358922"}, - {file = "debugpy-1.8.16-cp39-cp39-win_amd64.whl", hash = "sha256:c2c47c2e52b40449552843b913786499efcc3dbc21d6c49287d939cd0dbc49fd"}, - {file = "debugpy-1.8.16-py2.py3-none-any.whl", hash = "sha256:19c9521962475b87da6f673514f7fd610328757ec993bf7ec0d8c96f9a325f9e"}, - {file = "debugpy-1.8.16.tar.gz", hash = "sha256:31e69a1feb1cf6b51efbed3f6c9b0ef03bc46ff050679c4be7ea6d2e23540870"}, + {file = "debugpy-1.8.17-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:c41d2ce8bbaddcc0009cc73f65318eedfa3dbc88a8298081deb05389f1ab5542"}, + {file = "debugpy-1.8.17-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:1440fd514e1b815edd5861ca394786f90eb24960eb26d6f7200994333b1d79e3"}, + {file = "debugpy-1.8.17-cp310-cp310-win32.whl", hash = "sha256:3a32c0af575749083d7492dc79f6ab69f21b2d2ad4cd977a958a07d5865316e4"}, + {file = "debugpy-1.8.17-cp310-cp310-win_amd64.whl", hash = "sha256:a3aad0537cf4d9c1996434be68c6c9a6d233ac6f76c2a482c7803295b4e4f99a"}, + {file = "debugpy-1.8.17-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:d3fce3f0e3de262a3b67e69916d001f3e767661c6e1ee42553009d445d1cd840"}, + {file = "debugpy-1.8.17-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c6bdf134457ae0cac6fb68205776be635d31174eeac9541e1d0c062165c6461f"}, + {file = "debugpy-1.8.17-cp311-cp311-win32.whl", hash = "sha256:e79a195f9e059edfe5d8bf6f3749b2599452d3e9380484cd261f6b7cd2c7c4da"}, + {file = "debugpy-1.8.17-cp311-cp311-win_amd64.whl", hash = "sha256:b532282ad4eca958b1b2d7dbcb2b7218e02cb934165859b918e3b6ba7772d3f4"}, + {file = "debugpy-1.8.17-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:f14467edef672195c6f6b8e27ce5005313cb5d03c9239059bc7182b60c176e2d"}, + {file = "debugpy-1.8.17-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:24693179ef9dfa20dca8605905a42b392be56d410c333af82f1c5dff807a64cc"}, + {file = "debugpy-1.8.17-cp312-cp312-win32.whl", hash = "sha256:6a4e9dacf2cbb60d2514ff7b04b4534b0139facbf2abdffe0639ddb6088e59cf"}, + {file = "debugpy-1.8.17-cp312-cp312-win_amd64.whl", hash = "sha256:e8f8f61c518952fb15f74a302e068b48d9c4691768ade433e4adeea961993464"}, + {file = "debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464"}, + {file = "debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088"}, + {file = "debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83"}, + {file = "debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420"}, + {file = "debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1"}, + {file = "debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f"}, + {file = "debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670"}, + {file = "debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c"}, + {file = "debugpy-1.8.17-cp38-cp38-macosx_15_0_x86_64.whl", hash = "sha256:8deb4e31cd575c9f9370042876e078ca118117c1b5e1f22c32befcfbb6955f0c"}, + {file = "debugpy-1.8.17-cp38-cp38-manylinux_2_34_x86_64.whl", hash = "sha256:b75868b675949a96ab51abc114c7163f40ff0d8f7d6d5fd63f8932fd38e9c6d7"}, + {file = "debugpy-1.8.17-cp38-cp38-win32.whl", hash = "sha256:17e456da14848d618662354e1dccfd5e5fb75deec3d1d48dc0aa0baacda55860"}, + {file = "debugpy-1.8.17-cp38-cp38-win_amd64.whl", hash = "sha256:e851beb536a427b5df8aa7d0c7835b29a13812f41e46292ff80b2ef77327355a"}, + {file = "debugpy-1.8.17-cp39-cp39-macosx_15_0_x86_64.whl", hash = "sha256:f2ac8055a0c4a09b30b931100996ba49ef334c6947e7ae365cdd870416d7513e"}, + {file = "debugpy-1.8.17-cp39-cp39-manylinux_2_34_x86_64.whl", hash = "sha256:eaa85bce251feca8e4c87ce3b954aba84b8c645b90f0e6a515c00394a9f5c0e7"}, + {file = "debugpy-1.8.17-cp39-cp39-win32.whl", hash = "sha256:b13eea5587e44f27f6c48588b5ad56dcb74a4f3a5f89250443c94587f3eb2ea1"}, + {file = "debugpy-1.8.17-cp39-cp39-win_amd64.whl", hash = "sha256:bb1bbf92317e1f35afcf3ef0450219efb3afe00be79d8664b250ac0933b9015f"}, + {file = "debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef"}, + {file = "debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e"}, ] [[package]] @@ -1410,27 +1414,26 @@ wcwidth = "*" [[package]] name = "psutil" -version = "7.0.0" -description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." +version = "7.1.0" +description = "Cross-platform lib for process and system monitoring." optional = false python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, - {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, - {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, - {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, - {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, - {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, - {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, - {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, - {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, - {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, + {file = "psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13"}, + {file = "psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5"}, + {file = "psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3"}, + {file = "psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3"}, + {file = "psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d"}, + {file = "psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca"}, + {file = "psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d"}, + {file = "psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07"}, + {file = "psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2"}, ] [package.extras] -dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] -test = ["pytest", "pytest-xdist", "setuptools"] +dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pyreadline ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] +test = ["pytest", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "setuptools", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""] [[package]] name = "ptyprocess" From 396bbdca03ffb6bba417a081bfd0f91636f1a798 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Sep 2025 17:42:18 +0700 Subject: [PATCH 03/43] chore(deps): update sonarsource/sonarqube-scan-action action to v6 (#51) --- .github/workflows/ci-test.yml | 2 +- README.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 7116769..c4b147d 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -75,7 +75,7 @@ jobs: - name: SonarQube Scan if: matrix.python-version == env.TARGET_PYTHON_VERSION - uses: SonarSource/sonarqube-scan-action@1a6d90ebcb0e6a6b1d87e37ba693fe453195ae25 # v5.3.1 + uses: SonarSource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/README.md b/README.md index 58fb42b..f6a1b6a 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # LeetCode Practice Environment Generator ๐Ÿš€ +[![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/leetcode-py/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/leetcode-py/actions/workflows/cd.yml) [![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) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=wisarootl_leetcode-py&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=wisarootl_leetcode-py) [![codecov](https://codecov.io/gh/wisarootl/leetcode-py/graph/badge.svg?token=TI97VUIA4Z)](https://codecov.io/gh/wisarootl/leetcode-py) -[![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/leetcode-py/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/leetcode-py/actions/workflows/cd.yml) -[![downloads](https://static.pepy.tech/personalized-badge/leetcode-py-sdk?period=total&units=international_system&left_color=grey&right_color=brightgreen&left_text=pypi%20downloads)](https://pepy.tech/projects/leetcode-py-sdk) -[![python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-brightgreen?logo=python)](https://pypi.org/project/leetcode-py-sdk/) +[![pypi](https://img.shields.io/pypi/v/leetcode-py-sdk.svg?color=blue)](https://pypi.python.org/pypi/leetcode-py-sdk) +[![downloads](https://static.pepy.tech/personalized-badge/leetcode-py-sdk?period=total&units=international_system&left_color=grey&right_color=blue&left_text=pypi%20downloads)](https://pepy.tech/projects/leetcode-py-sdk) +[![python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue?logo=python)](https://github.com/wisarootl/leetcode-py/) 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. From 10a73e31b7fd950feaba2d56a5b186423772f62f Mon Sep 17 00:00:00 2001 From: Wisaroot <66859294+wisarootl@users.noreply.github.com> Date: Thu, 18 Sep 2025 18:27:41 +0700 Subject: [PATCH 04/43] ci: add check_tag_problems.py (#52) --- Makefile | 1 + scripts/check_tag_problems.py | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100755 scripts/check_tag_problems.py diff --git a/Makefile b/Makefile index 9a0b7ca..ab376fd 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,7 @@ endef lint: poetry run python scripts/sort_tags.py + poetry run python scripts/check_tag_problems.py poetry sort npx prettier --write "**/*.{ts,tsx,css,json,yaml,yml,md}" $(call lint_target,.) diff --git a/scripts/check_tag_problems.py b/scripts/check_tag_problems.py new file mode 100755 index 0000000..9948d01 --- /dev/null +++ b/scripts/check_tag_problems.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import sys +from pathlib import Path + +import json5 + + +def main(): + # Load tags file + tags_file = Path("leetcode_py/cli/resources/leetcode/json/tags.json5") + problems_dir = Path("leetcode_py/cli/resources/leetcode/json/problems") + + with open(tags_file) as f: + tags_data = json5.load(f) + + # Get all existing problem JSON files + existing_problems = {p.stem for p in problems_dir.glob("*.json")} + + # Check each tag's problems + missing_problems = [] + for tag_name, problems in tags_data.items(): + if isinstance(problems, list): + for problem in problems: + if isinstance(problem, str): # Skip dict entries like {"tag": "grind-75"} + if problem not in existing_problems: + missing_problems.append((tag_name, problem)) + + if missing_problems: + print("โŒ Found problems in tags that don't have JSON files:") + for tag_name, problem in missing_problems: + print(f" {tag_name}: {problem}") + sys.exit(1) + else: + print("โœ… All tagged problems have corresponding JSON files!") + sys.exit(0) + + +if __name__ == "__main__": + main() From 94c5dfd1cf0df3bbd83ca8bfa1d65b1a53324e25 Mon Sep 17 00:00:00 2001 From: Wisaroot <66859294+wisarootl@users.noreply.github.com> Date: Fri, 19 Sep 2025 08:08:52 +0700 Subject: [PATCH 05/43] feat: add 5 more problems (#54) --- Makefile | 2 +- .../README.md | 47 ++++ .../__init__.py | 0 .../helpers.py | 25 ++ .../playground.py | 30 ++ .../solution.py | 37 +++ .../test_solution.py | 130 +++++++++ leetcode/group_anagrams/README.md | 46 +++ leetcode/group_anagrams/__init__.py | 0 leetcode/group_anagrams/helpers.py | 13 + leetcode/group_anagrams/playground.py | 29 ++ leetcode/group_anagrams/solution.py | 25 ++ leetcode/group_anagrams/test_solution.py | 39 +++ leetcode/maximum_product_subarray/README.md | 37 +++ leetcode/maximum_product_subarray/__init__.py | 0 leetcode/maximum_product_subarray/helpers.py | 8 + .../maximum_product_subarray/playground.py | 29 ++ leetcode/maximum_product_subarray/solution.py | 16 ++ .../maximum_product_subarray/test_solution.py | 36 +++ leetcode/next_permutation/README.md | 51 ++++ leetcode/next_permutation/__init__.py | 0 leetcode/next_permutation/helpers.py | 10 + leetcode/next_permutation/playground.py | 29 ++ leetcode/next_permutation/solution.py | 23 ++ leetcode/next_permutation/test_solution.py | 36 +++ leetcode/valid_sudoku/README.md | 63 +++++ leetcode/valid_sudoku/__init__.py | 0 leetcode/valid_sudoku/helpers.py | 8 + leetcode/valid_sudoku/playground.py | 39 +++ leetcode/valid_sudoku/solution.py | 22 ++ leetcode/valid_sudoku/test_solution.py | 261 ++++++++++++++++++ ...n_add_and_search_words_data_structure.json | 80 ++++++ .../json/problems/group_anagrams.json | 72 +++++ .../problems/maximum_product_subarray.json | 73 +++++ .../json/problems/next_permutation.json | 70 +++++ .../leetcode/json/problems/valid_sudoku.json | 73 +++++ .../cli/resources/leetcode/json/tags.json5 | 12 +- poetry.lock | 186 +++++++------ 38 files changed, 1570 insertions(+), 87 deletions(-) create mode 100644 leetcode/design_add_and_search_words_data_structure/README.md create mode 100644 leetcode/design_add_and_search_words_data_structure/__init__.py create mode 100644 leetcode/design_add_and_search_words_data_structure/helpers.py create mode 100644 leetcode/design_add_and_search_words_data_structure/playground.py create mode 100644 leetcode/design_add_and_search_words_data_structure/solution.py create mode 100644 leetcode/design_add_and_search_words_data_structure/test_solution.py create mode 100644 leetcode/group_anagrams/README.md create mode 100644 leetcode/group_anagrams/__init__.py create mode 100644 leetcode/group_anagrams/helpers.py create mode 100644 leetcode/group_anagrams/playground.py create mode 100644 leetcode/group_anagrams/solution.py create mode 100644 leetcode/group_anagrams/test_solution.py create mode 100644 leetcode/maximum_product_subarray/README.md create mode 100644 leetcode/maximum_product_subarray/__init__.py create mode 100644 leetcode/maximum_product_subarray/helpers.py create mode 100644 leetcode/maximum_product_subarray/playground.py create mode 100644 leetcode/maximum_product_subarray/solution.py create mode 100644 leetcode/maximum_product_subarray/test_solution.py create mode 100644 leetcode/next_permutation/README.md create mode 100644 leetcode/next_permutation/__init__.py create mode 100644 leetcode/next_permutation/helpers.py create mode 100644 leetcode/next_permutation/playground.py create mode 100644 leetcode/next_permutation/solution.py create mode 100644 leetcode/next_permutation/test_solution.py create mode 100644 leetcode/valid_sudoku/README.md create mode 100644 leetcode/valid_sudoku/__init__.py create mode 100644 leetcode/valid_sudoku/helpers.py create mode 100644 leetcode/valid_sudoku/playground.py create mode 100644 leetcode/valid_sudoku/solution.py create mode 100644 leetcode/valid_sudoku/test_solution.py create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/design_add_and_search_words_data_structure.json create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/group_anagrams.json create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/maximum_product_subarray.json create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/next_permutation.json create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/valid_sudoku.json diff --git a/Makefile b/Makefile index ab376fd..6fd145a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= gas_station +PROBLEM ?= design_add_and_search_words_data_structure FORCE ?= 0 COMMA := , diff --git a/leetcode/design_add_and_search_words_data_structure/README.md b/leetcode/design_add_and_search_words_data_structure/README.md new file mode 100644 index 0000000..7ea8ac7 --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/README.md @@ -0,0 +1,47 @@ +# Design Add and Search Words Data Structure + +**Difficulty:** Medium +**Topics:** String, Depth-First Search, Design, Trie +**Tags:** grind + +**LeetCode:** [Problem 211](https://leetcode.com/problems/design-add-and-search-words-data-structure/description/) + +## Problem Description + +Design a data structure that supports adding new words and finding if a string matches any previously added string. + +Implement the `WordDictionary` class: + +- `WordDictionary()` Initializes the object. +- `void addWord(word)` Adds `word` to the data structure, it can be matched later. +- `bool search(word)` Returns `true` if there is any string in the data structure that matches `word` or `false` otherwise. `word` may contain dots `'.'` where dots can be matched with any letter. + +## Examples + +### Example 1: + +``` +Input +["WordDictionary","addWord","addWord","addWord","search","search","search","search"] +[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]] +Output +[null,null,null,null,false,true,true,true] + +Explanation +WordDictionary wordDictionary = new WordDictionary(); +wordDictionary.addWord("bad"); +wordDictionary.addWord("dad"); +wordDictionary.addWord("mad"); +wordDictionary.search("pad"); // return False +wordDictionary.search("bad"); // return True +wordDictionary.search(".ad"); // return True +wordDictionary.search("b.."); // return True +``` + +## Constraints + +- `1 <= word.length <= 25` +- `word` in `addWord` consists of lowercase English letters. +- `word` in `search` consist of `'.'` or lowercase English letters. +- There will be at most `2` dots in `word` for `search` queries. +- At most `10^4` calls will be made to `addWord` and `search`. diff --git a/leetcode/design_add_and_search_words_data_structure/__init__.py b/leetcode/design_add_and_search_words_data_structure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/design_add_and_search_words_data_structure/helpers.py b/leetcode/design_add_and_search_words_data_structure/helpers.py new file mode 100644 index 0000000..5fba0eb --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/helpers.py @@ -0,0 +1,25 @@ +from typing import Any + + +def run_word_dictionary(solution_class: type, operations: list[str], inputs: list[list[str]]): + wd: Any = None + results: list[bool | None] = [] + + for op, args in zip(operations, inputs): + if op == "WordDictionary": + wd = solution_class() + results.append(None) + elif op == "addWord": + assert wd is not None + wd.add_word(args[0]) + results.append(None) + elif op == "search": + assert wd is not None + results.append(wd.search(args[0])) + + return results + + +def assert_word_dictionary(result: list[bool | None], expected: list[bool | None]) -> bool: + assert result == expected + return True diff --git a/leetcode/design_add_and_search_words_data_structure/playground.py b/leetcode/design_add_and_search_words_data_structure/playground.py new file mode 100644 index 0000000..b365ea1 --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/playground.py @@ -0,0 +1,30 @@ +# --- +# 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_word_dictionary, run_word_dictionary +from solution import WordDictionary + +# %% +# Example test case +operations = ["WordDictionary", "addWord", "addWord", "addWord", "search", "search", "search", "search"] +inputs = [[], ["bad"], ["dad"], ["mad"], ["pad"], ["bad"], [".ad"], ["b.."]] +expected = [None, None, None, None, False, True, True, True] + +# %% +result = run_word_dictionary(WordDictionary, operations, inputs) +result + +# %% +assert_word_dictionary(result, expected) diff --git a/leetcode/design_add_and_search_words_data_structure/solution.py b/leetcode/design_add_and_search_words_data_structure/solution.py new file mode 100644 index 0000000..3cc2ba3 --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/solution.py @@ -0,0 +1,37 @@ +from typing import Any + + +class WordDictionary: + + # Time: O(1) + # Space: O(1) + def __init__(self) -> None: + self.root: dict[str, Any] = {} + + # Time: O(m) where m = len(word) + # Space: O(m) for new word + def add_word(self, word: str) -> None: + node = self.root + for char in word: + if char not in node: + node[char] = {} + node = node[char] + node["#"] = True + + # Time: O(n * 26^k) where n = len(word), k = number of dots + # Space: O(n) for recursion stack + def search(self, word: str) -> bool: + def dfs(i: int, node: dict[str, Any]) -> bool: + if i == len(word): + return "#" in node + + char = word[i] + if char == ".": + for key in node: + if key != "#" and dfs(i + 1, node[key]): + return True + return False + else: + return char in node and dfs(i + 1, node[char]) + + return dfs(0, self.root) diff --git a/leetcode/design_add_and_search_words_data_structure/test_solution.py b/leetcode/design_add_and_search_words_data_structure/test_solution.py new file mode 100644 index 0000000..2142c7d --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/test_solution.py @@ -0,0 +1,130 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_word_dictionary, run_word_dictionary +from .solution import WordDictionary + + +class TestDesignAddAndSearchWordsDataStructure: + + @logged_test + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + [ + "WordDictionary", + "addWord", + "addWord", + "addWord", + "search", + "search", + "search", + "search", + ], + [[], ["bad"], ["dad"], ["mad"], ["pad"], ["bad"], [".ad"], ["b.."]], + [None, None, None, None, False, True, True, True], + ), + ( + ["WordDictionary", "addWord", "search", "search", "search"], + [[], ["a"], ["a"], ["."], ["aa"]], + [None, None, True, True, False], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["at"], ["and"], ["an"], [".at"], ["an."]], + [None, None, None, False, False, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search"], + [[], ["word"], ["world"], ["word"], ["wor."]], + [None, None, None, True, True], + ), + ( + ["WordDictionary", "addWord", "search", "search"], + [[], ["test"], ["test"], ["t..t"]], + [None, None, True, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["a"], ["b"], ["a"], ["."], ["c"]], + [None, None, None, True, True, False], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["abc"], ["def"], ["..."], ["a.."], ["..f"]], + [None, None, None, True, True, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [ + [], + ["programming"], + ["algorithm"], + ["prog......."], + ["algo....."], + ["........ing"], + ], + [None, None, None, True, True, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search"], + [[], ["x"], ["xy"], ["."], [".."]], + [None, None, None, True, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["hello"], ["world"], ["hi"], ["word"], ["......"]], + [None, None, None, False, False, False], + ), + ( + [ + "WordDictionary", + "addWord", + "addWord", + "addWord", + "search", + "search", + "search", + "search", + ], + [[], ["cat"], ["car"], ["card"], ["c.."], ["ca."], ["c..d"], ["....."]], + [None, None, None, None, True, True, True, False], + ), + ( + [ + "WordDictionary", + "addWord", + "addWord", + "addWord", + "search", + "search", + "search", + ], + [ + [], + ["run"], + ["runner"], + ["running"], + ["run"], + ["run..."], + ["run....."], + ], + [None, None, None, None, True, True, False], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["abc"], ["xyz"], ["..."], [".."], ["...."]], + [None, None, None, True, False, False], + ), + ], + ) + def test_word_dictionary( + self, + operations: list[str], + inputs: list[list[str]], + expected: list[bool | None], + ): + result = run_word_dictionary(WordDictionary, operations, inputs) + assert_word_dictionary(result, expected) diff --git a/leetcode/group_anagrams/README.md b/leetcode/group_anagrams/README.md new file mode 100644 index 0000000..c7f9561 --- /dev/null +++ b/leetcode/group_anagrams/README.md @@ -0,0 +1,46 @@ +# Group Anagrams + +**Difficulty:** Medium +**Topics:** Array, Hash Table, String, Sorting +**Tags:** grind + +**LeetCode:** [Problem 49](https://leetcode.com/problems/group-anagrams/description/) + +## Problem Description + +Given an array of strings `strs`, group the anagrams together. You can return the answer in **any order**. + +An **anagram** is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once. + +## Examples + +### Example 1: + +``` +Input: strs = ["eat","tea","tan","ate","nat","bat"] +Output: [["bat"],["nat","tan"],["ate","eat","tea"]] +Explanation: +- There is no string in strs that can be rearranged to form "bat". +- The strings "nat" and "tan" are anagrams as they can be rearranged to form each other. +- The strings "ate", "eat", and "tea" are anagrams as they can be rearranged to form each other. +``` + +### Example 2: + +``` +Input: strs = [""] +Output: [[""]] +``` + +### Example 3: + +``` +Input: strs = ["a"] +Output: [["a"]] +``` + +## Constraints + +- `1 <= strs.length <= 10^4` +- `0 <= strs[i].length <= 100` +- `strs[i]` consists of lowercase English letters. diff --git a/leetcode/group_anagrams/__init__.py b/leetcode/group_anagrams/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/group_anagrams/helpers.py b/leetcode/group_anagrams/helpers.py new file mode 100644 index 0000000..fee8547 --- /dev/null +++ b/leetcode/group_anagrams/helpers.py @@ -0,0 +1,13 @@ +def run_group_anagrams(solution_class: type, strs: list[str]): + implementation = solution_class() + return implementation.group_anagrams(strs) + + +def assert_group_anagrams(result: list[list[str]], expected: list[list[str]]) -> bool: + # Sort both result and expected for comparison since order doesn't matter + result_sorted = [sorted(group) for group in result] + expected_sorted = [sorted(group) for group in expected] + result_sorted.sort() + expected_sorted.sort() + assert result_sorted == expected_sorted + return True diff --git a/leetcode/group_anagrams/playground.py b/leetcode/group_anagrams/playground.py new file mode 100644 index 0000000..dd57ae4 --- /dev/null +++ b/leetcode/group_anagrams/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_group_anagrams, run_group_anagrams +from solution import Solution + +# %% +# Example test case +strs = ["eat", "tea", "tan", "ate", "nat", "bat"] +expected = [["bat"], ["nat", "tan"], ["ate", "eat", "tea"]] + +# %% +result = run_group_anagrams(Solution, strs) +result + +# %% +assert_group_anagrams(result, expected) diff --git a/leetcode/group_anagrams/solution.py b/leetcode/group_anagrams/solution.py new file mode 100644 index 0000000..9230c16 --- /dev/null +++ b/leetcode/group_anagrams/solution.py @@ -0,0 +1,25 @@ +class Solution: + + # Time: O(n * k) - when k > 26 use counting O(k), when k โ‰ค 26 use sorting O(k log k) + # Space: O(n * k) + def group_anagrams(self, strs: list[str]) -> list[list[str]]: + groups: dict[str | tuple[int, ...], list[str]] = {} + + for s in strs: + if len(s) >= 26: + # Use counting for short strings (better time) + # Time: O(k) - single pass through string + O(26) for tuple + # Space: O(26) = O(1) per key + count = [0] * 26 + for c in s: + count[ord(c) - ord("a")] += 1 + key: tuple[int, ...] | str = tuple(count) + else: + # Use sorting for long strings (better space) + # Time: O(k log k) - sorting dominates + # Space: O(k) per key + key: tuple[int, ...] | str = "".join(sorted(s)) + + groups.setdefault(key, []).append(s) + + return list(groups.values()) diff --git a/leetcode/group_anagrams/test_solution.py b/leetcode/group_anagrams/test_solution.py new file mode 100644 index 0000000..aa72701 --- /dev/null +++ b/leetcode/group_anagrams/test_solution.py @@ -0,0 +1,39 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_group_anagrams, run_group_anagrams +from .solution import Solution + + +class TestGroupAnagrams: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "strs, expected", + [ + ( + ["eat", "tea", "tan", "ate", "nat", "bat"], + [["bat"], ["nat", "tan"], ["ate", "eat", "tea"]], + ), + ([""], [[""]]), + (["a"], [["a"]]), + (["abc", "bca", "cab", "xyz"], [["abc", "bca", "cab"], ["xyz"]]), + (["ab", "ba"], [["ab", "ba"]]), + (["abc"], [["abc"]]), + (["listen", "silent", "hello"], [["listen", "silent"], ["hello"]]), + (["aab", "aba", "baa"], [["aab", "aba", "baa"]]), + (["race", "care", "acre"], [["race", "care", "acre"]]), + (["", "b"], [[""], ["b"]]), + (["a", "aa", "aaa"], [["a"], ["aa"], ["aaa"]]), + (["abc", "def", "ghi"], [["abc"], ["def"], ["ghi"]]), + (["abcd", "dcba", "lls", "sll"], [["abcd", "dcba"], ["lls", "sll"]]), + (["ac", "c"], [["ac"], ["c"]]), + (["huh", "tit"], [["huh"], ["tit"]]), + ], + ) + def test_group_anagrams(self, strs: list[str], expected: list[list[str]]): + result = run_group_anagrams(Solution, strs) + assert_group_anagrams(result, expected) diff --git a/leetcode/maximum_product_subarray/README.md b/leetcode/maximum_product_subarray/README.md new file mode 100644 index 0000000..6ced65a --- /dev/null +++ b/leetcode/maximum_product_subarray/README.md @@ -0,0 +1,37 @@ +# Maximum Product Subarray + +**Difficulty:** Medium +**Topics:** Array, Dynamic Programming +**Tags:** grind + +**LeetCode:** [Problem 152](https://leetcode.com/problems/maximum-product-subarray/description/) + +## Problem Description + +Given an integer array `nums`, find a subarray that has the largest product, and return the product. + +The test cases are generated so that the answer will fit in a **32-bit** integer. + +## Examples + +### Example 1: + +``` +Input: nums = [2,3,-2,4] +Output: 6 +Explanation: [2,3] has the largest product 6. +``` + +### Example 2: + +``` +Input: nums = [-2,0,-1] +Output: 0 +Explanation: The result cannot be 2, because [-2,-1] is not a subarray. +``` + +## Constraints + +- `1 <= nums.length <= 2 * 10^4` +- `-10 <= nums[i] <= 10` +- The product of any subarray of `nums` is **guaranteed** to fit in a **32-bit** integer. diff --git a/leetcode/maximum_product_subarray/__init__.py b/leetcode/maximum_product_subarray/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/maximum_product_subarray/helpers.py b/leetcode/maximum_product_subarray/helpers.py new file mode 100644 index 0000000..d9330e9 --- /dev/null +++ b/leetcode/maximum_product_subarray/helpers.py @@ -0,0 +1,8 @@ +def run_max_product(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.max_product(nums) + + +def assert_max_product(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/maximum_product_subarray/playground.py b/leetcode/maximum_product_subarray/playground.py new file mode 100644 index 0000000..98c1811 --- /dev/null +++ b/leetcode/maximum_product_subarray/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_max_product, run_max_product +from solution import Solution + +# %% +# Example test case +nums = [2, 3, -2, 4] +expected = 6 + +# %% +result = run_max_product(Solution, nums) +result + +# %% +assert_max_product(result, expected) diff --git a/leetcode/maximum_product_subarray/solution.py b/leetcode/maximum_product_subarray/solution.py new file mode 100644 index 0000000..9a58990 --- /dev/null +++ b/leetcode/maximum_product_subarray/solution.py @@ -0,0 +1,16 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def max_product(self, nums: list[int]) -> int: + max_prod = min_prod = result = nums[0] + + for num in nums[1:]: + if num < 0: + max_prod, min_prod = min_prod, max_prod + + max_prod = max(num, max_prod * num) + min_prod = min(num, min_prod * num) + result = max(result, max_prod) + + return result diff --git a/leetcode/maximum_product_subarray/test_solution.py b/leetcode/maximum_product_subarray/test_solution.py new file mode 100644 index 0000000..d9eedec --- /dev/null +++ b/leetcode/maximum_product_subarray/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_max_product, run_max_product +from .solution import Solution + + +class TestMaximumProductSubarray: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([2, 3, -2, 4], 6), + ([-2, 0, -1], 0), + ([1], 1), + ([0], 0), + ([-1], -1), + ([2, -1, 3], 3), + ([-2, -3], 6), + ([1, 2, 3, 4], 24), + ([-1, -2, -3], 6), + ([0, 2], 2), + ([3, -1, 4], 4), + ([-2, 3, -4], 24), + ([2, 3, -2, 4, -1], 48), + ([1, 0, -1, 2, 3], 6), + ([-3, 0, 1, -2], 1), + ], + ) + def test_max_product(self, nums: list[int], expected: int): + result = run_max_product(Solution, nums) + assert_max_product(result, expected) diff --git a/leetcode/next_permutation/README.md b/leetcode/next_permutation/README.md new file mode 100644 index 0000000..f6829ca --- /dev/null +++ b/leetcode/next_permutation/README.md @@ -0,0 +1,51 @@ +# Next Permutation + +**Difficulty:** Medium +**Topics:** Array, Two Pointers +**Tags:** grind + +**LeetCode:** [Problem 31](https://leetcode.com/problems/next-permutation/description/) + +## Problem Description + +A **permutation** of an array of integers is an arrangement of its members into a sequence or linear order. + +- For example, for `arr = [1,2,3]`, the following are all the permutations of `arr`: `[1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1]`. + +The **next permutation** of an array of integers is the next lexicographically greater permutation of its integer. More formally, if all the permutations of the array are sorted in one container according to their lexicographical order, then the **next permutation** of that array is the permutation that follows it in the sorted container. If such arrangement is not possible, the array must be rearranged as the lowest possible order (i.e., sorted in ascending order). + +- For example, the next permutation of `arr = [1,2,3]` is `[1,3,2]`. +- Similarly, the next permutation of `arr = [2,3,1]` is `[3,1,2]`. +- While the next permutation of `arr = [3,2,1]` is `[1,2,3]` because `[3,2,1]` does not have a lexicographical larger rearrangement. + +Given an array of integers `nums`, find the next permutation of `nums`. + +The replacement must be **in place** and use only constant extra memory. + +## Examples + +### Example 1: + +``` +Input: nums = [1,2,3] +Output: [1,3,2] +``` + +### Example 2: + +``` +Input: nums = [3,2,1] +Output: [1,2,3] +``` + +### Example 3: + +``` +Input: nums = [1,1,5] +Output: [1,5,1] +``` + +## Constraints + +- `1 <= nums.length <= 100` +- `0 <= nums[i] <= 100` diff --git a/leetcode/next_permutation/__init__.py b/leetcode/next_permutation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/next_permutation/helpers.py b/leetcode/next_permutation/helpers.py new file mode 100644 index 0000000..de58c67 --- /dev/null +++ b/leetcode/next_permutation/helpers.py @@ -0,0 +1,10 @@ +def run_next_permutation(solution_class: type, nums: list[int]): + implementation = solution_class() + nums_copy = nums.copy() + implementation.next_permutation(nums_copy) + return nums_copy + + +def assert_next_permutation(result: list[int], expected: list[int]) -> bool: + assert result == expected + return True diff --git a/leetcode/next_permutation/playground.py b/leetcode/next_permutation/playground.py new file mode 100644 index 0000000..a4c8779 --- /dev/null +++ b/leetcode/next_permutation/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_next_permutation, run_next_permutation +from solution import Solution + +# %% +# Example test case +nums = [1, 2, 3] +expected = [1, 3, 2] + +# %% +result = run_next_permutation(Solution, nums) +result + +# %% +assert_next_permutation(result, expected) diff --git a/leetcode/next_permutation/solution.py b/leetcode/next_permutation/solution.py new file mode 100644 index 0000000..92c8fef --- /dev/null +++ b/leetcode/next_permutation/solution.py @@ -0,0 +1,23 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def next_permutation(self, nums: list[int]) -> None: + # Find pivot (rightmost ascending pair) + i = len(nums) - 2 + while i >= 0 and nums[i] >= nums[i + 1]: + i -= 1 + + if i >= 0: + # Find successor (rightmost element > pivot) + j = len(nums) - 1 + while nums[j] <= nums[i]: + j -= 1 + nums[i], nums[j] = nums[j], nums[i] + + # Reverse suffix + left, right = i + 1, len(nums) - 1 + while left < right: + nums[left], nums[right] = nums[right], nums[left] + left += 1 + right -= 1 diff --git a/leetcode/next_permutation/test_solution.py b/leetcode/next_permutation/test_solution.py new file mode 100644 index 0000000..9aca312 --- /dev/null +++ b/leetcode/next_permutation/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_next_permutation, run_next_permutation +from .solution import Solution + + +class TestNextPermutation: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([1, 2, 3], [1, 3, 2]), + ([3, 2, 1], [1, 2, 3]), + ([1, 1, 5], [1, 5, 1]), + ([1], [1]), + ([1, 2], [2, 1]), + ([2, 1], [1, 2]), + ([1, 3, 2], [2, 1, 3]), + ([2, 3, 1], [3, 1, 2]), + ([1, 2, 3, 4], [1, 2, 4, 3]), + ([4, 3, 2, 1], [1, 2, 3, 4]), + ([1, 1, 1], [1, 1, 1]), + ([1, 2, 1], [2, 1, 1]), + ([5, 4, 7, 5, 3, 2], [5, 5, 2, 3, 4, 7]), + ([1, 3, 2, 1], [2, 1, 1, 3]), + ([2, 1, 3], [2, 3, 1]), + ], + ) + def test_next_permutation(self, nums: list[int], expected: list[int]): + result = run_next_permutation(Solution, nums) + assert_next_permutation(result, expected) diff --git a/leetcode/valid_sudoku/README.md b/leetcode/valid_sudoku/README.md new file mode 100644 index 0000000..b7cccfa --- /dev/null +++ b/leetcode/valid_sudoku/README.md @@ -0,0 +1,63 @@ +# Valid Sudoku + +**Difficulty:** Medium +**Topics:** Array, Hash Table, Matrix +**Tags:** grind + +**LeetCode:** [Problem 36](https://leetcode.com/problems/valid-sudoku/description/) + +## Problem Description + +Determine if a `9 x 9` Sudoku board is valid. Only the filled cells need to be validated **according to the following rules**: + +1. Each row must contain the digits `1-9` without repetition. +2. Each column must contain the digits `1-9` without repetition. +3. Each of the nine `3 x 3` sub-boxes of the grid must contain the digits `1-9` without repetition. + +**Note:** + +- A Sudoku board (partially filled) could be valid but is not necessarily solvable. +- Only the filled cells need to be validated according to the mentioned rules. + +## Examples + +### Example 1: + +![Example 1](https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png) + +``` +Input: board = +[["5","3",".",".","7",".",".",".","."] +,["6",".",".","1","9","5",".",".","."] +,[".","9","8",".",".",".",".","6","."] +,["8",".",".",".","6",".",".",".","3"] +,["4",".",".","8",".","3",".",".","1"] +,["7",".",".",".","2",".",".",".","6"] +,[".","6",".",".",".",".","2","8","."] +,[".",".",".","4","1","9",".",".","5"] +,[".",".",".",".","8",".",".","7","9"]] +Output: true +``` + +### Example 2: + +``` +Input: board = +[["8","3",".",".","7",".",".",".","."] +,["6",".",".","1","9","5",".",".","."] +,[".","9","8",".",".",".",".","6","."] +,["8",".",".",".","6",".",".",".","3"] +,["4",".",".","8",".","3",".",".","1"] +,["7",".",".",".","2",".",".",".","6"] +,[".","6",".",".",".",".","2","8","."] +,[".",".",".","4","1","9",".",".","5"] +,[".",".",".",".","8",".",".","7","9"]] +Output: false +Explanation: Same as Example 1, except with the 5 in the top left corner being modified to 8. Since there are two 8's in the top left 3x3 sub-box, it is invalid. +``` + +## Constraints + +- `board.length == 9` +- `board[i].length == 9` +- `board[i][j]` is a digit `1-9` or `'.'`. diff --git a/leetcode/valid_sudoku/__init__.py b/leetcode/valid_sudoku/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/valid_sudoku/helpers.py b/leetcode/valid_sudoku/helpers.py new file mode 100644 index 0000000..0ecf3da --- /dev/null +++ b/leetcode/valid_sudoku/helpers.py @@ -0,0 +1,8 @@ +def run_is_valid_sudoku(solution_class: type, board: list[list[str]]): + implementation = solution_class() + return implementation.is_valid_sudoku(board) + + +def assert_is_valid_sudoku(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/valid_sudoku/playground.py b/leetcode/valid_sudoku/playground.py new file mode 100644 index 0000000..68b4e1e --- /dev/null +++ b/leetcode/valid_sudoku/playground.py @@ -0,0 +1,39 @@ +# --- +# 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_is_valid_sudoku, run_is_valid_sudoku +from solution import Solution + +# %% +# Example test case +board = [ + ["5", "3", ".", ".", "7", ".", ".", ".", "."], + ["6", ".", ".", "1", "9", "5", ".", ".", "."], + [".", "9", "8", ".", ".", ".", ".", "6", "."], + ["8", ".", ".", ".", "6", ".", ".", ".", "3"], + ["4", ".", ".", "8", ".", "3", ".", ".", "1"], + ["7", ".", ".", ".", "2", ".", ".", ".", "6"], + [".", "6", ".", ".", ".", ".", "2", "8", "."], + [".", ".", ".", "4", "1", "9", ".", ".", "5"], + [".", ".", ".", ".", ".", ".", ".", "7", "9"], +] +expected = True + +# %% +result = run_is_valid_sudoku(Solution, board) +result + +# %% +assert_is_valid_sudoku(result, expected) diff --git a/leetcode/valid_sudoku/solution.py b/leetcode/valid_sudoku/solution.py new file mode 100644 index 0000000..18233fa --- /dev/null +++ b/leetcode/valid_sudoku/solution.py @@ -0,0 +1,22 @@ +class Solution: + + # Time: O(1) - fixed 9x9 board + # Space: O(1) - fixed size sets + def is_valid_sudoku(self, board: list[list[str]]) -> bool: + rows: list[set[str]] = [set() for _ in range(9)] + cols: list[set[str]] = [set() for _ in range(9)] + boxes: list[list[set[str]]] = [[set() for _ in range(3)] for _ in range(3)] + + for i in range(9): + for j in range(9): + if board[i][j] != ".": + num = board[i][j] + + if num in rows[i] or num in cols[j] or num in boxes[i // 3][j // 3]: + return False + + rows[i].add(num) + cols[j].add(num) + boxes[i // 3][j // 3].add(num) + + return True diff --git a/leetcode/valid_sudoku/test_solution.py b/leetcode/valid_sudoku/test_solution.py new file mode 100644 index 0000000..18054bf --- /dev/null +++ b/leetcode/valid_sudoku/test_solution.py @@ -0,0 +1,261 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_is_valid_sudoku, run_is_valid_sudoku +from .solution import Solution + + +class TestValidSudoku: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "board, expected", + [ + # Valid sudoku example 1 + ( + [ + ["5", "3", ".", ".", "7", ".", ".", ".", "."], + ["6", ".", ".", "1", "9", "5", ".", ".", "."], + [".", "9", "8", ".", ".", ".", ".", "6", "."], + ["8", ".", ".", ".", "6", ".", ".", ".", "3"], + ["4", ".", ".", "8", ".", "3", ".", ".", "1"], + ["7", ".", ".", ".", "2", ".", ".", ".", "6"], + [".", "6", ".", ".", ".", ".", "2", "8", "."], + [".", ".", ".", "4", "1", "9", ".", ".", "5"], + [".", ".", ".", ".", "8", ".", ".", "7", "9"], + ], + True, + ), + # Invalid sudoku example 2 (duplicate 8 in top-left box) + ( + [ + ["8", "3", ".", ".", "7", ".", ".", ".", "."], + ["6", ".", ".", "1", "9", "5", ".", ".", "."], + [".", "9", "8", ".", ".", ".", ".", "6", "."], + ["8", ".", ".", ".", "6", ".", ".", ".", "3"], + ["4", ".", ".", "8", ".", "3", ".", ".", "1"], + ["7", ".", ".", ".", "2", ".", ".", ".", "6"], + [".", "6", ".", ".", ".", ".", "2", "8", "."], + [".", ".", ".", "4", "1", "9", ".", ".", "5"], + [".", ".", ".", ".", "8", ".", ".", "7", "9"], + ], + False, + ), + # Empty board (valid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + # Duplicate in row (invalid) + ( + [ + ["1", "1", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Duplicate in column (invalid) + ( + [ + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Duplicate in 3x3 box (invalid) + ( + [ + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", "1", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Valid single row filled + ( + [ + ["1", "2", "3", "4", "5", "6", "7", "8", "9"], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + # Valid single column filled + ( + [ + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + ["2", ".", ".", ".", ".", ".", ".", ".", "."], + ["3", ".", ".", ".", ".", ".", ".", ".", "."], + ["4", ".", ".", ".", ".", ".", ".", ".", "."], + ["5", ".", ".", ".", ".", ".", ".", ".", "."], + ["6", ".", ".", ".", ".", ".", ".", ".", "."], + ["7", ".", ".", ".", ".", ".", ".", ".", "."], + ["8", ".", ".", ".", ".", ".", ".", ".", "."], + ["9", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + # Valid 3x3 box filled (top-left) + ( + [ + ["1", "2", "3", ".", ".", ".", ".", ".", "."], + ["4", "5", "6", ".", ".", ".", ".", ".", "."], + ["7", "8", "9", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + # Duplicate in middle 3x3 box (invalid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", "5", ".", ".", ".", ".", "."], + [".", ".", ".", ".", "5", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Duplicate in bottom-right 3x3 box (invalid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", "7", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", "7", "."], + ], + False, + ), + # Multiple duplicates in same row (invalid) + ( + [ + ["3", "3", "3", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Duplicate in last row (invalid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ["9", ".", ".", ".", ".", ".", ".", ".", "9"], + ], + False, + ), + # Duplicate in last column (invalid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "4"], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "4"], + ], + False, + ), + # Valid with all digits 1-9 in different positions + ( + [ + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + [".", "2", ".", ".", ".", ".", ".", ".", "."], + [".", ".", "3", ".", ".", ".", ".", ".", "."], + [".", ".", ".", "4", ".", ".", ".", ".", "."], + [".", ".", ".", ".", "5", ".", ".", ".", "."], + [".", ".", ".", ".", ".", "6", ".", ".", "."], + [".", ".", ".", ".", ".", ".", "7", ".", "."], + [".", ".", ".", ".", ".", ".", ".", "8", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "9"], + ], + True, + ), + # Valid board with multiple possible solutions (demonstrates "not necessarily solvable" constraint) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", "1", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + ], + ) + def test_is_valid_sudoku(self, board: list[list[str]], expected: bool): + result = run_is_valid_sudoku(Solution, board) + assert_is_valid_sudoku(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/design_add_and_search_words_data_structure.json b/leetcode_py/cli/resources/leetcode/json/problems/design_add_and_search_words_data_structure.json new file mode 100644 index 0000000..3841663 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/design_add_and_search_words_data_structure.json @@ -0,0 +1,80 @@ +{ + "problem_name": "design_add_and_search_words_data_structure", + "solution_class_name": "WordDictionary", + "problem_number": "211", + "problem_title": "Design Add and Search Words Data Structure", + "difficulty": "Medium", + "topics": "String, Depth-First Search, Design, Trie", + "_tags": { "list": ["grind"] }, + + "readme_description": "Design a data structure that supports adding new words and finding if a string matches any previously added string.\n\nImplement the `WordDictionary` class:\n\n- `WordDictionary()` Initializes the object.\n- `void addWord(word)` Adds `word` to the data structure, it can be matched later.\n- `bool search(word)` Returns `true` if there is any string in the data structure that matches `word` or `false` otherwise. `word` may contain dots `'.'` where dots can be matched with any letter.", + + "_readme_examples": { + "list": [ + { + "content": "```\nInput\n[\"WordDictionary\",\"addWord\",\"addWord\",\"addWord\",\"search\",\"search\",\"search\",\"search\"]\n[[],[\"bad\"],[\"dad\"],[\"mad\"],[\"pad\"],[\"bad\"],[\".ad\"],[\"b..\"]]\nOutput\n[null,null,null,null,false,true,true,true]\n\nExplanation\nWordDictionary wordDictionary = new WordDictionary();\nwordDictionary.addWord(\"bad\");\nwordDictionary.addWord(\"dad\");\nwordDictionary.addWord(\"mad\");\nwordDictionary.search(\"pad\"); // return False\nwordDictionary.search(\"bad\"); // return True\nwordDictionary.search(\".ad\"); // return True\nwordDictionary.search(\"b..\"); // return True\n```" + } + ] + }, + + "readme_constraints": "- `1 <= word.length <= 25`\n- `word` in `addWord` consists of lowercase English letters.\n- `word` in `search` consist of `'.'` or lowercase English letters.\n- There will be at most `2` dots in `word` for `search` queries.\n- At most `10^4` calls will be made to `addWord` and `search`.", + + "helpers_imports": "from typing import Any", + "helpers_content": "", + "helpers_run_name": "word_dictionary", + "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[str]])", + "helpers_run_body": " wd: Any = None\n results: list[bool | None] = []\n \n for op, args in zip(operations, inputs):\n if op == 'WordDictionary':\n wd = solution_class()\n results.append(None)\n elif op == 'addWord':\n assert wd is not None\n wd.add_word(args[0])\n results.append(None)\n elif op == 'search':\n assert wd is not None\n results.append(wd.search(args[0]))\n \n return results", + "helpers_assert_name": "word_dictionary", + "helpers_assert_signature": "(result: list[bool | None], expected: list[bool | None]) -> 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_word_dictionary, run_word_dictionary\nfrom .solution import WordDictionary", + "test_content": "", + "test_class_name": "DesignAddAndSearchWordsDataStructure", + "test_class_content": "", + + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self) -> None", + "body": " # TODO: Initialize data structure\n pass" + }, + { + "name": "add_word", + "signature": "(self, word: str) -> None", + "body": " # TODO: Implement add_word\n pass" + }, + { + "name": "search", + "signature": "(self, word: str) -> bool", + "body": " # TODO: Implement search\n return False" + } + ] + }, + + "_test_helper_methods": { + "list": [] + }, + + "_test_methods": { + "list": [ + { + "name": "test_word_dictionary", + "signature": "(self, operations: list[str], inputs: list[list[str]], expected: list[bool | None])", + "parametrize": "operations, inputs, expected", + "test_cases": "[(['WordDictionary', 'addWord', 'addWord', 'addWord', 'search', 'search', 'search', 'search'], [[], ['bad'], ['dad'], ['mad'], ['pad'], ['bad'], ['.ad'], ['b..']], [None, None, None, None, False, True, True, True]), (['WordDictionary', 'addWord', 'search', 'search', 'search'], [[], ['a'], ['a'], ['.'], ['aa']], [None, None, True, True, False]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['at'], ['and'], ['an'], ['.at'], ['an.']], [None, None, None, False, False, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search'], [[], ['word'], ['world'], ['word'], ['wor.']], [None, None, None, True, True]), (['WordDictionary', 'addWord', 'search', 'search'], [[], ['test'], ['test'], ['t..t']], [None, None, True, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['a'], ['b'], ['a'], ['.'], ['c']], [None, None, None, True, True, False]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['abc'], ['def'], ['...'], ['a..'], ['..f']], [None, None, None, True, True, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['programming'], ['algorithm'], ['prog.......'], ['algo.....'], ['........ing']], [None, None, None, True, True, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search'], [[], ['x'], ['xy'], ['.'], ['..']], [None, None, None, True, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['hello'], ['world'], ['hi'], ['word'], ['......']], [None, None, None, False, False, False]), (['WordDictionary', 'addWord', 'addWord', 'addWord', 'search', 'search', 'search', 'search'], [[], ['cat'], ['car'], ['card'], ['c..'], ['ca.'], ['c..d'], ['.....']], [None, None, None, None, True, True, True, False]), (['WordDictionary', 'addWord', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['run'], ['runner'], ['running'], ['run'], ['run...'], ['run.....']], [None, None, None, None, True, True, False]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['abc'], ['xyz'], ['...'], ['..'], ['....']], [None, None, None, True, False, False])]", + "body": " result = run_word_dictionary(WordDictionary, operations, inputs)\n assert_word_dictionary(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_word_dictionary, assert_word_dictionary\nfrom solution import WordDictionary", + "playground_setup": "# Example test case\noperations = ['WordDictionary', 'addWord', 'addWord', 'addWord', 'search', 'search', 'search', 'search']\ninputs = [[], ['bad'], ['dad'], ['mad'], ['pad'], ['bad'], ['.ad'], ['b..']]\nexpected = [None, None, None, None, False, True, True, True]", + "playground_run": "result = run_word_dictionary(WordDictionary, operations, inputs)\nresult", + "playground_assert": "assert_word_dictionary(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/group_anagrams.json b/leetcode_py/cli/resources/leetcode/json/problems/group_anagrams.json new file mode 100644 index 0000000..16926b3 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/group_anagrams.json @@ -0,0 +1,72 @@ +{ + "problem_name": "group_anagrams", + "solution_class_name": "Solution", + "problem_number": "49", + "problem_title": "Group Anagrams", + "difficulty": "Medium", + "topics": "Array, Hash Table, String, Sorting", + "_tags": { "list": ["grind"] }, + + "readme_description": "Given an array of strings `strs`, group the anagrams together. You can return the answer in **any order**.\n\nAn **anagram** is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.", + + "_readme_examples": { + "list": [ + { + "content": "```\nInput: strs = [\"eat\",\"tea\",\"tan\",\"ate\",\"nat\",\"bat\"]\nOutput: [[\"bat\"],[\"nat\",\"tan\"],[\"ate\",\"eat\",\"tea\"]]\nExplanation:\n- There is no string in strs that can be rearranged to form \"bat\".\n- The strings \"nat\" and \"tan\" are anagrams as they can be rearranged to form each other.\n- The strings \"ate\", \"eat\", and \"tea\" are anagrams as they can be rearranged to form each other.\n```" + }, + { "content": "```\nInput: strs = [\"\"]\nOutput: [[\"\"]]\n```" }, + { "content": "```\nInput: strs = [\"a\"]\nOutput: [[\"a\"]]\n```" } + ] + }, + + "readme_constraints": "- `1 <= strs.length <= 10^4`\n- `0 <= strs[i].length <= 100`\n- `strs[i]` consists of lowercase English letters.", + + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "group_anagrams", + "helpers_run_signature": "(solution_class: type, strs: list[str])", + "helpers_run_body": " implementation = solution_class()\n return implementation.group_anagrams(strs)", + "helpers_assert_name": "group_anagrams", + "helpers_assert_signature": "(result: list[list[str]], expected: list[list[str]]) -> bool", + "helpers_assert_body": " # Sort both result and expected for comparison since order doesn't matter\n result_sorted = [sorted(group) for group in result]\n expected_sorted = [sorted(group) for group in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert result_sorted == expected_sorted\n return True", + + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_group_anagrams, run_group_anagrams\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "GroupAnagrams", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + + "_solution_methods": { + "list": [ + { + "name": "group_anagrams", + "signature": "(self, strs: list[str]) -> list[list[str]]", + "body": " # TODO: Implement group_anagrams\n return []" + } + ] + }, + + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + + "_test_methods": { + "list": [ + { + "name": "test_group_anagrams", + "signature": "(self, strs: list[str], expected: list[list[str]])", + "parametrize": "strs, expected", + "test_cases": "[(['eat', 'tea', 'tan', 'ate', 'nat', 'bat'], [['bat'], ['nat', 'tan'], ['ate', 'eat', 'tea']]), ([''], [['']]), (['a'], [['a']]), (['abc', 'bca', 'cab', 'xyz'], [['abc', 'bca', 'cab'], ['xyz']]), (['ab', 'ba'], [['ab', 'ba']]), (['abc'], [['abc']]), (['listen', 'silent', 'hello'], [['listen', 'silent'], ['hello']]), (['aab', 'aba', 'baa'], [['aab', 'aba', 'baa']]), (['race', 'care', 'acre'], [['race', 'care', 'acre']]), (['', 'b'], [[''], ['b']]), (['a', 'aa', 'aaa'], [['a'], ['aa'], ['aaa']]), (['abc', 'def', 'ghi'], [['abc'], ['def'], ['ghi']]), (['abcd', 'dcba', 'lls', 'sll'], [['abcd', 'dcba'], ['lls', 'sll']]), (['ac', 'c'], [['ac'], ['c']]), (['huh', 'tit'], [['huh'], ['tit']])]", + "body": " result = run_group_anagrams(Solution, strs)\n assert_group_anagrams(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_group_anagrams, assert_group_anagrams\nfrom solution import Solution", + "playground_setup": "# Example test case\nstrs = ['eat', 'tea', 'tan', 'ate', 'nat', 'bat']\nexpected = [['bat'], ['nat', 'tan'], ['ate', 'eat', 'tea']]", + "playground_run": "result = run_group_anagrams(Solution, strs)\nresult", + "playground_assert": "assert_group_anagrams(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/maximum_product_subarray.json b/leetcode_py/cli/resources/leetcode/json/problems/maximum_product_subarray.json new file mode 100644 index 0000000..4cd6fc9 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/maximum_product_subarray.json @@ -0,0 +1,73 @@ +{ + "problem_name": "maximum_product_subarray", + "solution_class_name": "Solution", + "problem_number": "152", + "problem_title": "Maximum Product Subarray", + "difficulty": "Medium", + "topics": "Array, Dynamic Programming", + "_tags": { "list": ["grind"] }, + + "readme_description": "Given an integer array `nums`, find a subarray that has the largest product, and return the product.\n\nThe test cases are generated so that the answer will fit in a **32-bit** integer.", + + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [2,3,-2,4]\nOutput: 6\nExplanation: [2,3] has the largest product 6.\n```" + }, + { + "content": "```\nInput: nums = [-2,0,-1]\nOutput: 0\nExplanation: The result cannot be 2, because [-2,-1] is not a subarray.\n```" + } + ] + }, + + "readme_constraints": "- `1 <= nums.length <= 2 * 10^4`\n- `-10 <= nums[i] <= 10`\n- The product of any subarray of `nums` is **guaranteed** to fit in a **32-bit** integer.", + + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "max_product", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.max_product(nums)", + "helpers_assert_name": "max_product", + "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_max_product, run_max_product\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MaximumProductSubarray", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + + "_solution_methods": { + "list": [ + { + "name": "max_product", + "signature": "(self, nums: list[int]) -> int", + "body": " # TODO: Implement max_product\n return 0" + } + ] + }, + + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + + "_test_methods": { + "list": [ + { + "name": "test_max_product", + "signature": "(self, nums: list[int], expected: int)", + "parametrize": "nums, expected", + "test_cases": "[([2, 3, -2, 4], 6), ([-2, 0, -1], 0), ([1], 1), ([0], 0), ([-1], -1), ([2, -1, 3], 3), ([-2, -3], 6), ([1, 2, 3, 4], 24), ([-1, -2, -3], 6), ([0, 2], 2), ([3, -1, 4], 4), ([-2, 3, -4], 24), ([2, 3, -2, 4, -1], 48), ([1, 0, -1, 2, 3], 6), ([-3, 0, 1, -2], 1)]", + "body": " result = run_max_product(Solution, nums)\n assert_max_product(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_max_product, assert_max_product\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [2, 3, -2, 4]\nexpected = 6", + "playground_run": "result = run_max_product(Solution, nums)\nresult", + "playground_assert": "assert_max_product(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/next_permutation.json b/leetcode_py/cli/resources/leetcode/json/problems/next_permutation.json new file mode 100644 index 0000000..dfeb02d --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/next_permutation.json @@ -0,0 +1,70 @@ +{ + "problem_name": "next_permutation", + "solution_class_name": "Solution", + "problem_number": "31", + "problem_title": "Next Permutation", + "difficulty": "Medium", + "topics": "Array, Two Pointers", + "_tags": { "list": ["grind"] }, + + "readme_description": "A **permutation** of an array of integers is an arrangement of its members into a sequence or linear order.\n\n- For example, for `arr = [1,2,3]`, the following are all the permutations of `arr`: `[1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1]`.\n\nThe **next permutation** of an array of integers is the next lexicographically greater permutation of its integer. More formally, if all the permutations of the array are sorted in one container according to their lexicographical order, then the **next permutation** of that array is the permutation that follows it in the sorted container. If such arrangement is not possible, the array must be rearranged as the lowest possible order (i.e., sorted in ascending order).\n\n- For example, the next permutation of `arr = [1,2,3]` is `[1,3,2]`.\n- Similarly, the next permutation of `arr = [2,3,1]` is `[3,1,2]`.\n- While the next permutation of `arr = [3,2,1]` is `[1,2,3]` because `[3,2,1]` does not have a lexicographical larger rearrangement.\n\nGiven an array of integers `nums`, find the next permutation of `nums`.\n\nThe replacement must be **in place** and use only constant extra memory.", + + "_readme_examples": { + "list": [ + { "content": "```\nInput: nums = [1,2,3]\nOutput: [1,3,2]\n```" }, + { "content": "```\nInput: nums = [3,2,1]\nOutput: [1,2,3]\n```" }, + { "content": "```\nInput: nums = [1,1,5]\nOutput: [1,5,1]\n```" } + ] + }, + + "readme_constraints": "- `1 <= nums.length <= 100`\n- `0 <= nums[i] <= 100`", + + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "next_permutation", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n nums_copy = nums.copy()\n implementation.next_permutation(nums_copy)\n return nums_copy", + "helpers_assert_name": "next_permutation", + "helpers_assert_signature": "(result: list[int], expected: list[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_next_permutation, run_next_permutation\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "NextPermutation", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + + "_solution_methods": { + "list": [ + { + "name": "next_permutation", + "signature": "(self, nums: list[int]) -> None", + "body": " # TODO: Implement next_permutation\n pass" + } + ] + }, + + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + + "_test_methods": { + "list": [ + { + "name": "test_next_permutation", + "signature": "(self, nums: list[int], expected: list[int])", + "parametrize": "nums, expected", + "test_cases": "[([1, 2, 3], [1, 3, 2]), ([3, 2, 1], [1, 2, 3]), ([1, 1, 5], [1, 5, 1]), ([1], [1]), ([1, 2], [2, 1]), ([2, 1], [1, 2]), ([1, 3, 2], [2, 1, 3]), ([2, 3, 1], [3, 1, 2]), ([1, 2, 3, 4], [1, 2, 4, 3]), ([4, 3, 2, 1], [1, 2, 3, 4]), ([1, 1, 1], [1, 1, 1]), ([1, 2, 1], [2, 1, 1]), ([5, 4, 7, 5, 3, 2], [5, 5, 2, 3, 4, 7]), ([1, 3, 2, 1], [2, 1, 1, 3]), ([2, 1, 3], [2, 3, 1])]", + "body": " result = run_next_permutation(Solution, nums)\n assert_next_permutation(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_next_permutation, assert_next_permutation\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [1, 2, 3]\nexpected = [1, 3, 2]", + "playground_run": "result = run_next_permutation(Solution, nums)\nresult", + "playground_assert": "assert_next_permutation(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/valid_sudoku.json b/leetcode_py/cli/resources/leetcode/json/problems/valid_sudoku.json new file mode 100644 index 0000000..553e94a --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/valid_sudoku.json @@ -0,0 +1,73 @@ +{ + "problem_name": "valid_sudoku", + "solution_class_name": "Solution", + "problem_number": "36", + "problem_title": "Valid Sudoku", + "difficulty": "Medium", + "topics": "Array, Hash Table, Matrix", + "_tags": { "list": ["grind"] }, + + "readme_description": "Determine if a `9 x 9` Sudoku board is valid. Only the filled cells need to be validated **according to the following rules**:\n\n1. Each row must contain the digits `1-9` without repetition.\n2. Each column must contain the digits `1-9` without repetition.\n3. Each of the nine `3 x 3` sub-boxes of the grid must contain the digits `1-9` without repetition.\n\n**Note:**\n\n- A Sudoku board (partially filled) could be valid but is not necessarily solvable.\n- Only the filled cells need to be validated according to the mentioned rules.", + + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png)\n\n```\nInput: board = \n[[\"5\",\"3\",\".\",\".\",\"7\",\".\",\".\",\".\",\".\"]\n,[\"6\",\".\",\".\",\"1\",\"9\",\"5\",\".\",\".\",\".\"]\n,[\".\",\"9\",\"8\",\".\",\".\",\".\",\".\",\"6\",\".\"]\n,[\"8\",\".\",\".\",\".\",\"6\",\".\",\".\",\".\",\"3\"]\n,[\"4\",\".\",\".\",\"8\",\".\",\"3\",\".\",\".\",\"1\"]\n,[\"7\",\".\",\".\",\".\",\"2\",\".\",\".\",\".\",\"6\"]\n,[\".\",\"6\",\".\",\".\",\".\",\".\",\"2\",\"8\",\".\"]\n,[\".\",\".\",\".\",\"4\",\"1\",\"9\",\".\",\".\",\"5\"]\n,[\".\",\".\",\".\",\".\",\"8\",\".\",\".\",\"7\",\"9\"]]\nOutput: true\n```" + }, + { + "content": "```\nInput: board = \n[[\"8\",\"3\",\".\",\".\",\"7\",\".\",\".\",\".\",\".\"]\n,[\"6\",\".\",\".\",\"1\",\"9\",\"5\",\".\",\".\",\".\"]\n,[\".\",\"9\",\"8\",\".\",\".\",\".\",\".\",\"6\",\".\"]\n,[\"8\",\".\",\".\",\".\",\"6\",\".\",\".\",\".\",\"3\"]\n,[\"4\",\".\",\".\",\"8\",\".\",\"3\",\".\",\".\",\"1\"]\n,[\"7\",\".\",\".\",\".\",\"2\",\".\",\".\",\".\",\"6\"]\n,[\".\",\"6\",\".\",\".\",\".\",\".\",\"2\",\"8\",\".\"]\n,[\".\",\".\",\".\",\"4\",\"1\",\"9\",\".\",\".\",\"5\"]\n,[\".\",\".\",\".\",\".\",\"8\",\".\",\".\",\"7\",\"9\"]]\nOutput: false\nExplanation: Same as Example 1, except with the 5 in the top left corner being modified to 8. Since there are two 8's in the top left 3x3 sub-box, it is invalid.\n```" + } + ] + }, + + "readme_constraints": "- `board.length == 9`\n- `board[i].length == 9`\n- `board[i][j]` is a digit `1-9` or `'.'`.", + + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "is_valid_sudoku", + "helpers_run_signature": "(solution_class: type, board: list[list[str]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.is_valid_sudoku(board)", + "helpers_assert_name": "is_valid_sudoku", + "helpers_assert_signature": "(result: bool, expected: bool) -> 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_is_valid_sudoku, run_is_valid_sudoku\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ValidSudoku", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + + "_solution_methods": { + "list": [ + { + "name": "is_valid_sudoku", + "signature": "(self, board: list[list[str]]) -> bool", + "body": " # TODO: Implement is_valid_sudoku\n return False" + } + ] + }, + + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + + "_test_methods": { + "list": [ + { + "name": "test_is_valid_sudoku", + "signature": "(self, board: list[list[str]], expected: bool)", + "parametrize": "board, expected", + "test_cases": "[([['5','3','.','.','7','.','.','.','.'],['6','.','.','1','9','5','.','.','.'],['.','9','8','.','.','.','.','6','.'],['8','.','.','.','6','.','.','.','3'],['4','.','.','8','.','3','.','.','1'],['7','.','.','.','2','.','.','.','6'],['.','6','.','.','.','.','2','8','.'],['.','.','.','4','1','9','.','.','5'],['.','.','.','.','.','.','.','7','9']], True), ([['8','3','.','.','7','.','.','.','.'],['6','.','.','1','9','5','.','.','.'],['.','9','8','.','.','.','.','6','.'],['8','.','.','.','6','.','.','.','3'],['4','.','.','8','.','3','.','.','1'],['7','.','.','.','2','.','.','.','6'],['.','6','.','.','.','.','2','8','.'],['.','.','.','4','1','9','.','.','5'],['.','.','.','.','.','.','.','7','9']], False), ([['.','.','.','.','5','.','.','1','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['9','3','.','.','2','.','4','.','.'],['.','.','7','.','.','.','3','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','3','4','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','5','.','.','.']]], False), ([['.','.','4','.','.','.','6','3','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], True), ([['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], True), ([['1','2','3','4','5','6','7','8','9'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], True), ([['1','1','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], False), ([['1','.','.','.','.','.','.','.','.'],['.','1','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], False), ([['1','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','1','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], False), ([['1','.','.','.','.','.','.','.','2'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['3','.','.','.','.','.','.','.','1']]], True), ([['.','.','.','.','.','.','5','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','5','.','.']]], False), ([['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','1','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','1','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']], False)]", + "body": " result = run_is_valid_sudoku(Solution, board)\n assert_is_valid_sudoku(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_is_valid_sudoku, assert_is_valid_sudoku\nfrom solution import Solution", + "playground_setup": "# Example test case\nboard = [['5','3','.','.','7','.','.','.','.'],['6','.','.','1','9','5','.','.','.'],['.','9','8','.','.','.','.','6','.'],['8','.','.','.','6','.','.','.','3'],['4','.','.','8','.','3','.','.','1'],['7','.','.','.','2','.','.','.','6'],['.','6','.','.','.','.','2','8','.'],['.','.','.','4','1','9','.','.','5'],['.','.','.','.','.','.','.','7','9']]\nexpected = True", + "playground_run": "result = run_is_valid_sudoku(Solution, board)\nresult", + "playground_assert": "assert_is_valid_sudoku(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/tags.json5 b/leetcode_py/cli/resources/leetcode/json/tags.json5 index 2b9f498..e9c6c53 100644 --- a/leetcode_py/cli/resources/leetcode/json/tags.json5 +++ b/leetcode_py/cli/resources/leetcode/json/tags.json5 @@ -78,7 +78,17 @@ "zero_one_matrix", ], - grind: [{ tag: "grind-75" }, "daily_temperatures", "gas_station", "house_robber"], + grind: [ + { tag: "grind-75" }, + "daily_temperatures", + "design_add_and_search_words_data_structure", + "gas_station", + "group_anagrams", + "house_robber", + "maximum_product_subarray", + "next_permutation", + "valid_sudoku", + ], // Test tag for development and testing test: ["binary_search", "two_sum", "valid_palindrome"], diff --git a/poetry.lock b/poetry.lock index 5b08393..624fc46 100644 --- a/poetry.lock +++ b/poetry.lock @@ -114,34 +114,34 @@ chardet = ">=3.0.2" [[package]] name = "black" -version = "25.1.0" +version = "25.9.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, - {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, - {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, - {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, - {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, - {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, - {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, - {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, - {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, - {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, - {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, - {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, - {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, - {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, - {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, - {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, - {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, - {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, - {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, - {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, - {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, - {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, + {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, + {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, + {file = "black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713"}, + {file = "black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1"}, + {file = "black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa"}, + {file = "black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d"}, + {file = "black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608"}, + {file = "black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f"}, + {file = "black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0"}, + {file = "black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4"}, + {file = "black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e"}, + {file = "black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a"}, + {file = "black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175"}, + {file = "black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f"}, + {file = "black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831"}, + {file = "black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357"}, + {file = "black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47"}, + {file = "black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823"}, + {file = "black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140"}, + {file = "black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933"}, + {file = "black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae"}, + {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"}, ] [package.dependencies] @@ -150,6 +150,7 @@ mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" +pytokens = ">=0.1.10" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} @@ -384,14 +385,14 @@ files = [ [[package]] name = "click" -version = "8.2.1" +version = "8.3.0" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, + {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, + {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, ] [package.dependencies] @@ -1151,50 +1152,50 @@ files = [ [[package]] name = "mypy" -version = "1.18.1" +version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-1.18.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2761b6ae22a2b7d8e8607fb9b81ae90bc2e95ec033fd18fa35e807af6c657763"}, - {file = "mypy-1.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b10e3ea7f2eec23b4929a3fabf84505da21034a4f4b9613cda81217e92b74f3"}, - {file = "mypy-1.18.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:261fbfced030228bc0f724d5d92f9ae69f46373bdfd0e04a533852677a11dbea"}, - {file = "mypy-1.18.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4dc6b34a1c6875e6286e27d836a35c0d04e8316beac4482d42cfea7ed2527df8"}, - {file = "mypy-1.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1cabb353194d2942522546501c0ff75c4043bf3b63069cb43274491b44b773c9"}, - {file = "mypy-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:738b171690c8e47c93569635ee8ec633d2cdb06062f510b853b5f233020569a9"}, - {file = "mypy-1.18.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c903857b3e28fc5489e54042684a9509039ea0aedb2a619469438b544ae1961"}, - {file = "mypy-1.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a0c8392c19934c2b6c65566d3a6abdc6b51d5da7f5d04e43f0eb627d6eeee65"}, - {file = "mypy-1.18.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f85eb7efa2ec73ef63fc23b8af89c2fe5bf2a4ad985ed2d3ff28c1bb3c317c92"}, - {file = "mypy-1.18.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82ace21edf7ba8af31c3308a61dc72df30500f4dbb26f99ac36b4b80809d7e94"}, - {file = "mypy-1.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a2dfd53dfe632f1ef5d161150a4b1f2d0786746ae02950eb3ac108964ee2975a"}, - {file = "mypy-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:320f0ad4205eefcb0e1a72428dde0ad10be73da9f92e793c36228e8ebf7298c0"}, - {file = "mypy-1.18.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:502cde8896be8e638588b90fdcb4c5d5b8c1b004dfc63fd5604a973547367bb9"}, - {file = "mypy-1.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7509549b5e41be279afc1228242d0e397f1af2919a8f2877ad542b199dc4083e"}, - {file = "mypy-1.18.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5956ecaabb3a245e3f34100172abca1507be687377fe20e24d6a7557e07080e2"}, - {file = "mypy-1.18.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8750ceb014a96c9890421c83f0db53b0f3b8633e2864c6f9bc0a8e93951ed18d"}, - {file = "mypy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb89ea08ff41adf59476b235293679a6eb53a7b9400f6256272fb6029bec3ce5"}, - {file = "mypy-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:2657654d82fcd2a87e02a33e0d23001789a554059bbf34702d623dafe353eabf"}, - {file = "mypy-1.18.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d70d2b5baf9b9a20bc9c730015615ae3243ef47fb4a58ad7b31c3e0a59b5ef1f"}, - {file = "mypy-1.18.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8367e33506300f07a43012fc546402f283c3f8bcff1dc338636affb710154ce"}, - {file = "mypy-1.18.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:913f668ec50c3337b89df22f973c1c8f0b29ee9e290a8b7fe01cc1ef7446d42e"}, - {file = "mypy-1.18.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a0e70b87eb27b33209fa4792b051c6947976f6ab829daa83819df5f58330c71"}, - {file = "mypy-1.18.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c378d946e8a60be6b6ede48c878d145546fb42aad61df998c056ec151bf6c746"}, - {file = "mypy-1.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:2cd2c1e0f3a7465f22731987fff6fc427e3dcbb4ca5f7db5bbeaff2ff9a31f6d"}, - {file = "mypy-1.18.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ba24603c58e34dd5b096dfad792d87b304fc6470cbb1c22fd64e7ebd17edcc61"}, - {file = "mypy-1.18.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ed36662fb92ae4cb3cacc682ec6656208f323bbc23d4b08d091eecfc0863d4b5"}, - {file = "mypy-1.18.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:040ecc95e026f71a9ad7956fea2724466602b561e6a25c2e5584160d3833aaa8"}, - {file = "mypy-1.18.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:937e3ed86cb731276706e46e03512547e43c391a13f363e08d0fee49a7c38a0d"}, - {file = "mypy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f95cc4f01c0f1701ca3b0355792bccec13ecb2ec1c469e5b85a6ef398398b1d"}, - {file = "mypy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:e4f16c0019d48941220ac60b893615be2f63afedaba6a0801bdcd041b96991ce"}, - {file = "mypy-1.18.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e37763af63a8018308859bc83d9063c501a5820ec5bd4a19f0a2ac0d1c25c061"}, - {file = "mypy-1.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:51531b6e94f34b8bd8b01dee52bbcee80daeac45e69ec5c36e25bce51cbc46e6"}, - {file = "mypy-1.18.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbfdea20e90e9c5476cea80cfd264d8e197c6ef2c58483931db2eefb2f7adc14"}, - {file = "mypy-1.18.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99f272c9b59f5826fffa439575716276d19cbf9654abc84a2ba2d77090a0ba14"}, - {file = "mypy-1.18.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8c05a7f8c00300a52f3a4fcc95a185e99bf944d7e851ff141bae8dcf6dcfeac4"}, - {file = "mypy-1.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:2fbcecbe5cf213ba294aa8c0b8c104400bf7bb64db82fb34fe32a205da4b3531"}, - {file = "mypy-1.18.1-py3-none-any.whl", hash = "sha256:b76a4de66a0ac01da1be14ecc8ae88ddea33b8380284a9e3eae39d57ebcbe26e"}, - {file = "mypy-1.18.1.tar.gz", hash = "sha256:9e988c64ad3ac5987f43f5154f884747faf62141b7f842e87465b45299eea5a9"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, + {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, + {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, + {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, + {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, + {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, + {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, + {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, + {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, + {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, + {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, + {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, + {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, + {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, + {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, ] [package.dependencies] @@ -1580,6 +1581,21 @@ text-unidecode = ">=1.3" [package.extras] unidecode = ["Unidecode (>=1.1.1)"] +[[package]] +name = "pytokens" +version = "0.1.10" +description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"}, + {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, +] + +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] + [[package]] name = "pywin32" version = "311" @@ -2004,31 +2020,31 @@ files = [ [[package]] name = "ruff" -version = "0.13.0" +version = "0.13.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.13.0-py3-none-linux_armv6l.whl", hash = "sha256:137f3d65d58ee828ae136a12d1dc33d992773d8f7644bc6b82714570f31b2004"}, - {file = "ruff-0.13.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:21ae48151b66e71fd111b7d79f9ad358814ed58c339631450c66a4be33cc28b9"}, - {file = "ruff-0.13.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:64de45f4ca5441209e41742d527944635a05a6e7c05798904f39c85bafa819e3"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2c653ae9b9d46e0ef62fc6fbf5b979bda20a0b1d2b22f8f7eb0cde9f4963b8"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cec632534332062bc9eb5884a267b689085a1afea9801bf94e3ba7498a2d207"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd628101d9f7d122e120ac7c17e0a0f468b19bc925501dbe03c1cb7f5415b24"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afe37db8e1466acb173bb2a39ca92df00570e0fd7c94c72d87b51b21bb63efea"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f96a8d90bb258d7d3358b372905fe7333aaacf6c39e2408b9f8ba181f4b6ef2"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b5e3d883e4f924c5298e3f2ee0f3085819c14f68d1e5b6715597681433f153"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03447f3d18479df3d24917a92d768a89f873a7181a064858ea90a804a7538991"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fbc6b1934eb1c0033da427c805e27d164bb713f8e273a024a7e86176d7f462cf"}, - {file = "ruff-0.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8ab6a3e03665d39d4a25ee199d207a488724f022db0e1fe4002968abdb8001b"}, - {file = "ruff-0.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2a5c62f8ccc6dd2fe259917482de7275cecc86141ee10432727c4816235bc41"}, - {file = "ruff-0.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b7b85ca27aeeb1ab421bc787009831cffe6048faae08ad80867edab9f2760945"}, - {file = "ruff-0.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:79ea0c44a3032af768cabfd9616e44c24303af49d633b43e3a5096e009ebe823"}, - {file = "ruff-0.13.0-py3-none-win32.whl", hash = "sha256:4e473e8f0e6a04e4113f2e1de12a5039579892329ecc49958424e5568ef4f768"}, - {file = "ruff-0.13.0-py3-none-win_amd64.whl", hash = "sha256:48e5c25c7a3713eea9ce755995767f4dcd1b0b9599b638b12946e892123d1efb"}, - {file = "ruff-0.13.0-py3-none-win_arm64.whl", hash = "sha256:ab80525317b1e1d38614addec8ac954f1b3e662de9d59114ecbf771d00cf613e"}, - {file = "ruff-0.13.0.tar.gz", hash = "sha256:5b4b1ee7eb35afae128ab94459b13b2baaed282b1fb0f472a73c82c996c8ae60"}, + {file = "ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b"}, + {file = "ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334"}, + {file = "ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783"}, + {file = "ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a"}, + {file = "ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700"}, + {file = "ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae"}, + {file = "ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317"}, + {file = "ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0"}, + {file = "ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5"}, + {file = "ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a"}, + {file = "ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51"}, ] [[package]] From 53a669556a123c2fb0b952402a447f740f1661d1 Mon Sep 17 00:00:00 2001 From: Wisaroot <66859294+wisarootl@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:03:23 +0700 Subject: [PATCH 06/43] feat: add more problem and fix test issue (#56) --- .amazonq/rules/problem-creation.md | 227 +++++++++++++++++- Makefile | 4 +- .../pacific_atlantic_water_flow/README.md | 59 +++++ .../pacific_atlantic_water_flow/__init__.py | 0 .../pacific_atlantic_water_flow/helpers.py | 8 + .../pacific_atlantic_water_flow/playground.py | 29 +++ .../pacific_atlantic_water_flow/solution.py | 37 +++ .../test_solution.py | 170 +++++++++++++ leetcode/valid_sudoku/test_solution.py | 16 -- .../resources/leetcode/examples/example.json5 | 200 --------------- .../problems/pacific_atlantic_water_flow.json | 74 ++++++ .../leetcode/json/problems/valid_sudoku.json | 2 +- .../cli/resources/leetcode/json/tags.json5 | 1 + leetcode_py/tools/check_test_cases.py | 23 +- 14 files changed, 617 insertions(+), 233 deletions(-) create mode 100644 leetcode/pacific_atlantic_water_flow/README.md create mode 100644 leetcode/pacific_atlantic_water_flow/__init__.py create mode 100644 leetcode/pacific_atlantic_water_flow/helpers.py create mode 100644 leetcode/pacific_atlantic_water_flow/playground.py create mode 100644 leetcode/pacific_atlantic_water_flow/solution.py create mode 100644 leetcode/pacific_atlantic_water_flow/test_solution.py delete mode 100644 leetcode_py/cli/resources/leetcode/examples/example.json5 create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/pacific_atlantic_water_flow.json diff --git a/.amazonq/rules/problem-creation.md b/.amazonq/rules/problem-creation.md index 8159741..97e2a21 100644 --- a/.amazonq/rules/problem-creation.md +++ b/.amazonq/rules/problem-creation.md @@ -42,15 +42,224 @@ Required fields for `leetcode_py/cli/resources/leetcode/json/problems/{problem_n - `playground_assertion`: Use single quotes for string literals - Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues -**Reference the complete template example:** - -See `leetcode_py/cli/resources/leetcode/examples/example.json5` for a comprehensive template with: - -- All field definitions and variations -- Comments explaining each field -- Examples for different problem types (basic, tree, linked list, design, trie) -- Proper JSON escaping rules for playground fields -- Multiple solution class patterns +**IMPORTANT: Create actual JSON files, not JSON5** + +The template below uses JSON5 format with comments for documentation purposes only. When creating the actual `.json` file, you must: + +1. **Remove all comments** (lines starting with `//`) +2. **Use proper JSON syntax** with quoted property names +3. **Save as `.json` file** (not `.json5`) + +**Template with comments (JSON5 format for reference only):** + +````json5 +{ + // ============================================================================ + // COMPREHENSIVE LEETCODE TEMPLATE EXAMPLE + // ============================================================================ + // This example demonstrates ALL template patterns using valid_anagram as base + // with comprehensive comments showing variations for different problem types. + // + // REFERENCE PROBLEMS (see .templates/leetcode/json/ for complete examples): + // 1. valid_anagram - Basic: string parameters, boolean return + // 2. invert_binary_tree - Tree: TreeNode imports/parameters + // 3. merge_two_sorted_lists - LinkedList: ListNode imports/parameters + // 4. lru_cache - Design: custom class, multiple methods, operations + // 5. implement_trie_prefix_tree - Trie: DictTree inheritance + // ============================================================================ + + // === PROBLEM IDENTIFICATION === + problem_name: "valid_anagram", // snake_case: used for directory/file names + solution_class_name: "Solution", // "Solution" for basic problems + // "LRUCache" for design problems + // "Trie(DictTree[str])" for inheritance + problem_number: "242", // LeetCode problem number as string + problem_title: "Valid Anagram", // Exact title from LeetCode + difficulty: "Easy", // Easy, Medium, Hard + topics: "Hash Table, String, Sorting", // Comma-separated topics from LeetCode + _tags: { list: ["grind-75"] }, // Optional: common problem set tags + // Use _tags wrapper for cookiecutter lists + + // === README CONTENT === + // IMPORTANT: Preserve rich HTML content from LeetCode including: + // - Code snippets with backticks: `code` + // - Bold text: **bold** or bold + // - Italic text: *italic* or italic + // - Images: ![Example](https://assets.leetcode.com/uploads/...) + // - HTML formatting:

,
,