From 3ddc5dc0fcb1fbe456a684161d126232e2c3f68b Mon Sep 17 00:00:00 2001 From: Shravan Chandra <52000043+shravnchandr@users.noreply.github.com> Date: Sat, 29 Nov 2025 22:07:25 +0530 Subject: [PATCH 1/5] Add pascals-triangle problem and fix N803 lint --- add_problems.py | 376 ++++++++++++++++++ leetcode/pascals_triangle/README.md | 44 ++ leetcode/pascals_triangle/__init__.py | 0 leetcode/pascals_triangle/helpers.py | 11 + leetcode/pascals_triangle/playground.ipynb | 56 +++ leetcode/pascals_triangle/solution.py | 10 + leetcode/pascals_triangle/test_solution.py | 19 + .../json/problems/pascals_triangle.json | 52 +++ pyproject.toml | 2 +- 9 files changed, 569 insertions(+), 1 deletion(-) create mode 100644 add_problems.py create mode 100644 leetcode/pascals_triangle/README.md create mode 100644 leetcode/pascals_triangle/__init__.py create mode 100644 leetcode/pascals_triangle/helpers.py create mode 100644 leetcode/pascals_triangle/playground.ipynb create mode 100644 leetcode/pascals_triangle/solution.py create mode 100644 leetcode/pascals_triangle/test_solution.py create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/pascals_triangle.json diff --git a/add_problems.py b/add_problems.py new file mode 100644 index 0000000..f56fc8d --- /dev/null +++ b/add_problems.py @@ -0,0 +1,376 @@ +import json +import os +import re +import shutil +import subprocess +import sys +import time + + +def get_missing_slugs(): + return ["pascals-triangle"] + + +def transform_scraped_data(data): + """Transforms scraped data into the format expected by cookiecutter/gen.""" + transformed = {} + + # Basic fields + # Sanitize problem_name + problem_name = data.get("slug", "").replace("-", "_") + if problem_name and problem_name[0].isdigit(): + problem_name = f"_{problem_name}" + transformed["problem_name"] = problem_name + transformed["problem_number"] = data.get("number", "0") + transformed["problem_title"] = data.get("title", "") + transformed["difficulty"] = data.get("difficulty", "Medium") + + # Topics: list -> string + topics = data.get("topics", []) + if isinstance(topics, list): + transformed["topics"] = ", ".join(topics) + else: + transformed["topics"] = str(topics) + + # Description + transformed["readme_description"] = data.get("description", "") + + # Constraints: list -> string + constraints = data.get("constraints", []) + if isinstance(constraints, list): + transformed["readme_constraints"] = "\n".join([f"- {c}" for c in constraints]) + else: + transformed["readme_constraints"] = str(constraints) + + # Examples + examples = data.get("examples", []) + readme_examples = [] + for ex in examples: + # Scraped example might be dict or string? + # 01_matrix.json shows examples as empty list []? + # But raw_content has them. + # If examples is list of dicts with 'text' or 'input'/'output' + if isinstance(ex, dict): + content = ex.get("text", "") + if not content: + inp = ex.get("input", "") + out = ex.get("output", "") + if inp and out: + content = f"```\nInput: {inp}\nOutput: {out}\n```" + if content: + readme_examples.append({"content": content}) + + transformed["_readme_examples"] = {"list": readme_examples} + + # Python Code Parsing + python_code = data.get("python_code", "") + solution_class_name = "Solution" + method_name = "unknown" + method_signature = "" + method_body = " pass" + + # Simple regex # Find Solution class name + if "class Solution:" in python_code: + solution_class_name = "Solution" + else: + class_match = re.search(r"class\s+(\w+):", python_code) + if class_match: + solution_class_name = class_match.group(1) + else: + solution_class_name = "Solution" + # Find method def + # def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]: + # Find class Solution + solution_match = re.search(r"class\s+Solution:\s*", python_code) + if solution_match: + # Search for method AFTER class Solution definition + start_index = solution_match.end() + method_match = re.search(r"def\s+(\w+)\s*\((.*?)\)\s*(->\s*.*?)?:", python_code[start_index:]) + else: + # Fallback if no Solution class (unlikely but possible) + method_match = re.search(r"def\s+(\w+)\s*\((.*?)\)\s*(->\s*.*?)?:", python_code) + + if method_match: + method_name = method_match.group(1) + params = method_match.group(2) + return_type = method_match.group(3) or "" + + # Clean params to remove 'self' + param_list = [p.strip() for p in params.split(",") if p.strip()] + if param_list and param_list[0] == "self": + param_list = param_list[1:] + + clean_params = ", ".join(param_list) + + # Construct signature + if clean_params: + method_signature = f"(self, {clean_params}){return_type}" + else: + method_signature = f"(self){return_type}" + + # Snake case method name + s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", method_name) + snake_method_name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() + + method_body = f" # TODO: Implement {snake_method_name}\n return " + if "ListNode" in return_type or "TreeNode" in return_type: + method_body += "None" + elif "List" in return_type: + method_body += "[]" + elif "int" in return_type: + method_body += "0" + elif "bool" in return_type: + method_body += "False" + elif "str" in return_type: + method_body += '""' + else: + method_body += "None" + + transformed["solution_class_name"] = solution_class_name + + transformed["_solution_methods"] = { + "list": [{"name": snake_method_name, "signature": method_signature, "body": method_body}] + } + + # Helpers and Tests + transformed["helpers_run_name"] = snake_method_name + + if clean_params: + helper_sig = f"(solution_class: type, {clean_params})" + else: + helper_sig = "(solution_class: type)" + + transformed["helpers_run_signature"] = helper_sig + + # Extract argument names for the call + arg_names = [p.split(":")[0].strip() for p in param_list] + call_args = ", ".join(arg_names) + + transformed["helpers_run_body"] = ( + f" implementation = solution_class()\n" + f" return implementation.{snake_method_name}({call_args})" + ) + + transformed["helpers_assert_name"] = snake_method_name + # Infer expected type from return type + expected_type = return_type.replace("->", "").strip() + transformed["helpers_assert_signature"] = ( + f"(result: {expected_type}, expected: {expected_type}) -> bool" + ) + transformed["helpers_assert_body"] = " assert result == expected\n return True" + + transformed["test_class_name"] = "".join( + [word.capitalize() for word in transformed["problem_name"].split("_")] + ) + transformed["test_class_content"] = ( + " def setup_method(self):\n self.solution = Solution()" + ) + + # Test Cases + formatted_test_cases = [] + raw_test_cases = data.get("test_cases", []) + + # Count args + arg_count = len(arg_names) + + for tc in raw_test_cases: + # Expected length is arg_count + 1 (for expected output) + if len(tc) == arg_count + 1: + args = ", ".join(tc[:-1]) + expected = tc[-1] + formatted_test_cases.append(f"({args}, {expected})") + else: + print( + f"[{data.get('slug')}] Warning: Skipping invalid test case " + f"(len={len(tc)}, expected={arg_count+1}): {tc}" + ) + + # Parametrize string + if call_args: + parametrize_str = f"{call_args}, expected" + else: + parametrize_str = "expected" + + # Test signature + if clean_params: + test_sig = f"(self, {clean_params}, expected: {expected_type})" + else: + test_sig = f"(self, expected: {expected_type})" + + transformed["_test_methods"] = { + "list": [ + { + "name": f"test_{snake_method_name}", + "signature": test_sig, + "parametrize": parametrize_str, + "test_cases": {"list": formatted_test_cases}, + "body": ( + f" result = run_{snake_method_name}(Solution, {call_args})\n" + f" assert_{snake_method_name}(result, expected)" + ), + } + ] + } + + # Imports + typing_imports = [] + for type_name in ["List", "Optional", "Dict", "Set", "Tuple"]: + if re.search(rf"\b{type_name}\b", method_signature): + typing_imports.append(type_name) + + ds_imports = [] + if "ListNode" in method_signature: + ds_imports.append("from leetcode_py.data_structures.list_node import ListNode") + if "TreeNode" in method_signature: + ds_imports.append("from leetcode_py.data_structures.tree_node import TreeNode") + + imports_list = [] + if typing_imports: + imports_list.append(f"from typing import {', '.join(typing_imports)}") + imports_list.extend(ds_imports) + + imports_str = "\n".join(imports_list) + + transformed["solution_imports"] = imports_str + + test_imports_list = ["import pytest", "from leetcode_py import logged_test"] + if imports_str: + test_imports_list.append(imports_str) + test_imports_list.append( + f"from .helpers import assert_{snake_method_name}, run_{snake_method_name}" + ) + test_imports_list.append("from .solution import Solution") + + transformed["test_imports"] = "\n".join(test_imports_list) + transformed["helpers_imports"] = imports_str + + return transformed + + +def process_problem(slug): + scrape_slug = slug.replace("_", "-") + + print(f"\n[{slug}] Starting process...") + + try: + # 1. Scrape + print(f"[{slug}] Scraping {scrape_slug}...") + result = subprocess.run( + ["uv", "run", "python", "-m", "leetcode_py.cli.main", "scrape", "-s", scrape_slug], + check=False, + capture_output=True, + text=True, + ) + + if result.returncode != 0: + print(f"[{slug}] Scrape FAILED with code {result.returncode}.") + return False + + output = result.stdout + json_start = output.find("{") + if json_start == -1: + print(f"[{slug}] Scrape output does not contain JSON start.") + return False + + json_content = output[json_start:] + try: + data = json.loads(json_content) + except json.JSONDecodeError: + print(f"[{slug}] Extracted content is not valid JSON.") + return False + + # TRANSFORM DATA + transformed_data = transform_scraped_data(data) + + # Convert to snake_case for file naming + snake_slug = slug.replace("-", "_") + + # If starts with digit, prepend _ + if snake_slug[0].isdigit(): + snake_slug = f"_{snake_slug}" + + # Save to file + json_dir = "leetcode_py/cli/resources/leetcode/json/problems" + os.makedirs(json_dir, exist_ok=True) + json_path = os.path.join(json_dir, f"{snake_slug}.json") + + with open(json_path, "w") as f: + json.dump(transformed_data, f, indent=2) + + abs_json_path = os.path.abspath(json_path) + print(f"[{slug}] Scrape successful. Saved to {abs_json_path}") + + # 2. Generate + print(f"[{snake_slug}] Generating...") + gen_result = subprocess.run( + [ + "uv", + "run", + "python", + "-m", + "leetcode_py.cli.main", + "gen", + "-s", + snake_slug, + "-o", + "leetcode", + "--force", + ], + check=False, + capture_output=True, + text=True, + ) + + if gen_result.returncode != 0: + print(f"[{slug}] Generation FAILED.") + print(f"Error output: {gen_result.stderr}") + print(f"Standard output: {gen_result.stdout}") + return False + + print(f"[{slug}] Generation successful.") + return True + + except Exception as e: + print(f"[{slug}] Unexpected error: {e}") + return False + + +def main(): + if not shutil.which("uv"): + print("Error: 'uv' executable not found in PATH.") + sys.exit(1) + + slugs = get_missing_slugs() + print(f"Found {len(slugs)} problems to process.") + + success_count = 0 + failed_slugs = [] + + for i, slug in enumerate(slugs): + print(f"\n--- Processing {i+1}/{len(slugs)}: {slug} ---") + if process_problem(slug): + success_count += 1 + else: + failed_slugs.append(slug) + + time.sleep(0.5) + + print("\n" + "=" * 30) + print(f"Completed. Success: {success_count}, Failed: {len(failed_slugs)}") + + if failed_slugs: + print("\nFailed slugs:") + for s in failed_slugs: + print(s) + + with open("failed_slugs.txt", "w") as f: + for s in failed_slugs: + f.write(s + "\n") + print("Failed slugs written to failed_slugs.txt") + else: + if os.path.exists("failed_slugs.txt"): + os.remove("failed_slugs.txt") + + +if __name__ == "__main__": + main() diff --git a/leetcode/pascals_triangle/README.md b/leetcode/pascals_triangle/README.md new file mode 100644 index 0000000..7aa54e0 --- /dev/null +++ b/leetcode/pascals_triangle/README.md @@ -0,0 +1,44 @@ +# Pascal's Triangle + +**Difficulty:** Easy +**Topics:** Array, Dynamic Programming +**Tags:** grind-75 + +**LeetCode:** [Problem 118](https://leetcode.com/problems/pascals-triangle/description/) + + +## Problem Description + +Given an integer numRows, return the first numRows of Pascal's triangle. + +In Pascal's triangle, each number is the sum of the two numbers directly above it as shown: + +  +Example 1: +Input: numRows = 5 +Output: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] +Example 2: +Input: numRows = 1 +Output: [[1]] + +  +Constraints: + + + 1 <= numRows <= 30 + +## Examples + +### Example 1: + +Input: numRows = 5 +Output: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] +Example 2: +Input: numRows = 1 +Output: [[1]] + +## Constraints + +- 1 <= numRows <= 30 + + diff --git a/leetcode/pascals_triangle/__init__.py b/leetcode/pascals_triangle/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/pascals_triangle/helpers.py b/leetcode/pascals_triangle/helpers.py new file mode 100644 index 0000000..0c714f0 --- /dev/null +++ b/leetcode/pascals_triangle/helpers.py @@ -0,0 +1,11 @@ +from typing import List + + +def run_generate(solution_class: type, numRows: int): + implementation = solution_class() + return implementation.generate(numRows) + + +def assert_generate(result: List[List[int]], expected: List[List[int]]) -> bool: + assert result == expected + return True diff --git a/leetcode/pascals_triangle/playground.ipynb b/leetcode/pascals_triangle/playground.ipynb new file mode 100644 index 0000000..38bebef --- /dev/null +++ b/leetcode/pascals_triangle/playground.ipynb @@ -0,0 +1,56 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": ["from helpers import run_two_sum, assert_two_sum\nfrom solution import Solution"] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": ["# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9\nexpected = [0, 1]"] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": ["result = run_two_sum(Solution, nums, target)\nresult"] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": ["assert_two_sum(result, expected)"] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/pascals_triangle/solution.py b/leetcode/pascals_triangle/solution.py new file mode 100644 index 0000000..8c8f696 --- /dev/null +++ b/leetcode/pascals_triangle/solution.py @@ -0,0 +1,10 @@ +from typing import List + + +class Solution: + + # Time: O(?) + # Space: O(?) + def generate(self, numRows: int) -> List[List[int]]: + # TODO: Implement generate + return [] diff --git a/leetcode/pascals_triangle/test_solution.py b/leetcode/pascals_triangle/test_solution.py new file mode 100644 index 0000000..772bb51 --- /dev/null +++ b/leetcode/pascals_triangle/test_solution.py @@ -0,0 +1,19 @@ +from typing import List + +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_generate, run_generate +from .solution import Solution + + +class TestPascalsTriangle: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize("numRows, expected", [(5, 1)]) + def test_generate(self, numRows: int, expected: List[List[int]]): + result = run_generate(Solution, numRows) + assert_generate(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/pascals_triangle.json b/leetcode_py/cli/resources/leetcode/json/problems/pascals_triangle.json new file mode 100644 index 0000000..7a3fc43 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/pascals_triangle.json @@ -0,0 +1,52 @@ +{ + "problem_name": "pascals_triangle", + "problem_number": "118", + "problem_title": "Pascal's Triangle", + "difficulty": "Easy", + "topics": "Array, Dynamic Programming", + "readme_description": "Given an integer numRows, return the first numRows of Pascal's triangle.\n\nIn Pascal's triangle, each number is the sum of the two numbers directly above it as shown:\n\n \nExample 1:\nInput: numRows = 5\nOutput: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]\nExample 2:\nInput: numRows = 1\nOutput: [[1]]\n\n \nConstraints:\n\n\n\t1 <= numRows <= 30", + "readme_constraints": "- 1 <= numRows <= 30", + "_readme_examples": { + "list": [ + { + "content": "Input: numRows = 5\nOutput: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]\nExample 2:\nInput: numRows = 1\nOutput: [[1]]" + } + ] + }, + "solution_class_name": "Solution", + "_solution_methods": { + "list": [ + { + "name": "generate", + "signature": "(self, numRows: int)-> List[List[int]]", + "body": " # TODO: Implement generate\n return []" + } + ] + }, + "helpers_run_name": "generate", + "helpers_run_signature": "(solution_class: type, numRows: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.generate(numRows)", + "helpers_assert_name": "generate", + "helpers_assert_signature": "(result: List[List[int]], expected: List[List[int]]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "test_class_name": "PascalsTriangle", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_test_methods": { + "list": [ + { + "name": "test_generate", + "signature": "(self, numRows: int, expected: List[List[int]])", + "parametrize": "numRows, expected", + "test_cases": { + "list": [ + "(5, 1)" + ] + }, + "body": " result = run_generate(Solution, numRows)\n assert_generate(result, expected)" + } + ] + }, + "solution_imports": "from typing import List", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom typing import List\nfrom .helpers import assert_generate, run_generate\nfrom .solution import Solution", + "helpers_imports": "from typing import List" +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 19d9ec5..532d6a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ select = [ "I", # isort "N", # pep8-naming ] -ignore = ["N806"] +ignore = ["N806", "N803"] [tool.ruff.lint.pydocstyle] convention = "numpy" From c96f64772febed771b0c483225612f914800f164 Mon Sep 17 00:00:00 2001 From: Shravan Chandra <52000043+shravnchandr@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:47:04 +0530 Subject: [PATCH 2/5] Revert "Add pascals-triangle problem and fix N803 lint" This reverts commit 3ddc5dc0fcb1fbe456a684161d126232e2c3f68b. --- add_problems.py | 376 ------------------ leetcode/pascals_triangle/README.md | 44 -- leetcode/pascals_triangle/__init__.py | 0 leetcode/pascals_triangle/helpers.py | 11 - leetcode/pascals_triangle/playground.ipynb | 56 --- leetcode/pascals_triangle/solution.py | 10 - leetcode/pascals_triangle/test_solution.py | 19 - .../json/problems/pascals_triangle.json | 52 --- pyproject.toml | 2 +- 9 files changed, 1 insertion(+), 569 deletions(-) delete mode 100644 add_problems.py delete mode 100644 leetcode/pascals_triangle/README.md delete mode 100644 leetcode/pascals_triangle/__init__.py delete mode 100644 leetcode/pascals_triangle/helpers.py delete mode 100644 leetcode/pascals_triangle/playground.ipynb delete mode 100644 leetcode/pascals_triangle/solution.py delete mode 100644 leetcode/pascals_triangle/test_solution.py delete mode 100644 leetcode_py/cli/resources/leetcode/json/problems/pascals_triangle.json diff --git a/add_problems.py b/add_problems.py deleted file mode 100644 index f56fc8d..0000000 --- a/add_problems.py +++ /dev/null @@ -1,376 +0,0 @@ -import json -import os -import re -import shutil -import subprocess -import sys -import time - - -def get_missing_slugs(): - return ["pascals-triangle"] - - -def transform_scraped_data(data): - """Transforms scraped data into the format expected by cookiecutter/gen.""" - transformed = {} - - # Basic fields - # Sanitize problem_name - problem_name = data.get("slug", "").replace("-", "_") - if problem_name and problem_name[0].isdigit(): - problem_name = f"_{problem_name}" - transformed["problem_name"] = problem_name - transformed["problem_number"] = data.get("number", "0") - transformed["problem_title"] = data.get("title", "") - transformed["difficulty"] = data.get("difficulty", "Medium") - - # Topics: list -> string - topics = data.get("topics", []) - if isinstance(topics, list): - transformed["topics"] = ", ".join(topics) - else: - transformed["topics"] = str(topics) - - # Description - transformed["readme_description"] = data.get("description", "") - - # Constraints: list -> string - constraints = data.get("constraints", []) - if isinstance(constraints, list): - transformed["readme_constraints"] = "\n".join([f"- {c}" for c in constraints]) - else: - transformed["readme_constraints"] = str(constraints) - - # Examples - examples = data.get("examples", []) - readme_examples = [] - for ex in examples: - # Scraped example might be dict or string? - # 01_matrix.json shows examples as empty list []? - # But raw_content has them. - # If examples is list of dicts with 'text' or 'input'/'output' - if isinstance(ex, dict): - content = ex.get("text", "") - if not content: - inp = ex.get("input", "") - out = ex.get("output", "") - if inp and out: - content = f"```\nInput: {inp}\nOutput: {out}\n```" - if content: - readme_examples.append({"content": content}) - - transformed["_readme_examples"] = {"list": readme_examples} - - # Python Code Parsing - python_code = data.get("python_code", "") - solution_class_name = "Solution" - method_name = "unknown" - method_signature = "" - method_body = " pass" - - # Simple regex # Find Solution class name - if "class Solution:" in python_code: - solution_class_name = "Solution" - else: - class_match = re.search(r"class\s+(\w+):", python_code) - if class_match: - solution_class_name = class_match.group(1) - else: - solution_class_name = "Solution" - # Find method def - # def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]: - # Find class Solution - solution_match = re.search(r"class\s+Solution:\s*", python_code) - if solution_match: - # Search for method AFTER class Solution definition - start_index = solution_match.end() - method_match = re.search(r"def\s+(\w+)\s*\((.*?)\)\s*(->\s*.*?)?:", python_code[start_index:]) - else: - # Fallback if no Solution class (unlikely but possible) - method_match = re.search(r"def\s+(\w+)\s*\((.*?)\)\s*(->\s*.*?)?:", python_code) - - if method_match: - method_name = method_match.group(1) - params = method_match.group(2) - return_type = method_match.group(3) or "" - - # Clean params to remove 'self' - param_list = [p.strip() for p in params.split(",") if p.strip()] - if param_list and param_list[0] == "self": - param_list = param_list[1:] - - clean_params = ", ".join(param_list) - - # Construct signature - if clean_params: - method_signature = f"(self, {clean_params}){return_type}" - else: - method_signature = f"(self){return_type}" - - # Snake case method name - s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", method_name) - snake_method_name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() - - method_body = f" # TODO: Implement {snake_method_name}\n return " - if "ListNode" in return_type or "TreeNode" in return_type: - method_body += "None" - elif "List" in return_type: - method_body += "[]" - elif "int" in return_type: - method_body += "0" - elif "bool" in return_type: - method_body += "False" - elif "str" in return_type: - method_body += '""' - else: - method_body += "None" - - transformed["solution_class_name"] = solution_class_name - - transformed["_solution_methods"] = { - "list": [{"name": snake_method_name, "signature": method_signature, "body": method_body}] - } - - # Helpers and Tests - transformed["helpers_run_name"] = snake_method_name - - if clean_params: - helper_sig = f"(solution_class: type, {clean_params})" - else: - helper_sig = "(solution_class: type)" - - transformed["helpers_run_signature"] = helper_sig - - # Extract argument names for the call - arg_names = [p.split(":")[0].strip() for p in param_list] - call_args = ", ".join(arg_names) - - transformed["helpers_run_body"] = ( - f" implementation = solution_class()\n" - f" return implementation.{snake_method_name}({call_args})" - ) - - transformed["helpers_assert_name"] = snake_method_name - # Infer expected type from return type - expected_type = return_type.replace("->", "").strip() - transformed["helpers_assert_signature"] = ( - f"(result: {expected_type}, expected: {expected_type}) -> bool" - ) - transformed["helpers_assert_body"] = " assert result == expected\n return True" - - transformed["test_class_name"] = "".join( - [word.capitalize() for word in transformed["problem_name"].split("_")] - ) - transformed["test_class_content"] = ( - " def setup_method(self):\n self.solution = Solution()" - ) - - # Test Cases - formatted_test_cases = [] - raw_test_cases = data.get("test_cases", []) - - # Count args - arg_count = len(arg_names) - - for tc in raw_test_cases: - # Expected length is arg_count + 1 (for expected output) - if len(tc) == arg_count + 1: - args = ", ".join(tc[:-1]) - expected = tc[-1] - formatted_test_cases.append(f"({args}, {expected})") - else: - print( - f"[{data.get('slug')}] Warning: Skipping invalid test case " - f"(len={len(tc)}, expected={arg_count+1}): {tc}" - ) - - # Parametrize string - if call_args: - parametrize_str = f"{call_args}, expected" - else: - parametrize_str = "expected" - - # Test signature - if clean_params: - test_sig = f"(self, {clean_params}, expected: {expected_type})" - else: - test_sig = f"(self, expected: {expected_type})" - - transformed["_test_methods"] = { - "list": [ - { - "name": f"test_{snake_method_name}", - "signature": test_sig, - "parametrize": parametrize_str, - "test_cases": {"list": formatted_test_cases}, - "body": ( - f" result = run_{snake_method_name}(Solution, {call_args})\n" - f" assert_{snake_method_name}(result, expected)" - ), - } - ] - } - - # Imports - typing_imports = [] - for type_name in ["List", "Optional", "Dict", "Set", "Tuple"]: - if re.search(rf"\b{type_name}\b", method_signature): - typing_imports.append(type_name) - - ds_imports = [] - if "ListNode" in method_signature: - ds_imports.append("from leetcode_py.data_structures.list_node import ListNode") - if "TreeNode" in method_signature: - ds_imports.append("from leetcode_py.data_structures.tree_node import TreeNode") - - imports_list = [] - if typing_imports: - imports_list.append(f"from typing import {', '.join(typing_imports)}") - imports_list.extend(ds_imports) - - imports_str = "\n".join(imports_list) - - transformed["solution_imports"] = imports_str - - test_imports_list = ["import pytest", "from leetcode_py import logged_test"] - if imports_str: - test_imports_list.append(imports_str) - test_imports_list.append( - f"from .helpers import assert_{snake_method_name}, run_{snake_method_name}" - ) - test_imports_list.append("from .solution import Solution") - - transformed["test_imports"] = "\n".join(test_imports_list) - transformed["helpers_imports"] = imports_str - - return transformed - - -def process_problem(slug): - scrape_slug = slug.replace("_", "-") - - print(f"\n[{slug}] Starting process...") - - try: - # 1. Scrape - print(f"[{slug}] Scraping {scrape_slug}...") - result = subprocess.run( - ["uv", "run", "python", "-m", "leetcode_py.cli.main", "scrape", "-s", scrape_slug], - check=False, - capture_output=True, - text=True, - ) - - if result.returncode != 0: - print(f"[{slug}] Scrape FAILED with code {result.returncode}.") - return False - - output = result.stdout - json_start = output.find("{") - if json_start == -1: - print(f"[{slug}] Scrape output does not contain JSON start.") - return False - - json_content = output[json_start:] - try: - data = json.loads(json_content) - except json.JSONDecodeError: - print(f"[{slug}] Extracted content is not valid JSON.") - return False - - # TRANSFORM DATA - transformed_data = transform_scraped_data(data) - - # Convert to snake_case for file naming - snake_slug = slug.replace("-", "_") - - # If starts with digit, prepend _ - if snake_slug[0].isdigit(): - snake_slug = f"_{snake_slug}" - - # Save to file - json_dir = "leetcode_py/cli/resources/leetcode/json/problems" - os.makedirs(json_dir, exist_ok=True) - json_path = os.path.join(json_dir, f"{snake_slug}.json") - - with open(json_path, "w") as f: - json.dump(transformed_data, f, indent=2) - - abs_json_path = os.path.abspath(json_path) - print(f"[{slug}] Scrape successful. Saved to {abs_json_path}") - - # 2. Generate - print(f"[{snake_slug}] Generating...") - gen_result = subprocess.run( - [ - "uv", - "run", - "python", - "-m", - "leetcode_py.cli.main", - "gen", - "-s", - snake_slug, - "-o", - "leetcode", - "--force", - ], - check=False, - capture_output=True, - text=True, - ) - - if gen_result.returncode != 0: - print(f"[{slug}] Generation FAILED.") - print(f"Error output: {gen_result.stderr}") - print(f"Standard output: {gen_result.stdout}") - return False - - print(f"[{slug}] Generation successful.") - return True - - except Exception as e: - print(f"[{slug}] Unexpected error: {e}") - return False - - -def main(): - if not shutil.which("uv"): - print("Error: 'uv' executable not found in PATH.") - sys.exit(1) - - slugs = get_missing_slugs() - print(f"Found {len(slugs)} problems to process.") - - success_count = 0 - failed_slugs = [] - - for i, slug in enumerate(slugs): - print(f"\n--- Processing {i+1}/{len(slugs)}: {slug} ---") - if process_problem(slug): - success_count += 1 - else: - failed_slugs.append(slug) - - time.sleep(0.5) - - print("\n" + "=" * 30) - print(f"Completed. Success: {success_count}, Failed: {len(failed_slugs)}") - - if failed_slugs: - print("\nFailed slugs:") - for s in failed_slugs: - print(s) - - with open("failed_slugs.txt", "w") as f: - for s in failed_slugs: - f.write(s + "\n") - print("Failed slugs written to failed_slugs.txt") - else: - if os.path.exists("failed_slugs.txt"): - os.remove("failed_slugs.txt") - - -if __name__ == "__main__": - main() diff --git a/leetcode/pascals_triangle/README.md b/leetcode/pascals_triangle/README.md deleted file mode 100644 index 7aa54e0..0000000 --- a/leetcode/pascals_triangle/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Pascal's Triangle - -**Difficulty:** Easy -**Topics:** Array, Dynamic Programming -**Tags:** grind-75 - -**LeetCode:** [Problem 118](https://leetcode.com/problems/pascals-triangle/description/) - - -## Problem Description - -Given an integer numRows, return the first numRows of Pascal's triangle. - -In Pascal's triangle, each number is the sum of the two numbers directly above it as shown: - -  -Example 1: -Input: numRows = 5 -Output: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] -Example 2: -Input: numRows = 1 -Output: [[1]] - -  -Constraints: - - - 1 <= numRows <= 30 - -## Examples - -### Example 1: - -Input: numRows = 5 -Output: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] -Example 2: -Input: numRows = 1 -Output: [[1]] - -## Constraints - -- 1 <= numRows <= 30 - - diff --git a/leetcode/pascals_triangle/__init__.py b/leetcode/pascals_triangle/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode/pascals_triangle/helpers.py b/leetcode/pascals_triangle/helpers.py deleted file mode 100644 index 0c714f0..0000000 --- a/leetcode/pascals_triangle/helpers.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import List - - -def run_generate(solution_class: type, numRows: int): - implementation = solution_class() - return implementation.generate(numRows) - - -def assert_generate(result: List[List[int]], expected: List[List[int]]) -> bool: - assert result == expected - return True diff --git a/leetcode/pascals_triangle/playground.ipynb b/leetcode/pascals_triangle/playground.ipynb deleted file mode 100644 index 38bebef..0000000 --- a/leetcode/pascals_triangle/playground.ipynb +++ /dev/null @@ -1,56 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": ["from helpers import run_two_sum, assert_two_sum\nfrom solution import Solution"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": ["# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9\nexpected = [0, 1]"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "run", - "metadata": {}, - "outputs": [], - "source": ["result = run_two_sum(Solution, nums, target)\nresult"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "assert", - "metadata": {}, - "outputs": [], - "source": ["assert_two_sum(result, expected)"] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode/pascals_triangle/solution.py b/leetcode/pascals_triangle/solution.py deleted file mode 100644 index 8c8f696..0000000 --- a/leetcode/pascals_triangle/solution.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import List - - -class Solution: - - # Time: O(?) - # Space: O(?) - def generate(self, numRows: int) -> List[List[int]]: - # TODO: Implement generate - return [] diff --git a/leetcode/pascals_triangle/test_solution.py b/leetcode/pascals_triangle/test_solution.py deleted file mode 100644 index 772bb51..0000000 --- a/leetcode/pascals_triangle/test_solution.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import List - -import pytest - -from leetcode_py import logged_test - -from .helpers import assert_generate, run_generate -from .solution import Solution - - -class TestPascalsTriangle: - def setup_method(self): - self.solution = Solution() - - @logged_test - @pytest.mark.parametrize("numRows, expected", [(5, 1)]) - def test_generate(self, numRows: int, expected: List[List[int]]): - result = run_generate(Solution, numRows) - assert_generate(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/pascals_triangle.json b/leetcode_py/cli/resources/leetcode/json/problems/pascals_triangle.json deleted file mode 100644 index 7a3fc43..0000000 --- a/leetcode_py/cli/resources/leetcode/json/problems/pascals_triangle.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "problem_name": "pascals_triangle", - "problem_number": "118", - "problem_title": "Pascal's Triangle", - "difficulty": "Easy", - "topics": "Array, Dynamic Programming", - "readme_description": "Given an integer numRows, return the first numRows of Pascal's triangle.\n\nIn Pascal's triangle, each number is the sum of the two numbers directly above it as shown:\n\n \nExample 1:\nInput: numRows = 5\nOutput: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]\nExample 2:\nInput: numRows = 1\nOutput: [[1]]\n\n \nConstraints:\n\n\n\t1 <= numRows <= 30", - "readme_constraints": "- 1 <= numRows <= 30", - "_readme_examples": { - "list": [ - { - "content": "Input: numRows = 5\nOutput: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]\nExample 2:\nInput: numRows = 1\nOutput: [[1]]" - } - ] - }, - "solution_class_name": "Solution", - "_solution_methods": { - "list": [ - { - "name": "generate", - "signature": "(self, numRows: int)-> List[List[int]]", - "body": " # TODO: Implement generate\n return []" - } - ] - }, - "helpers_run_name": "generate", - "helpers_run_signature": "(solution_class: type, numRows: int)", - "helpers_run_body": " implementation = solution_class()\n return implementation.generate(numRows)", - "helpers_assert_name": "generate", - "helpers_assert_signature": "(result: List[List[int]], expected: List[List[int]]) -> bool", - "helpers_assert_body": " assert result == expected\n return True", - "test_class_name": "PascalsTriangle", - "test_class_content": " def setup_method(self):\n self.solution = Solution()", - "_test_methods": { - "list": [ - { - "name": "test_generate", - "signature": "(self, numRows: int, expected: List[List[int]])", - "parametrize": "numRows, expected", - "test_cases": { - "list": [ - "(5, 1)" - ] - }, - "body": " result = run_generate(Solution, numRows)\n assert_generate(result, expected)" - } - ] - }, - "solution_imports": "from typing import List", - "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom typing import List\nfrom .helpers import assert_generate, run_generate\nfrom .solution import Solution", - "helpers_imports": "from typing import List" -} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 532d6a7..19d9ec5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ select = [ "I", # isort "N", # pep8-naming ] -ignore = ["N806", "N803"] +ignore = ["N806"] [tool.ruff.lint.pydocstyle] convention = "numpy" From 410e3f8c7bbf36c5992168c0e5e9f7b6934cdbf2 Mon Sep 17 00:00:00 2001 From: Shravan Chandra <52000043+shravnchandr@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:12:43 +0530 Subject: [PATCH 3/5] feat: add problem 131 palindrome partitioning --- leetcode/palindrome_partitioning/README.md | 32 +++++++++ leetcode/palindrome_partitioning/__init__.py | 0 leetcode/palindrome_partitioning/helpers.py | 15 ++++ .../palindrome_partitioning/playground.ipynb | 68 ++++++++++++++++++ leetcode/palindrome_partitioning/solution.py | 22 ++++++ .../palindrome_partitioning/test_solution.py | 17 +++++ .../problems/palindrome_partitioning.json | 71 +++++++++++++++++++ 7 files changed, 225 insertions(+) create mode 100644 leetcode/palindrome_partitioning/README.md create mode 100644 leetcode/palindrome_partitioning/__init__.py create mode 100644 leetcode/palindrome_partitioning/helpers.py create mode 100644 leetcode/palindrome_partitioning/playground.ipynb create mode 100644 leetcode/palindrome_partitioning/solution.py create mode 100644 leetcode/palindrome_partitioning/test_solution.py create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json diff --git a/leetcode/palindrome_partitioning/README.md b/leetcode/palindrome_partitioning/README.md new file mode 100644 index 0000000..5b5c0f5 --- /dev/null +++ b/leetcode/palindrome_partitioning/README.md @@ -0,0 +1,32 @@ +# Palindrome Partitioning + +**Difficulty:** Medium +**Topics:** String, Dynamic Programming, Backtracking +**Tags:** grind-75 + +**LeetCode:** [Problem 131](https://leetcode.com/problems/palindrome-partitioning/description/) + +## Problem Description + +Given a string `s`, partition `s` such that every substring of the partition is a **palindrome**. Return _all possible palindrome partitioning of `s`_. + +## Examples + +### Example 1: + +``` +Input: s = "aab" +Output: [["a","a","b"],["aa","b"]] +``` + +### Example 2: + +``` +Input: s = "a" +Output: [["a"]] +``` + +## Constraints + +- `1 <= s.length <= 16` +- `s` contains only lowercase English letters. diff --git a/leetcode/palindrome_partitioning/__init__.py b/leetcode/palindrome_partitioning/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/palindrome_partitioning/helpers.py b/leetcode/palindrome_partitioning/helpers.py new file mode 100644 index 0000000..c488424 --- /dev/null +++ b/leetcode/palindrome_partitioning/helpers.py @@ -0,0 +1,15 @@ +def run_partition(solution_class: type, s: str): + implementation = solution_class() + return implementation.partition(s) + + +def assert_partition(result: list[list[str]], expected: list[list[str]]) -> bool: + # Sort inner lists and outer list for comparison + # Note: Inner lists are partitions (lists of strings), order of partitions doesn't matter + # Order of strings within a partition DOES matter (it must reconstruct s) + # But wait, the problem says "partition s", so the order of substrings must match the order in s. + # So we only need to sort the outer list of partitions. + result_sorted = sorted(result) + expected_sorted = sorted(expected) + assert result_sorted == expected_sorted + return True diff --git a/leetcode/palindrome_partitioning/playground.ipynb b/leetcode/palindrome_partitioning/playground.ipynb new file mode 100644 index 0000000..82ede86 --- /dev/null +++ b/leetcode/palindrome_partitioning/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_partition, run_partition\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"aab\"\n", + "expected = [[\"a\", \"a\", \"b\"], [\"aa\", \"b\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_partition(Solution, s)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_partition(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/palindrome_partitioning/solution.py b/leetcode/palindrome_partitioning/solution.py new file mode 100644 index 0000000..76f56d2 --- /dev/null +++ b/leetcode/palindrome_partitioning/solution.py @@ -0,0 +1,22 @@ +class Solution: + # Time: O(N * 2^N) + # Space: O(N) + def partition(self, s: str) -> list[list[str]]: + result: list[list[str]] = [] + self._backtrack(s, 0, [], result) + return result + + def _backtrack(self, s: str, start: int, path: list[str], result: list[list[str]]) -> None: + if start == len(s): + result.append(path[:]) + return + + for end in range(start + 1, len(s) + 1): + substring = s[start:end] + if self._is_palindrome(substring): + path.append(substring) + self._backtrack(s, end, path, result) + path.pop() + + def _is_palindrome(self, s: str) -> bool: + return s == s[::-1] diff --git a/leetcode/palindrome_partitioning/test_solution.py b/leetcode/palindrome_partitioning/test_solution.py new file mode 100644 index 0000000..d371d47 --- /dev/null +++ b/leetcode/palindrome_partitioning/test_solution.py @@ -0,0 +1,17 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_partition, run_partition +from .solution import Solution + + +class TestPalindromePartitioning: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize("s, expected", [("aab", [["a", "a", "b"], ["aa", "b"]]), ("a", [["a"]])]) + def test_partition(self, s: str, expected: list[list[str]]): + result = run_partition(Solution, s) + assert_partition(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json b/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json new file mode 100644 index 0000000..030f9da --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json @@ -0,0 +1,71 @@ +{ + "problem_name": "palindrome_partitioning", + "solution_class_name": "Solution", + "problem_number": "131", + "problem_title": "Palindrome Partitioning", + "difficulty": "Medium", + "topics": "String, Dynamic Programming, Backtracking", + "readme_description": "Given a string `s`, partition `s` such that every substring of the partition is a **palindrome**. Return *all possible palindrome partitioning of `s`*.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"aab\"\nOutput: [[\"a\",\"a\",\"b\"],[\"aa\",\"b\"]]\n```" + }, + { + "content": "```\nInput: s = \"a\"\nOutput: [[\"a\"]]\n```" + } + ] + }, + "readme_constraints": "- `1 <= s.length <= 16`\n- `s` contains only lowercase English letters.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "partition", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.partition(s)", + "helpers_assert_name": "partition", + "helpers_assert_signature": "(result: list[list[str]], expected: list[list[str]]) -> bool", + "helpers_assert_body": " # Sort inner lists and outer list for comparison\n # Note: Inner lists are partitions (lists of strings), order of partitions doesn't matter\n # Order of strings within a partition DOES matter (it must reconstruct s)\n # But wait, the problem says \"partition s\", so the order of substrings must match the order in s.\n # So we only need to sort the outer list of partitions.\n result_sorted = sorted(result)\n expected_sorted = sorted(expected)\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_partition, run_partition\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "PalindromePartitioning", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "partition", + "signature": "(self, s: str) -> list[list[str]]", + "body": " # TODO: Implement partition\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "_test_methods": { + "list": [ + { + "name": "test_partition", + "signature": "(self, s: str, expected: list[list[str]])", + "parametrize": "s, expected", + "test_cases": { + "list": ["('aab', [['a', 'a', 'b'], ['aa', 'b']])", "('a', [['a']])"] + }, + "body": " result = run_partition(Solution, s)\n assert_partition(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_partition, assert_partition\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'aab'\nexpected = [['a', 'a', 'b'], ['aa', 'b']]", + "playground_run": "result = run_partition(Solution, s)\nresult", + "playground_assert": "assert_partition(result, expected)" +} From 4616f16c7656d3699e4c686b21df0258b2ca36d9 Mon Sep 17 00:00:00 2001 From: Shravan Chandra <52000043+shravnchandr@users.noreply.github.com> Date: Sun, 30 Nov 2025 22:54:30 +0530 Subject: [PATCH 4/5] fix(tests): add more test cases to meet minimum requirement of 10 --- .../palindrome_partitioning/test_solution.py | 18 +++++++++++++++++- .../json/problems/palindrome_partitioning.json | 17 +++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/leetcode/palindrome_partitioning/test_solution.py b/leetcode/palindrome_partitioning/test_solution.py index d371d47..9eb6203 100644 --- a/leetcode/palindrome_partitioning/test_solution.py +++ b/leetcode/palindrome_partitioning/test_solution.py @@ -11,7 +11,23 @@ def setup_method(self): self.solution = Solution() @logged_test - @pytest.mark.parametrize("s, expected", [("aab", [["a", "a", "b"], ["aa", "b"]]), ("a", [["a"]])]) + @pytest.mark.parametrize( + "s, expected", + [ + ("aab", [["a", "a", "b"], ["aa", "b"]]), + ("a", [["a"]]), + ("ab", [["a", "b"]]), + ("aa", [["a", "a"], ["aa"]]), + ("abc", [["a", "b", "c"]]), + ("aba", [["a", "b", "a"], ["aba"]]), + ("aaa", [["a", "a", "a"], ["a", "aa"], ["aa", "a"], ["aaa"]]), + ("abba", [["a", "b", "b", "a"], ["a", "bb", "a"], ["abba"]]), + ("zz", [["z", "z"], ["zz"]]), + ("efe", [["e", "f", "e"], ["efe"]]), + ("xyx", [["x", "y", "x"], ["xyx"]]), + ("noon", [["n", "o", "o", 'n'], ['n', 'oo', 'n'], ['noon']]), + ], + ) def test_partition(self, s: str, expected: list[list[str]]): result = run_partition(Solution, s) assert_partition(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json b/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json index 030f9da..798191d 100644 --- a/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json +++ b/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json @@ -58,7 +58,20 @@ "signature": "(self, s: str, expected: list[list[str]])", "parametrize": "s, expected", "test_cases": { - "list": ["('aab', [['a', 'a', 'b'], ['aa', 'b']])", "('a', [['a']])"] + "list": [ + "('aab', [['a', 'a', 'b'], ['aa', 'b']])", + "('a', [['a']])", + "('ab', [['a', 'b']])", + "('aa', [['a', 'a'], ['aa']])", + "('abc', [['a', 'b', 'c']])", + "('aba', [['a', 'b', 'a'], ['aba']])", + "('aaa', [['a', 'a', 'a'], ['a', 'aa'], ['aa', 'a'], ['aaa']])", + "('abba', [['a', 'b', 'b', 'a'], ['a', 'bb', 'a'], ['abba']])", + "('zz', [['z', 'z'], ['zz']])", + "('efe', [['e', 'f', 'e'], ['efe']])", + "('xyx', [['x', 'y', 'x'], ['xyx']])", + "('noon', [['n', 'o', 'o', 'n'], ['n', 'oo', 'n'], ['noon']])" + ] }, "body": " result = run_partition(Solution, s)\n assert_partition(result, expected)" } @@ -68,4 +81,4 @@ "playground_setup": "# Example test case\ns = 'aab'\nexpected = [['a', 'a', 'b'], ['aa', 'b']]", "playground_run": "result = run_partition(Solution, s)\nresult", "playground_assert": "assert_partition(result, expected)" -} +} \ No newline at end of file From 9d6f97e3cd9103cc3751fbd3b87e59bfae867542 Mon Sep 17 00:00:00 2001 From: Shravan Chandra <52000043+shravnchandr@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:35:04 +0530 Subject: [PATCH 5/5] style: apply pre-commit fixes (formatting, notebook conversion) --- .pre-commit-config.yaml | 2 +- .../palindrome_partitioning/playground.ipynb | 68 ------------------- .../palindrome_partitioning/playground.py | 29 ++++++++ .../palindrome_partitioning/test_solution.py | 2 +- .../problems/palindrome_partitioning.json | 2 +- 5 files changed, 32 insertions(+), 71 deletions(-) delete mode 100644 leetcode/palindrome_partitioning/playground.ipynb create mode 100644 leetcode/palindrome_partitioning/playground.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 171afc3..13c351d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: check-added-large-files args: ["--maxkb=20000"] - repo: https://github.com/gitleaks/gitleaks - rev: v8.29.1 + rev: v8.30.0 hooks: - name: gitleaks id: gitleaks diff --git a/leetcode/palindrome_partitioning/playground.ipynb b/leetcode/palindrome_partitioning/playground.ipynb deleted file mode 100644 index 82ede86..0000000 --- a/leetcode/palindrome_partitioning/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_partition, run_partition\n", - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"aab\"\n", - "expected = [[\"a\", \"a\", \"b\"], [\"aa\", \"b\"]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "run", - "metadata": {}, - "outputs": [], - "source": [ - "result = run_partition(Solution, s)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "assert", - "metadata": {}, - "outputs": [], - "source": [ - "assert_partition(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode/palindrome_partitioning/playground.py b/leetcode/palindrome_partitioning/playground.py new file mode 100644 index 0000000..a89cd84 --- /dev/null +++ b/leetcode/palindrome_partitioning/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.18.1 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_partition, run_partition +from solution import Solution + +# %% +# Example test case +s = "aab" +expected = [["a", "a", "b"], ["aa", "b"]] + +# %% +result = run_partition(Solution, s) +result + +# %% +assert_partition(result, expected) diff --git a/leetcode/palindrome_partitioning/test_solution.py b/leetcode/palindrome_partitioning/test_solution.py index 9eb6203..bd56078 100644 --- a/leetcode/palindrome_partitioning/test_solution.py +++ b/leetcode/palindrome_partitioning/test_solution.py @@ -25,7 +25,7 @@ def setup_method(self): ("zz", [["z", "z"], ["zz"]]), ("efe", [["e", "f", "e"], ["efe"]]), ("xyx", [["x", "y", "x"], ["xyx"]]), - ("noon", [["n", "o", "o", 'n'], ['n', 'oo', 'n'], ['noon']]), + ("noon", [["n", "o", "o", "n"], ["n", "oo", "n"], ["noon"]]), ], ) def test_partition(self, s: str, expected: list[list[str]]): diff --git a/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json b/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json index 798191d..45e0a40 100644 --- a/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json +++ b/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json @@ -81,4 +81,4 @@ "playground_setup": "# Example test case\ns = 'aab'\nexpected = [['a', 'a', 'b'], ['aa', 'b']]", "playground_run": "result = run_partition(Solution, s)\nresult", "playground_assert": "assert_partition(result, expected)" -} \ No newline at end of file +}