diff --git a/.amazonq/rules/problem-creation.md b/.amazonq/rules/problem-creation.md
index 4eb1fee..4cafbf6 100644
--- a/.amazonq/rules/problem-creation.md
+++ b/.amazonq/rules/problem-creation.md
@@ -135,6 +135,7 @@ When creating JSON properties that use PascalCase (solution_class_name, test_cla
- Multiple methods including `__init__`
- Complex test setup with operation sequences
- Import custom class in test_imports
+- **NEVER include custom solution classes** in test_imports - only import the main solution class specified in solution_class_name
### Dict-based Tree Problems (Trie, etc.)
diff --git a/.amazonq/rules/test-case-enhancement.md b/.amazonq/rules/test-case-enhancement.md
new file mode 100644
index 0000000..7a65e36
--- /dev/null
+++ b/.amazonq/rules/test-case-enhancement.md
@@ -0,0 +1,129 @@
+# Test Case Enhancement Rules
+
+## Assistant Workflow for Adding Comprehensive Test Cases
+
+When user requests to enhance test cases for a problem, the assistant will:
+
+### 1. Problem Resolution (Priority Order)
+
+- **FIRST**: Try to resolve from context - check active file path or user-provided problem name
+- **SECOND**: If context resolution fails, THEN run `poetry run python .templates/check_test_cases.py --threshold=10 --max=1` to auto-detect 1 problem with <10 test cases
+- **LAST**: If both above fail, ask user to explicitly specify problem name
+
+### 2. Test Case Generation
+
+- Read `leetcode/{problem_name}/README.md` for problem understanding
+- Analyze existing test cases in `leetcode/{problem_name}/tests.py`
+- Generate comprehensive test cases covering:
+ - **Edge cases**: Empty inputs, single elements, boundary values
+ - **Corner cases**: Maximum/minimum constraints, special patterns
+ - **Normal cases**: Typical scenarios with varied complexity
+ - **Error cases**: Invalid inputs (if applicable)
+
+### 3. Initial Validation
+
+- Run `make p-test PROBLEM={problem_name}` to verify current implementation
+- **If errors found**:
+ - DO NOT update implementation automatically
+ - Only update test cases if they're incorrect
+ - If implementation seems wrong, ASK USER first before modifying
+
+### 4. JSON Template Update
+
+- Update corresponding `.templates/leetcode/json/{problem_name}.json`
+- Add new test cases to `test_cases` field in proper format
+- Maintain existing test structure and naming conventions
+
+### 5. Backup and Regeneration Process
+
+- **Backup**: Move `leetcode/{problem_name}/` to `.cache/leetcode/{problem_name}/`
+- **Regenerate**: Run `make p-gen PROBLEM={problem_name} FORCE=1`
+- **Lint check**: Run `make p-lint PROBLEM={problem_name}`
+- **Iterate**: If lint fails, update JSON and regenerate until passes
+
+### 6. Solution Preservation
+
+- Copy `solution.py` from backup to newly generated structure
+- Run `make p-test PROBLEM={problem_name}` to verify tests pass
+- **If tests fail**: Go back to step 4, update JSON, and iterate until passes
+
+### 7. Cleanup and Restore
+
+- **CRITICAL**: Remove entire newly generated `leetcode/{problem_name}/` directory
+- **CRITICAL**: Restore original structure from `.cache/leetcode/{problem_name}/` backup
+- **CRITICAL**: Only THEN copy enhanced `test_solution.py` from generated files to restored structure
+- **CRITICAL**: Preserve existing solution class parametrization - if original test had multiple solution classes, restore them
+- Verify final state with `make p-test PROBLEM={problem_name}`
+- Clean up backup directory after successful verification
+
+## Test Case Quality Standards
+
+### Coverage Requirements
+
+- **Minimum 10 test cases** per problem
+- **Edge cases**: 20-30% of total test cases
+- **Normal cases**: 50-60% of total test cases
+- **Corner cases**: 20-30% of total test cases
+
+### Test Case Categories
+
+#### Edge Cases
+
+- Empty inputs: `[]`, `""`, `None`
+- Single element: `[1]`, `"a"`
+- Boundary values: `[0]`, `[1]`, `[-1]`
+- Maximum/minimum constraints from problem description
+
+#### Corner Cases
+
+- Duplicate elements: `[1,1,1]`
+- Sorted/reverse sorted arrays: `[1,2,3]`, `[3,2,1]`
+- All same elements: `[5,5,5,5]`
+- Alternating patterns: `[1,0,1,0]`
+
+#### Normal Cases
+
+- Mixed positive/negative numbers
+- Various array sizes within constraints
+- Different data patterns and structures
+- Representative problem scenarios
+
+### JSON Format Requirements
+
+- Use single quotes for Python strings in test cases
+- Follow existing parametrize format
+- Maintain type hints in parametrize_typed
+- Ensure test_cases string is valid Python list syntax
+- **NEVER include custom solution classes** in test_imports - only import the main solution class specified in solution_class_name
+- **PRESERVE existing solution class parametrization** - if original test had multiple solution classes, restore them after JSON regeneration
+
+## Commands Reference
+
+```bash
+# Find problems needing more test cases
+poetry run python .templates/check_test_cases.py --threshold=10 --max=1
+
+# Test specific problem
+make p-test PROBLEM={problem_name}
+
+# Generate from JSON template
+make p-gen PROBLEM={problem_name} FORCE=1
+
+# Lint specific problem
+make p-lint PROBLEM={problem_name}
+```
+
+## Error Handling
+
+- **Implementation errors**: Ask user before modifying solution code
+- **Test failures**: Update JSON template and regenerate
+- **Lint failures**: Fix JSON format and iterate
+- **Backup failures**: Ensure `.cache/leetcode/` directory exists
+
+## Success Criteria
+
+- All tests pass with enhanced test cases
+- Minimum 10 comprehensive test cases per problem
+- Original solution code preserved and working
+- JSON template updated for future regeneration
+- Clean final state with no temporary files
diff --git a/.github/workflows/ci-test-reproducibility.yml b/.github/workflows/ci-test-reproducibility.yml
new file mode 100644
index 0000000..42c71c2
--- /dev/null
+++ b/.github/workflows/ci-test-reproducibility.yml
@@ -0,0 +1,48 @@
+name: ci
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+jobs:
+ test-reproducibility:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+
+ - name: Set up Python
+ uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
+ with:
+ python-version: "3.13"
+
+ - name: Install Poetry
+ uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1
+ with:
+ virtualenvs-create: true
+ virtualenvs-in-project: true
+ installer-parallel: true
+
+ - name: Load cached venv
+ id: cached-poetry-dependencies
+ uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
+ with:
+ path: .venv
+ key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
+
+ - name: Install dependencies
+ run: poetry install --no-interaction --no-ansi
+
+ - name: Delete existing problems
+ run: rm -rf leetcode/*/
+
+ - name: Regenerate all problems from templates
+ run: make gen-all-problems FORCE=1
+ env:
+ # Skip interactive confirmation
+ CI: true
+
+ - name: Run linting to verify reproducibility
+ run: make lint
diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml
index 8204474..7526068 100644
--- a/.github/workflows/ci-test.yml
+++ b/.github/workflows/ci-test.yml
@@ -33,19 +33,33 @@ jobs:
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
- if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-ansi
- - name: Cache Graphviz
+ - name: Cache Graphviz installation
id: cache-graphviz
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
- path: /usr/bin/dot
- key: graphviz-${{ runner.os }}
+ path: ~/graphviz-cache
+ key: graphviz-installed-${{ runner.os }}
- name: Install Graphviz
- if: steps.cache-graphviz.outputs.cache-hit != 'true'
- run: sudo apt-get update && sudo apt-get install -y graphviz
+ run: |
+ if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" = "true" ]; then
+ sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null || true
+ sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null || true
+ sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null || true
+ sudo cp -r ~/graphviz-cache/lib/graphviz /usr/lib/x86_64-linux-gnu/ 2>/dev/null || true
+ sudo ldconfig
+ sudo dot -c
+ else
+ sudo apt-get update
+ sudo apt-get install -y graphviz
+ mkdir -p ~/graphviz-cache/{bin,lib,share}
+ cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null || true
+ cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut,ann,gts}* ~/graphviz-cache/lib/ 2>/dev/null || true
+ cp -r /usr/lib/x86_64-linux-gnu/graphviz ~/graphviz-cache/lib/ 2>/dev/null || true
+ cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null || true
+ fi
- name: Run tests
run: make test
diff --git a/.templates/check_test_cases.py b/.templates/check_test_cases.py
new file mode 100644
index 0000000..4c36ffe
--- /dev/null
+++ b/.templates/check_test_cases.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+
+import json
+from pathlib import Path
+from typing import Optional
+import typer
+
+
+def count_test_cases(json_data):
+ """Count total test cases across all test methods."""
+ total = 0
+
+ # Handle both direct test_methods and nested _test_methods.list
+ test_methods = json_data.get("test_methods", [])
+ if not test_methods and "_test_methods" in json_data:
+ test_methods = json_data["_test_methods"].get("list", [])
+
+ for method in test_methods:
+ test_cases = method.get("test_cases", "")
+ if test_cases.strip():
+ # Parse the test_cases string to count actual test cases
+ try:
+ # Remove outer brackets and split by top-level commas
+ cases_str = test_cases.strip()
+ if cases_str.startswith("[") and cases_str.endswith("]"):
+ cases_str = cases_str[1:-1] # Remove outer brackets
+
+ # Count test cases by counting commas at parenthesis depth 0
+ depth = 0
+ case_count = 1 if cases_str.strip() else 0
+
+ for char in cases_str:
+ if char in "([{":
+ depth += 1
+ elif char in ")]}":
+ depth -= 1
+ elif char == "," and depth == 0:
+ case_count += 1
+
+ total += case_count
+ except Exception:
+ # Fallback to old method if parsing fails
+ total += test_cases.count("(") - test_cases.count("([") + test_cases.count("[(")
+ return total
+
+
+def main(
+ threshold: int = typer.Option(
+ 10, "--threshold", "-t", help="Show files with test cases <= threshold"
+ ),
+ max_results: str = typer.Option(
+ 1, "--max", "-m", help="Maximum number of results to show ('none' for no limit)"
+ ),
+):
+ """Check test case counts in LeetCode JSON templates."""
+ json_dir = Path(".templates/leetcode/json")
+ all_files = []
+
+ for json_file in json_dir.glob("*.json"):
+ try:
+ with open(json_file) as f:
+ data = json.load(f)
+
+ test_count = count_test_cases(data)
+ all_files.append((json_file.name, test_count))
+ except Exception as e:
+ typer.echo(f"Error reading {json_file.name}: {e}", err=True)
+
+ # Sort by test count
+ all_files.sort(key=lambda x: x[1])
+
+ # Filter by threshold
+ filtered_files = [f for f in all_files if f[1] <= threshold]
+
+ # Apply max results limit
+ if max_results.lower() not in ["none", "null", "-1"]:
+ try:
+ max_count = int(max_results)
+ if max_count > 0:
+ filtered_files = filtered_files[:max_count]
+ except ValueError:
+ typer.echo(f"Invalid max_results value: {max_results}", err=True)
+ raise typer.Exit(1)
+
+ typer.echo(f"Files with ≤{threshold} test cases ({len(filtered_files)} total):")
+ for filename, count in filtered_files:
+ typer.echo(f"{filename}: {count} test cases")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/.templates/leetcode/cookiecutter.json b/.templates/leetcode/cookiecutter.json
index adc4fcb..4287ea1 100644
--- a/.templates/leetcode/cookiecutter.json
+++ b/.templates/leetcode/cookiecutter.json
@@ -18,20 +18,32 @@
"readme_constraints": "- 2 <= nums.length <= 10^4\n- -10^9 <= nums[i] <= 10^9\n- -10^9 <= target <= 10^9\n- Only one valid answer exists.",
"readme_additional": "",
+ "helpers_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "helpers_content": "",
+ "helpers_run_name": "two_sum",
+ "helpers_run_signature": "(solution_class: type, nums: list[int], target: int)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.two_sum(nums, target)",
+ "helpers_assert_name": "two_sum",
+ "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.test_utils import logged_test\nfrom .helpers import assert_two_sum, run_two_sum\nfrom .solution import Solution",
+ "test_content": "",
+ "test_class_name": "TwoSum",
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
"_solution_methods": {
"list": [
{
"name": "two_sum",
- "parameters": "nums: list[int], target: int",
- "return_type": "list[int]",
- "dummy_return": "[]"
+ "signature": "(self, nums: list[int], target: int) -> list[int]",
+ "body": " # TODO: Implement two_sum\n return []"
}
]
},
-
- "test_imports": "import pytest\nfrom loguru import logger\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
- "test_class_name": "TwoSum",
"_test_helper_methods": {
"list": [
{
@@ -45,16 +57,16 @@
"list": [
{
"name": "test_two_sum",
+ "signature": "(self, nums: list[int], target: int, expected: list[int])",
"parametrize": "nums, target, expected",
- "parametrize_typed": "nums: list[int], target: int, expected: list[int]",
"test_cases": "[([2, 7, 11, 15], 9, [0, 1]), ([3, 2, 4], 6, [1, 2])]",
- "body": "result = self.solution.two_sum(nums, target)\nassert result == expected"
+ "body": " result = run_two_sum(Solution, nums, target)\n assert_two_sum(result, expected)"
}
]
},
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9\nexpected = [0, 1]",
- "playground_execution": "result = Solution().two_sum(nums, target)\nresult",
- "playground_assertion": "assert result == expected"
+ "playground_imports": "from helpers import run_two_sum, assert_two_sum\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9\nexpected = [0, 1]",
+ "playground_run": "result = run_two_sum(Solution, nums, target)\nresult",
+ "playground_assert": "assert_two_sum(result, expected)"
}
diff --git a/.templates/leetcode/examples/README.md b/.templates/leetcode/examples/README.md
deleted file mode 100644
index df7d99b..0000000
--- a/.templates/leetcode/examples/README.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# JSON Template Examples
-
-This directory contains comprehensive examples for creating LeetCode problem templates.
-
-## Files
-
-- **`basic.json5`** - Covers all standard problem types:
- - Array problems (Container With Most Water)
- - String problems (with JSON escaping notes)
- - Tree problems (import and parameter examples)
- - Linked list problems (import and parameter examples)
- - Matrix problems
- - Number problems
-
-- **`design.json5`** - Data structure design problems:
- - Custom class names (LRUCache, not Solution)
- - Multiple methods including `__init__`
- - Complex test setup with operation sequences
- - Custom imports
-
-## Key Differences
-
-### Standard Problems (basic.json5)
-
-- `solution_class_name`: Always "Solution"
-- Single method (usually)
-- Simple test cases with direct assertions
-- Standard imports
-
-### Design Problems (design.json5)
-
-- `solution_class_name`: Custom class name (e.g., "LRUCache")
-- Multiple methods including constructor
-- Operation sequence testing
-- Import custom class in tests
-
-## Critical Notes
-
-- **JSON Escaping**: Use single quotes for Python strings in playground fields
-- **Type Hints**: Use modern syntax (`list[int]`, `TreeNode | None`)
-- **PascalCase**: Keep acronyms ALL CAPS (LRUCache, ReverseLinkedListII)
diff --git a/.templates/leetcode/examples/basic.json5 b/.templates/leetcode/examples/basic.json5
deleted file mode 100644
index 391c7c6..0000000
--- a/.templates/leetcode/examples/basic.json5
+++ /dev/null
@@ -1,88 +0,0 @@
-{
- // Basic problem template - for array, string, number problems
- // Example: Container With Most Water, Spiral Matrix
- // NOTE: PascalCase naming - keep acronyms/Roman numerals ALL CAPS (LRUCache, ReverseLinkedListII)
-
- // === PROBLEM IDENTIFICATION ===
- "problem_name": "container_with_most_water", // snake_case: used for directory/file names
- "solution_class_name": "Solution", // Always "Solution" for basic problems
- "problem_number": "11", // LeetCode problem number as string
- "problem_title": "Container With Most Water", // Exact title from LeetCode
- "difficulty": "Medium", // Easy, Medium, Hard
- "topics": "Array, Two Pointers, Greedy", // Comma-separated topics from LeetCode
- "tags": ["grind-75"], // Optional: common problem set tags
-
- // === 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:
tags with proper src and styling
- // - HTML formatting:
,
,
, - , etc.
- // - Mathematical expressions and special characters
- "readme_description": "You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the `i`th line are `(i, 0)` and `(i, height[i])`.\n\nFind two lines that together with the x-axis form a container, such that the container contains the most water.\n\nReturn the maximum amount of water a container can store.\n\nNotice that you may not slant the container.",
-
- "readme_examples": [
- {
- // Include images for visual problems - use HTML img tags
- "content": "
\n\n```\nInput: height = [1,8,6,2,5,4,8,3,7]\nOutput: 49\nExplanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.\n```"
- },
- {
- // Additional examples without images
- "content": "```\nInput: height = [1,1]\nOutput: 1\n```"
- }
- ],
-
- "readme_constraints": "- n == height.length\n- 2 <= n <= 10^5\n- 0 <= height[i] <= 10^4",
- "readme_additional": "", // Optional: additional notes, follow-up questions
-
- // === SOLUTION TEMPLATE ===
- "solution_imports": "", // Empty for basic problems
- // For tree: "from leetcode_py import TreeNode"
- // For linked list: "from leetcode_py import ListNode"
- "solution_methods": [
- {
- "name": "max_area", // snake_case method name
- "parameters": "height: list[int]", // Modern Python type hints (list[int], not List[int])
- // For tree: "root: TreeNode | None"
- // For linked list: "head: ListNode | None"
- // For string: "s: str"
- "return_type": "int", // Return type annotation
- "dummy_return": "0" // Default return value
- // For string: "\"\""
- // For bool: "False"
- // For list: "[]"
- // For tree/linked list: "None"
- }
- ],
-
- // === TEST CONFIGURATION ===
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
- "test_class_name": "ContainerWithMostWater", // PascalCase: TestClassName for pytest class
- "test_helper_methods": [
- {
- "name": "setup_method",
- "parameters": "",
- "body": "self.solution = Solution()"
- }
- ],
- "test_methods": [
- {
- "name": "test_max_area", // test_{method_name}
- "parametrize": "height, expected", // pytest parametrize parameters
- "parametrize_typed": "height: list[int], expected: int", // Method signature with type hints
- "test_cases": "[([1,8,6,2,5,4,8,3,7], 49), ([1,1], 1), ([1,2,1], 2)]", // Test data as string
- "body": "result = self.solution.max_area(height)\nassert result == expected"
- }
- ],
-
- // === PLAYGROUND NOTEBOOK ===
- // CRITICAL: Use single quotes for Python strings to avoid JSON escaping issues with Jupyter notebooks
- // Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nheight = [1,8,6,2,5,4,8,3,7]\nexpected = 49",
- // For string problems: "s = 'hello'\nexpected = 'olleh'"
- // For tree: "root_list = [3,9,20,None,None,15,7]\nroot = TreeNode.from_list(root_list)"
- "playground_execution": "result = Solution().max_area(height)\nresult",
- "playground_assertion": "assert result == expected"
-}
diff --git a/.templates/leetcode/examples/design.json5 b/.templates/leetcode/examples/design.json5
deleted file mode 100644
index dbe2096..0000000
--- a/.templates/leetcode/examples/design.json5
+++ /dev/null
@@ -1,84 +0,0 @@
-{
- // Design problem template - for data structure design problems
- // Example: LRU Cache
- // Key differences: Custom class name, multiple methods, complex test setup
- // NOTE: PascalCase naming - keep acronyms/Roman numerals ALL CAPS (LRUCache, ReverseLinkedListII)
-
- // === PROBLEM IDENTIFICATION ===
- problem_name: "lru_cache", // snake_case: used for directory/file names
- solution_class_name: "LRUCache", // IMPORTANT: Custom class name for design problems
- problem_number: "146", // LeetCode problem number as string
- problem_title: "LRU Cache", // Exact title from LeetCode
- difficulty: "Medium", // Easy, Medium, Hard
- topics: "Hash Table, Linked List, Design, Doubly-Linked List", // Design-related topics
- tags: ["grind-75"], // Optional: common problem set tags
-
- // === 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:
tags with proper src and styling
- // - HTML formatting: ,
,
, - , etc.
- // - Mathematical expressions and special characters
- readme_description: "Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.\\n\\nImplement the `LRUCache` class:\\n\\n- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity\\n- `int get(int key)` Return the value of the key if the key exists, otherwise return -1\\n- `void put(int key, int value)` Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key\\n\\nThe functions `get` and `put` must each run in `O(1)` average time complexity.",
-
- readme_examples: [
- {
- // Design problems often have complex operation sequences
- content: '```\\nInput\\n[\\"LRUCache\\", \\"put\\", \\"put\\", \\"get\\", \\"put\\", \\"get\\", \\"put\\", \\"get\\", \\"get\\", \\"get\\"]\\n[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\\nOutput\\n[null, null, null, 1, null, -1, null, -1, 3, 4]\\n\\nExplanation\\nLRUCache lRUCache = new LRUCache(2);\\nlRUCache.put(1, 1); // cache is {1=1}\\nlRUCache.put(2, 2); // cache is {1=1, 2=2}\\nlRUCache.get(1); // return 1\\nlRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}\\nlRUCache.get(2); // returns -1 (not found)\\nlRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}\\nlRUCache.get(1); // return -1 (not found)\\nlRUCache.get(3); // return 3\\nlRUCache.get(4); // return 4\\n```',
- },
- ],
-
- readme_constraints: "- 1 <= capacity <= 3000\\n- 0 <= key <= 10^4\\n- 0 <= value <= 10^5\\n- At most 2 * 10^5 calls will be made to get and put",
- readme_additional: "",
-
- // === SOLUTION TEMPLATE ===
- solution_imports: "", // Usually empty for design problems
- // IMPORTANT: Design problems have multiple methods including __init__
- solution_methods: [
- {
- name: "__init__", // Constructor method
- parameters: "capacity: int", // Constructor parameters
- return_type: "None", // Constructor returns None
- dummy_return: "", // Empty for None return
- },
- {
- name: "get", // Instance method
- parameters: "key: int",
- return_type: "int",
- dummy_return: "-1", // Specific default for this method
- },
- {
- name: "put", // Instance method
- parameters: "key: int, value: int",
- return_type: "None",
- dummy_return: "", // Empty for None return
- },
- ],
-
- // === TEST CONFIGURATION ===
- // IMPORTANT: Design problems import the custom class, not Solution
- test_imports: "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import LRUCache",
- test_class_name: "LRUCache", // PascalCase: TestClassName for pytest class
- test_helper_methods: [], // Often empty for design problems
- test_methods: [
- {
- name: "test_lru_cache",
- // IMPORTANT: Design tests use operations, inputs, expected pattern
- parametrize: "operations, inputs, expected",
- parametrize_typed: "operations: list[str], inputs: list[list[int]], expected: list[int | None]",
- test_cases: '[(["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"], [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], [None, None, None, 1, None, -1, None, -1, 3, 4])]',
- // IMPORTANT: Complex test body that executes operation sequences
- body: 'cache: LRUCache | None = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == "LRUCache":\n cache = LRUCache(inputs[i][0])\n results.append(None)\n elif op == "get" and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == "put" and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\nassert results == expected',
- },
- ],
-
- // === PLAYGROUND NOTEBOOK ===
- // CRITICAL: Use single quotes for Python strings to avoid JSON escaping issues with Jupyter notebooks
- // Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues
- playground_imports: "from solution import LRUCache",
- playground_test_case: "# Example test case\noperations = ['LRUCache', 'put', 'put', 'get', 'put', 'get', 'put', 'get', 'get', 'get']\ninputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nexpected = [None, None, None, 1, None, -1, None, -1, 3, 4]",
- playground_execution: "cache = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == 'LRUCache':\n cache = LRUCache(inputs[i][0])\n results.append(None)\n elif op == 'get' and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == 'put' and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\nresults",
- playground_assertion: "assert results == expected",
-}
diff --git a/.templates/leetcode/examples/example.json5 b/.templates/leetcode/examples/example.json5
new file mode 100644
index 0000000..09c1c24
--- /dev/null
+++ b/.templates/leetcode/examples/example.json5
@@ -0,0 +1,196 @@
+{
+ // ============================================================================
+ // 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: 
+ // - HTML formatting:
,
,
, - , etc.
+ // - Mathematical expressions and special characters
+ "readme_description": "Given two strings `s` and `t`, return `true` if `t` is an anagram of `s`, and `false` otherwise.",
+
+ "_readme_examples": { // Use _readme_examples wrapper for cookiecutter lists
+ "list": [
+ { "content": "```\nInput: s = \"anagram\", t = \"nagaram\"\nOutput: true\n```" },
+ { "content": "```\nInput: s = \"rat\", t = \"car\"\nOutput: false\n```" }
+ // For tree problems: Include images
+ // { "content": "\n\n```\nInput: root = [4,2,7,1,3,6,9]\nOutput: [4,7,2,9,6,3,1]\n```" }
+ ]
+ },
+
+ "readme_constraints": "- 1 <= s.length, t.length <= 5 * 10^4\n- s and t consist of lowercase English letters.",
+ "readme_additional": "**Follow up:** What if the inputs contain Unicode characters? How would you adapt your solution to such a case?",
+
+ // === HELPER FUNCTIONS ===
+ // New template system uses helper functions for cleaner test organization
+ "helpers_imports": "", // Empty for basic problems
+ // "from leetcode_py import TreeNode" for tree problems
+ // "from leetcode_py import ListNode" for linked list problems
+ "helpers_content": "", // Additional helper content if needed
+ "helpers_run_name": "is_anagram", // Function name matching main method
+ "helpers_run_signature": "(solution_class: type, s: str, t: str)",
+ // For tree: "(solution_class: type, root_list: list[int | None])"
+ // For linked list: "(solution_class: type, list1_vals: list[int], list2_vals: list[int])"
+ // For design: "(solution_class: type, operations: list[str], inputs: list[list[int]])"
+ "helpers_run_body": " implementation = solution_class()\n return implementation.is_anagram(s, t)",
+ // For tree: " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.invert_tree(root)"
+ // For design: " cache = None\n results: list[int | None] = []\n # ... operation loop ...\n return results, cache"
+ "helpers_assert_name": "is_anagram", // Function name matching main method
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ // For tree: "(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool"
+ // For design: "(result: list[int | None], expected: list[int | None]) -> bool"
+ "helpers_assert_body": " assert result == expected\n return True",
+ // For tree: " expected = TreeNode[int].from_list(expected_list)\n assert result == expected\n return True"
+
+ // === SOLUTION TEMPLATE ===
+ "solution_imports": "", // Empty for basic problems
+ // "from leetcode_py import TreeNode" for tree problems
+ // "from leetcode_py import ListNode" for linked list problems
+ // "from leetcode_py.data_structures import DictTree, RecursiveDict" for trie problems
+ "solution_contents": "", // Additional content before class definition
+ "solution_class_content": "", // Content inside class definition (usually empty)
+
+ // === TEST CONFIGURATION ===
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_anagram, run_is_anagram\nfrom .solution import Solution",
+ // For design: "from .solution import LRUCache" instead of Solution
+ "test_content": "", // Additional test content
+ "test_class_name": "ValidAnagram", // PascalCase: TestClassName for pytest class
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ // Empty for design problems: ""
+
+ // === SOLUTION METHODS ===
+ "_solution_methods": { // Use _solution_methods wrapper for cookiecutter lists
+ "list": [
+ {
+ "name": "is_anagram", // snake_case method name
+ "signature": "(self, s: str, t: str) -> bool", // Full method signature with type hints
+ // For tree: "(self, root: TreeNode[int] | None) -> TreeNode[int] | None"
+ // For linked list: "(self, list1: ListNode[int] | None, list2: ListNode[int] | None) -> ListNode[int] | None"
+ "body": " # TODO: Implement is_anagram\n return False"
+ // For design problems with __init__:
+ // { "name": "__init__", "signature": "(self, capacity: int) -> None", "body": " # TODO: Initialize\n pass" }
+ }
+ ]
+ },
+
+ // === TEST HELPER METHODS ===
+ "_test_helper_methods": { // Use _test_helper_methods wrapper for cookiecutter lists
+ "list": [
+ { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
+ // Empty list for design problems: []
+ ]
+ },
+
+ // === TEST METHODS ===
+ "_test_methods": { // Use _test_methods wrapper for cookiecutter lists
+ "list": [
+ {
+ "name": "test_is_anagram", // test_{method_name}
+ "signature": "(self, s: str, t: str, expected: bool)", // Method signature with type hints
+ "parametrize": "s, t, expected", // pytest parametrize parameters
+ // For tree: "root_list, expected_list"
+ // For design: "operations, inputs, expected"
+ "test_cases": "[('anagram', 'nagaram', True), ('rat', 'car', False), ('listen', 'silent', True), ('hello', 'bello', False), ('', '', True), ('a', 'a', True), ('a', 'b', False), ('ab', 'ba', True), ('abc', 'bca', True), ('abc', 'def', False), ('aab', 'abb', False), ('aabbcc', 'abcabc', True), ('abcd', 'abcde', False), ('race', 'care', True), ('elbow', 'below', True), ('study', 'dusty', True), ('night', 'thing', True), ('stressed', 'desserts', True)]",
+ // For tree: "[([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], [])]"
+ // For design: "[(['LRUCache', 'put', 'get'], [[2], [1, 1], [1]], [None, None, 1])]"
+ "body": " result = run_is_anagram(Solution, s, t)\n assert_is_anagram(result, expected)"
+ // For tree: " result = run_invert_tree(Solution, root_list)\n assert_invert_tree(result, expected_list)"
+ // For design: " result, _ = run_lru_cache(LRUCache, operations, inputs)\n assert_lru_cache(result, expected)"
+ }
+ ]
+ },
+
+ // === PLAYGROUND NOTEBOOK ===
+ // CRITICAL: Use single quotes for Python strings to avoid JSON escaping issues with Jupyter notebooks
+ // Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues
+ // ALWAYS use single quotes: s = 'hello', not s = "hello"
+ "playground_imports": "from helpers import run_is_anagram, assert_is_anagram\nfrom solution import Solution",
+ // For tree: "from helpers import run_invert_tree, assert_invert_tree\nfrom solution import Solution\nfrom leetcode_py import TreeNode"
+ // For design: "from helpers import run_lru_cache, assert_lru_cache\nfrom solution import LRUCache"
+ "playground_setup": "# Example test case\ns = 'anagram'\nt = 'nagaram'\nexpected = True",
+ // For tree: "# Example test case\nroot_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\nexpected_list: list[int | None] = [4, 7, 2, 9, 6, 3, 1]"
+ // For design: "# Example test case\noperations = ['LRUCache', 'put', 'get']\ninputs = [[2], [1, 1], [1]]\nexpected = [None, None, 1]"
+ "playground_run": "result = run_is_anagram(Solution, s, t)\nresult",
+ // For tree: "result = run_invert_tree(Solution, root_list)\nresult"
+ // For design: "result, cache = run_lru_cache(LRUCache, operations, inputs)\nprint(result)\ncache"
+ "playground_assert": "assert_is_anagram(result, expected)"
+ // For tree: "assert_invert_tree(result, expected_list)"
+ // For design: "assert_lru_cache(result, expected)"
+
+ // ============================================================================
+ // PROBLEM TYPE VARIATIONS SUMMARY:
+ // ============================================================================
+ //
+ // BASIC PROBLEMS (valid_anagram):
+ // - solution_class_name: "Solution"
+ // - solution_imports: ""
+ // - Simple method signatures: "(self, s: str, t: str) -> bool"
+ // - Basic test cases: string/number parameters
+ // - Playground: single quotes for strings
+ //
+ // TREE PROBLEMS (invert_binary_tree):
+ // - solution_class_name: "Solution"
+ // - solution_imports: "from leetcode_py import TreeNode"
+ // - Tree method signatures: "(self, root: TreeNode[int] | None) -> TreeNode[int] | None"
+ // - Helper functions use TreeNode.from_list()
+ // - Test cases: list representations of trees
+ // - Playground: TreeNode imports and list conversions
+ //
+ // LINKED LIST PROBLEMS (merge_two_sorted_lists):
+ // - solution_class_name: "Solution"
+ // - solution_imports: "from leetcode_py import ListNode"
+ // - List method signatures: "(self, list1: ListNode[int] | None, list2: ListNode[int] | None) -> ListNode[int] | None"
+ // - Helper functions use ListNode.from_list()
+ // - Test cases: list representations of linked lists
+ // - Playground: ListNode imports and list conversions
+ //
+ // DESIGN PROBLEMS (lru_cache):
+ // - solution_class_name: "LRUCache" (custom class name)
+ // - Multiple methods including __init__
+ // - Operations-based testing: operations, inputs, expected arrays
+ // - Complex test body with operation loops
+ // - Helper functions return (results, instance) for debugging
+ // - Playground: print results, return instance
+ // - test_class_content: "" (no setup_method)
+ //
+ // INHERITANCE PROBLEMS (implement_trie_prefix_tree):
+ // - solution_class_name: "Trie(DictTree[str])" (with inheritance)
+ // - solution_imports: "from leetcode_py.data_structures import DictTree, RecursiveDict"
+ // - Custom class with inheritance from DictTree
+ // - Operations-based testing like design problems
+ // - Helper functions return (results, instance) for debugging
+ //
+ // MULTIPLE SOLUTIONS (invert_binary_tree, lru_cache):
+ // - Add parametrize for solution classes in test files:
+ // @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS])
+ // @pytest.mark.parametrize("solution_class", [LRUCache, LRUCacheWithDoublyList])
+ // - Update test method signature to include solution_class parameter
+ // - Import all solution classes in test file
+ // ============================================================================
+}
diff --git a/.templates/leetcode/json/accounts_merge.json b/.templates/leetcode/json/accounts_merge.json
index 9a601d1..436e531 100644
--- a/.templates/leetcode/json/accounts_merge.json
+++ b/.templates/leetcode/json/accounts_merge.json
@@ -16,32 +16,36 @@
}
],
"readme_constraints": "- `1 <= accounts.length <= 1000`\n- `2 <= accounts[i].length <= 10`\n- `1 <= accounts[i][j].length <= 30`\n- `accounts[i][0]` consists of English letters.\n- `accounts[i][j] (for j > 0)` is a valid email.",
- "readme_additional": "",
- "solution_imports": "",
+ "helpers_imports": "",
+ "helpers_run_name": "accounts_merge",
+ "helpers_run_signature": "(solution_class: type, accounts: list[list[str]])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.accounts_merge(accounts)",
+ "helpers_assert_name": "accounts_merge",
+ "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(account) for account in sorted(result)]\n expected_sorted = [sorted(account) for account in sorted(expected)]\n assert result_sorted == expected_sorted\n return True",
"solution_methods": [
{
"name": "accounts_merge",
- "parameters": "accounts: list[list[str]]",
- "return_type": "list[list[str]]",
- "dummy_return": "[]"
+ "signature": "(self, accounts: list[list[str]]) -> list[list[str]]",
+ "body": " # TODO: Implement accounts_merge\n return []"
}
],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_accounts_merge, run_accounts_merge\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "AccountsMerge",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "test_helper_methods": [],
"test_methods": [
{
"name": "test_accounts_merge",
+ "signature": "(self, accounts: list[list[str]], expected: list[list[str]])",
"parametrize": "accounts, expected",
- "parametrize_typed": "accounts: list[list[str]], expected: list[list[str]]",
- "test_cases": "[([[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]], [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]), ([[\"Gabe\", \"Gabe0@m.co\", \"Gabe3@m.co\", \"Gabe1@m.co\"], [\"Kevin\", \"Kevin3@m.co\", \"Kevin5@m.co\", \"Kevin0@m.co\"], [\"Ethan\", \"Ethan5@m.co\", \"Ethan4@m.co\", \"Ethan0@m.co\"], [\"Hanzo\", \"Hanzo3@m.co\", \"Hanzo1@m.co\", \"Hanzo0@m.co\"], [\"Fern\", \"Fern5@m.co\", \"Fern1@m.co\", \"Fern0@m.co\"]], [[\"Ethan\", \"Ethan0@m.co\", \"Ethan4@m.co\", \"Ethan5@m.co\"], [\"Gabe\", \"Gabe0@m.co\", \"Gabe1@m.co\", \"Gabe3@m.co\"], [\"Hanzo\", \"Hanzo0@m.co\", \"Hanzo1@m.co\", \"Hanzo3@m.co\"], [\"Kevin\", \"Kevin0@m.co\", \"Kevin3@m.co\", \"Kevin5@m.co\"], [\"Fern\", \"Fern0@m.co\", \"Fern1@m.co\", \"Fern5@m.co\"]])]",
- "body": "result = self.solution.accounts_merge(accounts)\n# Sort both result and expected for comparison since order doesn't matter\nresult_sorted = [sorted(account) for account in sorted(result)]\nexpected_sorted = [sorted(account) for account in sorted(expected)]\nassert result_sorted == expected_sorted"
+ "test_cases": "[([[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]], [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]), ([[\"Gabe\", \"Gabe0@m.co\", \"Gabe3@m.co\", \"Gabe1@m.co\"], [\"Kevin\", \"Kevin3@m.co\", \"Kevin5@m.co\", \"Kevin0@m.co\"], [\"Ethan\", \"Ethan5@m.co\", \"Ethan4@m.co\", \"Ethan0@m.co\"], [\"Hanzo\", \"Hanzo3@m.co\", \"Hanzo1@m.co\", \"Hanzo0@m.co\"], [\"Fern\", \"Fern5@m.co\", \"Fern1@m.co\", \"Fern0@m.co\"]], [[\"Ethan\", \"Ethan0@m.co\", \"Ethan4@m.co\", \"Ethan5@m.co\"], [\"Gabe\", \"Gabe0@m.co\", \"Gabe1@m.co\", \"Gabe3@m.co\"], [\"Hanzo\", \"Hanzo0@m.co\", \"Hanzo1@m.co\", \"Hanzo3@m.co\"], [\"Kevin\", \"Kevin0@m.co\", \"Kevin3@m.co\", \"Kevin5@m.co\"], [\"Fern\", \"Fern0@m.co\", \"Fern1@m.co\", \"Fern5@m.co\"]]), ([[\"John\", \"john@mail.com\"]], [[\"John\", \"john@mail.com\"]]), ([[\"John\"]], [[\"John\"]]), ([[\"John\", \"john1@mail.com\"], [\"John\", \"john2@mail.com\"], [\"John\", \"john3@mail.com\"]], [[\"John\", \"john1@mail.com\"], [\"John\", \"john2@mail.com\"], [\"John\", \"john3@mail.com\"]]), ([[\"John\", \"a@mail.com\", \"b@mail.com\"], [\"John\", \"b@mail.com\", \"c@mail.com\"], [\"John\", \"d@mail.com\"]], [[\"John\", \"a@mail.com\", \"b@mail.com\", \"c@mail.com\"], [\"John\", \"d@mail.com\"]]), ([[\"Alice\", \"alice@mail.com\", \"alice1@mail.com\"], [\"Alice\", \"alice2@mail.com\", \"alice3@mail.com\"], [\"Alice\", \"alice1@mail.com\", \"alice2@mail.com\"]], [[\"Alice\", \"alice1@mail.com\", \"alice2@mail.com\", \"alice3@mail.com\", \"alice@mail.com\"]]), ([[\"John\", \"shared@mail.com\"], [\"Jane\", \"shared@mail.com\"]], [[\"John\", \"shared@mail.com\"]])]",
+ "body": " result = run_accounts_merge(Solution, accounts)\n assert_accounts_merge(result, expected)"
}
],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\naccounts = [[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]\nexpected = [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]",
- "playground_execution": "result = Solution().accounts_merge(accounts)\nresult",
- "playground_assertion": "# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(account) for account in sorted(result)]\nexpected_sorted = [sorted(account) for account in sorted(expected)]\nassert result_sorted == expected_sorted"
+ "playground_imports": "from helpers import run_accounts_merge, assert_accounts_merge\nfrom solution import Solution",
+ "playground_setup": "# Example test case\naccounts = [[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]\nexpected = [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]",
+ "playground_run": "result = run_accounts_merge(Solution, accounts)\nresult",
+ "playground_assert": "assert_accounts_merge(result, expected)"
}
diff --git a/.templates/leetcode/json/add_binary.json b/.templates/leetcode/json/add_binary.json
index e89d7f9..5b2e293 100644
--- a/.templates/leetcode/json/add_binary.json
+++ b/.templates/leetcode/json/add_binary.json
@@ -5,39 +5,56 @@
"problem_title": "Add Binary",
"difficulty": "Easy",
"topics": "Math, String, Bit Manipulation, Simulation",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given two binary strings `a` and `b`, return *their sum as a binary string*.",
- "readme_examples": [
- { "content": "```\nInput: a = \"11\", b = \"1\"\nOutput: \"100\"\n```" },
- { "content": "```\nInput: a = \"1010\", b = \"1011\"\nOutput: \"10101\"\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: a = \"11\", b = \"1\"\nOutput: \"100\"\n```" },
+ { "content": "```\nInput: a = \"1010\", b = \"1011\"\nOutput: \"10101\"\n```" }
+ ]
+ },
"readme_constraints": "- `1 <= a.length, b.length <= 10^4`\n- `a` and `b` consist only of `'0'` or `'1'` characters.\n- Each string does not contain leading zeros except for the zero itself.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "add_binary",
+ "helpers_run_signature": "(solution_class: type, a: str, b: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.add_binary(a, b)",
+ "helpers_assert_name": "add_binary",
+ "helpers_assert_signature": "(result: str, expected: str) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "add_binary",
- "parameters": "a: str, b: str",
- "return_type": "str",
- "dummy_return": "\"\""
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_add_binary, run_add_binary\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "AddBinary",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_add_binary",
- "parametrize": "a, b, expected",
- "parametrize_typed": "a: str, b: str, expected: str",
- "test_cases": "[('11', '1', '100'), ('1010', '1011', '10101'), ('0', '0', '0'), ('1', '1', '10'), ('1111', '1111', '11110')]",
- "body": "result = self.solution.add_binary(a, b)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\na = '11'\nb = '1'\nexpected = '100'",
- "playground_execution": "result = Solution().add_binary(a, b)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "add_binary",
+ "signature": "(self, a: str, b: str) -> str",
+ "body": " # TODO: Implement add_binary\n return \"\""
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_add_binary",
+ "signature": "(self, a: str, b: str, expected: str)",
+ "parametrize": "a, b, expected",
+ "test_cases": "[('11', '1', '100'), ('1010', '1011', '10101'), ('0', '0', '0'), ('1', '1', '10'), ('1111', '1111', '11110'), ('1', '0', '1'), ('0', '1', '1'), ('1', '111', '1000'), ('111', '1', '1000'), ('1010', '1', '1011'), ('1111', '1', '10000')]",
+ "body": " result = run_add_binary(Solution, a, b)\n assert_add_binary(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_add_binary, assert_add_binary\nfrom solution import Solution",
+ "playground_setup": "# Example test case\na = '11'\nb = '1'\nexpected = '100'",
+ "playground_run": "result = run_add_binary(Solution, a, b)\nresult",
+ "playground_assert": "assert_add_binary(result, expected)"
}
diff --git a/.templates/leetcode/json/balanced_binary_tree.json b/.templates/leetcode/json/balanced_binary_tree.json
index b9f3446..1d9955d 100644
--- a/.templates/leetcode/json/balanced_binary_tree.json
+++ b/.templates/leetcode/json/balanced_binary_tree.json
@@ -5,44 +5,61 @@
"problem_title": "Balanced Binary Tree",
"difficulty": "Easy",
"topics": "Tree, Depth-First Search, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a binary tree, determine if it is **height-balanced**.\n\nA height-balanced binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: true\n```"
- },
- {
- "content": "\n\n```\nInput: root = [1,2,2,3,3,null,null,4,4]\nOutput: false\n```"
- },
- { "content": "```\nInput: root = []\nOutput: true\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: true\n```"
+ },
+ {
+ "content": "\n\n```\nInput: root = [1,2,2,3,3,null,null,4,4]\nOutput: false\n```"
+ },
+ { "content": "```\nInput: root = []\nOutput: true\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range `[0, 5000]`.\n- `-10^4 <= Node.val <= 10^4`",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "is_balanced",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None])",
+ "helpers_run_body": " implementation = solution_class()\n root = TreeNode.from_list(root_list)\n return implementation.is_balanced(root)",
+ "helpers_assert_name": "is_balanced",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "is_balanced",
- "parameters": "root: TreeNode | None",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_balanced, run_is_balanced\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "BalancedBinaryTree",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_is_balanced",
- "parametrize": "root_list, expected",
- "parametrize_typed": "root_list: list[int | None], expected: bool",
- "test_cases": "[([3, 9, 20, None, None, 15, 7], True), ([1, 2, 2, 3, 3, None, None, 4, 4], False), ([], True)]",
- "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.is_balanced(root)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list = [3, 9, 20, None, None, 15, 7]\nroot = TreeNode.from_list(root_list)\nexpected = True",
- "playground_execution": "result = Solution().is_balanced(root)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "is_balanced",
+ "signature": "(self, root: TreeNode[int] | None) -> bool",
+ "body": " # TODO: Implement is_balanced\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_is_balanced",
+ "signature": "(self, root_list: list[int | None], expected: bool)",
+ "parametrize": "root_list, expected",
+ "test_cases": "[([3, 9, 20, None, None, 15, 7], True), ([1, 2, 2, 3, 3, None, None, 4, 4], False), ([], True), ([1], True), ([1, 2], True), ([1, None, 2], True), ([1, 2, 3, 4], True), ([1, 2, 2, 3, None, None, 3, 4, None, None, 4], False)]",
+ "body": " result = run_is_balanced(Solution, root_list)\n assert_is_balanced(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_is_balanced, assert_is_balanced\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list: list[int | None] = [3, 9, 20, None, None, 15, 7]\nexpected = True",
+ "playground_run": "result = run_is_balanced(Solution, root_list)\nresult",
+ "playground_assert": "assert_is_balanced(result, expected)"
}
diff --git a/.templates/leetcode/json/basic_calculator.json b/.templates/leetcode/json/basic_calculator.json
index d8c0cce..6644911 100644
--- a/.templates/leetcode/json/basic_calculator.json
+++ b/.templates/leetcode/json/basic_calculator.json
@@ -5,35 +5,57 @@
"problem_title": "Basic Calculator",
"difficulty": "Hard",
"topics": "Math, String, Stack, Recursion",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a string `s` representing a valid expression, implement a basic calculator to evaluate it, and return the result of the evaluation.\n\n**Note:** You are **not** allowed to use any built-in function which evaluates strings as mathematical expressions, such as `eval()`.",
- "readme_examples": [
- { "content": "```\nInput: s = \"1 + 1\"\nOutput: 2\n```" },
- { "content": "```\nInput: s = \" 2-1 + 2 \"\nOutput: 3\n```" },
- { "content": "```\nInput: s = \"(1+(4+5+2)-3)+(6+8)\"\nOutput: 23\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: s = \"1 + 1\"\nOutput: 2\n```" },
+ { "content": "```\nInput: s = \" 2-1 + 2 \"\nOutput: 3\n```" },
+ { "content": "```\nInput: s = \"(1+(4+5+2)-3)+(6+8)\"\nOutput: 23\n```" }
+ ]
+ },
"readme_constraints": "- `1 <= s.length <= 3 * 10^5`\n- `s` consists of digits, `'+'`, `'-'`, `'('`, `')'`, and `' '`.\n- `s` represents a valid expression.\n- `'+'` is **not** used as a unary operation (i.e., `\"+1\"` and `\"+(2 + 3)\"` is invalid).\n- `'-'` could be used as a unary operation (i.e., `\"-1\"` and `\"-(2 + 3)\"` is valid).\n- There will be no two consecutive operators in the input.\n- Every number and running calculation will fit in a signed 32-bit integer.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "calculate",
+ "helpers_run_signature": "(solution_class: type, s: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.calculate(s)",
+ "helpers_assert_name": "calculate",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "calculate", "parameters": "s: str", "return_type": "int", "dummy_return": "0" }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_calculate, run_calculate\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "BasicCalculator",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_calculate",
- "parametrize": "s, expected",
- "parametrize_typed": "s: str, expected: int",
- "test_cases": "[(\"1 + 1\", 2), (\" 2-1 + 2 \", 3), (\"(1+(4+5+2)-3)+(6+8)\", 23), (\"1\", 1), (\"-1\", -1), (\"-(1+2)\", -3), (\"2147483647\", 2147483647), (\"1-1+1\", 1)]",
- "body": "result = self.solution.calculate(s)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = '(1+(4+5+2)-3)+(6+8)'\nexpected = 23",
- "playground_execution": "result = Solution().calculate(s)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "calculate",
+ "signature": "(self, s: str) -> int",
+ "body": " # TODO: Implement calculate\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_calculate",
+ "signature": "(self, s: str, expected: int)",
+ "parametrize": "s, expected",
+ "test_cases": "[('1 + 1', 2), (' 2-1 + 2 ', 3), ('(1+(4+5+2)-3)+(6+8)', 23), ('1', 1), ('-1', -1), ('-(1+2)', -3), ('2147483647', 2147483647), ('1-1+1', 1), ('0', 0), ('-0', 0), ('1+(2+3)', 6), ('(1+2)+3', 6), ('1-(2+3)', -4), ('(1-2)+3', 2), ('-(-1)', 1), ('-(-(-1))', -1), ('1000000-999999', 1), ('10+20-30+40', 40), ('((1+2)+(3+4))', 10), ('1+(2-(3+4))', -4), ('-(1+(2+3))', -6), (' 1 + 2 ', 3), ('123+456', 579), ('-2147483648', -2147483648)]",
+ "body": " result = run_calculate(Solution, s)\n assert_calculate(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_calculate, assert_calculate\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = '(1+(4+5+2)-3)+(6+8)'\nexpected = 23",
+ "playground_run": "result = run_calculate(Solution, s)\nresult",
+ "playground_assert": "assert_calculate(result, expected)"
}
diff --git a/.templates/leetcode/json/best_time_to_buy_and_sell_stock.json b/.templates/leetcode/json/best_time_to_buy_and_sell_stock.json
index 9822f0a..1cd4038 100644
--- a/.templates/leetcode/json/best_time_to_buy_and_sell_stock.json
+++ b/.templates/leetcode/json/best_time_to_buy_and_sell_stock.json
@@ -5,43 +5,60 @@
"problem_title": "Best Time to Buy and Sell Stock",
"difficulty": "Easy",
"topics": "Array, Dynamic Programming",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given an array `prices` where `prices[i]` is the price of a given stock on the ith day.\n\nYou want to maximize your profit by choosing a **single day** to buy one stock and choosing a **different day in the future** to sell that stock.\n\nReturn *the maximum profit you can achieve from this transaction*. If you cannot achieve any profit, return `0`.",
- "readme_examples": [
- {
- "content": "```\nInput: prices = [7,1,5,3,6,4]\nOutput: 5\n```\n**Explanation:** Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.\nNote that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell."
- },
- {
- "content": "```\nInput: prices = [7,6,4,3,1]\nOutput: 0\n```\n**Explanation:** In this case, no transactions are done and the max profit = 0."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: prices = [7,1,5,3,6,4]\nOutput: 5\n```\n**Explanation:** Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.\nNote that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell."
+ },
+ {
+ "content": "```\nInput: prices = [7,6,4,3,1]\nOutput: 0\n```\n**Explanation:** In this case, no transactions are done and the max profit = 0."
+ }
+ ]
+ },
"readme_constraints": "- 1 <= prices.length <= 10^5\n- 0 <= prices[i] <= 10^4",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "max_profit",
+ "helpers_run_signature": "(solution_class: type, prices: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.max_profit(prices)",
+ "helpers_assert_name": "max_profit",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "max_profit",
- "parameters": "prices: list[int]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_max_profit, run_max_profit\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "BestTimeToBuyAndSellStock",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_max_profit",
- "parametrize": "prices, expected",
- "parametrize_typed": "prices: list[int], expected: int",
- "test_cases": "[([7, 1, 5, 3, 6, 4], 5), ([7, 6, 4, 3, 1], 0), ([1, 2, 3, 4, 5], 4), ([5, 4, 3, 2, 1], 0), ([1], 0), ([2, 1], 0), ([1, 2], 1), ([3, 2, 6, 5, 0, 3], 4)]",
- "body": "result = self.solution.max_profit(prices)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nprices = [7, 1, 5, 3, 6, 4]\nexpected = 5",
- "playground_execution": "result = Solution().max_profit(prices)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "max_profit",
+ "signature": "(self, prices: list[int]) -> int",
+ "body": " # TODO: Implement max_profit\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_max_profit",
+ "signature": "(self, prices: list[int], expected: int)",
+ "parametrize": "prices, expected",
+ "test_cases": "[([7, 1, 5, 3, 6, 4], 5), ([7, 6, 4, 3, 1], 0), ([1, 2, 3, 4, 5], 4), ([5, 4, 3, 2, 1], 0), ([1], 0), ([2, 1], 0), ([1, 2], 1), ([3, 2, 6, 5, 0, 3], 4)]",
+ "body": " result = run_max_profit(Solution, prices)\n assert_max_profit(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_max_profit, assert_max_profit\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nprices = [7, 1, 5, 3, 6, 4]\nexpected = 5",
+ "playground_run": "result = run_max_profit(Solution, prices)\nresult",
+ "playground_assert": "assert_max_profit(result, expected)"
}
diff --git a/.templates/leetcode/json/binary_search.json b/.templates/leetcode/json/binary_search.json
index ed3b7c4..8a15f0d 100644
--- a/.templates/leetcode/json/binary_search.json
+++ b/.templates/leetcode/json/binary_search.json
@@ -5,43 +5,60 @@
"problem_title": "Binary Search",
"difficulty": "Easy",
"topics": "Array, Binary Search",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an array of integers `nums` which is sorted in ascending order, and an integer `target`, write a function to search `target` in `nums`. If `target` exists, then return its index. Otherwise, return `-1`.\n\nYou must write an algorithm with `O(log n)` runtime complexity.",
- "readme_examples": [
- {
- "content": "```\nInput: nums = [-1,0,3,5,9,12], target = 9\nOutput: 4\n```\n**Explanation:** 9 exists in nums and its index is 4"
- },
- {
- "content": "```\nInput: nums = [-1,0,3,5,9,12], target = 2\nOutput: -1\n```\n**Explanation:** 2 does not exist in nums so return -1"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: nums = [-1,0,3,5,9,12], target = 9\nOutput: 4\n```\n**Explanation:** 9 exists in nums and its index is 4"
+ },
+ {
+ "content": "```\nInput: nums = [-1,0,3,5,9,12], target = 2\nOutput: -1\n```\n**Explanation:** 2 does not exist in nums so return -1"
+ }
+ ]
+ },
"readme_constraints": "- `1 <= nums.length <= 10^4`\n- `-10^4 < nums[i], target < 10^4`\n- All the integers in `nums` are **unique**.\n- `nums` is sorted in ascending order.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "search",
+ "helpers_run_signature": "(solution_class: type, nums: list[int], target: int)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.search(nums, target)",
+ "helpers_assert_name": "search",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "search",
- "parameters": "nums: list[int], target: int",
- "return_type": "int",
- "dummy_return": "-1"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_search, run_search\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "BinarySearch",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_search",
- "parametrize": "nums, target, expected",
- "parametrize_typed": "nums: list[int], target: int, expected: int",
- "test_cases": "[([\u22121, 0, 3, 5, 9, 12], 9, 4), ([\u22121, 0, 3, 5, 9, 12], 2, \u22121), ([5], 5, 0), ([5], \u22125, \u22121), ([1, 3, 5, 7, 9], 1, 0), ([1, 3, 5, 7, 9], 9, 4), ([1, 3, 5, 7, 9], 4, \u22121)]",
- "body": "result = self.solution.search(nums, target)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [-1, 0, 3, 5, 9, 12]\ntarget = 9\nexpected = 4",
- "playground_execution": "result = Solution().search(nums, target)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "search",
+ "signature": "(self, nums: list[int], target: int) -> int",
+ "body": " # TODO: Implement search\n return -1"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_search",
+ "signature": "(self, nums: list[int], target: int, expected: int)",
+ "parametrize": "nums, target, expected",
+ "test_cases": "[([-1, 0, 3, 5, 9, 12], 9, 4), ([-1, 0, 3, 5, 9, 12], 2, -1), ([5], 5, 0), ([5], -5, -1), ([1, 3, 5, 7, 9], 1, 0), ([1, 3, 5, 7, 9], 9, 4), ([1, 3, 5, 7, 9], 4, -1), ([1, 3], 1, 0), ([1, 3], 3, 1), ([1, 3], 2, -1), ([-5, -2, 0, 3, 7], -2, 1), ([-5, -2, 0, 3, 7], 0, 2), ([-5, -2, 0, 3, 7], -1, -1)]",
+ "body": " result = run_search(Solution, nums, target)\n assert_search(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_search, assert_search\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [-1, 0, 3, 5, 9, 12]\ntarget = 9\nexpected = 4",
+ "playground_run": "result = run_search(Solution, nums, target)\nresult",
+ "playground_assert": "assert_search(result, expected)"
}
diff --git a/.templates/leetcode/json/binary_tree_level_order_traversal.json b/.templates/leetcode/json/binary_tree_level_order_traversal.json
index b9f6380..ba006e5 100644
--- a/.templates/leetcode/json/binary_tree_level_order_traversal.json
+++ b/.templates/leetcode/json/binary_tree_level_order_traversal.json
@@ -5,42 +5,59 @@
"problem_title": "Binary Tree Level Order Traversal",
"difficulty": "Medium",
"topics": "Tree, Breadth-First Search, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given the `root` of a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level).",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: [[3],[9,20],[15,7]]\n```"
- },
- { "content": "```\nInput: root = [1]\nOutput: [[1]]\n```" },
- { "content": "```\nInput: root = []\nOutput: []\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: [[3],[9,20],[15,7]]\n```"
+ },
+ { "content": "```\nInput: root = [1]\nOutput: [[1]]\n```" },
+ { "content": "```\nInput: root = []\nOutput: []\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range [0, 2000]\n- -1000 <= Node.val <= 1000",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "level_order",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None])",
+ "helpers_run_body": " implementation = solution_class()\n root = TreeNode.from_list(root_list) if root_list else None\n return implementation.level_order(root)",
+ "helpers_assert_name": "level_order",
+ "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "level_order",
- "parameters": "root: TreeNode | None",
- "return_type": "list[list[int]]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_level_order, run_level_order\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "BinaryTreeLevelOrderTraversal",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_level_order",
- "parametrize": "root_list, expected",
- "parametrize_typed": "root_list: list[int | None], expected: list[list[int]]",
- "test_cases": "[([3, 9, 20, None, None, 15, 7], [[3], [9, 20], [15, 7]]), ([1], [[1]]), ([], []), ([1, 2, 3, 4, 5, 6, 7], [[1], [2, 3], [4, 5, 6, 7]]), ([1, 2, None, 3, None, 4, None, 5], [[1], [2], [3], [4], [5]])]",
- "body": "root = TreeNode.from_list(root_list) if root_list else None\nresult = self.solution.level_order(root)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list = [3, 9, 20, None, None, 15, 7]\nroot = TreeNode.from_list(root_list)\nexpected = [[3], [9, 20], [15, 7]]",
- "playground_execution": "result = Solution().level_order(root)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "level_order",
+ "signature": "(self, root: TreeNode[int] | None) -> list[list[int]]",
+ "body": " # TODO: Implement level_order\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_level_order",
+ "signature": "(self, root_list: list[int | None], expected: list[list[int]])",
+ "parametrize": "root_list, expected",
+ "test_cases": "[([3, 9, 20, None, None, 15, 7], [[3], [9, 20], [15, 7]]), ([1], [[1]]), ([], []), ([1, 2, 3, 4, 5, 6, 7], [[1], [2, 3], [4, 5, 6, 7]]), ([1, 2, None, 3, None, 4, None, 5], [[1], [2], [3], [4], [5]]), ([1, None, 2, None, 3], [[1], [2], [3]]), ([1, 2, None, 3, None], [[1], [2], [3]]), ([0], [[0]]), ([-1, -2, -3], [[-1], [-2, -3]]), ([1, 2, 3, None, None, None, 4], [[1], [2, 3], [4]]), ([5, 4, 8, 11, None, 13, 4, 7, 2, None, None, None, 1], [[5], [4, 8], [11, 13, 4], [7, 2, 1]]), ([1, 2, 2, 3, 3, 3, 3], [[1], [2, 2], [3, 3, 3, 3]]), ([1, None, None], [[1]])]",
+ "body": " result = run_level_order(Solution, root_list)\n assert_level_order(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_level_order, assert_level_order\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list: list[int | None] = [3, 9, 20, None, None, 15, 7]\nexpected = [[3], [9, 20], [15, 7]]",
+ "playground_run": "result = run_level_order(Solution, root_list)\nresult",
+ "playground_assert": "assert_level_order(result, expected)"
}
diff --git a/.templates/leetcode/json/binary_tree_right_side_view.json b/.templates/leetcode/json/binary_tree_right_side_view.json
index a5165db..56eba70 100644
--- a/.templates/leetcode/json/binary_tree_right_side_view.json
+++ b/.templates/leetcode/json/binary_tree_right_side_view.json
@@ -5,45 +5,62 @@
"problem_title": "Binary Tree Right Side View",
"difficulty": "Medium",
"topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given the `root` of a binary tree, imagine yourself standing on the **right side** of it, return *the values of the nodes you can see ordered from top to bottom*.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: root = [1,2,3,null,5,null,4]\nOutput: [1,3,4]\n```"
- },
- {
- "content": "\n\n```\nInput: root = [1,2,3,4,null,null,null,5]\nOutput: [1,3,4,5]\n```"
- },
- { "content": "```\nInput: root = [1,null,3]\nOutput: [1,3]\n```" },
- { "content": "```\nInput: root = []\nOutput: []\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: root = [1,2,3,null,5,null,4]\nOutput: [1,3,4]\n```"
+ },
+ {
+ "content": "\n\n```\nInput: root = [1,2,3,4,null,null,null,5]\nOutput: [1,3,4,5]\n```"
+ },
+ { "content": "```\nInput: root = [1,null,3]\nOutput: [1,3]\n```" },
+ { "content": "```\nInput: root = []\nOutput: []\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range `[0, 100]`.\n- `-100 <= Node.val <= 100`",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "right_side_view",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None])",
+ "helpers_run_body": " implementation = solution_class()\n root = TreeNode.from_list(root_list) if root_list else None\n return implementation.right_side_view(root)",
+ "helpers_assert_name": "right_side_view",
+ "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "right_side_view",
- "parameters": "root: TreeNode[int] | None",
- "return_type": "list[int]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_right_side_view, run_right_side_view\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "BinaryTreeRightSideView",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_right_side_view",
- "parametrize": "root_list, expected",
- "parametrize_typed": "root_list: list[int | None], expected: list[int]",
- "test_cases": "[([1, 2, 3, None, 5, None, 4], [1, 3, 4]), ([1, 2, 3, 4, None, None, None, 5], [1, 3, 4, 5]), ([1, None, 3], [1, 3]), ([], [])]",
- "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.right_side_view(root)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list: list[int | None] = [1, 2, 3, None, 5, None, 4]\nexpected = [1, 3, 4]",
- "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().right_side_view(root)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "right_side_view",
+ "signature": "(self, root: TreeNode[int] | None) -> list[int]",
+ "body": " # TODO: Implement right_side_view\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_right_side_view",
+ "signature": "(self, root_list: list[int | None], expected: list[int])",
+ "parametrize": "root_list, expected",
+ "test_cases": "[([1, 2, 3, None, 5, None, 4], [1, 3, 4]), ([1, 2, 3, 4, None, None, None, 5], [1, 3, 4, 5]), ([1, None, 3], [1, 3]), ([], []), ([1], [1]), ([1, 2], [1, 2]), ([1, None, 2], [1, 2])]",
+ "body": " result = run_right_side_view(Solution, root_list)\n assert_right_side_view(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_right_side_view, assert_right_side_view\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list: list[int | None] = [1, 2, 3, None, 5, None, 4]\nexpected = [1, 3, 4]",
+ "playground_run": "result = run_right_side_view(Solution, root_list)\nresult",
+ "playground_assert": "assert_right_side_view(result, expected)"
}
diff --git a/.templates/leetcode/json/climbing_stairs.json b/.templates/leetcode/json/climbing_stairs.json
index 37a0d7f..8dcb90b 100644
--- a/.templates/leetcode/json/climbing_stairs.json
+++ b/.templates/leetcode/json/climbing_stairs.json
@@ -5,38 +5,60 @@
"problem_title": "Climbing Stairs",
"difficulty": "Easy",
"topics": "Math, Dynamic Programming, Memoization",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are climbing a staircase. It takes `n` steps to reach the top.\n\nEach time you can either climb `1` or `2` steps. In how many distinct ways can you climb to the top?",
- "readme_examples": [
- {
- "content": "```\nInput: n = 2\nOutput: 2\n```\n**Explanation:** There are two ways to climb to the top.\n1. 1 step + 1 step\n2. 2 steps"
- },
- {
- "content": "```\nInput: n = 3\nOutput: 3\n```\n**Explanation:** There are three ways to climb to the top.\n1. 1 step + 1 step + 1 step\n2. 1 step + 2 steps\n3. 2 steps + 1 step"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: n = 2\nOutput: 2\n```\n**Explanation:** There are two ways to climb to the top.\n1. 1 step + 1 step\n2. 2 steps"
+ },
+ {
+ "content": "```\nInput: n = 3\nOutput: 3\n```\n**Explanation:** There are three ways to climb to the top.\n1. 1 step + 1 step + 1 step\n2. 1 step + 2 steps\n3. 2 steps + 1 step"
+ }
+ ]
+ },
"readme_constraints": "- 1 <= n <= 45",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "climb_stairs",
+ "helpers_run_signature": "(solution_class: type, n: int)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.climb_stairs(n)",
+ "helpers_assert_name": "climb_stairs",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "climb_stairs", "parameters": "n: int", "return_type": "int", "dummy_return": "1" }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_climb_stairs, run_climb_stairs\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ClimbingStairs",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_climb_stairs",
- "parametrize": "n, expected",
- "parametrize_typed": "n: int, expected: int",
- "test_cases": "[(1, 1), (2, 2), (3, 3), (4, 5), (5, 8), (6, 13), (10, 89), (20, 10946), (45, 1836311903)]",
- "body": "result = self.solution.climb_stairs(n)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nn = 3\nexpected = 3",
- "playground_execution": "result = Solution().climb_stairs(n)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "climb_stairs",
+ "signature": "(self, n: int) -> int",
+ "body": " # TODO: Implement climb_stairs\n return 1"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_climb_stairs",
+ "signature": "(self, n: int, expected: int)",
+ "parametrize": "n, expected",
+ "test_cases": "[(1, 1), (2, 2), (3, 3), (4, 5), (5, 8), (6, 13), (10, 89), (20, 10946), (45, 1836311903)]",
+ "body": " result = run_climb_stairs(Solution, n)\n assert_climb_stairs(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_climb_stairs, assert_climb_stairs\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nn = 3\nexpected = 3",
+ "playground_run": "result = run_climb_stairs(Solution, n)\nresult",
+ "playground_assert": "assert_climb_stairs(result, expected)"
}
diff --git a/.templates/leetcode/json/clone_graph.json b/.templates/leetcode/json/clone_graph.json
index 2e338d6..7165af5 100644
--- a/.templates/leetcode/json/clone_graph.json
+++ b/.templates/leetcode/json/clone_graph.json
@@ -5,46 +5,63 @@
"problem_title": "Clone Graph",
"difficulty": "Medium",
"topics": "Hash Table, Depth-First Search, Breadth-First Search, Graph",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a reference of a node in a **connected** undirected graph.\n\nReturn a **deep copy** (clone) of the graph.\n\nEach node in the graph contains a value (`int`) and a list (`List[Node]`) of its neighbors.\n\n```\nclass Node {\n public int val;\n public List neighbors;\n}\n```\n\n**Test case format:**\n\nFor simplicity, each node's value is the same as the node's index (1-indexed). For example, the first node with `val == 1`, the second node with `val == 2`, and so on. The graph is represented in the test case using an adjacency list.\n\n**An adjacency list** is a collection of unordered **lists** used to represent a finite graph. Each list describes the set of neighbors of a node in the graph.\n\nThe given node will always be the first node with `val = 1`. You must return the **copy of the given node** as a reference to the cloned graph.",
- "readme_examples": [
- {
- "content": "
\n\n```\nInput: adjList = [[2,4],[1,3],[2,4],[1,3]]\nOutput: [[2,4],[1,3],[2,4],[1,3]]\nExplanation: There are 4 nodes in the graph.\n1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).\n2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).\n3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).\n4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).\n```"
- },
- {
- "content": "
\n\n```\nInput: adjList = [[]]\nOutput: [[]]\nExplanation: Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors.\n```"
- },
- {
- "content": "```\nInput: adjList = []\nOutput: []\nExplanation: This an empty graph, it does not have any nodes.\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: adjList = [[2,4],[1,3],[2,4],[1,3]]\nOutput: [[2,4],[1,3],[2,4],[1,3]]\n```\n**Explanation:** There are 4 nodes in the graph.\n1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).\n2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).\n3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).\n4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3)."
+ },
+ {
+ "content": "\n\n```\nInput: adjList = [[]]\nOutput: [[]]\n```\n**Explanation:** Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors."
+ },
+ {
+ "content": "```\nInput: adjList = []\nOutput: []\n```\n**Explanation:** This an empty graph, it does not have any nodes."
+ }
+ ]
+ },
"readme_constraints": "- The number of nodes in the graph is in the range `[0, 100]`.\n- `1 <= Node.val <= 100`\n- `Node.val` is unique for each node.\n- There are no repeated edges and no self-loops in the graph.\n- The Graph is connected and all nodes can be visited starting from the given node.",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import GraphNode",
+ "helpers_content": "",
+ "helpers_run_name": "clone_graph",
+ "helpers_run_signature": "(solution_class: type, adj_list: list[list[int]])",
+ "helpers_run_body": " node = GraphNode.from_adjacency_list(adj_list)\n implementation = solution_class()\n return implementation.clone_graph(node)",
+ "helpers_assert_name": "clone_graph",
+ "helpers_assert_signature": "(result: GraphNode | None, adj_list: list[list[int]]) -> bool",
+ "helpers_assert_body": " original = GraphNode.from_adjacency_list(adj_list)\n if result is None:\n assert original is None\n else:\n assert result.is_clone(original)\n return True",
"solution_imports": "from leetcode_py import GraphNode",
- "solution_methods": [
- {
- "name": "clone_graph",
- "parameters": "node: GraphNode | None",
- "return_type": "GraphNode | None",
- "dummy_return": "None"
- }
- ],
- "test_imports": "import pytest\n\nfrom leetcode_py import GraphNode\nfrom leetcode_py.test_utils import logged_test\n\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_clone_graph, run_clone_graph\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "CloneGraph",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_clone_graph",
- "parametrize": "adj_list",
- "parametrize_typed": "adj_list: list[list[int]]",
- "test_cases": "[[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], []]",
- "body": "node = GraphNode.from_adjacency_list(adj_list)\nresult = self.solution.clone_graph(node)\nassert result.is_clone(node) if result else node is None"
- }
- ],
- "playground_imports": "from solution import Solution\n\nfrom leetcode_py import GraphNode",
- "playground_test_case": "# Example test case\nadj_list = [[2,4],[1,3],[2,4],[1,3]]\nnode = GraphNode.from_adjacency_list(adj_list)",
- "playground_execution": "result = Solution().clone_graph(node)\nresult",
- "playground_assertion": "assert result.is_clone(node) if result else node is None"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "clone_graph",
+ "signature": "(self, node: GraphNode | None) -> GraphNode | None",
+ "body": " # TODO: Implement clone_graph\n return None"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_clone_graph",
+ "signature": "(self, adj_list: list[list[int]])",
+ "parametrize": "adj_list",
+ "test_cases": "[[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], [], [[2], [1]], [[2, 3], [1], [1]], [[2], [3], [4], []], [[2, 3, 4], [1], [1], [1]], [[2, 3], [1, 3], [1, 2]], [[2, 5], [1, 3], [2, 4], [3, 5], [1, 4]], [[2, 3], [1, 4], [1, 4], [2, 3]], [[2, 3, 4, 5], [1], [1], [1], [1]], [[2], [3], [4], [5], []]]",
+ "body": " result = run_clone_graph(Solution, adj_list)\n assert_clone_graph(result, adj_list)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_clone_graph, assert_clone_graph\nfrom solution import Solution\nfrom leetcode_py import GraphNode",
+ "playground_setup": "# Example test case\nadj_list = [[2,4],[1,3],[2,4],[1,3]]",
+ "playground_run": "result = run_clone_graph(Solution, adj_list)\nresult",
+ "playground_assert": "assert_clone_graph(result, adj_list)"
}
diff --git a/.templates/leetcode/json/coin_change.json b/.templates/leetcode/json/coin_change.json
index 61ebbc8..7702878 100644
--- a/.templates/leetcode/json/coin_change.json
+++ b/.templates/leetcode/json/coin_change.json
@@ -5,42 +5,59 @@
"problem_title": "Coin Change",
"difficulty": "Medium",
"topics": "Array, Dynamic Programming, Breadth-First Search",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given an integer array `coins` representing coins of different denominations and an integer `amount` representing a total amount of money.\n\nReturn the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return `-1`.\n\nYou may assume that you have an infinite number of each kind of coin.",
- "readme_examples": [
- {
- "content": "```\nInput: coins = [1,2,5], amount = 11\nOutput: 3\n```\n**Explanation:** 11 = 5 + 5 + 1"
- },
- { "content": "```\nInput: coins = [2], amount = 3\nOutput: -1\n```" },
- { "content": "```\nInput: coins = [1], amount = 0\nOutput: 0\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: coins = [1,2,5], amount = 11\nOutput: 3\n```\n**Explanation:** 11 = 5 + 5 + 1"
+ },
+ { "content": "```\nInput: coins = [2], amount = 3\nOutput: -1\n```" },
+ { "content": "```\nInput: coins = [1], amount = 0\nOutput: 0\n```" }
+ ]
+ },
"readme_constraints": "- `1 <= coins.length <= 12`\n- `1 <= coins[i] <= 2^31 - 1`\n- `0 <= amount <= 10^4`",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "coin_change",
+ "helpers_run_signature": "(solution_class: type, coins: list[int], amount: int)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.coin_change(coins, amount)",
+ "helpers_assert_name": "coin_change",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "coin_change",
- "parameters": "coins: list[int], amount: int",
- "return_type": "int",
- "dummy_return": "-1"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_coin_change, run_coin_change\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "CoinChange",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_coin_change",
- "parametrize": "coins, amount, expected",
- "parametrize_typed": "coins: list[int], amount: int, expected: int",
- "test_cases": "[([1, 2, 5], 11, 3), ([2], 3, -1), ([1], 0, 0), ([1, 3, 4], 6, 2), ([2, 5, 10, 1], 27, 4), ([5], 3, -1), ([1], 1, 1), ([1, 2], 2, 1), ([186, 419, 83, 408], 6249, 20)]",
- "body": "result = self.solution.coin_change(coins, amount)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ncoins = [1, 2, 5]\namount = 11\nexpected = 3",
- "playground_execution": "result = Solution().coin_change(coins, amount)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "coin_change",
+ "signature": "(self, coins: list[int], amount: int) -> int",
+ "body": " # TODO: Implement coin_change\n return -1"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_coin_change",
+ "signature": "(self, coins: list[int], amount: int, expected: int)",
+ "parametrize": "coins, amount, expected",
+ "test_cases": "[([1, 2, 5], 11, 3), ([2], 3, -1), ([1], 0, 0), ([1, 3, 4], 6, 2), ([2, 5, 10, 1], 27, 4), ([5], 3, -1), ([1], 1, 1), ([1, 2], 2, 1), ([186, 419, 83, 408], 6249, 20)]",
+ "body": " result = run_coin_change(Solution, coins, amount)\n assert_coin_change(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_coin_change, assert_coin_change\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ncoins = [1, 2, 5]\namount = 11\nexpected = 3",
+ "playground_run": "result = run_coin_change(Solution, coins, amount)\nresult",
+ "playground_assert": "assert_coin_change(result, expected)"
}
diff --git a/.templates/leetcode/json/combination_sum.json b/.templates/leetcode/json/combination_sum.json
index de4cbfb..d7f7e64 100644
--- a/.templates/leetcode/json/combination_sum.json
+++ b/.templates/leetcode/json/combination_sum.json
@@ -5,44 +5,61 @@
"problem_title": "Combination Sum",
"difficulty": "Medium",
"topics": "Array, Backtracking",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an array of **distinct** integers `candidates` and a target integer `target`, return *a list of all **unique combinations** of* `candidates` *where the chosen numbers sum to* `target`. You may return the combinations in **any order**.\n\nThe **same** number may be chosen from `candidates` an **unlimited number of times**. Two combinations are unique if the frequency of at least one of the chosen numbers is different.\n\nThe test cases are generated such that the number of unique combinations that sum up to `target` is less than `150` combinations for the given input.",
- "readme_examples": [
- {
- "content": "```\nInput: candidates = [2,3,6,7], target = 7\nOutput: [[2,2,3],[7]]\n```\n**Explanation:** 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. 7 is a candidate, and 7 = 7. These are the only two combinations."
- },
- {
- "content": "```\nInput: candidates = [2,3,5], target = 8\nOutput: [[2,2,2,2],[2,3,3],[3,5]]\n```"
- },
- { "content": "```\nInput: candidates = [2], target = 1\nOutput: []\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: candidates = [2,3,6,7], target = 7\nOutput: [[2,2,3],[7]]\n```\n**Explanation:** 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. 7 is a candidate, and 7 = 7. These are the only two combinations."
+ },
+ {
+ "content": "```\nInput: candidates = [2,3,5], target = 8\nOutput: [[2,2,2,2],[2,3,3],[3,5]]\n```"
+ },
+ { "content": "```\nInput: candidates = [2], target = 1\nOutput: []\n```" }
+ ]
+ },
"readme_constraints": "- 1 <= candidates.length <= 30\n- 2 <= candidates[i] <= 40\n- All elements of candidates are distinct.\n- 1 <= target <= 40",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "combination_sum",
+ "helpers_run_signature": "(solution_class: type, candidates: list[int], target: int)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.combination_sum(candidates, target)",
+ "helpers_assert_name": "combination_sum",
+ "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool",
+ "helpers_assert_body": " # Sort both result and expected for comparison\n result_sorted = [sorted(combo) for combo in result]\n expected_sorted = [sorted(combo) for combo in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert result_sorted == expected_sorted\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "combination_sum",
- "parameters": "candidates: list[int], target: int",
- "return_type": "list[list[int]]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_combination_sum, run_combination_sum\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "CombinationSum",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_combination_sum",
- "parametrize": "candidates, target, expected",
- "parametrize_typed": "candidates: list[int], target: int, expected: list[list[int]]",
- "test_cases": "[([2, 3, 6, 7], 7, [[2, 2, 3], [7]]), ([2, 3, 5], 8, [[2, 2, 2, 2], [2, 3, 3], [3, 5]]), ([2], 1, [])]",
- "body": "result = self.solution.combination_sum(candidates, target)\n# Sort both result and expected for comparison\nresult_sorted = [sorted(combo) for combo in result]\nexpected_sorted = [sorted(combo) for combo in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ncandidates = [2, 3, 6, 7]\ntarget = 7\nexpected = [[2, 2, 3], [7]]",
- "playground_execution": "result = Solution().combination_sum(candidates, target)\nresult",
- "playground_assertion": "# Sort for comparison\nresult_sorted = [sorted(combo) for combo in result]\nexpected_sorted = [sorted(combo) for combo in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "combination_sum",
+ "signature": "(self, candidates: list[int], target: int) -> list[list[int]]",
+ "body": " # TODO: Implement combination_sum\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_combination_sum",
+ "signature": "(self, candidates: list[int], target: int, expected: list[list[int]])",
+ "parametrize": "candidates, target, expected",
+ "test_cases": "[([2, 3, 6, 7], 7, [[2, 2, 3], [7]]), ([2, 3, 5], 8, [[2, 2, 2, 2], [2, 3, 3], [3, 5]]), ([2], 1, [])]",
+ "body": " result = run_combination_sum(Solution, candidates, target)\n assert_combination_sum(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_combination_sum, assert_combination_sum\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ncandidates = [2, 3, 6, 7]\ntarget = 7\nexpected = [[2, 2, 3], [7]]",
+ "playground_run": "result = run_combination_sum(Solution, candidates, target)\nresult",
+ "playground_assert": "assert_combination_sum(result, expected)"
}
diff --git a/.templates/leetcode/json/container_with_most_water.json b/.templates/leetcode/json/container_with_most_water.json
index 86f29ac..604d452 100644
--- a/.templates/leetcode/json/container_with_most_water.json
+++ b/.templates/leetcode/json/container_with_most_water.json
@@ -5,41 +5,58 @@
"problem_title": "Container With Most Water",
"difficulty": "Medium",
"topics": "Array, Two Pointers, Greedy",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the `i`th line are `(i, 0)` and `(i, height[i])`.\n\nFind two lines that together with the x-axis form a container, such that the container contains the most water.\n\nReturn the maximum amount of water a container can store.\n\nNotice that you may not slant the container.",
- "readme_examples": [
- {
- "content": "
\n\n```\nInput: height = [1,8,6,2,5,4,8,3,7]\nOutput: 49\nExplanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.\n```"
- },
- { "content": "```\nInput: height = [1,1]\nOutput: 1\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: height = [1,8,6,2,5,4,8,3,7]\nOutput: 49\n```\n**Explanation:** The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49."
+ },
+ { "content": "```\nInput: height = [1,1]\nOutput: 1\n```" }
+ ]
+ },
"readme_constraints": "- n == height.length\n- 2 <= n <= 10^5\n- 0 <= height[i] <= 10^4",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "max_area",
+ "helpers_run_signature": "(solution_class: type, height: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.max_area(height)",
+ "helpers_assert_name": "max_area",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "max_area",
- "parameters": "height: list[int]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_max_area, run_max_area\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ContainerWithMostWater",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_max_area",
- "parametrize": "height, expected",
- "parametrize_typed": "height: list[int], expected: int",
- "test_cases": "[([1,8,6,2,5,4,8,3,7], 49), ([1,1], 1), ([1,2,1], 2)]",
- "body": "result = self.solution.max_area(height)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nheight = [1,8,6,2,5,4,8,3,7]\nexpected = 49",
- "playground_execution": "result = Solution().max_area(height)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "max_area",
+ "signature": "(self, height: list[int]) -> int",
+ "body": " # TODO: Implement max_area\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_max_area",
+ "signature": "(self, height: list[int], expected: int)",
+ "parametrize": "height, expected",
+ "test_cases": "[([1,8,6,2,5,4,8,3,7], 49), ([1,1], 1), ([1,2,1], 2)]",
+ "body": " result = run_max_area(Solution, height)\n assert_max_area(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_max_area, assert_max_area\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nheight = [1,8,6,2,5,4,8,3,7]\nexpected = 49",
+ "playground_run": "result = run_max_area(Solution, height)\nresult",
+ "playground_assert": "assert_max_area(result, expected)"
}
diff --git a/.templates/leetcode/json/contains_duplicate.json b/.templates/leetcode/json/contains_duplicate.json
index 23ce24b..8b540d0 100644
--- a/.templates/leetcode/json/contains_duplicate.json
+++ b/.templates/leetcode/json/contains_duplicate.json
@@ -5,44 +5,61 @@
"problem_title": "Contains Duplicate",
"difficulty": "Easy",
"topics": "Array, Hash Table, Sorting",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an integer array `nums`, return `true` if any value appears **at least twice** in the array, and return `false` if every element is distinct.",
- "readme_examples": [
- {
- "content": "```\nInput: nums = [1,2,3,1]\nOutput: true\n```\n**Explanation:** The element 1 occurs at the indices 0 and 3."
- },
- {
- "content": "```\nInput: nums = [1,2,3,4]\nOutput: false\n```\n**Explanation:** All elements are distinct."
- },
- { "content": "```\nInput: nums = [1,1,1,3,3,4,3,2,4,2]\nOutput: true\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: nums = [1,2,3,1]\nOutput: true\n```\n**Explanation:** The element 1 occurs at the indices 0 and 3."
+ },
+ {
+ "content": "```\nInput: nums = [1,2,3,4]\nOutput: false\n```\n**Explanation:** All elements are distinct."
+ },
+ { "content": "```\nInput: nums = [1,1,1,3,3,4,3,2,4,2]\nOutput: true\n```" }
+ ]
+ },
"readme_constraints": "- 1 <= nums.length <= 10^5\n- -10^9 <= nums[i] <= 10^9",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "contains_duplicate",
+ "helpers_run_signature": "(solution_class: type, nums: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.contains_duplicate(nums)",
+ "helpers_assert_name": "contains_duplicate",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "contains_duplicate",
- "parameters": "nums: list[int]",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_contains_duplicate, run_contains_duplicate\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ContainsDuplicate",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_contains_duplicate",
- "parametrize": "nums, expected",
- "parametrize_typed": "nums: list[int], expected: bool",
- "test_cases": "[([1, 2, 3, 1], True), ([1, 2, 3, 4], False), ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True)]",
- "body": "result = self.solution.contains_duplicate(nums)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [1, 2, 3, 1]\nexpected = True",
- "playground_execution": "result = Solution().contains_duplicate(nums)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "contains_duplicate",
+ "signature": "(self, nums: list[int]) -> bool",
+ "body": " # TODO: Implement contains_duplicate\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_contains_duplicate",
+ "signature": "(self, nums: list[int], expected: bool)",
+ "parametrize": "nums, expected",
+ "test_cases": "[([1, 2, 3, 1], True), ([1, 2, 3, 4], False), ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True)]",
+ "body": " result = run_contains_duplicate(Solution, nums)\n assert_contains_duplicate(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_contains_duplicate, assert_contains_duplicate\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [1, 2, 3, 1]\nexpected = True",
+ "playground_run": "result = run_contains_duplicate(Solution, nums)\nresult",
+ "playground_assert": "assert_contains_duplicate(result, expected)"
}
diff --git a/.templates/leetcode/json/course_schedule.json b/.templates/leetcode/json/course_schedule.json
index 9ac990d..f04589d 100644
--- a/.templates/leetcode/json/course_schedule.json
+++ b/.templates/leetcode/json/course_schedule.json
@@ -5,43 +5,60 @@
"problem_title": "Course Schedule",
"difficulty": "Medium",
"topics": "Depth-First Search, Breadth-First Search, Graph, Topological Sort",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "There are a total of `numCourses` courses you have to take, labeled from `0` to `numCourses - 1`. You are given an array `prerequisites` where `prerequisites[i] = [ai, bi]` indicates that you **must** take course `bi` first if you want to take course `ai`.\n\n- For example, the pair `[0, 1]`, indicates that to take course `0` you have to first take course `1`.\n\nReturn `true` if you can finish all courses. Otherwise, return `false`.",
- "readme_examples": [
- {
- "content": "```\nInput: numCourses = 2, prerequisites = [[1,0]]\nOutput: true\n```\n**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible."
- },
- {
- "content": "```\nInput: numCourses = 2, prerequisites = [[1,0],[0,1]]\nOutput: false\n```\n**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: numCourses = 2, prerequisites = [[1,0]]\nOutput: true\n```\n**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible."
+ },
+ {
+ "content": "```\nInput: numCourses = 2, prerequisites = [[1,0],[0,1]]\nOutput: false\n```\n**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible."
+ }
+ ]
+ },
"readme_constraints": "- `1 <= numCourses <= 2000`\n- `0 <= prerequisites.length <= 5000`\n- `prerequisites[i].length == 2`\n- `0 <= ai, bi < numCourses`\n- All the pairs prerequisites[i] are **unique**.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "can_finish",
+ "helpers_run_signature": "(solution_class: type, num_courses: int, prerequisites: list[list[int]])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.can_finish(num_courses, prerequisites)",
+ "helpers_assert_name": "can_finish",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "can_finish",
- "parameters": "num_courses: int, prerequisites: list[list[int]]",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_can_finish, run_can_finish\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "CourseSchedule",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_can_finish",
- "parametrize": "num_courses, prerequisites, expected",
- "parametrize_typed": "num_courses: int, prerequisites: list[list[int]], expected: bool",
- "test_cases": "[(2, [[1, 0]], True), (2, [[1, 0], [0, 1]], False), (1, [], True), (3, [[1, 0], [2, 1]], True), (4, [[1, 0], [2, 1], [3, 2], [1, 3]], False)]",
- "body": "result = self.solution.can_finish(num_courses, prerequisites)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnum_courses = 2\nprerequisites = [[1, 0]]\nexpected = True",
- "playground_execution": "result = Solution().can_finish(num_courses, prerequisites)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "can_finish",
+ "signature": "(self, num_courses: int, prerequisites: list[list[int]]) -> bool",
+ "body": " # TODO: Implement can_finish\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_can_finish",
+ "signature": "(self, num_courses: int, prerequisites: list[list[int]], expected: bool)",
+ "parametrize": "num_courses, prerequisites, expected",
+ "test_cases": "[(2, [[1, 0]], True), (2, [[1, 0], [0, 1]], False), (1, [], True), (3, [[1, 0], [2, 1]], True), (4, [[1, 0], [2, 1], [3, 2], [1, 3]], False)]",
+ "body": " result = run_can_finish(Solution, num_courses, prerequisites)\n assert_can_finish(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_can_finish, assert_can_finish\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnum_courses = 2\nprerequisites = [[1, 0]]\nexpected = True",
+ "playground_run": "result = run_can_finish(Solution, num_courses, prerequisites)\nresult",
+ "playground_assert": "assert_can_finish(result, expected)"
}
diff --git a/.templates/leetcode/json/diameter_of_binary_tree.json b/.templates/leetcode/json/diameter_of_binary_tree.json
index bf3d6bb..37ca4c0 100644
--- a/.templates/leetcode/json/diameter_of_binary_tree.json
+++ b/.templates/leetcode/json/diameter_of_binary_tree.json
@@ -5,41 +5,58 @@
"problem_title": "Diameter of Binary Tree",
"difficulty": "Easy",
"topics": "Tree, Depth-First Search, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given the `root` of a binary tree, return the length of the **diameter** of the tree.\n\nThe **diameter** of a binary tree is the **length** of the longest path between any two nodes in a tree. This path may or may not pass through the `root`.\n\nThe **length** of a path between two nodes is represented by the number of edges between them.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: root = [1,2,3,4,5]\nOutput: 3\n```\n**Explanation:** 3 is the length of the path [4,2,1,3] or [5,2,1,3]."
- },
- { "content": "```\nInput: root = [1,2]\nOutput: 1\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: root = [1,2,3,4,5]\nOutput: 3\n```\n**Explanation:** 3 is the length of the path [4,2,1,3] or [5,2,1,3]."
+ },
+ { "content": "```\nInput: root = [1,2]\nOutput: 1\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range [1, 10^4].\n- -100 <= Node.val <= 100",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "diameter_of_binary_tree",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None])",
+ "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.diameter_of_binary_tree(root)",
+ "helpers_assert_name": "diameter_of_binary_tree",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "diameter_of_binary_tree",
- "parameters": "root: TreeNode | None",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_diameter_of_binary_tree, run_diameter_of_binary_tree\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "DiameterOfBinaryTree",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_diameter_of_binary_tree",
- "parametrize": "root_list, expected",
- "parametrize_typed": "root_list: list[int | None], expected: int",
- "test_cases": "[([1, 2, 3, 4, 5], 3), ([1, 2], 1)]",
- "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.diameter_of_binary_tree(root)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list: list[int | None] = [1, 2, 3, 4, 5]\nexpected = 3",
- "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().diameter_of_binary_tree(root)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "diameter_of_binary_tree",
+ "signature": "(self, root: TreeNode[int] | None) -> int",
+ "body": " # TODO: Implement diameter_of_binary_tree\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_diameter_of_binary_tree",
+ "signature": "(self, root_list: list[int | None], expected: int)",
+ "parametrize": "root_list, expected",
+ "test_cases": "[([1, 2, 3, 4, 5], 3), ([1, 2], 1), ([], 0), ([1], 0), ([1, 2, 3], 2), ([1, None, 2], 1)]",
+ "body": " result = run_diameter_of_binary_tree(Solution, root_list)\n assert_diameter_of_binary_tree(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_diameter_of_binary_tree, assert_diameter_of_binary_tree\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list: list[int | None] = [1, 2, 3, 4, 5]\nexpected = 3",
+ "playground_run": "result = run_diameter_of_binary_tree(Solution, root_list)\nresult",
+ "playground_assert": "assert_diameter_of_binary_tree(result, expected)"
}
diff --git a/.templates/leetcode/json/evaluate_reverse_polish_notation.json b/.templates/leetcode/json/evaluate_reverse_polish_notation.json
index 08e7061..781c301 100644
--- a/.templates/leetcode/json/evaluate_reverse_polish_notation.json
+++ b/.templates/leetcode/json/evaluate_reverse_polish_notation.json
@@ -5,46 +5,63 @@
"problem_title": "Evaluate Reverse Polish Notation",
"difficulty": "Medium",
"topics": "Array, Math, Stack",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given an array of strings `tokens` that represents an arithmetic expression in a **Reverse Polish Notation**.\n\nEvaluate the expression. Return *an integer that represents the value of the expression*.\n\n**Note that:**\n\n- The valid operators are `'+'`, `'-'`, `'*'`, and `'/'`.\n- Each operand may be an integer or another expression.\n- The division between two integers always **truncates toward zero**.\n- There will not be any division by zero.\n- The input represents a valid arithmetic expression in a reverse polish notation.\n- The answer and all the intermediate calculations can be represented in a **32-bit** integer.",
- "readme_examples": [
- {
- "content": "```\nInput: tokens = [\"2\",\"1\",\"+\",\"3\",\"*\"]\nOutput: 9\n```\n**Explanation:** ((2 + 1) * 3) = 9"
- },
- {
- "content": "```\nInput: tokens = [\"4\",\"13\",\"5\",\"/\",\"+\"]\nOutput: 6\n```\n**Explanation:** (4 + (13 / 5)) = 6"
- },
- {
- "content": "```\nInput: tokens = [\"10\",\"6\",\"9\",\"3\",\"+\",\"-11\",\"*\",\"/\",\"*\",\"17\",\"+\",\"5\",\"+\"]\nOutput: 22\n```\n**Explanation:** ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = 22"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: tokens = [\"2\",\"1\",\"+\",\"3\",\"*\"]\nOutput: 9\n```\n**Explanation:** ((2 + 1) * 3) = 9"
+ },
+ {
+ "content": "```\nInput: tokens = [\"4\",\"13\",\"5\",\"/\",\"+\"]\nOutput: 6\n```\n**Explanation:** (4 + (13 / 5)) = 6"
+ },
+ {
+ "content": "```\nInput: tokens = [\"10\",\"6\",\"9\",\"3\",\"+\",\"-11\",\"*\",\"/\",\"*\",\"17\",\"+\",\"5\",\"+\"]\nOutput: 22\n```\n**Explanation:** ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = 22"
+ }
+ ]
+ },
"readme_constraints": "- `1 <= tokens.length <= 10^4`\n- `tokens[i]` is either an operator: `\"+\"`, `\"-\"`, `\"*\"`, or `\"/\"`, or an integer in the range `[-200, 200]`.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "eval_rpn",
+ "helpers_run_signature": "(solution_class: type, tokens: list[str])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.eval_rpn(tokens)",
+ "helpers_assert_name": "eval_rpn",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "eval_rpn",
- "parameters": "tokens: list[str]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_eval_rpn, run_eval_rpn\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "EvaluateReversePolishNotation",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_eval_rpn",
- "parametrize": "tokens, expected",
- "parametrize_typed": "tokens: list[str], expected: int",
- "test_cases": "[([\"2\", \"1\", \"+\", \"3\", \"*\"], 9), ([\"4\", \"13\", \"5\", \"/\", \"+\"], 6), ([\"10\", \"6\", \"9\", \"3\", \"+\", \"-11\", \"*\", \"/\", \"*\", \"17\", \"+\", \"5\", \"+\"], 22)]",
- "body": "result = self.solution.eval_rpn(tokens)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ntokens = [\\\"2\\\", \\\"1\\\", \\\"+\\\", \\\"3\\\", \\\"*\\\"]\nexpected = 9",
- "playground_execution": "result = Solution().eval_rpn(tokens)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "eval_rpn",
+ "signature": "(self, tokens: list[str]) -> int",
+ "body": " # TODO: Implement eval_rpn\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_eval_rpn",
+ "signature": "(self, tokens: list[str], expected: int)",
+ "parametrize": "tokens, expected",
+ "test_cases": "[(['2', '1', '+', '3', '*'], 9), (['4', '13', '5', '/', '+'], 6), (['10', '6', '9', '3', '+', '-11', '*', '/', '*', '17', '+', '5', '+'], 22)]",
+ "body": " result = run_eval_rpn(Solution, tokens)\n assert_eval_rpn(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_eval_rpn, assert_eval_rpn\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ntokens = ['2', '1', '+', '3', '*']\nexpected = 9",
+ "playground_run": "result = run_eval_rpn(Solution, tokens)\nresult",
+ "playground_assert": "assert_eval_rpn(result, expected)"
}
diff --git a/.templates/leetcode/json/find_median_from_data_stream.json b/.templates/leetcode/json/find_median_from_data_stream.json
index 7cee350..3a85fa4 100644
--- a/.templates/leetcode/json/find_median_from_data_stream.json
+++ b/.templates/leetcode/json/find_median_from_data_stream.json
@@ -5,35 +5,65 @@
"problem_title": "Find Median from Data Stream",
"difficulty": "Hard",
"topics": "Two Pointers, Design, Sorting, Heap (Priority Queue), Data Stream",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "The **median** is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values.\n\n- For example, for `arr = [2,3,4]`, the median is `3`.\n- For example, for `arr = [2,3]`, the median is `(2 + 3) / 2 = 2.5`.\n\nImplement the MedianFinder class:\n\n- `MedianFinder()` initializes the `MedianFinder` object.\n- `void addNum(int num)` adds the integer `num` from the data stream to the data structure.\n- `double findMedian()` returns the median of all elements so far. Answers within `10^-5` of the actual answer will be accepted.",
- "readme_examples": [
- {
- "content": "```\nInput\n[\"MedianFinder\", \"addNum\", \"addNum\", \"findMedian\", \"addNum\", \"findMedian\"]\n[[], [1], [2], [], [3], []]\nOutput\n[null, null, null, 1.5, null, 2.0]\n```\n\n**Explanation:**\n```\nMedianFinder medianFinder = new MedianFinder();\nmedianFinder.addNum(1); // arr = [1]\nmedianFinder.addNum(2); // arr = [1, 2]\nmedianFinder.findMedian(); // return 1.5 (i.e., (1 + 2) / 2)\nmedianFinder.addNum(3); // arr = [1, 2, 3]\nmedianFinder.findMedian(); // return 2.0\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput\n[\"MedianFinder\", \"addNum\", \"addNum\", \"findMedian\", \"addNum\", \"findMedian\"]\n[[], [1], [2], [], [3], []]\nOutput\n[null, null, null, 1.5, null, 2.0]\n```\n\n**Explanation:**\n```\nMedianFinder medianFinder = new MedianFinder();\nmedianFinder.addNum(1); // arr = [1]\nmedianFinder.addNum(2); // arr = [1, 2]\nmedianFinder.findMedian(); // return 1.5 (i.e., (1 + 2) / 2)\nmedianFinder.addNum(3); // arr = [1, 2, 3]\nmedianFinder.findMedian(); // return 2.0\n```"
+ }
+ ]
+ },
"readme_constraints": "- `-10^5 <= num <= 10^5`\n- There will be at least one element in the data structure before calling `findMedian`.\n- At most `5 * 10^4` calls will be made to `addNum` and `findMedian`.",
"readme_additional": "**Follow up:**\n\n- If all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution?\n- If `99%` of all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution?",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "median_finder",
+ "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[int]])",
+ "helpers_run_body": " mf = None\n results: list[float | None] = []\n for i, op in enumerate(operations):\n if op == 'MedianFinder':\n mf = solution_class()\n results.append(None)\n elif op == 'addNum' and mf is not None:\n mf.add_num(inputs[i][0])\n results.append(None)\n elif op == 'findMedian' and mf is not None:\n results.append(mf.find_median())\n return results, mf",
+ "helpers_assert_name": "median_finder",
+ "helpers_assert_signature": "(result: list[float | None], expected: list[float | None]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "__init__", "parameters": "", "return_type": "None", "dummy_return": "" },
- { "name": "add_num", "parameters": "num: int", "return_type": "None", "dummy_return": "" },
- { "name": "find_median", "parameters": "", "return_type": "float", "dummy_return": "0.0" }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import MedianFinder",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_median_finder, run_median_finder\nfrom .solution import MedianFinder",
+ "test_content": "",
"test_class_name": "FindMedianFromDataStream",
- "test_helper_methods": [],
- "test_methods": [
- {
- "name": "test_median_finder",
- "parametrize": "operations, inputs, expected",
- "parametrize_typed": "operations: list[str], inputs: list[list[int]], expected: list[float | None]",
- "test_cases": "[([\"MedianFinder\", \"addNum\", \"addNum\", \"findMedian\", \"addNum\", \"findMedian\"], [[], [1], [2], [], [3], []], [None, None, None, 1.5, None, 2.0])]",
- "body": "mf: MedianFinder | None = None\nresults: list[float | None] = []\nfor i, op in enumerate(operations):\n if op == \"MedianFinder\":\n mf = MedianFinder()\n results.append(None)\n elif op == \"addNum\" and mf is not None:\n mf.add_num(inputs[i][0])\n results.append(None)\n elif op == \"findMedian\" and mf is not None:\n results.append(mf.find_median())\nassert results == expected"
- }
- ],
- "playground_imports": "from solution import MedianFinder",
- "playground_test_case": "# Example test case\noperations = ['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'findMedian']\ninputs = [[], [1], [2], [], [3], []]\nexpected = [None, None, None, 1.5, None, 2.0]",
- "playground_execution": "mf = None\nresults: list[float | None] = []\nfor i, op in enumerate(operations):\n if op == 'MedianFinder':\n mf = MedianFinder()\n results.append(None)\n elif op == 'addNum' and mf is not None:\n mf.add_num(inputs[i][0])\n results.append(None)\n elif op == 'findMedian' and mf is not None:\n results.append(mf.find_median())\nresults",
- "playground_assertion": "assert results == expected"
+ "test_class_content": "",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "__init__",
+ "signature": "(self) -> None",
+ "body": " # TODO: Initialize\n pass"
+ },
+ {
+ "name": "add_num",
+ "signature": "(self, num: int) -> None",
+ "body": " # TODO: Implement add_num\n pass"
+ },
+ {
+ "name": "find_median",
+ "signature": "(self) -> float",
+ "body": " # TODO: Implement find_median\n return 0.0"
+ }
+ ]
+ },
+ "_test_helper_methods": { "list": [] },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_median_finder",
+ "signature": "(self, operations: list[str], inputs: list[list[int]], expected: list[float | None])",
+ "parametrize": "operations, inputs, expected",
+ "test_cases": "[(['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'findMedian'], [[], [1], [2], [], [3], []], [None, None, None, 1.5, None, 2.0]), (['MedianFinder', 'addNum', 'findMedian'], [[], [1], []], [None, None, 1.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [1], [1], [1], []], [None, None, None, None, 1.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [1], [2], [3], [4], []], [None, None, None, None, None, 2.5]), (['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'addNum', 'findMedian'], [[], [-1], [0], [], [1], [2], []], [None, None, None, -0.5, None, None, 0.5]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [5], [1], [3], [2], [4], []], [None, None, None, None, None, None, 3.0]), (['MedianFinder', 'addNum', 'findMedian', 'addNum', 'findMedian', 'addNum', 'findMedian'], [[], [100000], [], [-100000], [], [0], []], [None, None, 100000.0, None, 0.0, None, 0.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [10], [5], [15], [3], [7], [12], [18], []], [None, None, None, None, None, None, None, None, 10.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [6], [10], [2], [6], [5], [0], []], [None, None, None, None, None, None, None, 5.5]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [1], [2], [3], [4], [5], [6], [7], [8], []], [None, None, None, None, None, None, None, None, None, 4.5]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [9], [8], [7], [6], [5], [4], [3], [2], [1], []], [None, None, None, None, None, None, None, None, None, None, 5.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [0], [0], [0], [0], [0], []], [None, None, None, None, None, None, 0.0])]",
+ "body": " result, _ = run_median_finder(MedianFinder, operations, inputs)\n assert_median_finder(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_median_finder, assert_median_finder\nfrom solution import MedianFinder",
+ "playground_setup": "# Example test case\noperations = ['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'findMedian']\ninputs = [[], [1], [2], [], [3], []]\nexpected = [None, None, None, 1.5, None, 2.0]",
+ "playground_run": "result, mf = run_median_finder(MedianFinder, operations, inputs)\nprint(result)\nmf",
+ "playground_assert": "assert_median_finder(result, expected)"
}
diff --git a/.templates/leetcode/json/first_bad_version.json b/.templates/leetcode/json/first_bad_version.json
index cdaf3bf..20b50af 100644
--- a/.templates/leetcode/json/first_bad_version.json
+++ b/.templates/leetcode/json/first_bad_version.json
@@ -5,52 +5,63 @@
"problem_title": "First Bad Version",
"difficulty": "Easy",
"topics": "Binary Search, Interactive",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad.\n\nSuppose you have `n` versions `[1, 2, ..., n]` and you want to find out the first bad one, which causes all the following ones to be bad.\n\nYou are given an API `bool isBadVersion(version)` which returns whether `version` is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API.",
- "readme_examples": [
- {
- "content": "```\nInput: n = 5, bad = 4\nOutput: 4\n```\n**Explanation:**\n```\ncall isBadVersion(3) -> false\ncall isBadVersion(5) -> true\ncall isBadVersion(4) -> true\n```\nThen 4 is the first bad version."
- },
- { "content": "```\nInput: n = 1, bad = 1\nOutput: 1\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: n = 5, bad = 4\nOutput: 4\n```\n**Explanation:**\n```\ncall isBadVersion(3) -> false\ncall isBadVersion(5) -> true\ncall isBadVersion(4) -> true\n```\nThen 4 is the first bad version."
+ },
+ { "content": "```\nInput: n = 1, bad = 1\nOutput: 1\n```" }
+ ]
+ },
"readme_constraints": "- 1 <= bad <= n <= 2^31 - 1",
"readme_additional": "**Note:** The `isBadVersion` API is already defined for you.",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "first_bad_version",
+ "helpers_run_signature": "(solution_class: type, n: int, bad: int)",
+ "helpers_run_body": " solution = solution_class(bad)\n return solution.first_bad_version(n)",
+ "helpers_assert_name": "first_bad_version",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "is_bad_version",
- "parameters": "version: int",
- "return_type": "bool",
- "dummy_return": "False"
- },
- {
- "name": "first_bad_version",
- "parameters": "n: int",
- "return_type": "int",
- "dummy_return": "1"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_first_bad_version, run_first_bad_version\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "FirstBadVersion",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" },
- {
- "name": "is_bad_version",
- "parameters": "version: int, bad: int",
- "body": "return version >= bad"
- }
- ],
- "test_methods": [
- {
- "name": "test_first_bad_version",
- "parametrize": "n, bad, expected",
- "parametrize_typed": "n: int, bad: int, expected: int",
- "test_cases": "[(5, 4, 4), (1, 1, 1), (3, 1, 1), (10, 7, 7), (5, -1, 1)]",
- "body": "solution = Solution()\n# Mock is_bad_version for this test\nsolution.is_bad_version = lambda version: self.is_bad_version(version, bad)\nresult = solution.first_bad_version(n)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nn = 5\nbad = 4\nexpected = 4",
- "playground_execution": "solution = Solution()\nsolution.is_bad_version = lambda version: version >= bad\nresult = solution.first_bad_version(n)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "__init__",
+ "signature": "(self, first_bad: int = 1) -> None",
+ "body": " self.is_bad_version = lambda version: version >= first_bad"
+ },
+ {
+ "name": "first_bad_version",
+ "signature": "(self, n: int) -> int",
+ "body": " # TODO: Implement first_bad_version\n return 1"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_first_bad_version",
+ "signature": "(self, n: int, bad: int, expected: int)",
+ "parametrize": "n, bad, expected",
+ "test_cases": "[(5, 4, 4), (1, 1, 1), (3, 1, 1), (10, 7, 7), (100, 50, 50), (2, 1, 1), (2, 2, 2), (1000, 1, 1), (1000, 999, 999), (1000, 500, 500)]",
+ "body": " result = run_first_bad_version(Solution, n, bad)\n assert_first_bad_version(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_first_bad_version, assert_first_bad_version\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nn = 5\nbad = 4\nexpected = 4",
+ "playground_run": "result = run_first_bad_version(Solution, n, bad)\nresult",
+ "playground_assert": "assert_first_bad_version(result, expected)"
}
diff --git a/.templates/leetcode/json/flood_fill.json b/.templates/leetcode/json/flood_fill.json
index 5cab119..4c660d1 100644
--- a/.templates/leetcode/json/flood_fill.json
+++ b/.templates/leetcode/json/flood_fill.json
@@ -5,43 +5,60 @@
"problem_title": "Flood Fill",
"difficulty": "Easy",
"topics": "Array, Depth-First Search, Breadth-First Search, Matrix",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given an image represented by an `m x n` grid of integers `image`, where `image[i][j]` represents the pixel value of the image. You are also given three integers `sr`, `sc`, and `color`. Your task is to perform a **flood fill** on the image starting from the pixel `image[sr][sc]`.\n\nTo perform a **flood fill**:\n\n1. Begin with the starting pixel and change its color to `color`.\n2. Perform the same process for each pixel that is **directly adjacent** (pixels that share a side with the original pixel, either horizontally or vertically) and shares the **same color** as the starting pixel.\n3. Keep **repeating** this process by checking neighboring pixels of the *updated* pixels and modifying their color if it matches the original color of the starting pixel.\n4. The process **stops** when there are **no more** adjacent pixels of the original color to update.\n\nReturn the **modified** image after performing the flood fill.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, color = 2\nOutput: [[2,2,2],[2,2,0],[2,0,1]]\n```\n**Explanation:** From the center of the image with position `(sr, sc) = (1, 1)` (i.e., the red pixel), all pixels connected by a path of the same color as the starting pixel (i.e., the blue pixels) are colored with the new color. Note the bottom corner is not colored 2, because it is not horizontally or vertically connected to the starting pixel."
- },
- {
- "content": "```\nInput: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 0\nOutput: [[0,0,0],[0,0,0]]\n```\n**Explanation:** The starting pixel is already colored with 0, which is the same as the target color. Therefore, no changes are made to the image."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, color = 2\nOutput: [[2,2,2],[2,2,0],[2,0,1]]\n```\n**Explanation:** From the center of the image with position `(sr, sc) = (1, 1)` (i.e., the red pixel), all pixels connected by a path of the same color as the starting pixel (i.e., the blue pixels) are colored with the new color. Note the bottom corner is not colored 2, because it is not horizontally or vertically connected to the starting pixel."
+ },
+ {
+ "content": "```\nInput: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 0\nOutput: [[0,0,0],[0,0,0]]\n```\n**Explanation:** The starting pixel is already colored with 0, which is the same as the target color. Therefore, no changes are made to the image."
+ }
+ ]
+ },
"readme_constraints": "- `m == image.length`\n- `n == image[i].length`\n- `1 <= m, n <= 50`\n- `0 <= image[i][j], color < 2^16`\n- `0 <= sr < m`\n- `0 <= sc < n`",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "flood_fill",
+ "helpers_run_signature": "(solution_class: type, image: list[list[int]], sr: int, sc: int, color: int)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.flood_fill(image, sr, sc, color)",
+ "helpers_assert_name": "flood_fill",
+ "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "flood_fill",
- "parameters": "image: list[list[int]], sr: int, sc: int, color: int",
- "return_type": "list[list[int]]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_flood_fill, run_flood_fill\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "FloodFill",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_flood_fill",
- "parametrize": "image, sr, sc, color, expected",
- "parametrize_typed": "image: list[list[int]], sr: int, sc: int, color: int, expected: list[list[int]]",
- "test_cases": "[([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 2, [[2, 2, 2], [2, 2, 0], [2, 0, 1]]), ([[0, 0, 0], [0, 0, 0]], 0, 0, 0, [[0, 0, 0], [0, 0, 0]]), ([[0, 0, 0], [0, 1, 1]], 1, 1, 1, [[0, 0, 0], [0, 1, 1]]), ([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 1, [[1, 1, 1], [1, 1, 0], [1, 0, 1]])]",
- "body": "result = self.solution.flood_fill(image, sr, sc, color)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nimage = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\nsr = 1\nsc = 1\ncolor = 2\nexpected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]",
- "playground_execution": "result = Solution().flood_fill(image, sr, sc, color)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "flood_fill",
+ "signature": "(self, image: list[list[int]], sr: int, sc: int, color: int) -> list[list[int]]",
+ "body": " # TODO: Implement flood_fill\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_flood_fill",
+ "signature": "(self, image: list[list[int]], sr: int, sc: int, color: int, expected: list[list[int]])",
+ "parametrize": "image, sr, sc, color, expected",
+ "test_cases": "[([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 2, [[2, 2, 2], [2, 2, 0], [2, 0, 1]]), ([[0, 0, 0], [0, 0, 0]], 0, 0, 0, [[0, 0, 0], [0, 0, 0]]), ([[0, 0, 0], [0, 1, 1]], 1, 1, 1, [[0, 0, 0], [0, 1, 1]]), ([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 1, [[1, 1, 1], [1, 1, 0], [1, 0, 1]])]",
+ "body": " result = run_flood_fill(Solution, image, sr, sc, color)\n assert_flood_fill(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_flood_fill, assert_flood_fill\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nimage = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\nsr = 1\nsc = 1\ncolor = 2\nexpected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]",
+ "playground_run": "result = run_flood_fill(Solution, image, sr, sc, color)\nresult",
+ "playground_assert": "assert_flood_fill(result, expected)"
}
diff --git a/.templates/leetcode/json/implement_queue_using_stacks.json b/.templates/leetcode/json/implement_queue_using_stacks.json
index cc38ff1..a412491 100644
--- a/.templates/leetcode/json/implement_queue_using_stacks.json
+++ b/.templates/leetcode/json/implement_queue_using_stacks.json
@@ -5,37 +5,75 @@
"problem_title": "Implement Queue using Stacks",
"difficulty": "Easy",
"topics": "Stack, Design, Queue",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue (`push`, `peek`, `pop`, and `empty`).\n\nImplement the `MyQueue` class:\n\n- `void push(int x)` Pushes element x to the back of the queue.\n- `int pop()` Removes the element from the front of the queue and returns it.\n- `int peek()` Returns the element at the front of the queue.\n- `boolean empty()` Returns `true` if the queue is empty, `false` otherwise.",
- "readme_examples": [
- {
- "content": "```\nInput\n[\"MyQueue\", \"push\", \"push\", \"peek\", \"pop\", \"empty\"]\n[[], [1], [2], [], [], []]\nOutput\n[null, null, null, 1, 1, false]\n```\n**Explanation:**\n```\nMyQueue myQueue = new MyQueue();\nmyQueue.push(1); // queue is: [1]\nmyQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)\nmyQueue.peek(); // return 1\nmyQueue.pop(); // return 1, queue is [2]\nmyQueue.empty(); // return false\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput\n[\"MyQueue\", \"push\", \"push\", \"peek\", \"pop\", \"empty\"]\n[[], [1], [2], [], [], []]\nOutput\n[null, null, null, 1, 1, false]\n```\n**Explanation:**\n```\nMyQueue myQueue = new MyQueue();\nmyQueue.push(1); // queue is: [1]\nmyQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)\nmyQueue.peek(); // return 1\nmyQueue.pop(); // return 1, queue is [2]\nmyQueue.empty(); // return false\n```"
+ }
+ ]
+ },
"readme_constraints": "- 1 <= x <= 9\n- At most 100 calls will be made to push, pop, peek, and empty.\n- All the calls to pop and peek are valid.",
"readme_additional": "**Notes:**\n- You must use **only** standard operations of a stack, which means only `push to top`, `peek/pop from top`, `size`, and `is empty` operations are valid.\n- Depending on your language, the stack may not be supported natively. You may simulate a stack using a list or deque (double-ended queue) as long as you use only a stack's standard operations.\n\n**Follow-up:** Can you implement the queue such that each operation is amortized `O(1)` time complexity? In other words, performing `n` operations will take overall `O(n)` time even if one of those operations may take longer.",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "my_queue",
+ "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[int]])",
+ "helpers_run_body": " queue = None\n results: list[int | None | bool] = []\n for i, op in enumerate(operations):\n if op == 'MyQueue':\n queue = solution_class()\n results.append(None)\n elif op == 'push' and queue is not None:\n queue.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and queue is not None:\n results.append(queue.pop())\n elif op == 'peek' and queue is not None:\n results.append(queue.peek())\n elif op == 'empty' and queue is not None:\n results.append(queue.empty())\n return results, queue",
+ "helpers_assert_name": "my_queue",
+ "helpers_assert_signature": "(result: list[int | None | bool], expected: list[int | None | bool]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "__init__", "parameters": "", "return_type": "None", "dummy_return": "" },
- { "name": "push", "parameters": "x: int", "return_type": "None", "dummy_return": "" },
- { "name": "pop", "parameters": "", "return_type": "int", "dummy_return": "0" },
- { "name": "peek", "parameters": "", "return_type": "int", "dummy_return": "0" },
- { "name": "empty", "parameters": "", "return_type": "bool", "dummy_return": "True" }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import MyQueue",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_my_queue, run_my_queue\nfrom .solution import MyQueue",
+ "test_content": "",
"test_class_name": "ImplementQueueUsingStacks",
- "test_helper_methods": [],
- "test_methods": [
- {
- "name": "test_queue_operations",
- "parametrize": "operations, inputs, expected",
- "parametrize_typed": "operations: list[str], inputs: list[list[int]], expected: list[int | None | bool]",
- "test_cases": "[(['MyQueue', 'push', 'push', 'peek', 'pop', 'empty'], [[], [1], [2], [], [], []], [None, None, None, 1, 1, False]), (['MyQueue', 'empty', 'push', 'peek', 'pop', 'empty'], [[], [], [1], [], [], []], [None, True, None, 1, 1, True]), (['MyQueue', 'push', 'push', 'push', 'pop', 'pop', 'peek', 'pop', 'empty'], [[], [1], [2], [3], [], [], [], [], []], [None, None, None, None, 1, 2, 3, 3, True])]",
- "body": "queue = None\nresults: list[int | None | bool] = []\nfor i, op in enumerate(operations):\n if op == 'MyQueue':\n queue = MyQueue()\n results.append(None)\n elif op == 'push' and queue is not None:\n queue.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and queue is not None:\n results.append(queue.pop())\n elif op == 'peek' and queue is not None:\n results.append(queue.peek())\n elif op == 'empty' and queue is not None:\n results.append(queue.empty())\nassert results == expected"
- }
- ],
- "playground_imports": "from solution import MyQueue",
- "playground_test_case": "# Example test case\nqueue = MyQueue()\nqueue.push(1)\nqueue.push(2)",
- "playground_execution": "result_peek = queue.peek()\nresult_pop = queue.pop()\nresult_empty = queue.empty()\nprint(f'peek: {result_peek}, pop: {result_pop}, empty: {result_empty}')",
- "playground_assertion": "assert result_peek == 1\nassert result_pop == 1\nassert result_empty == False"
+ "test_class_content": "",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "__init__",
+ "signature": "(self) -> None",
+ "body": " # TODO: Initialize\n pass"
+ },
+ {
+ "name": "push",
+ "signature": "(self, x: int) -> None",
+ "body": " # TODO: Implement push\n pass"
+ },
+ {
+ "name": "pop",
+ "signature": "(self) -> int",
+ "body": " # TODO: Implement pop\n return 0"
+ },
+ {
+ "name": "peek",
+ "signature": "(self) -> int",
+ "body": " # TODO: Implement peek\n return 0"
+ },
+ {
+ "name": "empty",
+ "signature": "(self) -> bool",
+ "body": " # TODO: Implement empty\n return True"
+ }
+ ]
+ },
+ "_test_helper_methods": { "list": [] },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_queue_operations",
+ "signature": "(self, operations: list[str], inputs: list[list[int]], expected: list[int | None | bool])",
+ "parametrize": "operations, inputs, expected",
+ "test_cases": "[(['MyQueue', 'push', 'push', 'peek', 'pop', 'empty'], [[], [1], [2], [], [], []], [None, None, None, 1, 1, False]), (['MyQueue', 'empty', 'push', 'peek', 'pop', 'empty'], [[], [], [1], [], [], []], [None, True, None, 1, 1, True]), (['MyQueue', 'push', 'push', 'push', 'pop', 'pop', 'peek', 'pop', 'empty'], [[], [1], [2], [3], [], [], [], [], []], [None, None, None, None, 1, 2, 3, 3, True])]",
+ "body": " result, _ = run_my_queue(MyQueue, operations, inputs)\n assert_my_queue(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_my_queue, assert_my_queue\nfrom solution import MyQueue",
+ "playground_setup": "# Example test case\noperations = ['MyQueue', 'push', 'push', 'peek', 'pop', 'empty']\ninputs = [[], [1], [2], [], [], []]\nexpected = [None, None, None, 1, 1, False]",
+ "playground_run": "result, queue = run_my_queue(MyQueue, operations, inputs)\nprint(result)\nqueue",
+ "playground_assert": "assert_my_queue(result, expected)"
}
diff --git a/.templates/leetcode/json/implement_trie_prefix_tree.json b/.templates/leetcode/json/implement_trie_prefix_tree.json
index 90ed349..dcc8fa0 100644
--- a/.templates/leetcode/json/implement_trie_prefix_tree.json
+++ b/.templates/leetcode/json/implement_trie_prefix_tree.json
@@ -5,46 +5,70 @@
"problem_title": "Implement Trie (Prefix Tree)",
"difficulty": "Medium",
"topics": "Hash Table, String, Design, Trie",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "A **trie** (pronounced as \"try\") or **prefix tree** is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker.\n\nImplement the Trie class:\n\n- `Trie()` Initializes the trie object.\n- `void insert(String word)` Inserts the string `word` into the trie.\n- `boolean search(String word)` Returns `true` if the string `word` is in the trie (i.e., was inserted before), and `false` otherwise.\n- `boolean startsWith(String prefix)` Returns `true` if there is a previously inserted string `word` that has the prefix `prefix`, and `false` otherwise.",
- "readme_examples": [
- {
- "content": "```\nInput\n[\"Trie\", \"insert\", \"search\", \"search\", \"startsWith\", \"insert\", \"search\"]\n[[], [\"apple\"], [\"apple\"], [\"app\"], [\"app\"], [\"app\"], [\"app\"]]\nOutput\n[null, null, true, false, true, null, true]\n```\n\n**Explanation:**\n```python\ntrie = Trie()\ntrie.insert(\"apple\")\ntrie.search(\"apple\") # return True\ntrie.search(\"app\") # return False\ntrie.starts_with(\"app\") # return True\ntrie.insert(\"app\")\ntrie.search(\"app\") # return True\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput\n[\"Trie\", \"insert\", \"search\", \"search\", \"startsWith\", \"insert\", \"search\"]\n[[], [\"apple\"], [\"apple\"], [\"app\"], [\"app\"], [\"app\"], [\"app\"]]\nOutput\n[null, null, true, false, true, null, true]\n```\n\n**Explanation:**\n```python\ntrie = Trie()\ntrie.insert(\"apple\")\ntrie.search(\"apple\") # return True\ntrie.search(\"app\") # return False\ntrie.starts_with(\"app\") # return True\ntrie.insert(\"app\")\ntrie.search(\"app\") # return True\n```"
+ }
+ ]
+ },
"readme_constraints": "- `1 <= word.length, prefix.length <= 2000`\n- `word` and `prefix` consist only of lowercase English letters.\n- At most `3 * 10^4` calls **in total** will be made to `insert`, `search`, and `starts_with`.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "trie_operations",
+ "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[str]])",
+ "helpers_run_body": " trie = None\n results: list[bool | None] = []\n for i, op in enumerate(operations):\n if op == 'Trie':\n trie = solution_class()\n results.append(None)\n elif op == 'insert' and trie is not None:\n trie.insert(inputs[i][0])\n results.append(None)\n elif op == 'search' and trie is not None:\n results.append(trie.search(inputs[i][0]))\n elif op == 'starts_with' and trie is not None:\n results.append(trie.starts_with(inputs[i][0]))\n return results, trie",
+ "helpers_assert_name": "trie_operations",
+ "helpers_assert_signature": "(result: list[bool | None], expected: list[bool | None]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "from leetcode_py.data_structures import DictTree, RecursiveDict",
- "solution_methods": [
- {
- "name": "__init__",
- "parameters": "",
- "return_type": "None",
- "dummy_return": "super().__init__()\n self.root: RecursiveDict[str] = {}"
- },
- { "name": "insert", "parameters": "word: str", "return_type": "None", "dummy_return": "" },
- { "name": "search", "parameters": "word: str", "return_type": "bool", "dummy_return": "False" },
- {
- "name": "starts_with",
- "parameters": "prefix: str",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Trie",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_trie_operations, run_trie_operations\nfrom .solution import Trie",
+ "test_content": "",
"test_class_name": "ImplementTriePrefixTree",
- "test_helper_methods": [],
- "test_methods": [
- {
- "name": "test_trie_operations",
- "parametrize": "operations, inputs, expected",
- "parametrize_typed": "operations: list[str], inputs: list[list[str]], expected: list[bool | None]",
- "test_cases": "[(['Trie', 'insert', 'search', 'search', 'starts_with', 'insert', 'search'], [[], ['apple'], ['apple'], ['app'], ['app'], ['app'], ['app']], [None, None, True, False, True, None, True]), (['Trie', 'insert', 'insert', 'search', 'search', 'starts_with', 'starts_with'], [[], ['hello'], ['world'], ['hello'], ['hi'], ['hel'], ['wor']], [None, None, None, True, False, True, True]), (['Trie', 'insert', 'insert', 'search', 'search', 'starts_with', 'starts_with'], [[], ['a'], ['aa'], ['a'], ['aa'], ['a'], ['aa']], [None, None, None, True, True, True, True]), (['Trie', 'insert', 'search', 'starts_with', 'insert', 'search', 'starts_with'], [[], ['test'], ['testing'], ['test'], ['testing'], ['testing'], ['test']], [None, None, False, True, None, True, True]), (['Trie', 'search', 'starts_with'], [[], ['empty'], ['empty']], [None, False, False])]",
- "body": "trie: Trie | None = None\nresults: list[bool | None] = []\nfor i, op in enumerate(operations):\n if op == 'Trie':\n trie = Trie()\n results.append(None)\n elif op == 'insert' and trie is not None:\n trie.insert(inputs[i][0])\n results.append(None)\n elif op == 'search' and trie is not None:\n results.append(trie.search(inputs[i][0]))\n elif op == 'starts_with' and trie is not None:\n results.append(trie.starts_with(inputs[i][0]))\nassert results == expected"
- }
- ],
- "playground_imports": "from solution import Trie",
- "playground_test_case": "# Example test case\noperations = ['Trie', 'insert', 'search', 'search', 'starts_with', 'insert', 'search']\ninputs = [[], ['apple'], ['apple'], ['app'], ['app'], ['app'], ['app']]\nexpected = [None, None, True, False, True, None, True]",
- "playground_execution": "trie = None\nresults: list[bool | None] = []\nfor i, op in enumerate(operations):\n if op == 'Trie':\n trie = Trie()\n results.append(None)\n elif op == 'insert' and trie is not None:\n trie.insert(inputs[i][0])\n results.append(None)\n elif op == 'search' and trie is not None:\n results.append(trie.search(inputs[i][0]))\n elif op == 'starts_with' and trie is not None:\n results.append(trie.starts_with(inputs[i][0]))\nresults",
- "playground_assertion": "assert results == expected"
+ "test_class_content": "",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "__init__",
+ "signature": "(self) -> None",
+ "body": " super().__init__()\n self.root: RecursiveDict[str] = {}"
+ },
+ {
+ "name": "insert",
+ "signature": "(self, word: str) -> None",
+ "body": " # TODO: Implement insert\n pass"
+ },
+ {
+ "name": "search",
+ "signature": "(self, word: str) -> bool",
+ "body": " # TODO: Implement search\n return False"
+ },
+ {
+ "name": "starts_with",
+ "signature": "(self, prefix: str) -> bool",
+ "body": " # TODO: Implement starts_with\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": { "list": [] },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_trie_operations",
+ "signature": "(self, operations: list[str], inputs: list[list[str]], expected: list[bool | None])",
+ "parametrize": "operations, inputs, expected",
+ "test_cases": "[(['Trie', 'insert', 'search', 'search', 'starts_with', 'insert', 'search'], [[], ['apple'], ['apple'], ['app'], ['app'], ['app'], ['app']], [None, None, True, False, True, None, True]), (['Trie', 'insert', 'insert', 'search', 'search', 'starts_with', 'starts_with'], [[], ['hello'], ['world'], ['hello'], ['hi'], ['hel'], ['wor']], [None, None, None, True, False, True, True]), (['Trie', 'insert', 'insert', 'search', 'search', 'starts_with', 'starts_with'], [[], ['a'], ['aa'], ['a'], ['aa'], ['a'], ['aa']], [None, None, None, True, True, True, True]), (['Trie', 'insert', 'search', 'starts_with', 'insert', 'search', 'starts_with'], [[], ['test'], ['testing'], ['test'], ['testing'], ['testing'], ['test']], [None, None, False, True, None, True, True]), (['Trie', 'search', 'starts_with'], [[], ['empty'], ['empty']], [None, False, False])]",
+ "body": " result, _ = run_trie_operations(Trie, operations, inputs)\n assert_trie_operations(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_trie_operations, assert_trie_operations\nfrom solution import Trie",
+ "playground_setup": "# Example test case\noperations = ['Trie', 'insert', 'search', 'search', 'starts_with', 'insert', 'search']\ninputs = [[], ['apple'], ['apple'], ['app'], ['app'], ['app'], ['app']]\nexpected = [None, None, True, False, True, None, True]",
+ "playground_run": "result, trie = run_trie_operations(Trie, operations, inputs)\nprint(result)\ntrie",
+ "playground_assert": "assert_trie_operations(result, expected)"
}
diff --git a/.templates/leetcode/json/insert_interval.json b/.templates/leetcode/json/insert_interval.json
index 7ffa1ca..64e4a18 100644
--- a/.templates/leetcode/json/insert_interval.json
+++ b/.templates/leetcode/json/insert_interval.json
@@ -5,43 +5,60 @@
"problem_title": "Insert Interval",
"difficulty": "Medium",
"topics": "Array",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given an array of non-overlapping intervals `intervals` where `intervals[i] = [starti, endi]` represent the start and the end of the ith interval and `intervals` is sorted in ascending order by `starti`. You are also given an interval `newInterval = [start, end]` that represents the start and end of another interval.\n\nInsert `newInterval` into `intervals` such that `intervals` is still sorted in ascending order by `starti` and `intervals` still does not have any overlapping intervals (merge overlapping intervals if necessary).\n\nReturn `intervals` after the insertion.",
- "readme_examples": [
- {
- "content": "```\nInput: intervals = [[1,3],[6,9]], newInterval = [2,5]\nOutput: [[1,5],[6,9]]\n```"
- },
- {
- "content": "```\nInput: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]\nOutput: [[1,2],[3,10],[12,16]]\nExplanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: intervals = [[1,3],[6,9]], newInterval = [2,5]\nOutput: [[1,5],[6,9]]\n```"
+ },
+ {
+ "content": "```\nInput: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]\nOutput: [[1,2],[3,10],[12,16]]\nExplanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].\n```"
+ }
+ ]
+ },
"readme_constraints": "- 0 <= intervals.length <= 10^4\n- intervals[i].length == 2\n- 0 <= starti <= endi <= 10^5\n- intervals is sorted by starti in ascending order\n- newInterval.length == 2\n- 0 <= start <= end <= 10^5",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "insert",
+ "helpers_run_signature": "(solution_class: type, intervals: list[list[int]], new_interval: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.insert(intervals, new_interval)",
+ "helpers_assert_name": "insert",
+ "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "insert",
- "parameters": "intervals: list[list[int]], new_interval: list[int]",
- "return_type": "list[list[int]]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_insert, run_insert\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "InsertInterval",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_insert",
- "parametrize": "intervals, new_interval, expected",
- "parametrize_typed": "intervals: list[list[int]], new_interval: list[int], expected: list[list[int]]",
- "test_cases": "[([[1,3],[6,9]], [2,5], [[1,5],[6,9]]), ([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8], [[1,2],[3,10],[12,16]])]",
- "body": "result = self.solution.insert(intervals, new_interval)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nintervals = [[1,3],[6,9]]\nnew_interval = [2,5]\nexpected = [[1,5],[6,9]]",
- "playground_execution": "result = Solution().insert(intervals, new_interval)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "insert",
+ "signature": "(self, intervals: list[list[int]], new_interval: list[int]) -> list[list[int]]",
+ "body": " # TODO: Implement insert\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_insert",
+ "signature": "(self, intervals: list[list[int]], new_interval: list[int], expected: list[list[int]])",
+ "parametrize": "intervals, new_interval, expected",
+ "test_cases": "[([[1,3],[6,9]], [2,5], [[1,5],[6,9]]), ([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8], [[1,2],[3,10],[12,16]]), ([], [5,7], [[5,7]]), ([[1,5]], [2,3], [[1,5]]), ([[1,5]], [6,8], [[1,5],[6,8]]), ([[1,5]], [0,0], [[0,0],[1,5]]), ([[3,5],[12,15]], [6,6], [[3,5],[6,6],[12,15]])]",
+ "body": " result = run_insert(Solution, intervals, new_interval)\n assert_insert(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_insert, assert_insert\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nintervals = [[1,3],[6,9]]\nnew_interval = [2,5]\nexpected = [[1,5],[6,9]]",
+ "playground_run": "result = run_insert(Solution, intervals, new_interval)\nresult",
+ "playground_assert": "assert_insert(result, expected)"
}
diff --git a/.templates/leetcode/json/invert_binary_tree.json b/.templates/leetcode/json/invert_binary_tree.json
index 8e1f324..d95e35c 100644
--- a/.templates/leetcode/json/invert_binary_tree.json
+++ b/.templates/leetcode/json/invert_binary_tree.json
@@ -5,40 +5,57 @@
"problem_title": "Invert Binary Tree",
"difficulty": "Easy",
"topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given the `root` of a binary tree, invert the tree, and return its root.",
- "readme_examples": [
- { "content": "```\nInput: root = [4,2,7,1,3,6,9]\nOutput: [4,7,2,9,6,3,1]\n```" },
- { "content": "```\nInput: root = [2,1,3]\nOutput: [2,3,1]\n```" },
- { "content": "```\nInput: root = []\nOutput: []\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: root = [4,2,7,1,3,6,9]\nOutput: [4,7,2,9,6,3,1]\n```" },
+ { "content": "```\nInput: root = [2,1,3]\nOutput: [2,3,1]\n```" },
+ { "content": "```\nInput: root = []\nOutput: []\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range [0, 100]\n- -100 <= Node.val <= 100",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "invert_tree",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None])",
+ "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.invert_tree(root)",
+ "helpers_assert_name": "invert_tree",
+ "helpers_assert_signature": "(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool",
+ "helpers_assert_body": " expected = TreeNode[int].from_list(expected_list)\n assert result == expected\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "invert_tree",
- "parameters": "root: TreeNode[int] | None",
- "return_type": "TreeNode[int] | None",
- "dummy_return": "None"
- }
- ],
- "test_imports": "import pytest\n\nfrom leetcode_py import TreeNode\nfrom leetcode_py.test_utils import logged_test\n\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_invert_tree, run_invert_tree\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "InvertBinaryTree",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_invert_tree",
- "parametrize": "root_list, expected_list",
- "parametrize_typed": "root_list: list[int | None], expected_list: list[int | None]",
- "test_cases": "[([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], [])]",
- "body": "root = TreeNode[int].from_list(root_list)\nexpected = TreeNode[int].from_list(expected_list)\nresult = self.solution.invert_tree(root)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\n\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\nroot = TreeNode[int].from_list(root_list)\nexpected = TreeNode[int].from_list([4, 7, 2, 9, 6, 3, 1])",
- "playground_execution": "result = Solution().invert_tree(root)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "invert_tree",
+ "signature": "(self, root: TreeNode[int] | None) -> TreeNode[int] | None",
+ "body": " # TODO: Implement invert_tree\n return None"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_invert_tree",
+ "signature": "(self, root_list: list[int | None], expected_list: list[int | None])",
+ "parametrize": "root_list, expected_list",
+ "test_cases": "[([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], []), ([1], [1]), ([1, 2], [1, None, 2]), ([1, None, 2], [1, 2]), ([1, 2, 3, 4, 5], [1, 3, 2, None, None, 5, 4]), ([1, 2, 3, None, None, 4, 5], [1, 3, 2, 5, 4])]",
+ "body": " result = run_invert_tree(Solution, root_list)\n assert_invert_tree(result, expected_list)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_invert_tree, assert_invert_tree\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\nexpected_list: list[int | None] = [4, 7, 2, 9, 6, 3, 1]",
+ "playground_run": "result = run_invert_tree(Solution, root_list)\nresult",
+ "playground_assert": "assert_invert_tree(result, expected_list)"
}
diff --git a/.templates/leetcode/json/k_closest_points_to_origin.json b/.templates/leetcode/json/k_closest_points_to_origin.json
index 25e7cfa..8a648e5 100644
--- a/.templates/leetcode/json/k_closest_points_to_origin.json
+++ b/.templates/leetcode/json/k_closest_points_to_origin.json
@@ -5,43 +5,60 @@
"problem_title": "K Closest Points to Origin",
"difficulty": "Medium",
"topics": "Array, Math, Divide and Conquer, Geometry, Sorting, Heap (Priority Queue), Quickselect",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an array of `points` where `points[i] = [xi, yi]` represents a point on the **X-Y** plane and an integer `k`, return the `k` closest points to the origin `(0, 0)`.\n\nThe distance between two points on the **X-Y** plane is the Euclidean distance (i.e., `\u221a(x1 - x2)\u00b2 + (y1 - y2)\u00b2`).\n\nYou may return the answer in **any order**. The answer is **guaranteed** to be **unique** (except for the order that it is in).",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: points = [[1,3],[-2,2]], k = 1\nOutput: [[-2,2]]\n```\n**Explanation:** The distance between (1, 3) and the origin is sqrt(10). The distance between (-2, 2) and the origin is sqrt(8). Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin. We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]]."
- },
- {
- "content": "```\nInput: points = [[3,3],[5,-1],[-2,4]], k = 2\nOutput: [[3,3],[-2,4]]\n```\n**Explanation:** The answer [[-2,4],[3,3]] would also be accepted."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: points = [[1,3],[-2,2]], k = 1\nOutput: [[-2,2]]\n```\n**Explanation:** The distance between (1, 3) and the origin is sqrt(10). The distance between (-2, 2) and the origin is sqrt(8). Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin. We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]]."
+ },
+ {
+ "content": "```\nInput: points = [[3,3],[5,-1],[-2,4]], k = 2\nOutput: [[3,3],[-2,4]]\n```\n**Explanation:** The answer [[-2,4],[3,3]] would also be accepted."
+ }
+ ]
+ },
"readme_constraints": "- `1 <= k <= points.length <= 10^4`\n- `-10^4 <= xi, yi <= 10^4`",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "k_closest",
+ "helpers_run_signature": "(solution_class: type, points: list[list[int]], k: int)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.k_closest(points, k)",
+ "helpers_assert_name": "k_closest",
+ "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool",
+ "helpers_assert_body": " # Sort both result and expected for comparison since order doesn't matter\n result_sorted = sorted(result)\n expected_sorted = sorted(expected)\n assert result_sorted == expected_sorted\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "k_closest",
- "parameters": "points: list[list[int]], k: int",
- "return_type": "list[list[int]]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_k_closest, run_k_closest\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "KClosestPointsToOrigin",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_k_closest",
- "parametrize": "points, k, expected",
- "parametrize_typed": "points: list[list[int]], k: int, expected: list[list[int]]",
- "test_cases": "[([[1, 3], [-2, 2]], 1, [[-2, 2]]), ([[3, 3], [5, -1], [-2, 4]], 2, [[3, 3], [-2, 4]]), ([[0, 1], [1, 0]], 2, [[0, 1], [1, 0]]), ([[1, 1], [1, 1], [1, 1]], 2, [[1, 1], [1, 1]])]",
- "body": "result = self.solution.k_closest(points, k)\n# Sort both result and expected for comparison since order doesn't matter\nresult_sorted = sorted(result)\nexpected_sorted = sorted(expected)\nassert result_sorted == expected_sorted"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\npoints = [[1, 3], [-2, 2]]\nk = 1\nexpected = [[-2, 2]]",
- "playground_execution": "result = Solution().k_closest(points, k)\nresult",
- "playground_assertion": "assert sorted(result) == sorted(expected)"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "k_closest",
+ "signature": "(self, points: list[list[int]], k: int) -> list[list[int]]",
+ "body": " # TODO: Implement k_closest\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_k_closest",
+ "signature": "(self, points: list[list[int]], k: int, expected: list[list[int]])",
+ "parametrize": "points, k, expected",
+ "test_cases": "[([[1, 3], [-2, 2]], 1, [[-2, 2]]), ([[3, 3], [5, -1], [-2, 4]], 2, [[3, 3], [-2, 4]]), ([[0, 1], [1, 0]], 2, [[0, 1], [1, 0]]), ([[1, 1], [1, 1], [1, 1]], 2, [[1, 1], [1, 1]]), ([[0, 0]], 1, [[0, 0]]), ([[1, 0], [2, 0], [3, 0]], 2, [[1, 0], [2, 0]])]",
+ "body": " result = run_k_closest(Solution, points, k)\n assert_k_closest(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_k_closest, assert_k_closest\nfrom solution import Solution",
+ "playground_setup": "# Example test case\npoints = [[1, 3], [-2, 2]]\nk = 1\nexpected = [[-2, 2]]",
+ "playground_run": "result = run_k_closest(Solution, points, k)\nresult",
+ "playground_assert": "assert_k_closest(result, expected)"
}
diff --git a/.templates/leetcode/json/kth_smallest_element_in_a_bst.json b/.templates/leetcode/json/kth_smallest_element_in_a_bst.json
index 3239379..e41818c 100644
--- a/.templates/leetcode/json/kth_smallest_element_in_a_bst.json
+++ b/.templates/leetcode/json/kth_smallest_element_in_a_bst.json
@@ -5,43 +5,60 @@
"problem_title": "Kth Smallest Element in a BST",
"difficulty": "Medium",
"topics": "Tree, Depth-First Search, Binary Search Tree, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given the `root` of a binary search tree, and an integer `k`, return the `k`th smallest value (1-indexed) of all the values of the nodes in the tree.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: root = [3,1,4,null,2], k = 1\nOutput: 1\n```"
- },
- {
- "content": "\n\n```\nInput: root = [5,3,6,2,4,null,null,1], k = 3\nOutput: 3\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: root = [3,1,4,null,2], k = 1\nOutput: 1\n```"
+ },
+ {
+ "content": "\n\n```\nInput: root = [5,3,6,2,4,null,null,1], k = 3\nOutput: 3\n```"
+ }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is `n`.\n- `1 <= k <= n <= 10^4`\n- `0 <= Node.val <= 10^4`",
"readme_additional": "**Follow up:** If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize?",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "kth_smallest",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None], k: int)",
+ "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.kth_smallest(root, k)",
+ "helpers_assert_name": "kth_smallest",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "kth_smallest",
- "parameters": "root: TreeNode | None, k: int",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_kth_smallest, run_kth_smallest\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "KthSmallestElementInABst",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_kth_smallest",
- "parametrize": "root_list, k, expected",
- "parametrize_typed": "root_list: list[int | None], k: int, expected: int",
- "test_cases": "[([3, 1, 4, None, 2], 1, 1), ([5, 3, 6, 2, 4, None, None, 1], 3, 3), ([1], 1, 1)]",
- "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.kth_smallest(root, k)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list = [3, 1, 4, None, 2]\nk = 1\nexpected = 1",
- "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().kth_smallest(root, k)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "kth_smallest",
+ "signature": "(self, root: TreeNode[int] | None, k: int) -> int",
+ "body": " # TODO: Implement kth_smallest\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_kth_smallest",
+ "signature": "(self, root_list: list[int | None], k: int, expected: int)",
+ "parametrize": "root_list, k, expected",
+ "test_cases": "[([3, 1, 4, None, 2], 1, 1), ([5, 3, 6, 2, 4, None, None, 1], 3, 3), ([1], 1, 1), ([2, 1, 3], 2, 2), ([4, 2, 6, 1, 3, 5, 7], 4, 4), ([1, None, 2], 2, 2)]",
+ "body": " result = run_kth_smallest(Solution, root_list, k)\n assert_kth_smallest(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_kth_smallest, assert_kth_smallest\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list = [3, 1, 4, None, 2]\nk = 1\nexpected = 1",
+ "playground_run": "result = run_kth_smallest(Solution, root_list, k)\nresult",
+ "playground_assert": "assert_kth_smallest(result, expected)"
}
diff --git a/.templates/leetcode/json/largest_rectangle_in_histogram.json b/.templates/leetcode/json/largest_rectangle_in_histogram.json
index 8ebbd85..2fe5780 100644
--- a/.templates/leetcode/json/largest_rectangle_in_histogram.json
+++ b/.templates/leetcode/json/largest_rectangle_in_histogram.json
@@ -5,43 +5,60 @@
"problem_title": "Largest Rectangle in Histogram",
"difficulty": "Hard",
"topics": "Array, Stack, Monotonic Stack",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an array of integers `heights` representing the histogram's bar height where the width of each bar is `1`, return the area of the largest rectangle in the histogram.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: heights = [2,1,5,6,2,3]\nOutput: 10\n```\n**Explanation:** The above is a histogram where width of each bar is 1. The largest rectangle is shown in the red area, which has an area = 10 units."
- },
- {
- "content": "\n\n```\nInput: heights = [2,4]\nOutput: 4\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: heights = [2,1,5,6,2,3]\nOutput: 10\n```\n**Explanation:** The above is a histogram where width of each bar is 1. The largest rectangle is shown in the red area, which has an area = 10 units."
+ },
+ {
+ "content": "\n\n```\nInput: heights = [2,4]\nOutput: 4\n```"
+ }
+ ]
+ },
"readme_constraints": "- `1 <= heights.length <= 10^5`\n- `0 <= heights[i] <= 10^4`",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "largest_rectangle_area",
+ "helpers_run_signature": "(solution_class: type, heights: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.largest_rectangle_area(heights)",
+ "helpers_assert_name": "largest_rectangle_area",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "largest_rectangle_area",
- "parameters": "heights: list[int]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_largest_rectangle_area, run_largest_rectangle_area\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "LargestRectangleInHistogram",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_largest_rectangle_area",
- "parametrize": "heights, expected",
- "parametrize_typed": "heights: list[int], expected: int",
- "test_cases": "[([2, 1, 5, 6, 2, 3], 10), ([2, 4], 4), ([1], 1), ([0], 0), ([1, 1], 2), ([0, 0, 0], 0), ([1, 2, 3, 4, 5], 9), ([5, 4, 3, 2, 1], 9), ([3, 3, 3, 3], 12), ([2, 1, 2], 3), ([1, 3, 1], 3), ([6, 7, 5, 2, 4, 5, 9, 3], 16), ([4, 2, 0, 3, 2, 5], 6), ([1, 2, 2, 1], 4), ([0, 9], 9), ([9, 0], 9), ([2, 1, 5, 6, 2, 3, 1, 5, 6, 2], 10), ([1, 8, 6, 2, 5, 4, 8, 3, 7], 16)]",
- "body": "result = self.solution.largest_rectangle_area(heights)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nheights = [2, 1, 5, 6, 2, 3]\nexpected = 10",
- "playground_execution": "result = Solution().largest_rectangle_area(heights)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "largest_rectangle_area",
+ "signature": "(self, heights: list[int]) -> int",
+ "body": " # TODO: Implement largest_rectangle_area\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_largest_rectangle_area",
+ "signature": "(self, heights: list[int], expected: int)",
+ "parametrize": "heights, expected",
+ "test_cases": "[([2, 1, 5, 6, 2, 3], 10), ([2, 4], 4), ([1], 1), ([0], 0), ([1, 1], 2), ([0, 0, 0], 0), ([1, 2, 3, 4, 5], 9), ([5, 4, 3, 2, 1], 9), ([3, 3, 3, 3], 12), ([2, 1, 2], 3), ([1, 3, 1], 3), ([6, 7, 5, 2, 4, 5, 9, 3], 16), ([4, 2, 0, 3, 2, 5], 6), ([1, 2, 2, 1], 4), ([0, 9], 9), ([9, 0], 9)]",
+ "body": " result = run_largest_rectangle_area(Solution, heights)\n assert_largest_rectangle_area(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_largest_rectangle_area, assert_largest_rectangle_area\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nheights = [2, 1, 5, 6, 2, 3]\nexpected = 10",
+ "playground_run": "result = run_largest_rectangle_area(Solution, heights)\nresult",
+ "playground_assert": "assert_largest_rectangle_area(result, expected)"
}
diff --git a/.templates/leetcode/json/linked_list_cycle.json b/.templates/leetcode/json/linked_list_cycle.json
index ae5dd54..eeb89c9 100644
--- a/.templates/leetcode/json/linked_list_cycle.json
+++ b/.templates/leetcode/json/linked_list_cycle.json
@@ -5,51 +5,63 @@
"problem_title": "Linked List Cycle",
"difficulty": "Easy",
"topics": "Hash Table, Linked List, Two Pointers",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given `head`, the head of a linked list, determine if the linked list has a cycle in it.\n\nThere is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the `next` pointer. Internally, `pos` is used to denote the index of the node that tail's `next` pointer is connected to. **Note that `pos` is not passed as a parameter**.\n\nReturn `true` *if there is a cycle in the linked list*. Otherwise, return `false`.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: head = [3,2,0,-4], pos = 1\nOutput: true\n```\n**Explanation:** There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed)."
- },
- {
- "content": "\n\n```\nInput: head = [1,2], pos = 0\nOutput: true\n```\n**Explanation:** There is a cycle in the linked list, where the tail connects to the 0th node."
- },
- {
- "content": "\n\n```\nInput: head = [1], pos = -1\nOutput: false\n```\n**Explanation:** There is no cycle in the linked list."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: head = [3,2,0,-4], pos = 1\nOutput: true\n```\n**Explanation:** There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed)."
+ },
+ {
+ "content": "\n\n```\nInput: head = [1,2], pos = 0\nOutput: true\n```\n**Explanation:** There is a cycle in the linked list, where the tail connects to the 0th node."
+ },
+ {
+ "content": "\n\n```\nInput: head = [1], pos = -1\nOutput: false\n```\n**Explanation:** There is no cycle in the linked list."
+ }
+ ]
+ },
"readme_constraints": "- The number of the nodes in the list is in the range `[0, 10^4]`.\n- `-10^5 <= Node.val <= 10^5`\n- `pos` is `-1` or a **valid index** in the linked-list.",
"readme_additional": "**Follow up:** Can you solve it using `O(1)` (i.e. constant) memory?",
+ "helpers_imports": "from leetcode_py import ListNode",
+ "helpers_content": "def create_cycle_list(values: list[int], pos: int) -> ListNode[int] | None:\n if not values:\n return None\n\n nodes = []\n head = ListNode(values[0])\n nodes.append(head)\n current = head\n\n for i in range(1, len(values)):\n current.next = ListNode(values[i])\n current = current.next\n nodes.append(current)\n\n if pos != -1 and pos < len(nodes):\n current.next = nodes[pos]\n\n return head",
+ "helpers_run_name": "has_cycle",
+ "helpers_run_signature": "(solution_class: type, values: list[int], pos: int)",
+ "helpers_run_body": " head = create_cycle_list(values, pos)\n implementation = solution_class()\n return implementation.has_cycle(head)",
+ "helpers_assert_name": "has_cycle",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "from leetcode_py import ListNode",
- "solution_methods": [
- {
- "name": "has_cycle",
- "parameters": "head: ListNode[int] | None",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py import ListNode\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_has_cycle, run_has_cycle\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "LinkedListCycle",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" },
- {
- "name": "create_cycle_list",
- "parameters": "values: list[int], pos: int",
- "body": "if not values:\n return None\n\nnodes = []\nhead = ListNode(values[0])\nnodes.append(head)\ncurrent = head\n\nfor i in range(1, len(values)):\n current.next = ListNode(values[i])\n current = current.next\n nodes.append(current)\n\nif pos != -1 and pos < len(nodes):\n current.next = nodes[pos]\n\nreturn head"
- }
- ],
- "test_methods": [
- {
- "name": "test_has_cycle",
- "parametrize": "values, pos, expected",
- "parametrize_typed": "values: list[int], pos: int, expected: bool",
- "test_cases": "[([3, 2, 0, -4], 1, True), ([1, 2], 0, True), ([1], -1, False), ([], -1, False), ([1, 2, 3], -1, False), ([1, 2, 3, 4, 5], 0, True), ([1, 2, 3, 4, 5], 2, True), ([1, 2, 3, 4, 5], 4, True), ([1], 0, True), ([1, 2], 1, True), ([1, 2, 3, 4, 5, 6, 7, 8], 3, True), ([1, 2, 3, 4, 5, 6, 7, 8], -1, False), ([1, 2], -1, False), ([5, 10], 0, True), ([5, 10], 1, True), ([0], -1, False), ([-1, -2, -3], 1, True), ([100, 200, 300], 0, True)]",
- "body": "head = self.create_cycle_list(values, pos)\nresult = self.solution.has_cycle(head)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "import os\nimport sys\nsys.path.append(os.path.join(os.getcwd(), \\\"..\\\"))\nfrom linked_list_cycle.tests import TestLinkedListCycle\n\n# Example test case\nvalues = [3, 2, 0, -4]\npos = 1\nexpected = True",
- "playground_execution": "head = TestLinkedListCycle().create_cycle_list(values, pos)\nresult = Solution().has_cycle(head)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "has_cycle",
+ "signature": "(self, head: ListNode[int] | None) -> bool",
+ "body": " # TODO: Implement has_cycle\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_has_cycle",
+ "signature": "(self, values: list[int], pos: int, expected: bool)",
+ "parametrize": "values, pos, expected",
+ "test_cases": "[([3, 2, 0, -4], 1, True), ([1, 2], 0, True), ([1], -1, False), ([], -1, False), ([1, 2, 3], -1, False), ([1, 2, 3, 4, 5], 0, True), ([1, 2, 3, 4, 5], 2, True), ([1, 2, 3, 4, 5], 4, True), ([1], 0, True), ([1, 2], 1, True)]",
+ "body": " result = run_has_cycle(Solution, values, pos)\n assert_has_cycle(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_has_cycle, assert_has_cycle, create_cycle_list\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nvalues = [3, 2, 0, -4]\npos = 1\nexpected = True",
+ "playground_run": "result = run_has_cycle(Solution, values, pos)\nresult",
+ "playground_assert": "assert_has_cycle(result, expected)"
}
diff --git a/.templates/leetcode/json/longest_palindrome.json b/.templates/leetcode/json/longest_palindrome.json
index 9561580..8208c5d 100644
--- a/.templates/leetcode/json/longest_palindrome.json
+++ b/.templates/leetcode/json/longest_palindrome.json
@@ -5,43 +5,60 @@
"problem_title": "Longest Palindrome",
"difficulty": "Easy",
"topics": "Hash Table, String, Greedy",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a string `s` which consists of lowercase or uppercase letters, return the length of the longest palindrome that can be built with those letters.\n\nLetters are case sensitive, for example, \"Aa\" is not considered a palindrome.",
- "readme_examples": [
- {
- "content": "```\nInput: s = \"abccccdd\"\nOutput: 7\n```\n**Explanation:** One longest palindrome that can be built is \"dccaccd\", whose length is 7."
- },
- {
- "content": "```\nInput: s = \"a\"\nOutput: 1\n```\n**Explanation:** The longest palindrome that can be built is \"a\", whose length is 1."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: s = \"abccccdd\"\nOutput: 7\n```\n**Explanation:** One longest palindrome that can be built is \"dccaccd\", whose length is 7."
+ },
+ {
+ "content": "```\nInput: s = \"a\"\nOutput: 1\n```\n**Explanation:** The longest palindrome that can be built is \"a\", whose length is 1."
+ }
+ ]
+ },
"readme_constraints": "- `1 <= s.length <= 2000`\n- `s` consists of lowercase and/or uppercase English letters only.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "longest_palindrome",
+ "helpers_run_signature": "(solution_class: type, s: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.longest_palindrome(s)",
+ "helpers_assert_name": "longest_palindrome",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "longest_palindrome",
- "parameters": "s: str",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_longest_palindrome, run_longest_palindrome\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "LongestPalindrome",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_longest_palindrome",
- "parametrize": "s, expected",
- "parametrize_typed": "s: str, expected: int",
- "test_cases": "[('abccccdd', 7), ('a', 1), ('Aa', 1), ('aabbcc', 6)]",
- "body": "result = self.solution.longest_palindrome(s)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = 'abccccdd'\nexpected = 7",
- "playground_execution": "result = Solution().longest_palindrome(s)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "longest_palindrome",
+ "signature": "(self, s: str) -> int",
+ "body": " # TODO: Implement longest_palindrome\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_longest_palindrome",
+ "signature": "(self, s: str, expected: int)",
+ "parametrize": "s, expected",
+ "test_cases": "[('abccccdd', 7), ('a', 1), ('Aa', 1), ('aabbcc', 6), ('', 0), ('aA', 1), ('abcdef', 1), ('aabbccdd', 8), ('aaaa', 4), ('abcdefg', 1), ('AAaa', 4), ('racecar', 7), ('abcABC', 1)]",
+ "body": " result = run_longest_palindrome(Solution, s)\n assert_longest_palindrome(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_longest_palindrome, assert_longest_palindrome\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = 'abccccdd'\nexpected = 7",
+ "playground_run": "result = run_longest_palindrome(Solution, s)\nresult",
+ "playground_assert": "assert_longest_palindrome(result, expected)"
}
diff --git a/.templates/leetcode/json/longest_palindromic_substring.json b/.templates/leetcode/json/longest_palindromic_substring.json
index 8ee3e31..de0e3fc 100644
--- a/.templates/leetcode/json/longest_palindromic_substring.json
+++ b/.templates/leetcode/json/longest_palindromic_substring.json
@@ -5,41 +5,58 @@
"problem_title": "Longest Palindromic Substring",
"difficulty": "Medium",
"topics": "Two Pointers, String, Dynamic Programming",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a string `s`, return the longest palindromic substring in `s`.",
- "readme_examples": [
- {
- "content": "```\nInput: s = \"babad\"\nOutput: \"bab\"\n```\n**Explanation:** \"aba\" is also a valid answer."
- },
- { "content": "```\nInput: s = \"cbbd\"\nOutput: \"bb\"\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: s = \"babad\"\nOutput: \"bab\"\n```\n**Explanation:** \"aba\" is also a valid answer."
+ },
+ { "content": "```\nInput: s = \"cbbd\"\nOutput: \"bb\"\n```" }
+ ]
+ },
"readme_constraints": "- `1 <= s.length <= 1000`\n- `s` consist of only digits and English letters.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "longest_palindrome",
+ "helpers_run_signature": "(solution_class: type, s: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.longest_palindrome(s)",
+ "helpers_assert_name": "longest_palindrome",
+ "helpers_assert_signature": "(result: str, expected: set[str]) -> bool",
+ "helpers_assert_body": " assert result in expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "longest_palindrome",
- "parameters": "s: str",
- "return_type": "str",
- "dummy_return": "\"\""
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_longest_palindrome, run_longest_palindrome\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "LongestPalindromicSubstring",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_longest_palindrome",
- "parametrize": "s, expected",
- "parametrize_typed": "s: str, expected: set[str]",
- "test_cases": "[('babad', {'bab', 'aba'}), ('cbbd', {'bb'}), ('a', {'a'}), ('ac', {'a', 'c'}), ('racecar', {'racecar'}), ('abcdef', {'a', 'b', 'c', 'd', 'e', 'f'}), ('aabbaa', {'aabbaa'}), ('abacabad', {'abacaba'}), ('noon', {'noon'}), ('abccba', {'abccba'}), ('', {''}), ('aa', {'aa'}), ('aba', {'aba'}), ('abcba', {'abcba'}), ('forgeeksskeegfor', {'geeksskeeg'}), ('bananas', {'anana'}), ('abcdefghijklmnopqrstuvwxyz', {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'})]",
- "body": "result = self.solution.longest_palindrome(s)\nassert result in expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = 'babad'\nexpected = {'bab', 'aba'}",
- "playground_execution": "result = Solution().longest_palindrome(s)\nresult",
- "playground_assertion": "assert result in expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "longest_palindrome",
+ "signature": "(self, s: str) -> str",
+ "body": " # TODO: Implement longest_palindrome\n return \"\""
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_longest_palindrome",
+ "signature": "(self, s: str, expected: set[str])",
+ "parametrize": "s, expected",
+ "test_cases": "[('babad', {'bab', 'aba'}), ('cbbd', {'bb'}), ('a', {'a'}), ('ac', {'a', 'c'}), ('racecar', {'racecar'}), ('aabbaa', {'aabbaa'}), ('abacabad', {'abacaba'}), ('noon', {'noon'}), ('abccba', {'abccba'}), ('aa', {'aa'}), ('aba', {'aba'}), ('abcba', {'abcba'})]",
+ "body": " result = run_longest_palindrome(Solution, s)\n assert_longest_palindrome(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_longest_palindrome, assert_longest_palindrome\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = 'babad'\nexpected = {'bab', 'aba'}",
+ "playground_run": "result = run_longest_palindrome(Solution, s)\nresult",
+ "playground_assert": "assert_longest_palindrome(result, expected)"
}
diff --git a/.templates/leetcode/json/longest_substring_without_repeating_characters.json b/.templates/leetcode/json/longest_substring_without_repeating_characters.json
index c1c9b9c..50c04c2 100644
--- a/.templates/leetcode/json/longest_substring_without_repeating_characters.json
+++ b/.templates/leetcode/json/longest_substring_without_repeating_characters.json
@@ -5,46 +5,63 @@
"problem_title": "Longest Substring Without Repeating Characters",
"difficulty": "Medium",
"topics": "Hash Table, String, Sliding Window",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a string `s`, find the length of the **longest** **substring** without duplicate characters.",
- "readme_examples": [
- {
- "content": "```\nInput: s = \"abcabcbb\"\nOutput: 3\n```\n**Explanation:** The answer is \"abc\", with the length of 3."
- },
- {
- "content": "```\nInput: s = \"bbbbb\"\nOutput: 1\n```\n**Explanation:** The answer is \"b\", with the length of 1."
- },
- {
- "content": "```\nInput: s = \"pwwkew\"\nOutput: 3\n```\n**Explanation:** The answer is \"wke\", with the length of 3.\nNotice that the answer must be a substring, \"pwke\" is a subsequence and not a substring."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: s = \"abcabcbb\"\nOutput: 3\n```\n**Explanation:** The answer is \"abc\", with the length of 3."
+ },
+ {
+ "content": "```\nInput: s = \"bbbbb\"\nOutput: 1\n```\n**Explanation:** The answer is \"b\", with the length of 1."
+ },
+ {
+ "content": "```\nInput: s = \"pwwkew\"\nOutput: 3\n```\n**Explanation:** The answer is \"wke\", with the length of 3.\nNotice that the answer must be a substring, \"pwke\" is a subsequence and not a substring."
+ }
+ ]
+ },
"readme_constraints": "- 0 <= s.length <= 5 * 10^4\n- s consists of English letters, digits, symbols and spaces.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "length_of_longest_substring",
+ "helpers_run_signature": "(solution_class: type, s: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.length_of_longest_substring(s)",
+ "helpers_assert_name": "length_of_longest_substring",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "length_of_longest_substring",
- "parameters": "s: str",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_length_of_longest_substring, run_length_of_longest_substring\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "LongestSubstringWithoutRepeatingCharacters",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_length_of_longest_substring",
- "parametrize": "s, expected",
- "parametrize_typed": "s: str, expected: int",
- "test_cases": "[('abcabcbb', 3), ('bbbbb', 1), ('pwwkew', 3), ('', 0), ('a', 1), ('au', 2), ('dvdf', 3)]",
- "body": "result = self.solution.length_of_longest_substring(s)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = 'abcabcbb'\nexpected = 3",
- "playground_execution": "result = Solution().length_of_longest_substring(s)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "length_of_longest_substring",
+ "signature": "(self, s: str) -> int",
+ "body": " # TODO: Implement length_of_longest_substring\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_length_of_longest_substring",
+ "signature": "(self, s: str, expected: int)",
+ "parametrize": "s, expected",
+ "test_cases": "[('abcabcbb', 3), ('bbbbb', 1), ('pwwkew', 3), ('', 0), ('a', 1), ('au', 2), ('dvdf', 3), ('abcdef', 6), ('aab', 2), ('tmmzuxt', 5), (' ', 1), (' ', 1), ('abba', 2)]",
+ "body": " result = run_length_of_longest_substring(Solution, s)\n assert_length_of_longest_substring(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_length_of_longest_substring, assert_length_of_longest_substring\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = 'abcabcbb'\nexpected = 3",
+ "playground_run": "result = run_length_of_longest_substring(Solution, s)\nresult",
+ "playground_assert": "assert_length_of_longest_substring(result, expected)"
}
diff --git a/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json b/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json
index f697b50..fab73c4 100644
--- a/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json
+++ b/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json
@@ -5,44 +5,61 @@
"problem_title": "Lowest Common Ancestor of a Binary Search Tree",
"difficulty": "Medium",
"topics": "Tree, Depth-First Search, Binary Search Tree, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST.\n\nAccording to the definition of LCA on Wikipedia: \"The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**).\"",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8\nOutput: 6\n```\n**Explanation:** The LCA of nodes 2 and 8 is 6."
- },
- {
- "content": "\n\n```\nInput: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4\nOutput: 2\n```\n**Explanation:** The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition."
- },
- { "content": "```\nInput: root = [2,1], p = 2, q = 1\nOutput: 2\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8\nOutput: 6\n```\n**Explanation:** The LCA of nodes 2 and 8 is 6."
+ },
+ {
+ "content": "\n\n```\nInput: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4\nOutput: 2\n```\n**Explanation:** The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition."
+ },
+ { "content": "```\nInput: root = [2,1], p = 2, q = 1\nOutput: 2\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range `[2, 10^5]`.\n- `-10^9 <= Node.val <= 10^9`\n- All `Node.val` are **unique**.\n- `p != q`\n- `p` and `q` will exist in the BST.",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "lowest_common_ancestor",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None], p_val: int, q_val: int)",
+ "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n assert root is not None\n p = root.find_node(p_val)\n q = root.find_node(q_val)\n assert p is not None and q is not None\n implementation = solution_class()\n return implementation.lowest_common_ancestor(root, p, q)",
+ "helpers_assert_name": "lowest_common_ancestor",
+ "helpers_assert_signature": "(result: TreeNode[int] | None, expected_val: int) -> bool",
+ "helpers_assert_body": " assert result is not None\n assert result.val == expected_val\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "lowest_common_ancestor",
- "parameters": "root: TreeNode[int] | None, p: TreeNode[int], q: TreeNode[int]",
- "return_type": "TreeNode[int] | None",
- "dummy_return": "None"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py import TreeNode\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "LowestCommonAncestorOfABinarySearchTree",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_lowest_common_ancestor",
- "parametrize": "root_list, p_val, q_val, expected_val",
- "parametrize_typed": "root_list: list[int | None], p_val: int, q_val: int, expected_val: int",
- "test_cases": "[([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 8, 6), ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 4, 2), ([2, 1], 2, 1, 2), ([2, 1], 1, 2, 2), ([6, 2, 8, 0, 4, 7, 9], 0, 4, 2), ([6, 2, 8, 0, 4, 7, 9], 7, 9, 8)]",
- "body": "root = TreeNode[int].from_list(root_list)\nassert root is not None\np = root.find_node(p_val)\nq = root.find_node(q_val)\nassert p is not None and q is not None\nresult = self.solution.lowest_common_ancestor(root, p, q)\nassert result is not None\nassert result.val == expected_val"
- }
- ],
- "playground_imports": "from leetcode_py import TreeNode\nfrom solution import Solution",
- "playground_test_case": "# Example test case\nroot_list = [6, 2, 8, 0, 4, 7, 9, None, None, 3, 5]\np_val = 2\nq_val = 8\nexpected_val = 6",
- "playground_execution": "root = TreeNode[int].from_list(root_list)\nassert root is not None\np = root.find_node(p_val)\nq = root.find_node(q_val)\nassert p is not None and q is not None\nresult = Solution().lowest_common_ancestor(root, p, q)\nresult.val if result else None",
- "playground_assertion": "assert result and result.val == expected_val"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "lowest_common_ancestor",
+ "signature": "(self, root: TreeNode[int] | None, p: TreeNode[int], q: TreeNode[int]) -> TreeNode[int] | None",
+ "body": " # TODO: Implement lowest_common_ancestor\n return None"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_lowest_common_ancestor",
+ "signature": "(self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int)",
+ "parametrize": "root_list, p_val, q_val, expected_val",
+ "test_cases": "[([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 8, 6), ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 4, 2), ([2, 1], 2, 1, 2), ([2, 1], 1, 2, 2), ([6, 2, 8, 0, 4, 7, 9], 0, 4, 2), ([6, 2, 8, 0, 4, 7, 9], 7, 9, 8)]",
+ "body": " result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\n assert_lowest_common_ancestor(result, expected_val)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_lowest_common_ancestor, assert_lowest_common_ancestor\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list = [6, 2, 8, 0, 4, 7, 9, None, None, 3, 5]\np_val = 2\nq_val = 8\nexpected_val = 6",
+ "playground_run": "result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\nresult.val if result else None",
+ "playground_assert": "assert_lowest_common_ancestor(result, expected_val)"
}
diff --git a/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json b/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json
index 2e19705..c1f4b0e 100644
--- a/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json
+++ b/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json
@@ -5,44 +5,61 @@
"problem_title": "Lowest Common Ancestor of a Binary Tree",
"difficulty": "Medium",
"topics": "Tree, Depth-First Search, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.\n\nAccording to the definition of LCA on Wikipedia: \"The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**).\"",
- "readme_examples": [
- {
- "content": "
\n\n```\nInput: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1\nOutput: 3\nExplanation: The LCA of nodes 5 and 1 is 3.\n```"
- },
- {
- "content": "
\n\n```\nInput: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4\nOutput: 5\nExplanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.\n```"
- },
- { "content": "```\nInput: root = [1,2], p = 1, q = 2\nOutput: 1\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "
\n\n```\nInput: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1\nOutput: 3\nExplanation: The LCA of nodes 5 and 1 is 3.\n```"
+ },
+ {
+ "content": "
\n\n```\nInput: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4\nOutput: 5\nExplanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.\n```"
+ },
+ { "content": "```\nInput: root = [1,2], p = 1, q = 2\nOutput: 1\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range [2, 10^5].\n- -10^9 <= Node.val <= 10^9\n- All Node.val are unique.\n- p != q\n- p and q will exist in the tree.",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "lowest_common_ancestor",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None], p_val: int, q_val: int)",
+ "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n assert root is not None\n p = root.find_node(p_val)\n q = root.find_node(q_val)\n assert p is not None and q is not None\n implementation = solution_class()\n return implementation.lowest_common_ancestor(root, p, q)",
+ "helpers_assert_name": "lowest_common_ancestor",
+ "helpers_assert_signature": "(result: TreeNode[int], expected_val: int) -> bool",
+ "helpers_assert_body": " assert result.val == expected_val\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "lowest_common_ancestor",
- "parameters": "root: TreeNode, p: TreeNode, q: TreeNode",
- "return_type": "TreeNode",
- "dummy_return": "root"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "LowestCommonAncestorOfABinaryTree",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_lowest_common_ancestor",
- "parametrize": "root_list, p_val, q_val, expected_val",
- "parametrize_typed": "root_list: list[int | None], p_val: int, q_val: int, expected_val: int",
- "test_cases": "[([3,5,1,6,2,0,8,None,None,7,4], 5, 1, 3), ([3,5,1,6,2,0,8,None,None,7,4], 5, 4, 5), ([1,2], 1, 2, 1)]",
- "body": "root = TreeNode.from_list(root_list)\nassert root is not None\np = root.find_node(p_val)\nq = root.find_node(q_val)\nassert p is not None and q is not None\nresult = self.solution.lowest_common_ancestor(root, p, q)\nassert result is not None\nassert result.val == expected_val"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list = [3,5,1,6,2,0,8,None,None,7,4]\nroot = TreeNode.from_list(root_list)\nassert root is not None\np = root.find_node(5)\nq = root.find_node(1)\nexpected_val = 3",
- "playground_execution": "result = Solution().lowest_common_ancestor(root, p, q)\nresult.val",
- "playground_assertion": "assert result.val == expected_val"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "lowest_common_ancestor",
+ "signature": "(self, root: TreeNode[int], p: TreeNode[int], q: TreeNode[int]) -> TreeNode[int]",
+ "body": " # TODO: Implement lowest_common_ancestor\n return root"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_lowest_common_ancestor",
+ "signature": "(self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int)",
+ "parametrize": "root_list, p_val, q_val, expected_val",
+ "test_cases": "[([3,5,1,6,2,0,8,None,None,7,4], 5, 1, 3), ([3,5,1,6,2,0,8,None,None,7,4], 5, 4, 5), ([1,2], 1, 2, 1), ([2,1], 2, 1, 2), ([3,5,1,6,2,0,8,None,None,7,4], 6, 7, 5), ([3,5,1,6,2,0,8,None,None,7,4], 0, 8, 1)]",
+ "body": " result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\n assert_lowest_common_ancestor(result, expected_val)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_lowest_common_ancestor, assert_lowest_common_ancestor\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list = [3,5,1,6,2,0,8,None,None,7,4]\np_val = 5\nq_val = 1\nexpected_val = 3",
+ "playground_run": "result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\nresult.val",
+ "playground_assert": "assert_lowest_common_ancestor(result, expected_val)"
}
diff --git a/.templates/leetcode/json/lru_cache.json b/.templates/leetcode/json/lru_cache.json
index 3fe70f1..3bc81d9 100644
--- a/.templates/leetcode/json/lru_cache.json
+++ b/.templates/leetcode/json/lru_cache.json
@@ -5,40 +5,65 @@
"problem_title": "LRU Cache",
"difficulty": "Medium",
"topics": "Hash Table, Linked List, Design, Doubly-Linked List",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.\n\nImplement the `LRUCache` class:\n\n- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity\n- `int get(int key)` Return the value of the key if the key exists, otherwise return -1\n- `void put(int key, int value)` Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key\n\nThe functions `get` and `put` must each run in `O(1)` average time complexity.",
- "readme_examples": [
- {
- "content": "```\nInput\n[\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"]\n[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nOutput\n[null, null, null, 1, null, -1, null, -1, 3, 4]\n\nExplanation\nLRUCache lRUCache = new LRUCache(2);\nlRUCache.put(1, 1); // cache is {1=1}\nlRUCache.put(2, 2); // cache is {1=1, 2=2}\nlRUCache.get(1); // return 1\nlRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}\nlRUCache.get(2); // returns -1 (not found)\nlRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}\nlRUCache.get(1); // return -1 (not found)\nlRUCache.get(3); // return 3\nlRUCache.get(4); // return 4\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput\n[\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"]\n[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nOutput\n[null, null, null, 1, null, -1, null, -1, 3, 4]\n\nExplanation\nLRUCache lRUCache = new LRUCache(2);\nlRUCache.put(1, 1); // cache is {1=1}\nlRUCache.put(2, 2); // cache is {1=1, 2=2}\nlRUCache.get(1); // return 1\nlRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}\nlRUCache.get(2); // returns -1 (not found)\nlRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}\nlRUCache.get(1); // return -1 (not found)\nlRUCache.get(3); // return 3\nlRUCache.get(4); // return 4\n```"
+ }
+ ]
+ },
"readme_constraints": "- 1 <= capacity <= 3000\n- 0 <= key <= 10^4\n- 0 <= value <= 10^5\n- At most 2 * 10^5 calls will be made to get and put",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "lru_cache",
+ "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[int]])",
+ "helpers_run_body": " cache = None\n results: list[int | None] = []\n for i, op in enumerate(operations):\n if op == 'LRUCache':\n cache = solution_class(inputs[i][0])\n results.append(None)\n elif op == 'get' and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == 'put' and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\n return results, cache",
+ "helpers_assert_name": "lru_cache",
+ "helpers_assert_signature": "(result: list[int | None], expected: list[int | None]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "__init__", "parameters": "capacity: int", "return_type": "None", "dummy_return": "" },
- { "name": "get", "parameters": "key: int", "return_type": "int", "dummy_return": "-1" },
- {
- "name": "put",
- "parameters": "key: int, value: int",
- "return_type": "None",
- "dummy_return": ""
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import LRUCache",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_lru_cache, run_lru_cache\nfrom .solution import LRUCache",
+ "test_content": "",
"test_class_name": "LRUCache",
- "test_helper_methods": [],
- "test_methods": [
- {
- "name": "test_lru_cache",
- "parametrize": "operations, inputs, expected",
- "parametrize_typed": "operations: list[str], inputs: list[list[int]], expected: list[int | None]",
- "test_cases": "[([\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"], [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], [None, None, None, 1, None, -1, None, -1, 3, 4])]",
- "body": "cache: LRUCache | None = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == \"LRUCache\":\n cache = LRUCache(inputs[i][0])\n results.append(None)\n elif op == \"get\" and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == \"put\" and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\nassert results == expected"
- }
- ],
- "playground_imports": "from solution import LRUCache",
- "playground_test_case": "# Example test case\noperations = ['LRUCache', 'put', 'put', 'get', 'put', 'get', 'put', 'get', 'get', 'get']\ninputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nexpected = [None, None, None, 1, None, -1, None, -1, 3, 4]",
- "playground_execution": "cache = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == 'LRUCache':\n cache = LRUCache(inputs[i][0])\n results.append(None)\n elif op == 'get' and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == 'put' and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\nresults",
- "playground_assertion": "assert results == expected"
+ "test_class_content": "",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "__init__",
+ "signature": "(self, capacity: int) -> None",
+ "body": " # TODO: Initialize LRU cache\n pass"
+ },
+ {
+ "name": "get",
+ "signature": "(self, key: int) -> int",
+ "body": " # TODO: Implement get\n return -1"
+ },
+ {
+ "name": "put",
+ "signature": "(self, key: int, value: int) -> None",
+ "body": " # TODO: Implement put\n pass"
+ }
+ ]
+ },
+ "_test_helper_methods": { "list": [] },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_lru_cache",
+ "signature": "(self, operations: list[str], inputs: list[list[int]], expected: list[int | None])",
+ "parametrize": "operations, inputs, expected",
+ "test_cases": "[(['LRUCache', 'put', 'put', 'get', 'put', 'get', 'put', 'get', 'get', 'get'], [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], [None, None, None, 1, None, -1, None, -1, 3, 4]), (['LRUCache', 'get', 'put', 'get', 'put', 'put', 'get', 'get'], [[2], [2], [2, 6], [1], [1, 5], [1, 2], [1], [2]], [None, -1, None, -1, None, None, 2, 6]), (['LRUCache', 'put', 'get', 'put', 'get', 'get'], [[1], [2, 1], [2], [3, 2], [2], [3]], [None, None, 1, None, -1, 2])]",
+ "body": " result, _ = run_lru_cache(LRUCache, operations, inputs)\n assert_lru_cache(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_lru_cache, assert_lru_cache\nfrom solution import LRUCache",
+ "playground_setup": "# Example test case\noperations = ['LRUCache', 'put', 'put', 'get', 'put', 'get', 'put', 'get', 'get', 'get']\ninputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nexpected = [None, None, None, 1, None, -1, None, -1, 3, 4]",
+ "playground_run": "result, cache = run_lru_cache(LRUCache, operations, inputs)\nprint(result)\ncache",
+ "playground_assert": "assert_lru_cache(result, expected)"
}
diff --git a/.templates/leetcode/json/majority_element.json b/.templates/leetcode/json/majority_element.json
index 135a1cf..1fc620a 100644
--- a/.templates/leetcode/json/majority_element.json
+++ b/.templates/leetcode/json/majority_element.json
@@ -5,39 +5,56 @@
"problem_title": "Majority Element",
"difficulty": "Easy",
"topics": "Array, Hash Table, Divide and Conquer, Sorting, Counting",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an array `nums` of size `n`, return the majority element.\n\nThe majority element is the element that appears more than `\u230an / 2\u230b` times. You may assume that the majority element always exists in the array.",
- "readme_examples": [
- { "content": "```\nInput: nums = [3,2,3]\nOutput: 3\n```" },
- { "content": "```\nInput: nums = [2,2,1,1,1,2,2]\nOutput: 2\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: nums = [3,2,3]\nOutput: 3\n```" },
+ { "content": "```\nInput: nums = [2,2,1,1,1,2,2]\nOutput: 2\n```" }
+ ]
+ },
"readme_constraints": "- n == nums.length\n- 1 <= n <= 5 * 10^4\n- -10^9 <= nums[i] <= 10^9",
"readme_additional": "**Follow-up:** Could you solve the problem in linear time and in O(1) space?",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "majority_element",
+ "helpers_run_signature": "(solution_class: type, nums: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.majority_element(nums)",
+ "helpers_assert_name": "majority_element",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "majority_element",
- "parameters": "nums: list[int]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_majority_element, run_majority_element\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MajorityElement",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_majority_element",
- "parametrize": "nums, expected",
- "parametrize_typed": "nums: list[int], expected: int",
- "test_cases": "[([3,2,3], 3), ([2,2,1,1,1,2,2], 2), ([1], 1), ([1,1,2], 1), ([2,2,2,1,1], 2)]",
- "body": "result = self.solution.majority_element(nums)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [3,2,3]\nexpected = 3",
- "playground_execution": "result = Solution().majority_element(nums)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "majority_element",
+ "signature": "(self, nums: list[int]) -> int",
+ "body": " # TODO: Implement majority_element\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_majority_element",
+ "signature": "(self, nums: list[int], expected: int)",
+ "parametrize": "nums, expected",
+ "test_cases": "[([3,2,3], 3), ([2,2,1,1,1,2,2], 2), ([1], 1), ([1,1,2], 1), ([2,2,2,1,1], 2), ([5,5,5,5,1,2,3], 5), ([1,2,3,4,4,4,4], 4), ([0,0,0], 0), ([-1,-1,-1,1,1], -1)]",
+ "body": " result = run_majority_element(Solution, nums)\n assert_majority_element(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_majority_element, assert_majority_element\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [3,2,3]\nexpected = 3",
+ "playground_run": "result = run_majority_element(Solution, nums)\nresult",
+ "playground_assert": "assert_majority_element(result, expected)"
}
diff --git a/.templates/leetcode/json/maximum_depth_of_binary_tree.json b/.templates/leetcode/json/maximum_depth_of_binary_tree.json
index d701c4f..fed5f39 100644
--- a/.templates/leetcode/json/maximum_depth_of_binary_tree.json
+++ b/.templates/leetcode/json/maximum_depth_of_binary_tree.json
@@ -5,41 +5,58 @@
"problem_title": "Maximum Depth of Binary Tree",
"difficulty": "Easy",
"topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given the `root` of a binary tree, return *its maximum depth*.\n\nA binary tree's **maximum depth** is the number of nodes along the longest path from the root node down to the farthest leaf node.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: 3\n```"
- },
- { "content": "```\nInput: root = [1,null,2]\nOutput: 2\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: 3\n```"
+ },
+ { "content": "```\nInput: root = [1,null,2]\nOutput: 2\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range `[0, 10^4]`.\n- `-100 <= Node.val <= 100`",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "max_depth",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None])",
+ "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.max_depth(root)",
+ "helpers_assert_name": "max_depth",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "max_depth",
- "parameters": "root: TreeNode[int] | None",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_max_depth, run_max_depth\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MaximumDepthOfBinaryTree",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_max_depth",
- "parametrize": "root_list, expected",
- "parametrize_typed": "root_list: list[int | None], expected: int",
- "test_cases": "[([3, 9, 20, None, None, 15, 7], 3), ([1, None, 2], 2), ([], 0)]",
- "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.max_depth(root)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list = [3, 9, 20, None, None, 15, 7]\nexpected = 3",
- "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().max_depth(root)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "max_depth",
+ "signature": "(self, root: TreeNode[int] | None) -> int",
+ "body": " # TODO: Implement max_depth\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_max_depth",
+ "signature": "(self, root_list: list[int | None], expected: int)",
+ "parametrize": "root_list, expected",
+ "test_cases": "[([3, 9, 20, None, None, 15, 7], 3), ([1, None, 2], 2), ([], 0), ([1], 1), ([1, 2], 2), ([1, 2, 3], 2), ([1, 2, 3, 4], 3), ([1, None, 2, None, 3], 3)]",
+ "body": " result = run_max_depth(Solution, root_list)\n assert_max_depth(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_max_depth, assert_max_depth\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list = [3, 9, 20, None, None, 15, 7]\nexpected = 3",
+ "playground_run": "result = run_max_depth(Solution, root_list)\nresult",
+ "playground_assert": "assert_max_depth(result, expected)"
}
diff --git a/.templates/leetcode/json/maximum_profit_in_job_scheduling.json b/.templates/leetcode/json/maximum_profit_in_job_scheduling.json
index 53b2f7c..c765195 100644
--- a/.templates/leetcode/json/maximum_profit_in_job_scheduling.json
+++ b/.templates/leetcode/json/maximum_profit_in_job_scheduling.json
@@ -5,46 +5,63 @@
"problem_title": "Maximum Profit in Job Scheduling",
"difficulty": "Hard",
"topics": "Array, Binary Search, Dynamic Programming, Sorting",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "We have `n` jobs, where every job is scheduled to be done from `startTime[i]` to `endTime[i]`, obtaining a profit of `profit[i]`.\n\nYou're given the `startTime`, `endTime` and `profit` arrays, return the maximum profit you can take such that there are no two jobs in the subset with overlapping time range.\n\nIf you choose a job that ends at time `X` you will be able to start another job that starts at time `X`.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70]\nOutput: 120\n```\n**Explanation:** The subset chosen is the first and fourth job. Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70."
- },
- {
- "content": "\n\n```\nInput: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60]\nOutput: 150\n```\n**Explanation:** The subset chosen is the first, fourth and fifth job. Profit obtained 150 = 20 + 70 + 60."
- },
- {
- "content": "\n\n```\nInput: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4]\nOutput: 6\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70]\nOutput: 120\n```\n**Explanation:** The subset chosen is the first and fourth job. Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70."
+ },
+ {
+ "content": "\n\n```\nInput: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60]\nOutput: 150\n```\n**Explanation:** The subset chosen is the first, fourth and fifth job. Profit obtained 150 = 20 + 70 + 60."
+ },
+ {
+ "content": "\n\n```\nInput: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4]\nOutput: 6\n```"
+ }
+ ]
+ },
"readme_constraints": "- `1 <= startTime.length == endTime.length == profit.length <= 5 * 10^4`\n- `1 <= startTime[i] < endTime[i] <= 10^9`\n- `1 <= profit[i] <= 10^4`",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "job_scheduling",
+ "helpers_run_signature": "(solution_class: type, start_time: list[int], end_time: list[int], profit: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.job_scheduling(start_time, end_time, profit)",
+ "helpers_assert_name": "job_scheduling",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "job_scheduling",
- "parameters": "start_time: list[int], end_time: list[int], profit: list[int]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_job_scheduling, run_job_scheduling\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MaximumProfitInJobScheduling",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_job_scheduling",
- "parametrize": "start_time, end_time, profit, expected",
- "parametrize_typed": "start_time: list[int], end_time: list[int], profit: list[int], expected: int",
- "test_cases": "[([1, 2, 3, 3], [3, 4, 5, 6], [50, 10, 40, 70], 120), ([1, 2, 3, 4, 6], [3, 5, 10, 6, 9], [20, 20, 100, 70, 60], 150), ([1, 1, 1], [2, 3, 4], [5, 6, 4], 6)]",
- "body": "result = self.solution.job_scheduling(start_time, end_time, profit)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nstart_time = [1, 2, 3, 3]\nend_time = [3, 4, 5, 6]\nprofit = [50, 10, 40, 70]\nexpected = 120",
- "playground_execution": "result = Solution().job_scheduling(start_time, end_time, profit)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "job_scheduling",
+ "signature": "(self, start_time: list[int], end_time: list[int], profit: list[int]) -> int",
+ "body": " # TODO: Implement job_scheduling\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_job_scheduling",
+ "signature": "(self, start_time: list[int], end_time: list[int], profit: list[int], expected: int)",
+ "parametrize": "start_time, end_time, profit, expected",
+ "test_cases": "[([1, 2, 3, 3], [3, 4, 5, 6], [50, 10, 40, 70], 120), ([1, 2, 3, 4, 6], [3, 5, 10, 6, 9], [20, 20, 100, 70, 60], 150), ([1, 1, 1], [2, 3, 4], [5, 6, 4], 6), ([1], [2], [100], 100)]",
+ "body": " result = run_job_scheduling(Solution, start_time, end_time, profit)\n assert_job_scheduling(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_job_scheduling, assert_job_scheduling\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nstart_time = [1, 2, 3, 3]\nend_time = [3, 4, 5, 6]\nprofit = [50, 10, 40, 70]\nexpected = 120",
+ "playground_run": "result = run_job_scheduling(Solution, start_time, end_time, profit)\nresult",
+ "playground_assert": "assert_job_scheduling(result, expected)"
}
diff --git a/.templates/leetcode/json/maximum_subarray.json b/.templates/leetcode/json/maximum_subarray.json
index 50da7c2..bb4e76a 100644
--- a/.templates/leetcode/json/maximum_subarray.json
+++ b/.templates/leetcode/json/maximum_subarray.json
@@ -5,46 +5,63 @@
"problem_title": "Maximum Subarray",
"difficulty": "Medium",
"topics": "Array, Divide and Conquer, Dynamic Programming",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an integer array `nums`, find the subarray with the largest sum, and return its sum.",
- "readme_examples": [
- {
- "content": "```\nInput: nums = [-2,1,-3,4,-1,2,1,-5,4]\nOutput: 6\n```\n**Explanation:** The subarray [4,-1,2,1] has the largest sum 6."
- },
- {
- "content": "```\nInput: nums = [1]\nOutput: 1\n```\n**Explanation:** The subarray [1] has the largest sum 1."
- },
- {
- "content": "```\nInput: nums = [5,4,-1,7,8]\nOutput: 23\n```\n**Explanation:** The subarray [5,4,-1,7,8] has the largest sum 23."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: nums = [-2,1,-3,4,-1,2,1,-5,4]\nOutput: 6\n```\n**Explanation:** The subarray [4,-1,2,1] has the largest sum 6."
+ },
+ {
+ "content": "```\nInput: nums = [1]\nOutput: 1\n```\n**Explanation:** The subarray [1] has the largest sum 1."
+ },
+ {
+ "content": "```\nInput: nums = [5,4,-1,7,8]\nOutput: 23\n```\n**Explanation:** The subarray [5,4,-1,7,8] has the largest sum 23."
+ }
+ ]
+ },
"readme_constraints": "- `1 <= nums.length <= 10^5`\n- `-10^4 <= nums[i] <= 10^4`",
"readme_additional": "**Follow up:** If you have figured out the `O(n)` solution, try coding another solution using the **divide and conquer** approach, which is more subtle.",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "max_sub_array",
+ "helpers_run_signature": "(solution_class: type, nums: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.max_sub_array(nums)",
+ "helpers_assert_name": "max_sub_array",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "max_sub_array",
- "parameters": "nums: list[int]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_max_sub_array, run_max_sub_array\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MaximumSubarray",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_max_sub_array",
- "parametrize": "nums, expected",
- "parametrize_typed": "nums: list[int], expected: int",
- "test_cases": "[([-2, 1, -3, 4, -1, 2, 1, -5, 4], 6), ([1], 1), ([5, 4, -1, 7, 8], 23), ([-1], -1), ([-2, -1], -1), ([1, 2, 3, 4, 5], 15), ([-5, -2, -8, -1], -1)]",
- "body": "result = self.solution.max_sub_array(nums)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]\nexpected = 6",
- "playground_execution": "result = Solution().max_sub_array(nums)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "max_sub_array",
+ "signature": "(self, nums: list[int]) -> int",
+ "body": " # TODO: Implement max_sub_array\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_max_sub_array",
+ "signature": "(self, nums: list[int], expected: int)",
+ "parametrize": "nums, expected",
+ "test_cases": "[([-2, 1, -3, 4, -1, 2, 1, -5, 4], 6), ([1], 1), ([5, 4, -1, 7, 8], 23), ([-1], -1), ([-2, -1], -1), ([1, 2, 3, 4, 5], 15), ([-5, -2, -8, -1], -1)]",
+ "body": " result = run_max_sub_array(Solution, nums)\n assert_max_sub_array(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_max_sub_array, assert_max_sub_array\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]\nexpected = 6",
+ "playground_run": "result = run_max_sub_array(Solution, nums)\nresult",
+ "playground_assert": "assert_max_sub_array(result, expected)"
}
diff --git a/.templates/leetcode/json/merge_intervals.json b/.templates/leetcode/json/merge_intervals.json
index 071e1bf..7c0efbf 100644
--- a/.templates/leetcode/json/merge_intervals.json
+++ b/.templates/leetcode/json/merge_intervals.json
@@ -5,46 +5,63 @@
"problem_title": "Merge Intervals",
"difficulty": "Medium",
"topics": "Array, Sorting",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an array of `intervals` where `intervals[i] = [starti, endi]`, merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.",
- "readme_examples": [
- {
- "content": "```\nInput: intervals = [[1,3],[2,6],[8,10],[15,18]]\nOutput: [[1,6],[8,10],[15,18]]\n```\n**Explanation:** Since intervals [1,3] and [2,6] overlap, merge them into [1,6]."
- },
- {
- "content": "```\nInput: intervals = [[1,4],[4,5]]\nOutput: [[1,5]]\n```\n**Explanation:** Intervals [1,4] and [4,5] are considered overlapping."
- },
- {
- "content": "```\nInput: intervals = [[4,7],[1,4]]\nOutput: [[1,7]]\n```\n**Explanation:** Intervals [1,4] and [4,7] are considered overlapping."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: intervals = [[1,3],[2,6],[8,10],[15,18]]\nOutput: [[1,6],[8,10],[15,18]]\n```\n**Explanation:** Since intervals [1,3] and [2,6] overlap, merge them into [1,6]."
+ },
+ {
+ "content": "```\nInput: intervals = [[1,4],[4,5]]\nOutput: [[1,5]]\n```\n**Explanation:** Intervals [1,4] and [4,5] are considered overlapping."
+ },
+ {
+ "content": "```\nInput: intervals = [[4,7],[1,4]]\nOutput: [[1,7]]\n```\n**Explanation:** Intervals [1,4] and [4,7] are considered overlapping."
+ }
+ ]
+ },
"readme_constraints": "- `1 <= intervals.length <= 10^4`\n- `intervals[i].length == 2`\n- `0 <= starti <= endi <= 10^4`",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "merge",
+ "helpers_run_signature": "(solution_class: type, intervals: list[list[int]])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.merge(intervals)",
+ "helpers_assert_name": "merge",
+ "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "merge",
- "parameters": "intervals: list[list[int]]",
- "return_type": "list[list[int]]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_merge, run_merge\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MergeIntervals",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_merge",
- "parametrize": "intervals, expected",
- "parametrize_typed": "intervals: list[list[int]], expected: list[list[int]]",
- "test_cases": "[([[1,3],[2,6],[8,10],[15,18]], [[1,6],[8,10],[15,18]]), ([[1,4],[4,5]], [[1,5]]), ([[4,7],[1,4]], [[1,7]])]",
- "body": "result = self.solution.merge(intervals)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nintervals = [[1,3],[2,6],[8,10],[15,18]]\nexpected = [[1,6],[8,10],[15,18]]",
- "playground_execution": "result = Solution().merge(intervals)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "merge",
+ "signature": "(self, intervals: list[list[int]]) -> list[list[int]]",
+ "body": " # TODO: Implement merge\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_merge",
+ "signature": "(self, intervals: list[list[int]], expected: list[list[int]])",
+ "parametrize": "intervals, expected",
+ "test_cases": "[([[1,3],[2,6],[8,10],[15,18]], [[1,6],[8,10],[15,18]]), ([[1,4],[4,5]], [[1,5]]), ([[4,7],[1,4]], [[1,7]]), ([[1,3]], [[1,3]]), ([[1,4],[2,3]], [[1,4]]), ([[1,2],[3,4],[5,6]], [[1,2],[3,4],[5,6]])]",
+ "body": " result = run_merge(Solution, intervals)\n assert_merge(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_merge, assert_merge\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nintervals = [[1,3],[2,6],[8,10],[15,18]]\nexpected = [[1,6],[8,10],[15,18]]",
+ "playground_run": "result = run_merge(Solution, intervals)\nresult",
+ "playground_assert": "assert_merge(result, expected)"
}
diff --git a/.templates/leetcode/json/merge_k_sorted_lists.json b/.templates/leetcode/json/merge_k_sorted_lists.json
index f5a1e19..3031430 100644
--- a/.templates/leetcode/json/merge_k_sorted_lists.json
+++ b/.templates/leetcode/json/merge_k_sorted_lists.json
@@ -5,42 +5,59 @@
"problem_title": "Merge k Sorted Lists",
"difficulty": "Hard",
"topics": "Linked List, Divide and Conquer, Heap (Priority Queue), Merge Sort",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given an array of `k` linked-lists `lists`, each linked-list is sorted in ascending order.\n\n*Merge all the linked-lists into one sorted linked-list and return it.*",
- "readme_examples": [
- {
- "content": "```\nInput: lists = [[1,4,5],[1,3,4],[2,6]]\nOutput: [1,1,2,3,4,4,5,6]\n```\n**Explanation:** The linked-lists are:\n```\n[\n 1->4->5,\n 1->3->4,\n 2->6\n]\n```\nmerging them into one sorted linked list:\n```\n1->1->2->3->4->4->5->6\n```"
- },
- { "content": "```\nInput: lists = []\nOutput: []\n```" },
- { "content": "```\nInput: lists = [[]]\nOutput: []\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: lists = [[1,4,5],[1,3,4],[2,6]]\nOutput: [1,1,2,3,4,4,5,6]\n```\n**Explanation:** The linked-lists are:\n```\n[\n 1->4->5,\n 1->3->4,\n 2->6\n]\n```\nmerging them into one sorted linked list:\n```\n1->1->2->3->4->4->5->6\n```"
+ },
+ { "content": "```\nInput: lists = []\nOutput: []\n```" },
+ { "content": "```\nInput: lists = [[]]\nOutput: []\n```" }
+ ]
+ },
"readme_constraints": "- `k == lists.length`\n- `0 <= k <= 10^4`\n- `0 <= lists[i].length <= 500`\n- `-10^4 <= lists[i][j] <= 10^4`\n- `lists[i]` is sorted in ascending order.\n- The sum of `lists[i].length` will not exceed `10^4`.",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import ListNode",
+ "helpers_content": "",
+ "helpers_run_name": "merge_k_lists",
+ "helpers_run_signature": "(solution_class: type, lists_data: list[list[int]])",
+ "helpers_run_body": " lists = [ListNode[int].from_list(lst) for lst in lists_data]\n implementation = solution_class()\n return implementation.merge_k_lists(lists)",
+ "helpers_assert_name": "merge_k_lists",
+ "helpers_assert_signature": "(result: ListNode[int] | None, expected_data: list[int]) -> bool",
+ "helpers_assert_body": " expected = ListNode[int].from_list(expected_data)\n assert result == expected\n return True",
"solution_imports": "from leetcode_py import ListNode",
- "solution_methods": [
- {
- "name": "merge_k_lists",
- "parameters": "lists: list[ListNode | None]",
- "return_type": "ListNode | None",
- "dummy_return": "None"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import ListNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_merge_k_lists, run_merge_k_lists\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MergeKSortedLists",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_merge_k_lists",
- "parametrize": "lists_data, expected_data",
- "parametrize_typed": "lists_data: list[list[int]], expected_data: list[int]",
- "test_cases": "[([[1, 4, 5], [1, 3, 4], [2, 6]], [1, 1, 2, 3, 4, 4, 5, 6]), ([], []), ([[]], []), ([[1]], [1]), ([[1, 2], [3, 4]], [1, 2, 3, 4]), ([[5], [1, 3], [2, 4, 6]], [1, 2, 3, 4, 5, 6]), ([[-1, 0, 1], [-2, 2]], [-2, -1, 0, 1, 2]), ([[1, 1, 1], [2, 2, 2]], [1, 1, 1, 2, 2, 2]), ([[]], []), ([[], [1], []], [1])]",
- "body": "lists = [ListNode.from_list(lst) for lst in lists_data]\nresult = self.solution.merge_k_lists(lists)\nexpected = ListNode.from_list(expected_data)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import ListNode",
- "playground_test_case": "# Example test case\nlists_data = [[1, 4, 5], [1, 3, 4], [2, 6]]\nlists = [ListNode.from_list(lst) for lst in lists_data]\nexpected_data = [1, 1, 2, 3, 4, 4, 5, 6]",
- "playground_execution": "result = Solution().merge_k_lists(lists)\nListNode.to_list(result) if result else []",
- "playground_assertion": "expected = ListNode.from_list(expected_data)\nassert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "merge_k_lists",
+ "signature": "(self, lists: list[ListNode[int] | None]) -> ListNode[int] | None",
+ "body": " # TODO: Implement merge_k_lists\n return None"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_merge_k_lists",
+ "signature": "(self, lists_data: list[list[int]], expected_data: list[int])",
+ "parametrize": "lists_data, expected_data",
+ "test_cases": "[([[1, 4, 5], [1, 3, 4], [2, 6]], [1, 1, 2, 3, 4, 4, 5, 6]), ([], []), ([[]], []), ([[1]], [1]), ([[1, 2], [3, 4]], [1, 2, 3, 4]), ([[5], [1, 3], [2, 4, 6]], [1, 2, 3, 4, 5, 6]), ([[-1, 0, 1], [-2, 2]], [-2, -1, 0, 1, 2]), ([[1, 1, 1], [2, 2, 2]], [1, 1, 1, 2, 2, 2]), ([[], [1], []], [1])]",
+ "body": " result = run_merge_k_lists(Solution, lists_data)\n assert_merge_k_lists(result, expected_data)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_merge_k_lists, assert_merge_k_lists\nfrom solution import Solution\nfrom leetcode_py import ListNode",
+ "playground_setup": "# Example test case\nlists_data = [[1, 4, 5], [1, 3, 4], [2, 6]]\nexpected_data = [1, 1, 2, 3, 4, 4, 5, 6]",
+ "playground_run": "result = run_merge_k_lists(Solution, lists_data)\nListNode[int].to_list(result) if result else []",
+ "playground_assert": "assert_merge_k_lists(result, expected_data)"
}
diff --git a/.templates/leetcode/json/merge_two_sorted_lists.json b/.templates/leetcode/json/merge_two_sorted_lists.json
index f00c2a1..fe7dfb6 100644
--- a/.templates/leetcode/json/merge_two_sorted_lists.json
+++ b/.templates/leetcode/json/merge_two_sorted_lists.json
@@ -5,42 +5,59 @@
"problem_title": "Merge Two Sorted Lists",
"difficulty": "Easy",
"topics": "Linked List, Recursion",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given the heads of two sorted linked lists `list1` and `list2`.\n\nMerge the two lists into one **sorted** list. The list should be made by splicing together the nodes of the first two lists.\n\nReturn *the head of the merged linked list*.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: list1 = [1,2,4], list2 = [1,3,4]\nOutput: [1,1,2,3,4,4]\n```"
- },
- { "content": "```\nInput: list1 = [], list2 = []\nOutput: []\n```" },
- { "content": "```\nInput: list1 = [], list2 = [0]\nOutput: [0]\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: list1 = [1,2,4], list2 = [1,3,4]\nOutput: [1,1,2,3,4,4]\n```"
+ },
+ { "content": "```\nInput: list1 = [], list2 = []\nOutput: []\n```" },
+ { "content": "```\nInput: list1 = [], list2 = [0]\nOutput: [0]\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in both lists is in the range `[0, 50]`.\n- `-100 <= Node.val <= 100`\n- Both `list1` and `list2` are sorted in **non-decreasing** order.",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import ListNode",
+ "helpers_content": "",
+ "helpers_run_name": "merge_two_lists",
+ "helpers_run_signature": "(solution_class: type, list1_vals: list[int], list2_vals: list[int])",
+ "helpers_run_body": " list1 = ListNode[int].from_list(list1_vals)\n list2 = ListNode[int].from_list(list2_vals)\n implementation = solution_class()\n return implementation.merge_two_lists(list1, list2)",
+ "helpers_assert_name": "merge_two_lists",
+ "helpers_assert_signature": "(result: ListNode[int] | None, expected_vals: list[int]) -> bool",
+ "helpers_assert_body": " expected = ListNode[int].from_list(expected_vals)\n assert result == expected\n return True",
"solution_imports": "from leetcode_py import ListNode",
- "solution_methods": [
- {
- "name": "merge_two_lists",
- "parameters": "list1: ListNode | None, list2: ListNode | None",
- "return_type": "ListNode | None",
- "dummy_return": "None"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import ListNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_merge_two_lists, run_merge_two_lists\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MergeTwoSortedLists",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_merge_two_lists",
- "parametrize": "list1_vals, list2_vals, expected_vals",
- "parametrize_typed": "list1_vals: list[int], list2_vals: list[int], expected_vals: list[int]",
- "test_cases": "[([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]), ([], [], []), ([], [0], [0]), ([1], [2], [1, 2]), ([2], [1], [1, 2])]",
- "body": "list1 = ListNode.from_list(list1_vals)\nlist2 = ListNode.from_list(list2_vals)\nexpected = ListNode.from_list(expected_vals)\nresult = self.solution.merge_two_lists(list1, list2)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import ListNode",
- "playground_test_case": "# Example test case\nlist1_vals = [1, 2, 4]\nlist2_vals = [1, 3, 4]\nexpected_vals = [1, 1, 2, 3, 4, 4]",
- "playground_execution": "list1 = ListNode.from_list(list1_vals)\nlist2 = ListNode.from_list(list2_vals)\nresult = Solution().merge_two_lists(list1, list2)\nresult",
- "playground_assertion": "expected = ListNode.from_list(expected_vals)\nassert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "merge_two_lists",
+ "signature": "(self, list1: ListNode[int] | None, list2: ListNode[int] | None) -> ListNode[int] | None",
+ "body": " # TODO: Implement merge_two_lists\n return None"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_merge_two_lists",
+ "signature": "(self, list1_vals: list[int], list2_vals: list[int], expected_vals: list[int])",
+ "parametrize": "list1_vals, list2_vals, expected_vals",
+ "test_cases": "[([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]), ([], [], []), ([], [0], [0]), ([1], [2], [1, 2]), ([2], [1], [1, 2]), ([1, 3, 5], [2, 4, 6], [1, 2, 3, 4, 5, 6]), ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2])]",
+ "body": " result = run_merge_two_lists(Solution, list1_vals, list2_vals)\n assert_merge_two_lists(result, expected_vals)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_merge_two_lists, assert_merge_two_lists\nfrom solution import Solution\nfrom leetcode_py import ListNode",
+ "playground_setup": "# Example test case\nlist1_vals = [1, 2, 4]\nlist2_vals = [1, 3, 4]\nexpected_vals = [1, 1, 2, 3, 4, 4]",
+ "playground_run": "result = run_merge_two_lists(Solution, list1_vals, list2_vals)\nListNode[int].to_list(result) if result else []",
+ "playground_assert": "assert_merge_two_lists(result, expected_vals)"
}
diff --git a/.templates/leetcode/json/middle_of_the_linked_list.json b/.templates/leetcode/json/middle_of_the_linked_list.json
index ac2deb4..9e6b152 100644
--- a/.templates/leetcode/json/middle_of_the_linked_list.json
+++ b/.templates/leetcode/json/middle_of_the_linked_list.json
@@ -5,43 +5,60 @@
"problem_title": "Middle of the Linked List",
"difficulty": "Easy",
"topics": "Linked List, Two Pointers",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given the `head` of a singly linked list, return *the middle node of the linked list*.\n\nIf there are two middle nodes, return **the second middle** node.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: head = [1,2,3,4,5]\nOutput: [3,4,5]\n```\n**Explanation:** The middle node of the list is node 3."
- },
- {
- "content": "\n\n```\nInput: head = [1,2,3,4,5,6]\nOutput: [4,5,6]\n```\n**Explanation:** Since the list has two middle nodes with values 3 and 4, we return the second one."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: head = [1,2,3,4,5]\nOutput: [3,4,5]\n```\n**Explanation:** The middle node of the list is node 3."
+ },
+ {
+ "content": "\n\n```\nInput: head = [1,2,3,4,5,6]\nOutput: [4,5,6]\n```\n**Explanation:** Since the list has two middle nodes with values 3 and 4, we return the second one."
+ }
+ ]
+ },
"readme_constraints": "- The number of nodes in the list is in the range `[1, 100]`.\n- `1 <= Node.val <= 100`",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import ListNode",
+ "helpers_content": "",
+ "helpers_run_name": "middle_node",
+ "helpers_run_signature": "(solution_class: type, head_list: list[int])",
+ "helpers_run_body": " head = ListNode[int].from_list(head_list)\n implementation = solution_class()\n return implementation.middle_node(head)",
+ "helpers_assert_name": "middle_node",
+ "helpers_assert_signature": "(result: ListNode[int] | None, expected_list: list[int]) -> bool",
+ "helpers_assert_body": " expected = ListNode[int].from_list(expected_list)\n assert result == expected\n return True",
"solution_imports": "from leetcode_py import ListNode",
- "solution_methods": [
- {
- "name": "middle_node",
- "parameters": "head: ListNode | None",
- "return_type": "ListNode | None",
- "dummy_return": "None"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import ListNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_middle_node, run_middle_node\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MiddleOfTheLinkedList",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_middle_node",
- "parametrize": "head_list, expected_list",
- "parametrize_typed": "head_list: list[int], expected_list: list[int]",
- "test_cases": "[([1, 2, 3, 4, 5], [3, 4, 5]), ([1, 2, 3, 4, 5, 6], [4, 5, 6]), ([1], [1]), ([1, 2], [2])]",
- "body": "head = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)\nresult = self.solution.middle_node(head)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import ListNode",
- "playground_test_case": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nexpected_list = [3, 4, 5]",
- "playground_execution": "head = ListNode.from_list(head_list)\nresult = Solution().middle_node(head)\nresult",
- "playground_assertion": "expected = ListNode.from_list(expected_list)\nassert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "middle_node",
+ "signature": "(self, head: ListNode[int] | None) -> ListNode[int] | None",
+ "body": " # TODO: Implement middle_node\n return None"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_middle_node",
+ "signature": "(self, head_list: list[int], expected_list: list[int])",
+ "parametrize": "head_list, expected_list",
+ "test_cases": "[([1, 2, 3, 4, 5], [3, 4, 5]), ([1, 2, 3, 4, 5, 6], [4, 5, 6]), ([1], [1]), ([1, 2], [2]), ([1, 2, 3], [2, 3]), ([1, 2, 3, 4], [3, 4]), ([10, 20, 30, 40, 50, 60, 70], [40, 50, 60, 70])]",
+ "body": " result = run_middle_node(Solution, head_list)\n assert_middle_node(result, expected_list)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_middle_node, assert_middle_node\nfrom solution import Solution\nfrom leetcode_py import ListNode",
+ "playground_setup": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nexpected_list = [3, 4, 5]",
+ "playground_run": "result = run_middle_node(Solution, head_list)\nListNode[int].to_list(result) if result else []",
+ "playground_assert": "assert_middle_node(result, expected_list)"
}
diff --git a/.templates/leetcode/json/min_stack.json b/.templates/leetcode/json/min_stack.json
index 1ed8390..5e902b9 100644
--- a/.templates/leetcode/json/min_stack.json
+++ b/.templates/leetcode/json/min_stack.json
@@ -5,37 +5,75 @@
"problem_title": "Min Stack",
"difficulty": "Medium",
"topics": "Stack, Design",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.\n\nImplement the `MinStack` class:\n\n- `MinStack()` initializes the stack object.\n- `void push(int val)` pushes the element `val` onto the stack.\n- `void pop()` removes the element on the top of the stack.\n- `int top()` gets the top element of the stack.\n- `int getMin()` retrieves the minimum element in the stack.\n\nYou must implement a solution with `O(1)` time complexity for each function.",
- "readme_examples": [
- {
- "content": "```\nInput\n[\"MinStack\",\"push\",\"push\",\"push\",\"getMin\",\"pop\",\"top\",\"getMin\"]\n[[],[-2],[0],[-3],[],[],[],[]]\n\nOutput\n[null,null,null,null,-3,null,0,-2]\n```\n**Explanation:**\n```\nMinStack minStack = new MinStack();\nminStack.push(-2);\nminStack.push(0);\nminStack.push(-3);\nminStack.getMin(); // return -3\nminStack.pop();\nminStack.top(); // return 0\nminStack.getMin(); // return -2\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput\n[\"MinStack\",\"push\",\"push\",\"push\",\"getMin\",\"pop\",\"top\",\"getMin\"]\n[[],[-2],[0],[-3],[],[],[],[]]\n\nOutput\n[null,null,null,null,-3,null,0,-2]\n```\n**Explanation:**\n```\nMinStack minStack = new MinStack();\nminStack.push(-2);\nminStack.push(0);\nminStack.push(-3);\nminStack.getMin(); // return -3\nminStack.pop();\nminStack.top(); // return 0\nminStack.getMin(); // return -2\n```"
+ }
+ ]
+ },
"readme_constraints": "- `-2^31 <= val <= 2^31 - 1`\n- Methods `pop`, `top` and `getMin` operations will always be called on **non-empty** stacks.\n- At most `3 * 10^4` calls will be made to `push`, `pop`, `top`, and `getMin`.",
"readme_additional": "",
+ "helpers_imports": "from typing import Any",
+ "helpers_content": "",
+ "helpers_run_name": "min_stack_operations",
+ "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[int]])",
+ "helpers_run_body": " stack: Any = None\n results: list[int | None] = []\n for i, op in enumerate(operations):\n if op == 'MinStack':\n stack = solution_class()\n results.append(None)\n elif op == 'push' and stack is not None:\n stack.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and stack is not None:\n stack.pop()\n results.append(None)\n elif op == 'top' and stack is not None:\n results.append(stack.top())\n elif op == 'getMin' and stack is not None:\n results.append(stack.get_min())\n return results",
+ "helpers_assert_name": "min_stack_operations",
+ "helpers_assert_signature": "(result: list[int | None], expected: list[int | None]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "__init__", "parameters": "", "return_type": "None", "dummy_return": "" },
- { "name": "push", "parameters": "val: int", "return_type": "None", "dummy_return": "" },
- { "name": "pop", "parameters": "", "return_type": "None", "dummy_return": "" },
- { "name": "top", "parameters": "", "return_type": "int", "dummy_return": "0" },
- { "name": "get_min", "parameters": "", "return_type": "int", "dummy_return": "0" }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import MinStack",
- "test_class_name": "MinStack",
- "test_helper_methods": [],
- "test_methods": [
- {
- "name": "test_min_stack",
- "parametrize": "operations, inputs, expected",
- "parametrize_typed": "operations: list[str], inputs: list[list[int]], expected: list[int | None]",
- "test_cases": "[([\"MinStack\", \"push\", \"push\", \"push\", \"getMin\", \"pop\", \"top\", \"getMin\"], [[], [-2], [0], [-3], [], [], [], []], [None, None, None, None, -3, None, 0, -2]), ([\"MinStack\", \"push\", \"top\", \"getMin\", \"pop\"], [[], [5], [], [], []], [None, None, 5, 5, None]), ([\"MinStack\", \"push\", \"push\", \"push\", \"getMin\", \"pop\", \"getMin\", \"pop\", \"getMin\"], [[], [1], [1], [2], [], [], [], [], []], [None, None, None, None, 1, None, 1, None, 1]), ([\"MinStack\", \"push\", \"push\", \"getMin\", \"push\", \"getMin\", \"pop\", \"getMin\"], [[], [3], [1], [], [0], [], [], []], [None, None, None, 1, None, 0, None, 1])]",
- "body": "stack: MinStack | None = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == \"MinStack\":\n stack = MinStack()\n results.append(None)\n elif op == \"push\" and stack is not None:\n stack.push(inputs[i][0])\n results.append(None)\n elif op == \"pop\" and stack is not None:\n stack.pop()\n results.append(None)\n elif op == \"top\" and stack is not None:\n results.append(stack.top())\n elif op == \"getMin\" and stack is not None:\n results.append(stack.get_min())\nassert results == expected"
- }
- ],
- "playground_imports": "from solution import MinStack",
- "playground_test_case": "# Example test case\noperations = ['MinStack', 'push', 'push', 'push', 'getMin', 'pop', 'top', 'getMin']\ninputs = [[], [-2], [0], [-3], [], [], [], []]",
- "playground_execution": "stack = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == 'MinStack':\n stack = MinStack()\n results.append(None)\n elif op == 'push' and stack is not None:\n stack.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and stack is not None:\n stack.pop()\n results.append(None)\n elif op == 'top' and stack is not None:\n results.append(stack.top())\n elif op == 'getMin' and stack is not None:\n results.append(stack.get_min())\nresults",
- "playground_assertion": "expected = [None, None, None, None, -3, None, 0, -2]\nassert results == expected"
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_min_stack_operations, run_min_stack_operations\nfrom .solution import MinStack",
+ "test_content": "",
+ "test_class_name": "TestMinStack",
+ "test_class_content": "",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "__init__",
+ "signature": "(self) -> None",
+ "body": " # TODO: Initialize MinStack\n pass"
+ },
+ {
+ "name": "push",
+ "signature": "(self, val: int) -> None",
+ "body": " # TODO: Implement push\n pass"
+ },
+ {
+ "name": "pop",
+ "signature": "(self) -> None",
+ "body": " # TODO: Implement pop\n pass"
+ },
+ {
+ "name": "top",
+ "signature": "(self) -> int",
+ "body": " # TODO: Implement top\n return 0"
+ },
+ {
+ "name": "get_min",
+ "signature": "(self) -> int",
+ "body": " # TODO: Implement get_min\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": { "list": [] },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_min_stack",
+ "signature": "(self, operations: list[str], inputs: list[list[int]], expected: list[int | None])",
+ "parametrize": "operations, inputs, expected",
+ "test_cases": "[(['MinStack', 'push', 'push', 'push', 'getMin', 'pop', 'top', 'getMin'], [[], [-2], [0], [-3], [], [], [], []], [None, None, None, None, -3, None, 0, -2]), (['MinStack', 'push', 'top', 'getMin', 'pop'], [[], [5], [], [], []], [None, None, 5, 5, None]), (['MinStack', 'push', 'push', 'push', 'getMin', 'pop', 'getMin', 'pop', 'getMin'], [[], [1], [1], [2], [], [], [], [], []], [None, None, None, None, 1, None, 1, None, 1])]",
+ "body": " result = run_min_stack_operations(MinStack, operations, inputs)\n assert_min_stack_operations(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_min_stack_operations, assert_min_stack_operations\nfrom solution import MinStack",
+ "playground_setup": "# Example test case\noperations = ['MinStack', 'push', 'push', 'push', 'getMin', 'pop', 'top', 'getMin']\ninputs = [[], [-2], [0], [-3], [], [], [], []]\nexpected = [None, None, None, None, -3, None, 0, -2]",
+ "playground_run": "result = run_min_stack_operations(MinStack, operations, inputs)\nresult",
+ "playground_assert": "assert_min_stack_operations(result, expected)"
}
diff --git a/.templates/leetcode/json/minimum_height_trees.json b/.templates/leetcode/json/minimum_height_trees.json
index 86669a2..bc83ead 100644
--- a/.templates/leetcode/json/minimum_height_trees.json
+++ b/.templates/leetcode/json/minimum_height_trees.json
@@ -5,43 +5,60 @@
"problem_title": "Minimum Height Trees",
"difficulty": "Medium",
"topics": "Depth-First Search, Breadth-First Search, Graph, Topological Sort",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "A tree is an undirected graph in which any two vertices are connected by *exactly* one path. In other words, any connected graph without simple cycles is a tree.\n\nGiven a tree of `n` nodes labelled from `0` to `n - 1`, and an array of `n - 1` `edges` where `edges[i] = [ai, bi]` indicates that there is an undirected edge between the two nodes `ai` and `bi` in the tree, you can choose any node of the tree as the root. When you select a node `x` as the root, the result tree has height `h`. Among all possible rooted trees, those with minimum height (i.e. `min(h)`) are called **minimum height trees** (MHTs).\n\nReturn *a list of all **MHTs'** root labels*. You can return the answer in **any order**.\n\nThe **height** of a rooted tree is the number of edges on the longest downward path between the root and a leaf.",
- "readme_examples": [
- {
- "content": "
\n\n```\nInput: n = 4, edges = [[1,0],[1,2],[1,3]]\nOutput: [1]\nExplanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT.\n```"
- },
- {
- "content": "
\n\n```\nInput: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]\nOutput: [3,4]\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "
\n\n```\nInput: n = 4, edges = [[1,0],[1,2],[1,3]]\nOutput: [1]\nExplanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT.\n```"
+ },
+ {
+ "content": "
\n\n```\nInput: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]\nOutput: [3,4]\n```"
+ }
+ ]
+ },
"readme_constraints": "- `1 <= n <= 2 * 10^4`\n- `edges.length == n - 1`\n- `0 <= ai, bi < n`\n- `ai != bi`\n- All the pairs `(ai, bi)` are distinct.\n- The given input is **guaranteed** to be a tree and there will be **no repeated** edges.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "find_min_height_trees",
+ "helpers_run_signature": "(solution_class: type, n: int, edges: list[list[int]])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.find_min_height_trees(n, edges)",
+ "helpers_assert_name": "find_min_height_trees",
+ "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool",
+ "helpers_assert_body": " assert sorted(result) == sorted(expected)\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "find_min_height_trees",
- "parameters": "n: int, edges: list[list[int]]",
- "return_type": "list[int]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_find_min_height_trees, run_find_min_height_trees\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MinimumHeightTrees",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_find_min_height_trees",
- "parametrize": "n, edges, expected",
- "parametrize_typed": "n: int, edges: list[list[int]], expected: list[int]",
- "test_cases": "[(4, [[1,0],[1,2],[1,3]], [1]), (6, [[3,0],[3,1],[3,2],[3,4],[5,4]], [3,4]), (1, [], [0])]",
- "body": "result = self.solution.find_min_height_trees(n, edges)\nassert sorted(result) == sorted(expected)"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nn = 4\nedges = [[1,0],[1,2],[1,3]]\nexpected = [1]",
- "playground_execution": "result = Solution().find_min_height_trees(n, edges)\nresult",
- "playground_assertion": "assert sorted(result) == sorted(expected)"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "find_min_height_trees",
+ "signature": "(self, n: int, edges: list[list[int]]) -> list[int]",
+ "body": " # TODO: Implement find_min_height_trees\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_find_min_height_trees",
+ "signature": "(self, n: int, edges: list[list[int]], expected: list[int])",
+ "parametrize": "n, edges, expected",
+ "test_cases": "[(4, [[1,0],[1,2],[1,3]], [1]), (6, [[3,0],[3,1],[3,2],[3,4],[5,4]], [3,4]), (1, [], [0]), (2, [[0,1]], [0,1]), (3, [[0,1],[1,2]], [1])]",
+ "body": " result = run_find_min_height_trees(Solution, n, edges)\n assert_find_min_height_trees(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_find_min_height_trees, assert_find_min_height_trees\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nn = 4\nedges = [[1,0],[1,2],[1,3]]\nexpected = [1]",
+ "playground_run": "result = run_find_min_height_trees(Solution, n, edges)\nresult",
+ "playground_assert": "assert_find_min_height_trees(result, expected)"
}
diff --git a/.templates/leetcode/json/minimum_window_substring.json b/.templates/leetcode/json/minimum_window_substring.json
index a4d151b..6a146ad 100644
--- a/.templates/leetcode/json/minimum_window_substring.json
+++ b/.templates/leetcode/json/minimum_window_substring.json
@@ -5,46 +5,63 @@
"problem_title": "Minimum Window Substring",
"difficulty": "Hard",
"topics": "Hash Table, String, Sliding Window",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given two strings `s` and `t` of lengths `m` and `n` respectively, return the **minimum window substring** of `s` such that every character in `t` (including duplicates) is included in the window. If there is no such substring, return the empty string `\"\"`.\n\nThe testcases will be generated such that the answer is unique.",
- "readme_examples": [
- {
- "content": "```\nInput: s = \"ADOBECODEBANC\", t = \"ABC\"\nOutput: \"BANC\"\n```\n**Explanation:** The minimum window substring \"BANC\" includes 'A', 'B', and 'C' from string t."
- },
- {
- "content": "```\nInput: s = \"a\", t = \"a\"\nOutput: \"a\"\n```\n**Explanation:** The entire string s is the minimum window."
- },
- {
- "content": "```\nInput: s = \"a\", t = \"aa\"\nOutput: \"\"\n```\n**Explanation:** Both 'a's from t must be included in the window. Since the largest window of s only has one 'a', return empty string."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: s = \"ADOBECODEBANC\", t = \"ABC\"\nOutput: \"BANC\"\n```\n**Explanation:** The minimum window substring \"BANC\" includes 'A', 'B', and 'C' from string t."
+ },
+ {
+ "content": "```\nInput: s = \"a\", t = \"a\"\nOutput: \"a\"\n```\n**Explanation:** The entire string s is the minimum window."
+ },
+ {
+ "content": "```\nInput: s = \"a\", t = \"aa\"\nOutput: \"\"\n```\n**Explanation:** Both 'a's from t must be included in the window. Since the largest window of s only has one 'a', return empty string."
+ }
+ ]
+ },
"readme_constraints": "- `m == s.length`\n- `n == t.length`\n- `1 <= m, n <= 10^5`\n- `s` and `t` consist of uppercase and lowercase English letters.",
"readme_additional": "**Follow up:** Could you find an algorithm that runs in `O(m + n)` time?",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "min_window",
+ "helpers_run_signature": "(solution_class: type, s: str, t: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.min_window(s, t)",
+ "helpers_assert_name": "min_window",
+ "helpers_assert_signature": "(result: str, expected: str) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "min_window",
- "parameters": "s: str, t: str",
- "return_type": "str",
- "dummy_return": "\"\""
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_min_window, run_min_window\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "MinimumWindowSubstring",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_min_window",
- "parametrize": "s, t, expected",
- "parametrize_typed": "s: str, t: str, expected: str",
- "test_cases": "[(\"ADOBECODEBANC\", \"ABC\", \"BANC\"), (\"a\", \"a\", \"a\"), (\"a\", \"aa\", \"\")]",
- "body": "result = self.solution.min_window(s, t)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = \\\"ADOBECODEBANC\\\"\nt = \\\"ABC\\\"\nexpected = \\\"BANC\\\"",
- "playground_execution": "result = Solution().min_window(s, t)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "min_window",
+ "signature": "(self, s: str, t: str) -> str",
+ "body": " # TODO: Implement min_window\n return \"\""
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_min_window",
+ "signature": "(self, s: str, t: str, expected: str)",
+ "parametrize": "s, t, expected",
+ "test_cases": "[(\"ADOBECODEBANC\", \"ABC\", \"BANC\"), (\"a\", \"a\", \"a\"), (\"a\", \"aa\", \"\"), (\"ab\", \"b\", \"b\"), (\"abc\", \"cba\", \"abc\")]",
+ "body": " result = run_min_window(Solution, s, t)\n assert_min_window(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_min_window, assert_min_window\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = 'ADOBECODEBANC'\nt = 'ABC'\nexpected = 'BANC'",
+ "playground_run": "result = run_min_window(Solution, s, t)\nresult",
+ "playground_assert": "assert_min_window(result, expected)"
}
diff --git a/.templates/leetcode/json/number_of_islands.json b/.templates/leetcode/json/number_of_islands.json
index 9a08c09..d1cf6ef 100644
--- a/.templates/leetcode/json/number_of_islands.json
+++ b/.templates/leetcode/json/number_of_islands.json
@@ -5,43 +5,60 @@
"problem_title": "Number of Islands",
"difficulty": "Medium",
"topics": "Array, Depth-First Search, Breadth-First Search, Union Find, Matrix",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an `m x n` 2D binary grid `grid` which represents a map of `'1'`s (land) and `'0'`s (water), return *the number of islands*.\n\nAn **island** is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.",
- "readme_examples": [
- {
- "content": "```\nInput: grid = [\n [\"1\",\"1\",\"1\",\"1\",\"0\"],\n [\"1\",\"1\",\"0\",\"1\",\"0\"],\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"0\",\"0\",\"0\",\"0\",\"0\"]\n]\nOutput: 1\n```"
- },
- {
- "content": "```\nInput: grid = [\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"0\",\"0\",\"1\",\"0\",\"0\"],\n [\"0\",\"0\",\"0\",\"1\",\"1\"]\n]\nOutput: 3\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: grid = [\n [\"1\",\"1\",\"1\",\"1\",\"0\"],\n [\"1\",\"1\",\"0\",\"1\",\"0\"],\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"0\",\"0\",\"0\",\"0\",\"0\"]\n]\nOutput: 1\n```"
+ },
+ {
+ "content": "```\nInput: grid = [\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"0\",\"0\",\"1\",\"0\",\"0\"],\n [\"0\",\"0\",\"0\",\"1\",\"1\"]\n]\nOutput: 3\n```"
+ }
+ ]
+ },
"readme_constraints": "- `m == grid.length`\n- `n == grid[i].length`\n- `1 <= m, n <= 300`\n- `grid[i][j]` is `'0'` or `'1'`.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "num_islands",
+ "helpers_run_signature": "(solution_class: type, grid: list[list[str]])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.num_islands(grid)",
+ "helpers_assert_name": "num_islands",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "num_islands",
- "parameters": "grid: list[list[str]]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_num_islands, run_num_islands\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "NumberOfIslands",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_num_islands",
- "parametrize": "grid, expected",
- "parametrize_typed": "grid: list[list[str]], expected: int",
- "test_cases": "[([['1','1','1','1','0'],['1','1','0','1','0'],['1','1','0','0','0'],['0','0','0','0','0']], 1), ([['1','1','0','0','0'],['1','1','0','0','0'],['0','0','1','0','0'],['0','0','0','1','1']], 3), ([['1']], 1), ([['0']], 0), ([['1','0','1'],['0','1','0'],['1','0','1']], 5)]",
- "body": "result = self.solution.num_islands(grid)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ngrid = [\n ['1','1','1','1','0'],\n ['1','1','0','1','0'],\n ['1','1','0','0','0'],\n ['0','0','0','0','0']\n]\nexpected = 1",
- "playground_execution": "result = Solution().num_islands(grid)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "num_islands",
+ "signature": "(self, grid: list[list[str]]) -> int",
+ "body": " # TODO: Implement num_islands\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_num_islands",
+ "signature": "(self, grid: list[list[str]], expected: int)",
+ "parametrize": "grid, expected",
+ "test_cases": "[([['1','1','1','1','0'],['1','1','0','1','0'],['1','1','0','0','0'],['0','0','0','0','0']], 1), ([['1','1','0','0','0'],['1','1','0','0','0'],['0','0','1','0','0'],['0','0','0','1','1']], 3), ([['1']], 1), ([['0']], 0), ([['1','0','1'],['0','1','0'],['1','0','1']], 5)]",
+ "body": " result = run_num_islands(Solution, grid)\n assert_num_islands(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_num_islands, assert_num_islands\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ngrid = [\n ['1','1','1','1','0'],\n ['1','1','0','1','0'],\n ['1','1','0','0','0'],\n ['0','0','0','0','0']\n]\nexpected = 1",
+ "playground_run": "result = run_num_islands(Solution, grid)\nresult",
+ "playground_assert": "assert_num_islands(result, expected)"
}
diff --git a/.templates/leetcode/json/partition_equal_subset_sum.json b/.templates/leetcode/json/partition_equal_subset_sum.json
new file mode 100644
index 0000000..da63474
--- /dev/null
+++ b/.templates/leetcode/json/partition_equal_subset_sum.json
@@ -0,0 +1,64 @@
+{
+ "problem_name": "partition_equal_subset_sum",
+ "solution_class_name": "Solution",
+ "problem_number": "416",
+ "problem_title": "Partition Equal Subset Sum",
+ "difficulty": "Medium",
+ "topics": "Array, Dynamic Programming",
+ "_tags": { "list": ["grind-75"] },
+ "readme_description": "Given an integer array `nums`, return `true` if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or `false` otherwise.",
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: nums = [1,5,11,5]\nOutput: true\n```\n**Explanation:** The array can be partitioned as [1, 5, 5] and [11]."
+ },
+ {
+ "content": "```\nInput: nums = [1,2,3,5]\nOutput: false\n```\n**Explanation:** The array cannot be partitioned into equal sum subsets."
+ }
+ ]
+ },
+ "readme_constraints": "- 1 <= nums.length <= 200\n- 1 <= nums[i] <= 100",
+ "readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "can_partition",
+ "helpers_run_signature": "(solution_class: type, nums: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.can_partition(nums)",
+ "helpers_assert_name": "can_partition",
+ "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.test_utils import logged_test\nfrom .helpers import assert_can_partition, run_can_partition\nfrom .solution import Solution",
+ "test_content": "",
+ "test_class_name": "PartitionEqualSubsetSum",
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "can_partition",
+ "signature": "(self, nums: list[int]) -> bool",
+ "body": " # TODO: Implement can_partition\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_can_partition",
+ "signature": "(self, nums: list[int], expected: bool)",
+ "parametrize": "nums, expected",
+ "test_cases": "[([1, 5, 11, 5], True), ([1, 2, 3, 5], False), ([1, 1], True), ([1], False), ([2, 2, 1, 1], True), ([100], False), ([1, 2, 5], False)]",
+ "body": " result = run_can_partition(Solution, nums)\n assert_can_partition(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_can_partition, assert_can_partition\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [1, 5, 11, 5]\nexpected = True",
+ "playground_run": "result = run_can_partition(Solution, nums)\nresult",
+ "playground_assert": "assert_can_partition(result, expected)"
+}
diff --git a/.templates/leetcode/json/permutations.json b/.templates/leetcode/json/permutations.json
index cc4f436..081332d 100644
--- a/.templates/leetcode/json/permutations.json
+++ b/.templates/leetcode/json/permutations.json
@@ -5,42 +5,59 @@
"problem_title": "Permutations",
"difficulty": "Medium",
"topics": "Array, Backtracking",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an array `nums` of distinct integers, return all the possible permutations. You can return the answer in any order.",
- "readme_examples": [
- {
- "content": "```\nInput: nums = [1,2,3]\nOutput: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]\n```"
- },
- { "content": "```\nInput: nums = [0,1]\nOutput: [[0,1],[1,0]]\n```" },
- { "content": "```\nInput: nums = [1]\nOutput: [[1]]\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: nums = [1,2,3]\nOutput: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]\n```"
+ },
+ { "content": "```\nInput: nums = [0,1]\nOutput: [[0,1],[1,0]]\n```" },
+ { "content": "```\nInput: nums = [1]\nOutput: [[1]]\n```" }
+ ]
+ },
"readme_constraints": "- 1 <= nums.length <= 6\n- -10 <= nums[i] <= 10\n- All the integers of nums are unique.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "permute",
+ "helpers_run_signature": "(solution_class: type, nums: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.permute(nums)",
+ "helpers_assert_name": "permute",
+ "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool",
+ "helpers_assert_body": " # Sort both result and expected for comparison since order doesn't matter\n result_sorted = [sorted(perm) for perm in result]\n expected_sorted = [sorted(perm) for perm in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert len(result) == len(expected)\n assert result_sorted == expected_sorted\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "permute",
- "parameters": "nums: list[int]",
- "return_type": "list[list[int]]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
- "test_class_name": "TestPermutations",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_permute",
- "parametrize": "nums, expected",
- "parametrize_typed": "nums: list[int], expected: list[list[int]]",
- "test_cases": "[([1, 2, 3], [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]), ([0, 1], [[0, 1], [1, 0]]), ([1], [[1]])]",
- "body": "result = self.solution.permute(nums)\n # Sort both result and expected for comparison since order doesn't matter\n result_sorted = [sorted(perm) for perm in result]\n expected_sorted = [sorted(perm) for perm in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert len(result) == len(expected)\n assert result_sorted == expected_sorted"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [1, 2, 3]\nexpected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]",
- "playground_execution": "result = Solution().permute(nums)\nresult",
- "playground_assertion": "# Check that we have the right number of permutations\nassert len(result) == len(expected)\n# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(perm) for perm in result]\nexpected_sorted = [sorted(perm) for perm in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_permute, run_permute\nfrom .solution import Solution",
+ "test_content": "",
+ "test_class_name": "Permutations",
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "permute",
+ "signature": "(self, nums: list[int]) -> list[list[int]]",
+ "body": " # TODO: Implement permute\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_permute",
+ "signature": "(self, nums: list[int], expected: list[list[int]])",
+ "parametrize": "nums, expected",
+ "test_cases": "[([1, 2, 3], [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]), ([0, 1], [[0, 1], [1, 0]]), ([1], [[1]]), ([2, 1], [[2, 1], [1, 2]])]",
+ "body": " result = run_permute(Solution, nums)\n assert_permute(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_permute, assert_permute\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [1, 2, 3]\nexpected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]",
+ "playground_run": "result = run_permute(Solution, nums)\nresult",
+ "playground_assert": "assert_permute(result, expected)"
}
diff --git a/.templates/leetcode/json/product_of_array_except_self.json b/.templates/leetcode/json/product_of_array_except_self.json
index 1df2c43..d99aa69 100644
--- a/.templates/leetcode/json/product_of_array_except_self.json
+++ b/.templates/leetcode/json/product_of_array_except_self.json
@@ -5,39 +5,56 @@
"problem_title": "Product of Array Except Self",
"difficulty": "Medium",
"topics": "Array, Prefix Sum",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an integer array `nums`, return an array `answer` such that `answer[i]` is equal to the product of all the elements of `nums` except `nums[i]`.\n\nThe product of any prefix or suffix of `nums` is guaranteed to fit in a 32-bit integer.\n\nYou must write an algorithm that runs in O(n) time and without using the division operation.",
- "readme_examples": [
- { "content": "```\nInput: nums = [1,2,3,4]\nOutput: [24,12,8,6]\n```" },
- { "content": "```\nInput: nums = [-1,1,0,-3,3]\nOutput: [0,0,9,0,0]\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: nums = [1,2,3,4]\nOutput: [24,12,8,6]\n```" },
+ { "content": "```\nInput: nums = [-1,1,0,-3,3]\nOutput: [0,0,9,0,0]\n```" }
+ ]
+ },
"readme_constraints": "- 2 <= nums.length <= 10^5\n- -30 <= nums[i] <= 30\n- The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer.",
"readme_additional": "**Follow up:** Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.)",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "product_except_self",
+ "helpers_run_signature": "(solution_class: type, nums: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.product_except_self(nums)",
+ "helpers_assert_name": "product_except_self",
+ "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "product_except_self",
- "parameters": "nums: list[int]",
- "return_type": "list[int]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_product_except_self, run_product_except_self\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ProductOfArrayExceptSelf",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_product_except_self",
- "parametrize": "nums, expected",
- "parametrize_typed": "nums: list[int], expected: list[int]",
- "test_cases": "[([1, 2, 3, 4], [24, 12, 8, 6]), ([-1, 1, 0, -3, 3], [0, 0, 9, 0, 0]), ([2, 3, 4, 5], [60, 40, 30, 24])]",
- "body": "result = self.solution.product_except_self(nums)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [1, 2, 3, 4]\nexpected = [24, 12, 8, 6]",
- "playground_execution": "result = Solution().product_except_self(nums)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "product_except_self",
+ "signature": "(self, nums: list[int]) -> list[int]",
+ "body": " # TODO: Implement product_except_self\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_product_except_self",
+ "signature": "(self, nums: list[int], expected: list[int])",
+ "parametrize": "nums, expected",
+ "test_cases": "[([1, 2, 3, 4], [24, 12, 8, 6]), ([-1, 1, 0, -3, 3], [0, 0, 9, 0, 0]), ([2, 3, 4, 5], [60, 40, 30, 24]), ([1, 1], [1, 1]), ([5, 2], [2, 5]), ([0, 1, 2, 3], [6, 0, 0, 0]), ([1, 0, 3, 4], [0, 12, 0, 0])]",
+ "body": " result = run_product_except_self(Solution, nums)\n assert_product_except_self(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_product_except_self, assert_product_except_self\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [1, 2, 3, 4]\nexpected = [24, 12, 8, 6]",
+ "playground_run": "result = run_product_except_self(Solution, nums)\nresult",
+ "playground_assert": "assert_product_except_self(result, expected)"
}
diff --git a/.templates/leetcode/json/ransom_note.json b/.templates/leetcode/json/ransom_note.json
index 013a6da..6354724 100644
--- a/.templates/leetcode/json/ransom_note.json
+++ b/.templates/leetcode/json/ransom_note.json
@@ -5,40 +5,57 @@
"problem_title": "Ransom Note",
"difficulty": "Easy",
"topics": "Hash Table, String, Counting",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given two strings `ransomNote` and `magazine`, return `true` if `ransomNote` can be constructed by using the letters from `magazine` and `false` otherwise.\n\nEach letter in `magazine` can only be used once in `ransomNote`.",
- "readme_examples": [
- { "content": "```\nInput: ransomNote = \"a\", magazine = \"b\"\nOutput: false\n```" },
- { "content": "```\nInput: ransomNote = \"aa\", magazine = \"ab\"\nOutput: false\n```" },
- { "content": "```\nInput: ransomNote = \"aa\", magazine = \"aab\"\nOutput: true\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: ransomNote = \"a\", magazine = \"b\"\nOutput: false\n```" },
+ { "content": "```\nInput: ransomNote = \"aa\", magazine = \"ab\"\nOutput: false\n```" },
+ { "content": "```\nInput: ransomNote = \"aa\", magazine = \"aab\"\nOutput: true\n```" }
+ ]
+ },
"readme_constraints": "- 1 <= ransomNote.length, magazine.length <= 10^5\n- ransomNote and magazine consist of lowercase English letters.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "can_construct",
+ "helpers_run_signature": "(solution_class: type, ransom_note: str, magazine: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.can_construct(ransom_note, magazine)",
+ "helpers_assert_name": "can_construct",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "can_construct",
- "parameters": "ransom_note: str, magazine: str",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_can_construct, run_can_construct\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "RansomNote",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_can_construct",
- "parametrize": "ransom_note, magazine, expected",
- "parametrize_typed": "ransom_note: str, magazine: str, expected: bool",
- "test_cases": "[('a', 'b', False), ('aa', 'ab', False), ('aa', 'aab', True), ('aab', 'baa', True)]",
- "body": "result = self.solution.can_construct(ransom_note, magazine)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nransom_note = 'aa'\nmagazine = 'aab'\nexpected = True",
- "playground_execution": "result = Solution().can_construct(ransom_note, magazine)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "can_construct",
+ "signature": "(self, ransom_note: str, magazine: str) -> bool",
+ "body": " # TODO: Implement can_construct\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_can_construct",
+ "signature": "(self, ransom_note: str, magazine: str, expected: bool)",
+ "parametrize": "ransom_note, magazine, expected",
+ "test_cases": "[('a', 'b', False), ('aa', 'ab', False), ('aa', 'aab', True), ('aab', 'baa', True), ('', '', True), ('', 'abc', True), ('abc', '', False), ('abc', 'abc', True), ('abc', 'cba', True), ('aaa', 'aa', False), ('ab', 'ba', True)]",
+ "body": " result = run_can_construct(Solution, ransom_note, magazine)\n assert_can_construct(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_can_construct, assert_can_construct\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nransom_note = 'aa'\nmagazine = 'aab'\nexpected = True",
+ "playground_run": "result = run_can_construct(Solution, ransom_note, magazine)\nresult",
+ "playground_assert": "assert_can_construct(result, expected)"
}
diff --git a/.templates/leetcode/json/reverse_linked_list.json b/.templates/leetcode/json/reverse_linked_list.json
index 2c036c2..2166957 100644
--- a/.templates/leetcode/json/reverse_linked_list.json
+++ b/.templates/leetcode/json/reverse_linked_list.json
@@ -5,44 +5,61 @@
"problem_title": "Reverse Linked List",
"difficulty": "Easy",
"topics": "Linked List, Recursion",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given the `head` of a singly linked list, reverse the list, and return the reversed list.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: head = [1,2,3,4,5]\nOutput: [5,4,3,2,1]\n```"
- },
- {
- "content": "\n\n```\nInput: head = [1,2]\nOutput: [2,1]\n```"
- },
- { "content": "```\nInput: head = []\nOutput: []\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: head = [1,2,3,4,5]\nOutput: [5,4,3,2,1]\n```"
+ },
+ {
+ "content": "\n\n```\nInput: head = [1,2]\nOutput: [2,1]\n```"
+ },
+ { "content": "```\nInput: head = []\nOutput: []\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the list is the range `[0, 5000]`.\n- `-5000 <= Node.val <= 5000`",
"readme_additional": "**Follow up:** A linked list can be reversed either iteratively or recursively. Could you implement both?",
+ "helpers_imports": "from leetcode_py import ListNode",
+ "helpers_content": "",
+ "helpers_run_name": "reverse_list",
+ "helpers_run_signature": "(solution_class: type, head_list: list[int])",
+ "helpers_run_body": " head = ListNode[int].from_list(head_list)\n implementation = solution_class()\n return implementation.reverse_list(head)",
+ "helpers_assert_name": "reverse_list",
+ "helpers_assert_signature": "(result: ListNode[int] | None, expected_list: list[int]) -> bool",
+ "helpers_assert_body": " expected = ListNode[int].from_list(expected_list)\n assert result == expected\n return True",
"solution_imports": "from leetcode_py import ListNode",
- "solution_methods": [
- {
- "name": "reverse_list",
- "parameters": "head: ListNode[int] | None",
- "return_type": "ListNode[int] | None",
- "dummy_return": "None"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import ListNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_reverse_list, run_reverse_list\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ReverseLinkedList",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_reverse_list",
- "parametrize": "head_list, expected_list",
- "parametrize_typed": "head_list: list[int], expected_list: list[int]",
- "test_cases": "[([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), ([1, 2], [2, 1]), ([1], [1]), ([], []), ([1, 2, 3], [3, 2, 1]), ([1, 2, 3, 4], [4, 3, 2, 1]), ([-1, -2, -3], [-3, -2, -1]), ([0], [0]), ([5000, -5000], [-5000, 5000]), ([1, 1, 1], [1, 1, 1])]",
- "body": "head = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)\nresult = self.solution.reverse_list(head)\nassert result == expected"
- }
- ],
- "playground_imports": "from leetcode_py import ListNode\nfrom solution import Solution",
- "playground_test_case": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nexpected_list = [5, 4, 3, 2, 1]\nhead = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)",
- "playground_execution": "result = Solution().reverse_list(head)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "reverse_list",
+ "signature": "(self, head: ListNode[int] | None) -> ListNode[int] | None",
+ "body": " # TODO: Implement reverse_list\n return None"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_reverse_list",
+ "signature": "(self, head_list: list[int], expected_list: list[int])",
+ "parametrize": "head_list, expected_list",
+ "test_cases": "[([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), ([1, 2], [2, 1]), ([1], [1]), ([], []), ([1, 2, 3], [3, 2, 1]), ([1, 2, 3, 4], [4, 3, 2, 1]), ([-1, -2, -3], [-3, -2, -1]), ([0], [0]), ([5000, -5000], [-5000, 5000]), ([1, 1, 1], [1, 1, 1])]",
+ "body": " result = run_reverse_list(Solution, head_list)\n assert_reverse_list(result, expected_list)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_reverse_list, assert_reverse_list\nfrom solution import Solution\nfrom leetcode_py import ListNode",
+ "playground_setup": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nexpected_list = [5, 4, 3, 2, 1]",
+ "playground_run": "result = run_reverse_list(Solution, head_list)\nresult",
+ "playground_assert": "assert_reverse_list(result, expected_list)"
}
diff --git a/.templates/leetcode/json/reverse_linked_list_ii.json b/.templates/leetcode/json/reverse_linked_list_ii.json
index 4eeb724..2efc947 100644
--- a/.templates/leetcode/json/reverse_linked_list_ii.json
+++ b/.templates/leetcode/json/reverse_linked_list_ii.json
@@ -5,39 +5,58 @@
"problem_title": "Reverse Linked List II",
"difficulty": "Medium",
"topics": "Linked List",
- "tags": [],
+ "_tags": { "list": [] },
"readme_description": "Given the `head` of a singly linked list and two integers `left` and `right` where `left <= right`, reverse the nodes of the list from position `left` to position `right`, and return the reversed list.",
- "readme_examples": [
- { "content": "```\nInput: head = [1,2,3,4,5], left = 2, right = 4\nOutput: [1,4,3,2,5]\n```" },
- { "content": "```\nInput: head = [5], left = 1, right = 1\nOutput: [5]\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: head = [1,2,3,4,5], left = 2, right = 4\nOutput: [1,4,3,2,5]\n```"
+ },
+ { "content": "```\nInput: head = [5], left = 1, right = 1\nOutput: [5]\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the list is n\n- 1 <= n <= 500\n- -500 <= Node.val <= 500\n- 1 <= left <= right <= n",
"readme_additional": "**Follow up:** Could you do it in one pass?",
+ "helpers_imports": "from leetcode_py import ListNode",
+ "helpers_content": "",
+ "helpers_run_name": "reverse_between",
+ "helpers_run_signature": "(solution_class: type, head_list: list[int], left: int, right: int)",
+ "helpers_run_body": " head = ListNode[int].from_list(head_list)\n implementation = solution_class()\n return implementation.reverse_between(head, left, right)",
+ "helpers_assert_name": "reverse_between",
+ "helpers_assert_signature": "(result: ListNode[int] | None, expected_list: list[int]) -> bool",
+ "helpers_assert_body": " expected = ListNode[int].from_list(expected_list)\n assert result == expected\n return True",
"solution_imports": "from leetcode_py import ListNode",
- "solution_methods": [
- {
- "name": "reverse_between",
- "parameters": "head: ListNode[int] | None, left: int, right: int",
- "return_type": "ListNode[int] | None",
- "dummy_return": "None"
- }
- ],
- "test_imports": "import pytest\n\nfrom leetcode_py import ListNode\nfrom leetcode_py.test_utils import logged_test\n\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_reverse_between, run_reverse_between\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ReverseLinkedListII",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_reverse_between",
- "parametrize": "head_list, left, right, expected_list",
- "parametrize_typed": "head_list: list[int], left: int, right: int, expected_list: list[int]",
- "test_cases": "[([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), ([5], 1, 1, [5])]",
- "body": "head = ListNode[int].from_list(head_list)\nexpected = ListNode[int].from_list(expected_list)\nresult = self.solution.reverse_between(head, left, right)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\n\nfrom leetcode_py import ListNode",
- "playground_test_case": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nhead = ListNode[int].from_list(head_list)\nleft, right = 2, 4\nexpected = ListNode[int].from_list([1, 4, 3, 2, 5])",
- "playground_execution": "result = Solution().reverse_between(head, left, right)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "reverse_between",
+ "signature": "(self, head: ListNode[int] | None, left: int, right: int) -> ListNode[int] | None",
+ "body": " # TODO: Implement reverse_between\n return None"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_reverse_between",
+ "signature": "(self, head_list: list[int], left: int, right: int, expected_list: list[int])",
+ "parametrize": "head_list, left, right, expected_list",
+ "test_cases": "[([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), ([5], 1, 1, [5]), ([1, 2], 1, 2, [2, 1]), ([1, 2, 3], 1, 3, [3, 2, 1]), ([1, 2, 3, 4, 5], 1, 5, [5, 4, 3, 2, 1]), ([1, 2, 3, 4, 5], 3, 3, [1, 2, 3, 4, 5])]",
+ "body": " result = run_reverse_between(Solution, head_list, left, right)\n assert_reverse_between(result, expected_list)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_reverse_between, assert_reverse_between\nfrom solution import Solution\nfrom leetcode_py import ListNode",
+ "playground_setup": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nleft, right = 2, 4\nexpected_list = [1, 4, 3, 2, 5]",
+ "playground_run": "result = run_reverse_between(Solution, head_list, left, right)\nresult",
+ "playground_assert": "assert_reverse_between(result, expected_list)"
}
diff --git a/.templates/leetcode/json/rotting_oranges.json b/.templates/leetcode/json/rotting_oranges.json
index 2467648..6081ed4 100644
--- a/.templates/leetcode/json/rotting_oranges.json
+++ b/.templates/leetcode/json/rotting_oranges.json
@@ -5,46 +5,63 @@
"problem_title": "Rotting Oranges",
"difficulty": "Medium",
"topics": "Array, Breadth-First Search, Matrix",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given an `m x n` `grid` where each cell can have one of three values:\n\n- `0` representing an empty cell,\n- `1` representing a fresh orange, or\n- `2` representing a rotten orange.\n\nEvery minute, any fresh orange that is **4-directionally adjacent** to a rotten orange becomes rotten.\n\nReturn *the minimum number of minutes that must elapse until no cell has a fresh orange*. If *this is impossible, return* `-1`.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: grid = [[2,1,1],[1,1,0],[0,1,1]]\nOutput: 4\n```"
- },
- {
- "content": "```\nInput: grid = [[2,1,1],[0,1,1],[1,0,1]]\nOutput: -1\n```\n**Explanation:** The orange in the bottom left corner (row 2, column 0) is never rotten, because rotting only happens 4-directionally."
- },
- {
- "content": "```\nInput: grid = [[0,2]]\nOutput: 0\n```\n**Explanation:** Since there are already no fresh oranges at minute 0, the answer is just 0."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: grid = [[2,1,1],[1,1,0],[0,1,1]]\nOutput: 4\n```"
+ },
+ {
+ "content": "```\nInput: grid = [[2,1,1],[0,1,1],[1,0,1]]\nOutput: -1\n```\n**Explanation:** The orange in the bottom left corner (row 2, column 0) is never rotten, because rotting only happens 4-directionally."
+ },
+ {
+ "content": "```\nInput: grid = [[0,2]]\nOutput: 0\n```\n**Explanation:** Since there are already no fresh oranges at minute 0, the answer is just 0."
+ }
+ ]
+ },
"readme_constraints": "- `m == grid.length`\n- `n == grid[i].length`\n- `1 <= m, n <= 10`\n- `grid[i][j]` is `0`, `1`, or `2`.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "oranges_rotting",
+ "helpers_run_signature": "(solution_class: type, grid: list[list[int]])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.oranges_rotting(grid)",
+ "helpers_assert_name": "oranges_rotting",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "oranges_rotting",
- "parameters": "grid: list[list[int]]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
- "test_class_name": "TestRottingOranges",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_oranges_rotting",
- "parametrize": "grid, expected",
- "parametrize_typed": "grid: list[list[int]], expected: int",
- "test_cases": "[([[2, 1, 1], [1, 1, 0], [0, 1, 1]], 4), ([[2, 1, 1], [0, 1, 1], [1, 0, 1]], -1), ([[0, 2]], 0), ([[0]], 0), ([[1]], -1), ([[2]], 0), ([[1, 2]], 1), ([[2, 1]], 1), ([[0, 1, 2]], 1), ([[2, 2], [1, 1], [0, 0]], 1), ([[2, 1, 1], [1, 1, 1], [0, 1, 2]], 2)]",
- "body": "result = self.solution.oranges_rotting(grid)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ngrid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\nexpected = 4",
- "playground_execution": "result = Solution().oranges_rotting(grid)\nresult",
- "playground_assertion": "assert result == expected"
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_oranges_rotting, run_oranges_rotting\nfrom .solution import Solution",
+ "test_content": "",
+ "test_class_name": "RottingOranges",
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "oranges_rotting",
+ "signature": "(self, grid: list[list[int]]) -> int",
+ "body": " # TODO: Implement oranges_rotting\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_oranges_rotting",
+ "signature": "(self, grid: list[list[int]], expected: int)",
+ "parametrize": "grid, expected",
+ "test_cases": "[([[2, 1, 1], [1, 1, 0], [0, 1, 1]], 4), ([[2, 1, 1], [0, 1, 1], [1, 0, 1]], -1), ([[0, 2]], 0), ([[0]], 0), ([[1]], -1), ([[2]], 0), ([[1, 2]], 1), ([[2, 1]], 1), ([[0, 1, 2]], 1), ([[2, 2], [1, 1], [0, 0]], 1), ([[2, 1, 1], [1, 1, 1], [0, 1, 2]], 2)]",
+ "body": " result = run_oranges_rotting(Solution, grid)\n assert_oranges_rotting(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_oranges_rotting, assert_oranges_rotting\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ngrid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\nexpected = 4",
+ "playground_run": "result = run_oranges_rotting(Solution, grid)\nresult",
+ "playground_assert": "assert_oranges_rotting(result, expected)"
}
diff --git a/.templates/leetcode/json/search_in_rotated_sorted_array.json b/.templates/leetcode/json/search_in_rotated_sorted_array.json
index 1235416..9f2165b 100644
--- a/.templates/leetcode/json/search_in_rotated_sorted_array.json
+++ b/.templates/leetcode/json/search_in_rotated_sorted_array.json
@@ -5,40 +5,57 @@
"problem_title": "Search in Rotated Sorted Array",
"difficulty": "Medium",
"topics": "Array, Binary Search",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "There is an integer array `nums` sorted in ascending order (with **distinct** values).\n\nPrior to being passed to your function, `nums` is **possibly left rotated** at an unknown index `k` (`1 <= k < nums.length`) such that the resulting array is `[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]` (**0-indexed**). For example, `[0,1,2,4,5,6,7]` might be left rotated by 3 indices and become `[4,5,6,7,0,1,2]`.\n\nGiven the array `nums` **after** the possible rotation and an integer `target`, return *the index of* `target` *if it is in* `nums`*, or* `-1` *if it is not in* `nums`.\n\nYou must write an algorithm with `O(log n)` runtime complexity.",
- "readme_examples": [
- { "content": "```\nInput: nums = [4,5,6,7,0,1,2], target = 0\nOutput: 4\n```" },
- { "content": "```\nInput: nums = [4,5,6,7,0,1,2], target = 3\nOutput: -1\n```" },
- { "content": "```\nInput: nums = [1], target = 0\nOutput: -1\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: nums = [4,5,6,7,0,1,2], target = 0\nOutput: 4\n```" },
+ { "content": "```\nInput: nums = [4,5,6,7,0,1,2], target = 3\nOutput: -1\n```" },
+ { "content": "```\nInput: nums = [1], target = 0\nOutput: -1\n```" }
+ ]
+ },
"readme_constraints": "- `1 <= nums.length <= 5000`\n- `-10^4 <= nums[i] <= 10^4`\n- All values of `nums` are **unique**.\n- `nums` is an ascending array that is possibly rotated.\n- `-10^4 <= target <= 10^4`",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "search",
+ "helpers_run_signature": "(solution_class: type, nums: list[int], target: int)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.search(nums, target)",
+ "helpers_assert_name": "search",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "search",
- "parameters": "nums: list[int], target: int",
- "return_type": "int",
- "dummy_return": "-1"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_search, run_search\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "SearchInRotatedSortedArray",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_search",
- "parametrize": "nums, target, expected",
- "parametrize_typed": "nums: list[int], target: int, expected: int",
- "test_cases": "[([4, 5, 6, 7, 0, 1, 2], 0, 4), ([4, 5, 6, 7, 0, 1, 2], 3, -1), ([1], 0, -1), ([1], 1, 0), ([3, 1], 1, 1)]",
- "body": "result = self.solution.search(nums, target)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [4, 5, 6, 7, 0, 1, 2]\ntarget = 0\nexpected = 4",
- "playground_execution": "result = Solution().search(nums, target)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "search",
+ "signature": "(self, nums: list[int], target: int) -> int",
+ "body": " # TODO: Implement search\n return -1"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_search",
+ "signature": "(self, nums: list[int], target: int, expected: int)",
+ "parametrize": "nums, target, expected",
+ "test_cases": "[([4, 5, 6, 7, 0, 1, 2], 0, 4), ([4, 5, 6, 7, 0, 1, 2], 3, -1), ([1], 0, -1), ([1], 1, 0), ([3, 1], 1, 1), ([1, 3], 3, 1), ([2, 1], 2, 0), ([5, 1, 3], 3, 2), ([4, 5, 6, 7, 8, 1, 2, 3], 8, 4)]",
+ "body": " result = run_search(Solution, nums, target)\n assert_search(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_search, assert_search\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [4, 5, 6, 7, 0, 1, 2]\ntarget = 0\nexpected = 4",
+ "playground_run": "result = run_search(Solution, nums, target)\nresult",
+ "playground_assert": "assert_search(result, expected)"
}
diff --git a/.templates/leetcode/json/serialize_and_deserialize_binary_tree.json b/.templates/leetcode/json/serialize_and_deserialize_binary_tree.json
index 36ae36f..552c8b7 100644
--- a/.templates/leetcode/json/serialize_and_deserialize_binary_tree.json
+++ b/.templates/leetcode/json/serialize_and_deserialize_binary_tree.json
@@ -5,48 +5,66 @@
"problem_title": "Serialize and Deserialize Binary Tree",
"difficulty": "Hard",
"topics": "String, Tree, Depth-First Search, Breadth-First Search, Design, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.\n\nDesign an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.\n\n**Clarification:** The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: root = [1,2,3,null,null,4,5]\nOutput: [1,2,3,null,null,4,5]\n```"
- },
- { "content": "```\nInput: root = []\nOutput: []\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: root = [1,2,3,null,null,4,5]\nOutput: [1,2,3,null,null,4,5]\n```"
+ },
+ { "content": "```\nInput: root = []\nOutput: []\n```" }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range [0, 10^4].\n- -1000 <= Node.val <= 1000",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "serialize_deserialize",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None])",
+ "helpers_run_body": " root = TreeNode[int].from_list(root_list) if root_list else None\n codec = solution_class()\n serialized = codec.serialize(root)\n deserialized = codec.deserialize(serialized)\n return deserialized",
+ "helpers_assert_name": "serialize_deserialize",
+ "helpers_assert_signature": "(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool",
+ "helpers_assert_body": " expected = TreeNode[int].from_list(expected_list) if expected_list else None\n if expected is None:\n assert result is None\n else:\n assert result is not None\n assert result.to_list() == expected.to_list()\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- { "name": "__init__", "parameters": "", "return_type": "", "dummy_return": "" },
- {
- "name": "serialize",
- "parameters": "root: TreeNode | None",
- "return_type": "str",
- "dummy_return": "''"
- },
- {
- "name": "deserialize",
- "parameters": "data: str",
- "return_type": "TreeNode | None",
- "dummy_return": "None"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Codec",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_serialize_deserialize, run_serialize_deserialize\nfrom .solution import Codec",
+ "test_content": "",
"test_class_name": "SerializeAndDeserializeBinaryTree",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.codec = Codec()" }
- ],
- "test_methods": [
- {
- "name": "test_serialize_deserialize",
- "parametrize": "root_list",
- "parametrize_typed": "root_list: list[int | None]",
- "test_cases": "[([1, 2, 3, None, None, 4, 5]), ([]), ([1]), ([1, 2]), ([1, None, 2]), ([1, 2, 3, 4, 5, 6, 7]), ([5, 2, 3, None, None, 2, 4, 3, 1])]",
- "body": "root = TreeNode.from_list(root_list) if root_list else None\nserialized = self.codec.serialize(root)\ndeserialized = self.codec.deserialize(serialized)\nif root is None:\n assert deserialized is None\nelse:\n assert deserialized is not None\n assert deserialized.to_list() == root.to_list()"
- }
- ],
- "playground_imports": "from solution import Codec\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list = [1, 2, 3, None, None, 4, 5]\nroot = TreeNode.from_list(root_list) if root_list else None",
- "playground_execution": "codec = Codec()\nserialized = codec.serialize(root)\ndeserialized = codec.deserialize(serialized)\nprint(f'Original: {root.to_list() if root else None}')\nprint(f'Serialized: {serialized}')\nprint(f'Deserialized: {deserialized.to_list() if deserialized else None}')\ndeserialized",
- "playground_assertion": "if root is None:\n assert deserialized is None\nelse:\n assert deserialized is not None\n assert deserialized.to_list() == root.to_list()"
+ "test_class_content": "",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "__init__",
+ "signature": "(self) -> None",
+ "body": " # TODO: Initialize\n pass"
+ },
+ {
+ "name": "serialize",
+ "signature": "(self, root: TreeNode[int] | None) -> str",
+ "body": " # TODO: Implement serialize\n return ''"
+ },
+ {
+ "name": "deserialize",
+ "signature": "(self, data: str) -> TreeNode[int] | None",
+ "body": " # TODO: Implement deserialize\n return None"
+ }
+ ]
+ },
+ "_test_helper_methods": { "list": [] },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_serialize_deserialize",
+ "signature": "(self, root_list: list[int | None])",
+ "parametrize": "root_list",
+ "test_cases": "[([1, 2, 3, None, None, 4, 5]), ([]), ([1]), ([1, 2]), ([1, None, 2]), ([1, 2, 3, 4, 5, 6, 7]), ([5, 2, 3, None, None, 2, 4, 3, 1])]",
+ "body": " result = run_serialize_deserialize(Codec, root_list)\n assert_serialize_deserialize(result, root_list)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_serialize_deserialize, assert_serialize_deserialize\nfrom solution import Codec\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list = [1, 2, 3, None, None, 4, 5]",
+ "playground_run": "result = run_serialize_deserialize(Codec, root_list)\nresult",
+ "playground_assert": "assert_serialize_deserialize(result, root_list)"
}
diff --git a/.templates/leetcode/json/sort_colors.json b/.templates/leetcode/json/sort_colors.json
index 0cca3e8..3525a54 100644
--- a/.templates/leetcode/json/sort_colors.json
+++ b/.templates/leetcode/json/sort_colors.json
@@ -5,39 +5,56 @@
"problem_title": "Sort Colors",
"difficulty": "Medium",
"topics": "Array, Two Pointers, Sorting",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an array `nums` with `n` objects colored red, white, or blue, sort them **in-place** so that objects of the same color are adjacent, with the colors in the order red, white, and blue.\n\nWe will use the integers `0`, `1`, and `2` to represent the color red, white, and blue, respectively.\n\nYou must solve this problem without using the library's sort function.",
- "readme_examples": [
- { "content": "```\nInput: nums = [2,0,2,1,1,0]\nOutput: [0,0,1,1,2,2]\n```" },
- { "content": "```\nInput: nums = [2,0,1]\nOutput: [0,1,2]\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: nums = [2,0,2,1,1,0]\nOutput: [0,0,1,1,2,2]\n```" },
+ { "content": "```\nInput: nums = [2,0,1]\nOutput: [0,1,2]\n```" }
+ ]
+ },
"readme_constraints": "- `n == nums.length`\n- `1 <= n <= 300`\n- `nums[i]` is either `0`, `1`, or `2`.",
"readme_additional": "**Follow up:** Could you come up with a one-pass algorithm using only constant extra space?",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "sort_colors",
+ "helpers_run_signature": "(solution_class: type, nums: list[int])",
+ "helpers_run_body": " nums_copy = nums.copy()\n implementation = solution_class()\n implementation.sort_colors(nums_copy)\n return nums_copy",
+ "helpers_assert_name": "sort_colors",
+ "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "sort_colors",
- "parameters": "nums: list[int]",
- "return_type": "None",
- "dummy_return": ""
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_sort_colors, run_sort_colors\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "SortColors",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_sort_colors",
- "parametrize": "nums, expected",
- "parametrize_typed": "nums: list[int], expected: list[int]",
- "test_cases": "[([2, 0, 2, 1, 1, 0], [0, 0, 1, 1, 2, 2]), ([2, 0, 1], [0, 1, 2]), ([0], [0]), ([1], [1]), ([2], [2]), ([0, 1, 2], [0, 1, 2])]",
- "body": "nums_copy = nums.copy()\nself.solution.sort_colors(nums_copy)\nassert nums_copy == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [2, 0, 2, 1, 1, 0]\nexpected = [0, 0, 1, 1, 2, 2]",
- "playground_execution": "nums_copy = nums.copy()\nSolution().sort_colors(nums_copy)\nnums_copy",
- "playground_assertion": "assert nums_copy == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "sort_colors",
+ "signature": "(self, nums: list[int]) -> None",
+ "body": " # TODO: Implement sort_colors\n pass"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_sort_colors",
+ "signature": "(self, nums: list[int], expected: list[int])",
+ "parametrize": "nums, expected",
+ "test_cases": "[([2, 0, 2, 1, 1, 0], [0, 0, 1, 1, 2, 2]), ([2, 0, 1], [0, 1, 2]), ([0], [0]), ([1], [1]), ([2], [2]), ([0, 1, 2], [0, 1, 2]), ([2, 2, 2], [2, 2, 2]), ([0, 0, 0], [0, 0, 0]), ([1, 1, 1], [1, 1, 1])]",
+ "body": " result = run_sort_colors(Solution, nums)\n assert_sort_colors(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_sort_colors, assert_sort_colors\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [2, 0, 2, 1, 1, 0]\nexpected = [0, 0, 1, 1, 2, 2]",
+ "playground_run": "result = run_sort_colors(Solution, nums)\nresult",
+ "playground_assert": "assert_sort_colors(result, expected)"
}
diff --git a/.templates/leetcode/json/spiral_matrix.json b/.templates/leetcode/json/spiral_matrix.json
index 35efad1..db80ac1 100644
--- a/.templates/leetcode/json/spiral_matrix.json
+++ b/.templates/leetcode/json/spiral_matrix.json
@@ -5,43 +5,60 @@
"problem_title": "Spiral Matrix",
"difficulty": "Medium",
"topics": "Array, Matrix, Simulation",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an `m x n` matrix, return all elements of the matrix in spiral order.",
- "readme_examples": [
- {
- "content": "
\n\n```\nInput: matrix = [[1,2,3],[4,5,6],[7,8,9]]\nOutput: [1,2,3,6,9,8,7,4,5]\n```"
- },
- {
- "content": "
\n\n```\nInput: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]\nOutput: [1,2,3,4,8,12,11,10,9,5,6,7]\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "
\n\n```\nInput: matrix = [[1,2,3],[4,5,6],[7,8,9]]\nOutput: [1,2,3,6,9,8,7,4,5]\n```"
+ },
+ {
+ "content": "
\n\n```\nInput: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]\nOutput: [1,2,3,4,8,12,11,10,9,5,6,7]\n```"
+ }
+ ]
+ },
"readme_constraints": "- m == matrix.length\n- n == matrix[i].length\n- 1 <= m, n <= 10\n- -100 <= matrix[i][j] <= 100",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "spiral_order",
+ "helpers_run_signature": "(solution_class: type, matrix: list[list[int]])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.spiral_order(matrix)",
+ "helpers_assert_name": "spiral_order",
+ "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "spiral_order",
- "parameters": "matrix: list[list[int]]",
- "return_type": "list[int]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_spiral_order, run_spiral_order\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "SpiralMatrix",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_spiral_order",
- "parametrize": "matrix, expected",
- "parametrize_typed": "matrix: list[list[int]], expected: list[int]",
- "test_cases": "[([[1,2,3],[4,5,6],[7,8,9]], [1,2,3,6,9,8,7,4,5]), ([[1,2,3,4],[5,6,7,8],[9,10,11,12]], [1,2,3,4,8,12,11,10,9,5,6,7])]",
- "body": "result = self.solution.spiral_order(matrix)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nmatrix = [[1,2,3],[4,5,6],[7,8,9]]\nexpected = [1,2,3,6,9,8,7,4,5]",
- "playground_execution": "result = Solution().spiral_order(matrix)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "spiral_order",
+ "signature": "(self, matrix: list[list[int]]) -> list[int]",
+ "body": " # TODO: Implement spiral_order\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_spiral_order",
+ "signature": "(self, matrix: list[list[int]], expected: list[int])",
+ "parametrize": "matrix, expected",
+ "test_cases": "[([[1,2,3],[4,5,6],[7,8,9]], [1,2,3,6,9,8,7,4,5]), ([[1,2,3,4],[5,6,7,8],[9,10,11,12]], [1,2,3,4,8,12,11,10,9,5,6,7]), ([[1]], [1]), ([[1,2]], [1,2]), ([[1],[2]], [1,2]), ([[1,2,3]], [1,2,3]), ([[1],[2],[3]], [1,2,3])]",
+ "body": " result = run_spiral_order(Solution, matrix)\n assert_spiral_order(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_spiral_order, assert_spiral_order\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nmatrix = [[1,2,3],[4,5,6],[7,8,9]]\nexpected = [1,2,3,6,9,8,7,4,5]",
+ "playground_run": "result = run_spiral_order(Solution, matrix)\nresult",
+ "playground_assert": "assert_spiral_order(result, expected)"
}
diff --git a/.templates/leetcode/json/string_to_integer_atoi.json b/.templates/leetcode/json/string_to_integer_atoi.json
index 5e54670..781b2c8 100644
--- a/.templates/leetcode/json/string_to_integer_atoi.json
+++ b/.templates/leetcode/json/string_to_integer_atoi.json
@@ -5,47 +5,69 @@
"problem_title": "String to Integer (atoi)",
"difficulty": "Medium",
"topics": "String",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Implement the `my_atoi(string s)` function, which converts a string to a 32-bit signed integer.\n\nThe algorithm for `my_atoi(string s)` is as follows:\n\n1. **Whitespace**: Ignore any leading whitespace (` `).\n2. **Signedness**: Determine the sign by checking if the next character is `-` or `+`, assuming positivity if neither present.\n3. **Conversion**: Read the integer by skipping leading zeros until a non-digit character is encountered or the end of the string is reached. If no digits were read, then the result is 0.\n4. **Rounding**: If the integer is out of the 32-bit signed integer range `[-2^31, 2^31 - 1]`, then round the integer to remain in the range. Specifically, integers less than `-2^31` should be rounded to `-2^31`, and integers greater than `2^31 - 1` should be rounded to `2^31 - 1`.\n\nReturn the integer as the final result.",
- "readme_examples": [
- {
- "content": "```\nInput: s = \"42\"\nOutput: 42\n```\n**Explanation:**\n```\nThe underlined characters are what is read in and the caret is the current reader position.\nStep 1: \"42\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"42\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"42\" (\"42\" is read in)\n ^\n```"
- },
- {
- "content": "```\nInput: s = \" -042\"\nOutput: -42\n```\n**Explanation:**\n```\nStep 1: \" -042\" (leading whitespace is read and ignored)\n ^\nStep 2: \" -042\" ('-' is read, so the result should be negative)\n ^\nStep 3: \" -042\" (\"042\" is read in, leading zeros ignored in the result)\n ^\n```"
- },
- {
- "content": "```\nInput: s = \"1337c0d3\"\nOutput: 1337\n```\n**Explanation:**\n```\nStep 1: \"1337c0d3\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"1337c0d3\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"1337c0d3\" (\"1337\" is read in; reading stops because the next character is a non-digit)\n ^\n```"
- },
- {
- "content": "```\nInput: s = \"0-1\"\nOutput: 0\n```\n**Explanation:**\n```\nStep 1: \"0-1\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"0-1\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"0-1\" (\"0\" is read in; reading stops because the next character is a non-digit)\n ^\n```"
- },
- {
- "content": "```\nInput: s = \"words and 987\"\nOutput: 0\n```\n**Explanation:** Reading stops at the first non-digit character 'w'."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: s = \"42\"\nOutput: 42\n```\n**Explanation:**\n```\nThe underlined characters are what is read in and the caret is the current reader position.\nStep 1: \"42\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"42\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"42\" (\"42\" is read in)\n ^\n```"
+ },
+ {
+ "content": "```\nInput: s = \" -042\"\nOutput: -42\n```\n**Explanation:**\n```\nStep 1: \" -042\" (leading whitespace is read and ignored)\n ^\nStep 2: \" -042\" ('-' is read, so the result should be negative)\n ^\nStep 3: \" -042\" (\"042\" is read in, leading zeros ignored in the result)\n ^\n```"
+ },
+ {
+ "content": "```\nInput: s = \"1337c0d3\"\nOutput: 1337\n```\n**Explanation:**\n```\nStep 1: \"1337c0d3\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"1337c0d3\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"1337c0d3\" (\"1337\" is read in; reading stops because the next character is a non-digit)\n ^\n```"
+ },
+ {
+ "content": "```\nInput: s = \"0-1\"\nOutput: 0\n```\n**Explanation:**\n```\nStep 1: \"0-1\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"0-1\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"0-1\" (\"0\" is read in; reading stops because the next character is a non-digit)\n ^\n```"
+ },
+ {
+ "content": "```\nInput: s = \"words and 987\"\nOutput: 0\n```\n**Explanation:** Reading stops at the first non-digit character 'w'."
+ }
+ ]
+ },
"readme_constraints": "- `0 <= s.length <= 200`\n- `s` consists of English letters (lower-case and upper-case), digits (0-9), ` `, `+`, `-`, and `.`.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "my_atoi",
+ "helpers_run_signature": "(solution_class: type, s: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.my_atoi(s)",
+ "helpers_assert_name": "my_atoi",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "my_atoi", "parameters": "s: str", "return_type": "int", "dummy_return": "0" }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_my_atoi, run_my_atoi\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "StringToIntegerAtoi",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_my_atoi",
- "parametrize": "s, expected",
- "parametrize_typed": "s: str, expected: int",
- "test_cases": "[('42', 42), (' -042', -42), ('1337c0d3', 1337), ('0-1', 0), ('words and 987', 0), ('', 0), (' ', 0), ('+1', 1), ('-1', -1), ('2147483647', 2147483647), ('-2147483648', -2147483648), ('2147483648', 2147483647), ('-2147483649', -2147483648)]",
- "body": "result = self.solution.my_atoi(s)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = '42'\nexpected = 42",
- "playground_execution": "result = Solution().my_atoi(s)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "my_atoi",
+ "signature": "(self, s: str) -> int",
+ "body": " # TODO: Implement my_atoi\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_my_atoi",
+ "signature": "(self, s: str, expected: int)",
+ "parametrize": "s, expected",
+ "test_cases": "[('42', 42), (' -042', -42), ('1337c0d3', 1337), ('0-1', 0), ('words and 987', 0), ('', 0), (' ', 0), ('+1', 1), ('-1', -1), ('2147483647', 2147483647), ('-2147483648', -2147483648), ('2147483648', 2147483647), ('-2147483649', -2147483648)]",
+ "body": " result = run_my_atoi(Solution, s)\n assert_my_atoi(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_my_atoi, assert_my_atoi\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = '42'\nexpected = 42",
+ "playground_run": "result = run_my_atoi(Solution, s)\nresult",
+ "playground_assert": "assert_my_atoi(result, expected)"
}
diff --git a/.templates/leetcode/json/task_scheduler.json b/.templates/leetcode/json/task_scheduler.json
index aa7481b..908512f 100644
--- a/.templates/leetcode/json/task_scheduler.json
+++ b/.templates/leetcode/json/task_scheduler.json
@@ -5,46 +5,63 @@
"problem_title": "Task Scheduler",
"difficulty": "Medium",
"topics": "Array, Hash Table, Greedy, Sorting, Heap (Priority Queue), Counting",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "You are given an array of CPU `tasks`, each labeled with a letter from A to Z, and a number `n`. Each CPU interval can be idle or allow the completion of one task. Tasks can be completed in any order, but there's a constraint: there has to be a gap of **at least** `n` intervals between two tasks with the same label.\n\nReturn the **minimum** number of CPU intervals required to complete all tasks.",
- "readme_examples": [
- {
- "content": "```\nInput: tasks = [\"A\",\"A\",\"A\",\"B\",\"B\",\"B\"], n = 2\nOutput: 8\n```\n**Explanation:** A possible sequence is: A -> B -> idle -> A -> B -> idle -> A -> B.\n\nAfter completing task A, you must wait two intervals before doing A again. The same applies to task B. In the 3rd interval, neither A nor B can be done, so you idle. By the 4th interval, you can do A again as 2 intervals have passed."
- },
- {
- "content": "```\nInput: tasks = [\"A\",\"C\",\"A\",\"B\",\"D\",\"B\"], n = 1\nOutput: 6\n```\n**Explanation:** A possible sequence is: A -> B -> C -> D -> A -> B.\n\nWith a cooling interval of 1, you can repeat a task after just one other task."
- },
- {
- "content": "```\nInput: tasks = [\"A\",\"A\",\"A\", \"B\",\"B\",\"B\"], n = 3\nOutput: 10\n```\n**Explanation:** A possible sequence is: A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B.\n\nThere are only two types of tasks, A and B, which need to be separated by 3 intervals. This leads to idling twice between repetitions of these tasks."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: tasks = [\"A\",\"A\",\"A\",\"B\",\"B\",\"B\"], n = 2\nOutput: 8\n```\n**Explanation:** A possible sequence is: A -> B -> idle -> A -> B -> idle -> A -> B.\n\nAfter completing task A, you must wait two intervals before doing A again. The same applies to task B. In the 3rd interval, neither A nor B can be done, so you idle. By the 4th interval, you can do A again as 2 intervals have passed."
+ },
+ {
+ "content": "```\nInput: tasks = [\"A\",\"C\",\"A\",\"B\",\"D\",\"B\"], n = 1\nOutput: 6\n```\n**Explanation:** A possible sequence is: A -> B -> C -> D -> A -> B.\n\nWith a cooling interval of 1, you can repeat a task after just one other task."
+ },
+ {
+ "content": "```\nInput: tasks = [\"A\",\"A\",\"A\", \"B\",\"B\",\"B\"], n = 3\nOutput: 10\n```\n**Explanation:** A possible sequence is: A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B.\n\nThere are only two types of tasks, A and B, which need to be separated by 3 intervals. This leads to idling twice between repetitions of these tasks."
+ }
+ ]
+ },
"readme_constraints": "- `1 <= tasks.length <= 10^4`\n- `tasks[i]` is an uppercase English letter.\n- `0 <= n <= 100`",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "least_interval",
+ "helpers_run_signature": "(solution_class: type, tasks: list[str], n: int)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.least_interval(tasks, n)",
+ "helpers_assert_name": "least_interval",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "least_interval",
- "parameters": "tasks: list[str], n: int",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_least_interval, run_least_interval\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "TaskScheduler",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_least_interval",
- "parametrize": "tasks, n, expected",
- "parametrize_typed": "tasks: list[str], n: int, expected: int",
- "test_cases": "[([\"A\", \"A\", \"A\", \"B\", \"B\", \"B\"], 2, 8), ([\"A\", \"C\", \"A\", \"B\", \"D\", \"B\"], 1, 6), ([\"A\", \"A\", \"A\", \"B\", \"B\", \"B\"], 3, 10)]",
- "body": "result = self.solution.least_interval(tasks, n)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ntasks = [\\\"A\\\", \\\"A\\\", \\\"A\\\", \\\"B\\\", \\\"B\\\", \\\"B\\\"]\nn = 2\nexpected = 8",
- "playground_execution": "result = Solution().least_interval(tasks, n)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "least_interval",
+ "signature": "(self, tasks: list[str], n: int) -> int",
+ "body": " # TODO: Implement least_interval\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_least_interval",
+ "signature": "(self, tasks: list[str], n: int, expected: int)",
+ "parametrize": "tasks, n, expected",
+ "test_cases": "[(['A', 'A', 'A', 'B', 'B', 'B'], 2, 8), (['A', 'C', 'A', 'B', 'D', 'B'], 1, 6), (['A', 'A', 'A', 'B', 'B', 'B'], 3, 10), (['A'], 0, 1), (['A', 'A'], 1, 3), (['A', 'B'], 0, 2)]",
+ "body": " result = run_least_interval(Solution, tasks, n)\n assert_least_interval(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_least_interval, assert_least_interval\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ntasks = ['A', 'A', 'A', 'B', 'B', 'B']\nn = 2\nexpected = 8",
+ "playground_run": "result = run_least_interval(Solution, tasks, n)\nresult",
+ "playground_assert": "assert_least_interval(result, expected)"
}
diff --git a/.templates/leetcode/json/three_sum.json b/.templates/leetcode/json/three_sum.json
index 3c161a0..e80a8d6 100644
--- a/.templates/leetcode/json/three_sum.json
+++ b/.templates/leetcode/json/three_sum.json
@@ -5,46 +5,63 @@
"problem_title": "3Sum",
"difficulty": "Medium",
"topics": "Array, Two Pointers, Sorting",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an integer array `nums`, return all the triplets `[nums[i], nums[j], nums[k]]` such that `i != j`, `i != k`, and `j != k`, and `nums[i] + nums[j] + nums[k] == 0`.\n\nNotice that the solution set must not contain duplicate triplets.",
- "readme_examples": [
- {
- "content": "```\nInput: nums = [-1,0,1,2,-1,-4]\nOutput: [[-1,-1,2],[-1,0,1]]\n```\n**Explanation:** \nnums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0.\nnums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0.\nnums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0.\nThe distinct triplets are [-1,0,1] and [-1,-1,2].\nNotice that the order of the output and the order of the triplets does not matter."
- },
- {
- "content": "```\nInput: nums = [0,1,1]\nOutput: []\n```\n**Explanation:** The only possible triplet does not sum up to 0."
- },
- {
- "content": "```\nInput: nums = [0,0,0]\nOutput: [[0,0,0]]\n```\n**Explanation:** The only possible triplet sums up to 0."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: nums = [-1,0,1,2,-1,-4]\nOutput: [[-1,-1,2],[-1,0,1]]\n```\n**Explanation:** \nnums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0.\nnums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0.\nnums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0.\nThe distinct triplets are [-1,0,1] and [-1,-1,2].\nNotice that the order of the output and the order of the triplets does not matter."
+ },
+ {
+ "content": "```\nInput: nums = [0,1,1]\nOutput: []\n```\n**Explanation:** The only possible triplet does not sum up to 0."
+ },
+ {
+ "content": "```\nInput: nums = [0,0,0]\nOutput: [[0,0,0]]\n```\n**Explanation:** The only possible triplet sums up to 0."
+ }
+ ]
+ },
"readme_constraints": "- 3 <= nums.length <= 3000\n- -10^5 <= nums[i] <= 10^5",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "three_sum",
+ "helpers_run_signature": "(solution_class: type, nums: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.three_sum(nums)",
+ "helpers_assert_name": "three_sum",
+ "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool",
+ "helpers_assert_body": " # Sort both result and expected for comparison since order doesn't matter\n result_sorted = [sorted(triplet) for triplet in result]\n expected_sorted = [sorted(triplet) for triplet in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert result_sorted == expected_sorted\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "three_sum",
- "parameters": "nums: list[int]",
- "return_type": "list[list[int]]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_three_sum, run_three_sum\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ThreeSum",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_three_sum",
- "parametrize": "nums, expected",
- "parametrize_typed": "nums: list[int], expected: list[list[int]]",
- "test_cases": "[([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]), ([0, 1, 1], []), ([0, 0, 0], [[0, 0, 0]]), ([-1, 0, 1], [[-1, 0, 1]]), ([1, 2, -2, -1], []), ([-2, 0, 1, 1, 2], [[-2, 0, 2], [-2, 1, 1]])]",
- "body": "result = self.solution.three_sum(nums)\n# Sort both result and expected for comparison since order doesn't matter\nresult_sorted = [sorted(triplet) for triplet in result]\nexpected_sorted = [sorted(triplet) for triplet in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nnums = [-1, 0, 1, 2, -1, -4]\nexpected = [[-1, -1, 2], [-1, 0, 1]]",
- "playground_execution": "result = Solution().three_sum(nums)\nresult",
- "playground_assertion": "# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(triplet) for triplet in result]\nexpected_sorted = [sorted(triplet) for triplet in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "three_sum",
+ "signature": "(self, nums: list[int]) -> list[list[int]]",
+ "body": " # TODO: Implement three_sum\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_three_sum",
+ "signature": "(self, nums: list[int], expected: list[list[int]])",
+ "parametrize": "nums, expected",
+ "test_cases": "[([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]), ([0, 1, 1], []), ([0, 0, 0], [[0, 0, 0]]), ([-1, 0, 1], [[-1, 0, 1]]), ([1, 2, -2, -1], []), ([-2, 0, 1, 1, 2], [[-2, 0, 2], [-2, 1, 1]])]",
+ "body": " result = run_three_sum(Solution, nums)\n assert_three_sum(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_three_sum, assert_three_sum\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nnums = [-1, 0, 1, 2, -1, -4]\nexpected = [[-1, -1, 2], [-1, 0, 1]]",
+ "playground_run": "result = run_three_sum(Solution, nums)\nresult",
+ "playground_assert": "assert_three_sum(result, expected)"
}
diff --git a/.templates/leetcode/json/time_based_key_value_store.json b/.templates/leetcode/json/time_based_key_value_store.json
index 11ace03..529ce24 100644
--- a/.templates/leetcode/json/time_based_key_value_store.json
+++ b/.templates/leetcode/json/time_based_key_value_store.json
@@ -5,45 +5,65 @@
"problem_title": "Time Based Key-Value Store",
"difficulty": "Medium",
"topics": "Hash Table, String, Binary Search, Design",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Design a time-based key-value data structure that can store multiple values for the same key at different time stamps and retrieve the key's value at a certain timestamp.\n\nImplement the `TimeMap` class:\n\n- `TimeMap()` Initializes the object of the data structure.\n- `void set(String key, String value, int timestamp)` Stores the key `key` with the value `value` at the given time `timestamp`.\n- `String get(String key, int timestamp)` Returns a value such that `set` was called previously, with `timestamp_prev <= timestamp`. If there are multiple such values, it returns the value associated with the largest `timestamp_prev`. If there are no values, it returns `\"\"`.",
- "readme_examples": [
- {
- "content": "```\nInput\n[\"TimeMap\", \"set\", \"get\", \"get\", \"set\", \"get\", \"get\"]\n[[], [\"foo\", \"bar\", 1], [\"foo\", 1], [\"foo\", 3], [\"foo\", \"bar2\", 4], [\"foo\", 4], [\"foo\", 5]]\nOutput\n[null, null, \"bar\", \"bar\", null, \"bar2\", \"bar2\"]\n```\n\n**Explanation:**\n```\nTimeMap timeMap = new TimeMap();\ntimeMap.set(\"foo\", \"bar\", 1); // store the key \"foo\" and value \"bar\" along with timestamp = 1.\ntimeMap.get(\"foo\", 1); // return \"bar\"\ntimeMap.get(\"foo\", 3); // return \"bar\", since there is no value corresponding to foo at timestamp 3 and timestamp 2, then the only value is at timestamp 1 is \"bar\".\ntimeMap.set(\"foo\", \"bar2\", 4); // store the key \"foo\" and value \"bar2\" along with timestamp = 4.\ntimeMap.get(\"foo\", 4); // return \"bar2\"\ntimeMap.get(\"foo\", 5); // return \"bar2\"\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput\n[\"TimeMap\", \"set\", \"get\", \"get\", \"set\", \"get\", \"get\"]\n[[], [\"foo\", \"bar\", 1], [\"foo\", 1], [\"foo\", 3], [\"foo\", \"bar2\", 4], [\"foo\", 4], [\"foo\", 5]]\nOutput\n[null, null, \"bar\", \"bar\", null, \"bar2\", \"bar2\"]\n```\n\n**Explanation:**\n```\nTimeMap timeMap = new TimeMap();\ntimeMap.set(\"foo\", \"bar\", 1); // store the key \"foo\" and value \"bar\" along with timestamp = 1.\ntimeMap.get(\"foo\", 1); // return \"bar\"\ntimeMap.get(\"foo\", 3); // return \"bar\", since there is no value corresponding to foo at timestamp 3 and timestamp 2, then the only value is at timestamp 1 is \"bar\".\ntimeMap.set(\"foo\", \"bar2\", 4); // store the key \"foo\" and value \"bar2\" along with timestamp = 4.\ntimeMap.get(\"foo\", 4); // return \"bar2\"\ntimeMap.get(\"foo\", 5); // return \"bar2\"\n```"
+ }
+ ]
+ },
"readme_constraints": "- `1 <= key.length, value.length <= 100`\n- `key` and `value` consist of lowercase English letters and digits.\n- `1 <= timestamp <= 10^7`\n- All the timestamps `timestamp` of `set` are strictly increasing.\n- At most `2 * 10^5` calls will be made to `set` and `get`.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "time_map_operations",
+ "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list])",
+ "helpers_run_body": " time_map = None\n results: list[str | None] = []\n for i, op in enumerate(operations):\n if op == 'TimeMap':\n time_map = solution_class()\n results.append(None)\n elif op == 'set' and time_map is not None:\n time_map.set(*inputs[i])\n results.append(None)\n elif op == 'get' and time_map is not None:\n results.append(time_map.get(*inputs[i]))\n return results, time_map",
+ "helpers_assert_name": "time_map_operations",
+ "helpers_assert_signature": "(result: list, expected: list) -> bool",
+ "helpers_assert_body": " results, _ = result\n assert results == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "__init__", "parameters": "", "return_type": "None", "dummy_return": "" },
- {
- "name": "set",
- "parameters": "key: str, value: str, timestamp: int",
- "return_type": "None",
- "dummy_return": ""
- },
- {
- "name": "get",
- "parameters": "key: str, timestamp: int",
- "return_type": "str",
- "dummy_return": "\"\""
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import TimeMap",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_time_map_operations, run_time_map_operations\nfrom .solution import TimeMap",
+ "test_content": "",
"test_class_name": "TimeBasedKeyValueStore",
- "test_helper_methods": [],
- "test_methods": [
- {
- "name": "test_time_map_operations",
- "parametrize": "operations, inputs, expected",
- "parametrize_typed": "operations: list[str], inputs: list[list], expected: list",
- "test_cases": "[(['TimeMap', 'set', 'get', 'get', 'set', 'get', 'get'], [[], ['foo', 'bar', 1], ['foo', 1], ['foo', 3], ['foo', 'bar2', 4], ['foo', 4], ['foo', 5]], [None, None, 'bar', 'bar', None, 'bar2', 'bar2'])]",
- "body": "time_map: TimeMap | None = None\nresult: list[str | None] = []\nfor i, op in enumerate(operations):\n if op == 'TimeMap':\n time_map = TimeMap()\n result.append(None)\n elif op == 'set' and time_map is not None:\n time_map.set(*inputs[i])\n result.append(None)\n elif op == 'get' and time_map is not None:\n result.append(time_map.get(*inputs[i]))\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import TimeMap",
- "playground_test_case": "# Example test case\ntime_map = TimeMap()\ntime_map.set('foo', 'bar', 1)\nresult1 = time_map.get('foo', 1)\nresult2 = time_map.get('foo', 3)\ntime_map.set('foo', 'bar2', 4)\nresult3 = time_map.get('foo', 4)\nresult4 = time_map.get('foo', 5)",
- "playground_execution": "print(f'get(foo, 1): {result1}')\nprint(f'get(foo, 3): {result2}')\nprint(f'get(foo, 4): {result3}')\nprint(f'get(foo, 5): {result4}')",
- "playground_assertion": "assert result1 == 'bar'\nassert result2 == 'bar'\nassert result3 == 'bar2'\nassert result4 == 'bar2'"
+ "test_class_content": "",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "__init__",
+ "signature": "(self) -> None",
+ "body": " # TODO: Initialize\n pass"
+ },
+ {
+ "name": "set",
+ "signature": "(self, key: str, value: str, timestamp: int) -> None",
+ "body": " # TODO: Implement set\n pass"
+ },
+ {
+ "name": "get",
+ "signature": "(self, key: str, timestamp: int) -> str",
+ "body": " # TODO: Implement get\n return \"\""
+ }
+ ]
+ },
+ "_test_helper_methods": { "list": [] },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_time_map_operations",
+ "signature": "(self, operations: list[str], inputs: list[list], expected: list)",
+ "parametrize": "operations, inputs, expected",
+ "test_cases": "[(['TimeMap', 'set', 'get', 'get', 'set', 'get', 'get'], [[], ['foo', 'bar', 1], ['foo', 1], ['foo', 3], ['foo', 'bar2', 4], ['foo', 4], ['foo', 5]], [None, None, 'bar', 'bar', None, 'bar2', 'bar2']), (['TimeMap', 'get'], [[], ['key', 1]], [None, '']), (['TimeMap', 'set', 'get'], [[], ['a', 'val', 1], ['a', 1]], [None, None, 'val']), (['TimeMap', 'set', 'get', 'get'], [[], ['key', 'value', 5], ['key', 3], ['key', 7]], [None, None, '', 'value']), (['TimeMap', 'set', 'set', 'get', 'get', 'get'], [[], ['x', 'v1', 1], ['x', 'v2', 2], ['x', 1], ['x', 2], ['x', 3]], [None, None, None, 'v1', 'v2', 'v2']), (['TimeMap', 'set', 'set', 'set', 'get', 'get', 'get'], [[], ['k', 'a', 10], ['k', 'b', 20], ['k', 'c', 30], ['k', 15], ['k', 25], ['k', 35]], [None, None, None, None, 'a', 'b', 'c']), (['TimeMap', 'set', 'set', 'get', 'get'], [[], ['key1', 'val1', 1], ['key2', 'val2', 2], ['key1', 1], ['key2', 2]], [None, None, None, 'val1', 'val2']), (['TimeMap', 'set', 'set', 'set', 'get', 'get', 'get'], [[], ['a', 'x', 1], ['b', 'y', 2], ['c', 'z', 3], ['a', 1], ['b', 2], ['c', 3]], [None, None, None, None, 'x', 'y', 'z']), (['TimeMap', 'set', 'get', 'set', 'get', 'set', 'get'], [[], ['test', 'first', 1], ['test', 1], ['test', 'second', 100], ['test', 50], ['test', 'third', 1000], ['test', 500]], [None, None, 'first', None, 'first', None, 'second']), (['TimeMap', 'set', 'set', 'set', 'set', 'get'], [[], ['data', 'v1', 1], ['data', 'v2', 10], ['data', 'v3', 100], ['data', 'v4', 1000], ['data', 555]], [None, None, None, None, None, 'v3']), (['TimeMap', 'set', 'get', 'get', 'get'], [[], ['single', 'value', 42], ['single', 1], ['single', 42], ['single', 100]], [None, None, '', 'value', 'value']), (['TimeMap', 'set', 'set', 'get', 'get', 'get', 'get'], [[], ['boundary', 'min', 1], ['boundary', 'max', 10000000], ['boundary', 0], ['boundary', 1], ['boundary', 5000000], ['boundary', 10000000]], [None, None, None, '', 'min', 'min', 'max'])]",
+ "body": " result = run_time_map_operations(TimeMap, operations, inputs)\n assert_time_map_operations(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_time_map_operations, assert_time_map_operations\nfrom solution import TimeMap",
+ "playground_setup": "# Example test case\noperations = ['TimeMap', 'set', 'get', 'get', 'set', 'get', 'get']\ninputs = [[], ['foo', 'bar', 1], ['foo', 1], ['foo', 3], ['foo', 'bar2', 4], ['foo', 4], ['foo', 5]]\nexpected = [None, None, 'bar', 'bar', None, 'bar2', 'bar2']",
+ "playground_run": "result = run_time_map_operations(TimeMap, operations, inputs)\nresult",
+ "playground_assert": "assert_time_map_operations(result, expected)"
}
diff --git a/.templates/leetcode/json/trapping_rain_water.json b/.templates/leetcode/json/trapping_rain_water.json
index dfdb0bb..5b44338 100644
--- a/.templates/leetcode/json/trapping_rain_water.json
+++ b/.templates/leetcode/json/trapping_rain_water.json
@@ -5,36 +5,58 @@
"problem_title": "Trapping Rain Water",
"difficulty": "Hard",
"topics": "Array, Two Pointers, Dynamic Programming, Stack, Monotonic Stack",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given `n` non-negative integers representing an elevation map where the width of each bar is `1`, compute how much water it can trap after raining.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: height = [0,1,0,2,1,0,1,3,2,1,2,1]\nOutput: 6\n```\n**Explanation:** The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped."
- },
- { "content": "```\nInput: height = [4,2,0,3,2,5]\nOutput: 9\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: height = [0,1,0,2,1,0,1,3,2,1,2,1]\nOutput: 6\n```\n**Explanation:** The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped."
+ },
+ { "content": "```\nInput: height = [4,2,0,3,2,5]\nOutput: 9\n```" }
+ ]
+ },
"readme_constraints": "- `n == height.length`\n- `1 <= n <= 2 * 10^4`\n- `0 <= height[i] <= 10^5`",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "trap",
+ "helpers_run_signature": "(solution_class: type, height: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.trap(height)",
+ "helpers_assert_name": "trap",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "trap", "parameters": "height: list[int]", "return_type": "int", "dummy_return": "0" }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_trap, run_trap\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "TrappingRainWater",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_trap",
- "parametrize": "height, expected",
- "parametrize_typed": "height: list[int], expected: int",
- "test_cases": "[([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6), ([4, 2, 0, 3, 2, 5], 9), ([3, 0, 2, 0, 4], 7), ([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6)]",
- "body": "result = self.solution.trap(height)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nheight = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]\nexpected = 6",
- "playground_execution": "result = Solution().trap(height)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "trap",
+ "signature": "(self, height: list[int]) -> int",
+ "body": " # TODO: Implement trap\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_trap",
+ "signature": "(self, height: list[int], expected: int)",
+ "parametrize": "height, expected",
+ "test_cases": "[([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6), ([4, 2, 0, 3, 2, 5], 9), ([3, 0, 2, 0, 4], 7), ([0], 0), ([1], 0), ([1, 2], 0), ([2, 1], 0)]",
+ "body": " result = run_trap(Solution, height)\n assert_trap(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_trap, assert_trap\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nheight = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]\nexpected = 6",
+ "playground_run": "result = run_trap(Solution, height)\nresult",
+ "playground_assert": "assert_trap(result, expected)"
}
diff --git a/.templates/leetcode/json/valid_anagram.json b/.templates/leetcode/json/valid_anagram.json
index 32a2ab1..2a0c3ce 100644
--- a/.templates/leetcode/json/valid_anagram.json
+++ b/.templates/leetcode/json/valid_anagram.json
@@ -5,39 +5,56 @@
"problem_title": "Valid Anagram",
"difficulty": "Easy",
"topics": "Hash Table, String, Sorting",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given two strings `s` and `t`, return `true` if `t` is an anagram of `s`, and `false` otherwise.",
- "readme_examples": [
- { "content": "```\nInput: s = \"anagram\", t = \"nagaram\"\nOutput: true\n```" },
- { "content": "```\nInput: s = \"rat\", t = \"car\"\nOutput: false\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: s = \"anagram\", t = \"nagaram\"\nOutput: true\n```" },
+ { "content": "```\nInput: s = \"rat\", t = \"car\"\nOutput: false\n```" }
+ ]
+ },
"readme_constraints": "- 1 <= s.length, t.length <= 5 * 10^4\n- s and t consist of lowercase English letters.",
"readme_additional": "**Follow up:** What if the inputs contain Unicode characters? How would you adapt your solution to such a case?",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "is_anagram",
+ "helpers_run_signature": "(solution_class: type, s: str, t: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.is_anagram(s, t)",
+ "helpers_assert_name": "is_anagram",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "is_anagram",
- "parameters": "s: str, t: str",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_anagram, run_is_anagram\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ValidAnagram",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_is_anagram",
- "parametrize": "s, t, expected",
- "parametrize_typed": "s: str, t: str, expected: bool",
- "test_cases": "[('anagram', 'nagaram', True), ('rat', 'car', False), ('listen', 'silent', True), ('hello', 'bello', False), ('', '', True), ('a', 'a', True), ('a', 'b', False), ('ab', 'ba', True)]",
- "body": "result = self.solution.is_anagram(s, t)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = 'anagram'\nt = 'nagaram'\nexpected = True",
- "playground_execution": "result = Solution().is_anagram(s, t)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "is_anagram",
+ "signature": "(self, s: str, t: str) -> bool",
+ "body": " # TODO: Implement is_anagram\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_is_anagram",
+ "signature": "(self, s: str, t: str, expected: bool)",
+ "parametrize": "s, t, expected",
+ "test_cases": "[('anagram', 'nagaram', True), ('rat', 'car', False), ('listen', 'silent', True), ('hello', 'bello', False), ('', '', True), ('a', 'a', True), ('a', 'b', False), ('ab', 'ba', True), ('abc', 'bca', True), ('abc', 'def', False), ('aab', 'abb', False), ('aabbcc', 'abcabc', True), ('abcd', 'abcde', False), ('race', 'care', True), ('elbow', 'below', True), ('study', 'dusty', True), ('night', 'thing', True), ('stressed', 'desserts', True)]",
+ "body": " result = run_is_anagram(Solution, s, t)\n assert_is_anagram(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_is_anagram, assert_is_anagram\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = 'anagram'\nt = 'nagaram'\nexpected = True",
+ "playground_run": "result = run_is_anagram(Solution, s, t)\nresult",
+ "playground_assert": "assert_is_anagram(result, expected)"
}
diff --git a/.templates/leetcode/json/valid_palindrome.json b/.templates/leetcode/json/valid_palindrome.json
index cb4b861..3e264eb 100644
--- a/.templates/leetcode/json/valid_palindrome.json
+++ b/.templates/leetcode/json/valid_palindrome.json
@@ -5,46 +5,63 @@
"problem_title": "Valid Palindrome",
"difficulty": "Easy",
"topics": "Two Pointers, String",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "A phrase is a **palindrome** if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.\n\nGiven a string `s`, return `true` if it is a **palindrome**, or `false` otherwise.",
- "readme_examples": [
- {
- "content": "```\nInput: s = \"A man, a plan, a canal: Panama\"\nOutput: true\n```\n**Explanation:** \"amanaplanacanalpanama\" is a palindrome."
- },
- {
- "content": "```\nInput: s = \"race a car\"\nOutput: false\n```\n**Explanation:** \"raceacar\" is not a palindrome."
- },
- {
- "content": "```\nInput: s = \" \"\nOutput: true\n```\n**Explanation:** s is an empty string \"\" after removing non-alphanumeric characters. Since an empty string reads the same forward and backward, it is a palindrome."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: s = \"A man, a plan, a canal: Panama\"\nOutput: true\n```\n**Explanation:** \"amanaplanacanalpanama\" is a palindrome."
+ },
+ {
+ "content": "```\nInput: s = \"race a car\"\nOutput: false\n```\n**Explanation:** \"raceacar\" is not a palindrome."
+ },
+ {
+ "content": "```\nInput: s = \" \"\nOutput: true\n```\n**Explanation:** s is an empty string \"\" after removing non-alphanumeric characters. Since an empty string reads the same forward and backward, it is a palindrome."
+ }
+ ]
+ },
"readme_constraints": "- `1 <= s.length <= 2 * 10^5`\n- `s` consists only of printable ASCII characters.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "is_palindrome",
+ "helpers_run_signature": "(solution_class: type, s: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.is_palindrome(s)",
+ "helpers_assert_name": "is_palindrome",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "is_palindrome",
- "parameters": "s: str",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_palindrome, run_is_palindrome\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ValidPalindrome",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_is_palindrome",
- "parametrize": "s, expected",
- "parametrize_typed": "s: str, expected: bool",
- "test_cases": "[(\"A man, a plan, a canal: Panama\", True), (\"race a car\", False), (\" \", True), (\"\", True), (\"a\", True), (\"Madam\", True), (\"No 'x' in Nixon\", True), (\"Mr. Owl ate my metal worm\", True)]",
- "body": "result = self.solution.is_palindrome(s)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = \"A man, a plan, a canal: Panama\"\nexpected = True",
- "playground_execution": "result = Solution().is_palindrome(s)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "is_palindrome",
+ "signature": "(self, s: str) -> bool",
+ "body": " # TODO: Implement is_palindrome\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_is_palindrome",
+ "signature": "(self, s: str, expected: bool)",
+ "parametrize": "s, expected",
+ "test_cases": "[(\"A man, a plan, a canal: Panama\", True), (\"race a car\", False), (\" \", True), (\"\", True), (\"a\", True), (\"Madam\", True), (\"No 'x' in Nixon\", True), (\"Mr. Owl ate my metal worm\", True)]",
+ "body": " result = run_is_palindrome(Solution, s)\n assert_is_palindrome(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_is_palindrome, assert_is_palindrome\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = \"A man, a plan, a canal: Panama\"\nexpected = True",
+ "playground_run": "result = run_is_palindrome(Solution, s)\nresult",
+ "playground_assert": "assert_is_palindrome(result, expected)"
}
diff --git a/.templates/leetcode/json/valid_parentheses.json b/.templates/leetcode/json/valid_parentheses.json
index 5029661..cc3c986 100644
--- a/.templates/leetcode/json/valid_parentheses.json
+++ b/.templates/leetcode/json/valid_parentheses.json
@@ -5,37 +5,59 @@
"problem_title": "Valid Parentheses",
"difficulty": "Easy",
"topics": "String, Stack",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid.\n\nAn input string is valid if:\n\n1. Open brackets must be closed by the same type of brackets.\n2. Open brackets must be closed in the correct order.\n3. Every close bracket has a corresponding open bracket of the same type.",
- "readme_examples": [
- { "content": "```\nInput: s = \"()\"\nOutput: true\n```" },
- { "content": "```\nInput: s = \"()[]{}\"\nOutput: true\n```" },
- { "content": "```\nInput: s = \"(]\"\nOutput: false\n```" },
- { "content": "```\nInput: s = \"([])\"\nOutput: true\n```" },
- { "content": "```\nInput: s = \"([)]\"\nOutput: false\n```" }
- ],
+ "_readme_examples": {
+ "list": [
+ { "content": "```\nInput: s = \"()\"\nOutput: true\n```" },
+ { "content": "```\nInput: s = \"()[]{}\"\nOutput: true\n```" },
+ { "content": "```\nInput: s = \"(]\"\nOutput: false\n```" },
+ { "content": "```\nInput: s = \"([])\"\nOutput: true\n```" },
+ { "content": "```\nInput: s = \"([)]\"\nOutput: false\n```" }
+ ]
+ },
"readme_constraints": "- `1 <= s.length <= 10^4`\n- `s` consists of parentheses only `'()[]{}'`.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "is_valid",
+ "helpers_run_signature": "(solution_class: type, s: str)",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.is_valid(s)",
+ "helpers_assert_name": "is_valid",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- { "name": "is_valid", "parameters": "s: str", "return_type": "bool", "dummy_return": "False" }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_valid, run_is_valid\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ValidParentheses",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_is_valid",
- "parametrize": "s, expected",
- "parametrize_typed": "s: str, expected: bool",
- "test_cases": "[('()', True), ('()[]{}', True), ('(]', False), ('([])', True), ('([)]', False), ('', True), ('(', False), (')', False), ('{[()]}', True), ('{[(])}', False)]",
- "body": "result = self.solution.is_valid(s)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = '()'\nexpected = True",
- "playground_execution": "result = Solution().is_valid(s)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "is_valid",
+ "signature": "(self, s: str) -> bool",
+ "body": " # TODO: Implement is_valid\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_is_valid",
+ "signature": "(self, s: str, expected: bool)",
+ "parametrize": "s, expected",
+ "test_cases": "[('()', True), ('()[]{}', True), ('(]', False), ('([])', True), ('([)]', False), ('', True), ('(', False), (')', False), ('{[()]}', True), ('{[(])}', False)]",
+ "body": " result = run_is_valid(Solution, s)\n assert_is_valid(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_is_valid, assert_is_valid\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = '()'\nexpected = True",
+ "playground_run": "result = run_is_valid(Solution, s)\nresult",
+ "playground_assert": "assert_is_valid(result, expected)"
}
diff --git a/.templates/leetcode/json/validate_binary_search_tree.json b/.templates/leetcode/json/validate_binary_search_tree.json
index fd834b8..ef141e2 100644
--- a/.templates/leetcode/json/validate_binary_search_tree.json
+++ b/.templates/leetcode/json/validate_binary_search_tree.json
@@ -5,43 +5,60 @@
"problem_title": "Validate Binary Search Tree",
"difficulty": "Medium",
"topics": "Tree, Depth-First Search, Binary Search Tree, Binary Tree",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given the `root` of a binary tree, determine if it is a valid binary search tree (BST).\n\nA **valid BST** is defined as follows:\n\n- The left subtree of a node contains only nodes with keys **strictly less than** the node's key.\n- The right subtree of a node contains only nodes with keys **strictly greater than** the node's key.\n- Both the left and right subtrees must also be binary search trees.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: root = [2,1,3]\nOutput: true\n```"
- },
- {
- "content": "\n\n```\nInput: root = [5,1,4,null,null,3,6]\nOutput: false\n```\n**Explanation:** The root node's value is 5 but its right child's value is 4."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: root = [2,1,3]\nOutput: true\n```"
+ },
+ {
+ "content": "\n\n```\nInput: root = [5,1,4,null,null,3,6]\nOutput: false\n```\n**Explanation:** The root node's value is 5 but its right child's value is 4."
+ }
+ ]
+ },
"readme_constraints": "- The number of nodes in the tree is in the range `[1, 10^4]`.\n- `-2^31 <= Node.val <= 2^31 - 1`",
"readme_additional": "",
+ "helpers_imports": "from leetcode_py import TreeNode",
+ "helpers_content": "",
+ "helpers_run_name": "is_valid_bst",
+ "helpers_run_signature": "(solution_class: type, root_list: list[int | None])",
+ "helpers_run_body": " root = TreeNode[int].from_list(root_list) if root_list else None\n implementation = solution_class()\n return implementation.is_valid_bst(root)",
+ "helpers_assert_name": "is_valid_bst",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "from leetcode_py import TreeNode",
- "solution_methods": [
- {
- "name": "is_valid_bst",
- "parameters": "root: TreeNode[int] | None",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_valid_bst, run_is_valid_bst\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ValidateBinarySearchTree",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_is_valid_bst",
- "parametrize": "root_list, expected",
- "parametrize_typed": "root_list: list[int | None], expected: bool",
- "test_cases": "[([2, 1, 3], True), ([5, 1, 4, None, None, 3, 6], False), ([2, 1, 3], True), ([1], True), ([1, 1], False)]",
- "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.is_valid_bst(root)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode",
- "playground_test_case": "# Example test case\nroot_list: list[int | None] = [2, 1, 3]\nexpected = True",
- "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().is_valid_bst(root)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "is_valid_bst",
+ "signature": "(self, root: TreeNode[int] | None) -> bool",
+ "body": " # TODO: Implement is_valid_bst\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_is_valid_bst",
+ "signature": "(self, root_list: list[int | None], expected: bool)",
+ "parametrize": "root_list, expected",
+ "test_cases": "[([2, 1, 3], True), ([5, 1, 4, None, None, 3, 6], False), ([1], True), ([1, 1], False), ([10, 5, 15, None, None, 6, 20], False), ([2, 1, 3, None, None, None, 4], True)]",
+ "body": " result = run_is_valid_bst(Solution, root_list)\n assert_is_valid_bst(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_is_valid_bst, assert_is_valid_bst\nfrom solution import Solution\nfrom leetcode_py import TreeNode",
+ "playground_setup": "# Example test case\nroot_list = [2, 1, 3]\nexpected = True",
+ "playground_run": "result = run_is_valid_bst(Solution, root_list)\nresult",
+ "playground_assert": "assert_is_valid_bst(result, expected)"
}
diff --git a/.templates/leetcode/json/word_break.json b/.templates/leetcode/json/word_break.json
index 83332be..1a0f5c0 100644
--- a/.templates/leetcode/json/word_break.json
+++ b/.templates/leetcode/json/word_break.json
@@ -5,46 +5,63 @@
"problem_title": "Word Break",
"difficulty": "Medium",
"topics": "Array, Hash Table, String, Dynamic Programming, Trie, Memoization",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given a string `s` and a dictionary of strings `wordDict`, return `true` if `s` can be segmented into a space-separated sequence of one or more dictionary words.\n\n**Note** that the same word in the dictionary may be reused multiple times in the segmentation.",
- "readme_examples": [
- {
- "content": "```\nInput: s = \"leetcode\", wordDict = [\"leet\",\"code\"]\nOutput: true\n```\n**Explanation:** Return true because \"leetcode\" can be segmented as \"leet code\"."
- },
- {
- "content": "```\nInput: s = \"applepenapple\", wordDict = [\"apple\",\"pen\"]\nOutput: true\n```\n**Explanation:** Return true because \"applepenapple\" can be segmented as \"apple pen apple\".\nNote that you are allowed to reuse a dictionary word."
- },
- {
- "content": "```\nInput: s = \"catsandog\", wordDict = [\"cats\",\"dog\",\"sand\",\"and\",\"cat\"]\nOutput: false\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: s = \"leetcode\", wordDict = [\"leet\",\"code\"]\nOutput: true\n```\n**Explanation:** Return true because \"leetcode\" can be segmented as \"leet code\"."
+ },
+ {
+ "content": "```\nInput: s = \"applepenapple\", wordDict = [\"apple\",\"pen\"]\nOutput: true\n```\n**Explanation:** Return true because \"applepenapple\" can be segmented as \"apple pen apple\".\nNote that you are allowed to reuse a dictionary word."
+ },
+ {
+ "content": "```\nInput: s = \"catsandog\", wordDict = [\"cats\",\"dog\",\"sand\",\"and\",\"cat\"]\nOutput: false\n```"
+ }
+ ]
+ },
"readme_constraints": "- `1 <= s.length <= 300`\n- `1 <= wordDict.length <= 1000`\n- `1 <= wordDict[i].length <= 20`\n- `s` and `wordDict[i]` consist of only lowercase English letters.\n- All the strings of `wordDict` are **unique**.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "word_break",
+ "helpers_run_signature": "(solution_class: type, s: str, word_dict: list[str])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.word_break(s, word_dict)",
+ "helpers_assert_name": "word_break",
+ "helpers_assert_signature": "(result: bool, expected: bool) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "word_break",
- "parameters": "s: str, word_dict: list[str]",
- "return_type": "bool",
- "dummy_return": "False"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_word_break, run_word_break\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "WordBreak",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_word_break",
- "parametrize": "s, word_dict, expected",
- "parametrize_typed": "s: str, word_dict: list[str], expected: bool",
- "test_cases": "[('leetcode', ['leet', 'code'], True), ('applepenapple', ['apple', 'pen'], True), ('catsandog', ['cats', 'dog', 'sand', 'and', 'cat'], False), ('', [], True), ('a', ['a'], True), ('ab', ['a', 'b'], True), ('abcd', ['a', 'abc', 'd'], True)]",
- "body": "result = self.solution.word_break(s, word_dict)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\ns = 'leetcode'\nword_dict = ['leet', 'code']\nexpected = True",
- "playground_execution": "result = Solution().word_break(s, word_dict)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "word_break",
+ "signature": "(self, s: str, word_dict: list[str]) -> bool",
+ "body": " # TODO: Implement word_break\n return False"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_word_break",
+ "signature": "(self, s: str, word_dict: list[str], expected: bool)",
+ "parametrize": "s, word_dict, expected",
+ "test_cases": "[('leetcode', ['leet', 'code'], True), ('applepenapple', ['apple', 'pen'], True), ('catsandog', ['cats', 'dog', 'sand', 'and', 'cat'], False), ('', [], True), ('a', ['a'], True), ('ab', ['a', 'b'], True), ('abcd', ['a', 'abc', 'd'], True)]",
+ "body": " result = run_word_break(Solution, s, word_dict)\n assert_word_break(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_word_break, assert_word_break\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ns = 'leetcode'\nword_dict = ['leet', 'code']\nexpected = True",
+ "playground_run": "result = run_word_break(Solution, s, word_dict)\nresult",
+ "playground_assert": "assert_word_break(result, expected)"
}
diff --git a/.templates/leetcode/json/word_ladder.json b/.templates/leetcode/json/word_ladder.json
index 0e417b3..78cf3f5 100644
--- a/.templates/leetcode/json/word_ladder.json
+++ b/.templates/leetcode/json/word_ladder.json
@@ -5,43 +5,60 @@
"problem_title": "Word Ladder",
"difficulty": "Hard",
"topics": "Hash Table, String, Breadth-First Search",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "A **transformation sequence** from word `beginWord` to word `endWord` using a dictionary `wordList` is a sequence of words `beginWord -> s1 -> s2 -> ... -> sk` such that:\n\n- Every adjacent pair of words differs by a single letter.\n- Every `si` for `1 <= i <= k` is in `wordList`. Note that `beginWord` does not need to be in `wordList`.\n- `sk == endWord`\n\nGiven two words, `beginWord` and `endWord`, and a dictionary `wordList`, return the **number of words** in the **shortest transformation sequence** from `beginWord` to `endWord`, or `0` if no such sequence exists.",
- "readme_examples": [
- {
- "content": "```\nInput: beginWord = \"hit\", endWord = \"cog\", wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\",\"cog\"]\nOutput: 5\n```\n**Explanation:** One shortest transformation sequence is \"hit\" -> \"hot\" -> \"dot\" -> \"dog\" -> \"cog\", which is 5 words long."
- },
- {
- "content": "```\nInput: beginWord = \"hit\", endWord = \"cog\", wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\"]\nOutput: 0\n```\n**Explanation:** The endWord \"cog\" is not in wordList, therefore there is no valid transformation sequence."
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: beginWord = \"hit\", endWord = \"cog\", wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\",\"cog\"]\nOutput: 5\n```\n**Explanation:** One shortest transformation sequence is \"hit\" -> \"hot\" -> \"dot\" -> \"dog\" -> \"cog\", which is 5 words long."
+ },
+ {
+ "content": "```\nInput: beginWord = \"hit\", endWord = \"cog\", wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\"]\nOutput: 0\n```\n**Explanation:** The endWord \"cog\" is not in wordList, therefore there is no valid transformation sequence."
+ }
+ ]
+ },
"readme_constraints": "- 1 <= beginWord.length <= 10\n- endWord.length == beginWord.length\n- 1 <= wordList.length <= 5000\n- wordList[i].length == beginWord.length\n- beginWord, endWord, and wordList[i] consist of lowercase English letters.\n- beginWord != endWord\n- All the words in wordList are unique.",
"readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "ladder_length",
+ "helpers_run_signature": "(solution_class: type, begin_word: str, end_word: str, word_list: list[str])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.ladder_length(begin_word, end_word, word_list)",
+ "helpers_assert_name": "ladder_length",
+ "helpers_assert_signature": "(result: int, expected: int) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "ladder_length",
- "parameters": "begin_word: str, end_word: str, word_list: list[str]",
- "return_type": "int",
- "dummy_return": "0"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_ladder_length, run_ladder_length\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "WordLadder",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_ladder_length",
- "parametrize": "begin_word, end_word, word_list, expected",
- "parametrize_typed": "begin_word: str, end_word: str, word_list: list[str], expected: int",
- "test_cases": "[('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log', 'cog'], 5), ('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log'], 0), ('a', 'c', ['a', 'b', 'c'], 2)]",
- "body": "result = self.solution.ladder_length(begin_word, end_word, word_list)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nbegin_word = 'hit'\nend_word = 'cog'\nword_list = ['hot', 'dot', 'dog', 'lot', 'log', 'cog']\nexpected = 5",
- "playground_execution": "result = Solution().ladder_length(begin_word, end_word, word_list)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "ladder_length",
+ "signature": "(self, begin_word: str, end_word: str, word_list: list[str]) -> int",
+ "body": " # TODO: Implement ladder_length\n return 0"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_ladder_length",
+ "signature": "(self, begin_word: str, end_word: str, word_list: list[str], expected: int)",
+ "parametrize": "begin_word, end_word, word_list, expected",
+ "test_cases": "[('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log', 'cog'], 5), ('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log'], 0), ('a', 'c', ['a', 'b', 'c'], 2), ('hot', 'dog', ['hot', 'dog'], 0), ('hot', 'dog', ['hot', 'hog', 'dog'], 3)]",
+ "body": " result = run_ladder_length(Solution, begin_word, end_word, word_list)\n assert_ladder_length(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_ladder_length, assert_ladder_length\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nbegin_word = 'hit'\nend_word = 'cog'\nword_list = ['hot', 'dot', 'dog', 'lot', 'log', 'cog']\nexpected = 5",
+ "playground_run": "result = run_ladder_length(Solution, begin_word, end_word, word_list)\nresult",
+ "playground_assert": "assert_ladder_length(result, expected)"
}
diff --git a/.templates/leetcode/json/zero_one_matrix.json b/.templates/leetcode/json/zero_one_matrix.json
index fd537af..81ab126 100644
--- a/.templates/leetcode/json/zero_one_matrix.json
+++ b/.templates/leetcode/json/zero_one_matrix.json
@@ -5,43 +5,60 @@
"problem_title": "01 Matrix",
"difficulty": "Medium",
"topics": "Array, Dynamic Programming, Breadth-First Search, Matrix",
- "tags": ["grind-75"],
+ "_tags": { "list": ["grind-75"] },
"readme_description": "Given an `m x n` binary matrix `mat`, return the distance of the nearest `0` for each cell.\n\nThe distance between two cells sharing a common edge is `1`.",
- "readme_examples": [
- {
- "content": "\n\n```\nInput: mat = [[0,0,0],[0,1,0],[0,0,0]]\nOutput: [[0,0,0],[0,1,0],[0,0,0]]\n```"
- },
- {
- "content": "\n\n```\nInput: mat = [[0,0,0],[0,1,0],[1,1,1]]\nOutput: [[0,0,0],[0,1,0],[1,2,1]]\n```"
- }
- ],
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "\n\n```\nInput: mat = [[0,0,0],[0,1,0],[0,0,0]]\nOutput: [[0,0,0],[0,1,0],[0,0,0]]\n```"
+ },
+ {
+ "content": "\n\n```\nInput: mat = [[0,0,0],[0,1,0],[1,1,1]]\nOutput: [[0,0,0],[0,1,0],[1,2,1]]\n```"
+ }
+ ]
+ },
"readme_constraints": "- `m == mat.length`\n- `n == mat[i].length`\n- `1 <= m, n <= 10^4`\n- `1 <= m * n <= 10^4`\n- `mat[i][j]` is either `0` or `1`\n- There is at least one `0` in `mat`",
"readme_additional": "**Note:** This question is the same as 1765: [Map of Highest Peak](https://leetcode.com/problems/map-of-highest-peak/)",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "update_matrix",
+ "helpers_run_signature": "(solution_class: type, mat: list[list[int]])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.update_matrix(mat)",
+ "helpers_assert_name": "update_matrix",
+ "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
- "solution_methods": [
- {
- "name": "update_matrix",
- "parameters": "mat: list[list[int]]",
- "return_type": "list[list[int]]",
- "dummy_return": "[]"
- }
- ],
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_update_matrix, run_update_matrix\nfrom .solution import Solution",
+ "test_content": "",
"test_class_name": "ZeroOneMatrix",
- "test_helper_methods": [
- { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
- ],
- "test_methods": [
- {
- "name": "test_update_matrix",
- "parametrize": "mat, expected",
- "parametrize_typed": "mat: list[list[int]], expected: list[list[int]]",
- "test_cases": "[([[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]]), ([[0, 0, 0], [0, 1, 0], [1, 1, 1]], [[0, 0, 0], [0, 1, 0], [1, 2, 1]])]",
- "body": "result = self.solution.update_matrix(mat)\nassert result == expected"
- }
- ],
- "playground_imports": "from solution import Solution",
- "playground_test_case": "# Example test case\nmat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\nexpected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]",
- "playground_execution": "result = Solution().update_matrix(mat)\nresult",
- "playground_assertion": "assert result == expected"
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "update_matrix",
+ "signature": "(self, mat: list[list[int]]) -> list[list[int]]",
+ "body": " # TODO: Implement update_matrix\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_update_matrix",
+ "signature": "(self, mat: list[list[int]], expected: list[list[int]])",
+ "parametrize": "mat, expected",
+ "test_cases": "[([[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]]), ([[0, 0, 0], [0, 1, 0], [1, 1, 1]], [[0, 0, 0], [0, 1, 0], [1, 2, 1]]), ([[0]], [[0]]), ([[1, 0]], [[1, 0]])]",
+ "body": " result = run_update_matrix(Solution, mat)\n assert_update_matrix(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_update_matrix, assert_update_matrix\nfrom solution import Solution",
+ "playground_setup": "# Example test case\nmat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\nexpected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]",
+ "playground_run": "result = run_update_matrix(Solution, mat)\nresult",
+ "playground_assert": "assert_update_matrix(result, expected)"
}
diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py b/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py
new file mode 100644
index 0000000..5ac7695
--- /dev/null
+++ b/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py
@@ -0,0 +1,21 @@
+{% if cookiecutter.helpers_imports -%}
+{{cookiecutter.helpers_imports}}
+{% endif -%}
+
+
+{% if cookiecutter.helpers_content -%}
+{{cookiecutter.helpers_content}}
+
+
+{% endif -%}
+
+{% if cookiecutter.helpers_run_name -%}
+def run_{{cookiecutter.helpers_run_name}}{{cookiecutter.helpers_run_signature}}:
+{{cookiecutter.helpers_run_body}}
+
+
+{% endif -%}
+{% if cookiecutter.helpers_assert_name -%}
+def assert_{{cookiecutter.helpers_assert_name}}{{cookiecutter.helpers_assert_signature}}:
+{{cookiecutter.helpers_assert_body}}
+{% endif -%}
diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb
index ac1863b..074410c 100644
--- a/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb
+++ b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb
@@ -6,7 +6,7 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": ["{{ cookiecutter.playground_imports | replace('\n', '\\n') }}"]
+ "source": ["{{ cookiecutter.playground_imports | replace('\n', '\\n') | replace('"', '\\"') }}"]
},
{
"cell_type": "code",
@@ -14,23 +14,23 @@
"id": "setup",
"metadata": {},
"outputs": [],
- "source": ["{{ cookiecutter.playground_test_case | replace('\n', '\\n') }}"]
+ "source": ["{{ cookiecutter.playground_setup | replace('\n', '\\n') | replace('"', '\\"') }}"]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
- "source": ["{{ cookiecutter.playground_execution | replace('\n', '\\n') }}"]
+ "source": ["{{ cookiecutter.playground_run | replace('\n', '\\n') | replace('"', '\\"') }}"]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
- "source": ["{{ cookiecutter.playground_assertion | replace('\n', '\\n') }}"]
+ "source": ["{{ cookiecutter.playground_assert | replace('\n', '\\n') | replace('"', '\\"') }}"]
}
],
"metadata": {
@@ -48,7 +48,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py
index a2014d3..fbc266c 100644
--- a/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py
+++ b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py
@@ -1,13 +1,29 @@
+{% if cookiecutter.solution_imports -%}
{{cookiecutter.solution_imports}}
-{# TODO: add helper class like class Node: .... #}
+
+
+{% endif -%}
+{% if cookiecutter.solution_contents -%}
+{{cookiecutter.solution_contents}}
+{% endif -%}
+{% if cookiecutter.solution_class_name -%}
+
+
class {{cookiecutter.solution_class_name}}:
- {%- for _, methods in cookiecutter._solution_methods | dictsort %}
- {%- for method in methods %}
+{% if cookiecutter.solution_class_content -%}
+{{cookiecutter.solution_class_content}}
+
+{% endif -%}
+{% if cookiecutter._solution_methods -%}
+{% for _, methods in cookiecutter._solution_methods | dictsort %}
+{% for method in methods %}
# Time: O(?)
- # Space: O(?){# TODO: add decorator // optional self. // optional return type // optional return #}
- def {{method.name}}(self, {{method.parameters}}) -> {{method.return_type}}:
- # TODO: Implement {{method.name}}{# TODO: add body #}
- return {{method.dummy_return}}
+ # Space: O(?)
+{% if method.decorator is defined %} {{method.decorator}}
+{% endif %} def {{method.name}}{{method.signature}}:
+{{method.body}}
- {%- endfor %}
- {%- endfor %}
+{% endfor %}
+{% endfor %}
+{% endif -%}
+{% endif -%}
diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py b/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py
new file mode 100644
index 0000000..11fc019
--- /dev/null
+++ b/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py
@@ -0,0 +1,37 @@
+{% if cookiecutter.test_imports -%}
+{{cookiecutter.test_imports}}
+
+
+{% endif -%}
+{% if cookiecutter.test_content -%}
+{{cookiecutter.test_content}}
+
+
+{% endif -%}
+{% if cookiecutter.test_class_name -%}
+class Test{{cookiecutter.test_class_name}}:
+{% if cookiecutter.test_class_content -%}
+ {{cookiecutter.test_class_content}}
+
+{% endif -%}
+{% for _, methods in cookiecutter._test_methods | dictsort %}
+{% for method in methods %}
+{% if method.decorator is defined %}
+ {{method.decorator}}
+{% endif %}
+{% if method.test_decorator is defined %}
+{% if method.test_decorator %}
+ {{method.test_decorator}}
+{% endif %}
+{% else %}
+ @logged_test
+{% endif %}
+{% if method.parametrize %}
+ @pytest.mark.parametrize("{{method.parametrize}}", {{method.test_cases}})
+{% endif %}
+ def {{method.name}}{{method.signature}}:
+{{method.body}}
+
+{% endfor %}
+{% endfor %}
+{% endif -%}
diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py b/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py
deleted file mode 100644
index 014cc33..0000000
--- a/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py
+++ /dev/null
@@ -1,21 +0,0 @@
-{{cookiecutter.test_imports}}
-
-
-class Test{{cookiecutter.test_class_name}}:
- {%- for _, helper_methods in cookiecutter._test_helper_methods | dictsort %}
- {%- for method in helper_methods %}{# TODO: add decorator // optional self. #}
- def {{method.name}}(self{% if method.parameters %}, {{method.parameters}}{% endif %}):
- {{method.body | indent(8, first=False)}}
-
- {%- endfor %}
- {%- endfor %}
-
- {%- for _, test_methods in cookiecutter._test_methods | dictsort %}
- {%- for method in test_methods %}
- @pytest.mark.parametrize("{{method.parametrize}}", {{method.test_cases}})
- @logged_test
- def {{method.name}}(self, {{method.parametrize_typed if method.parametrize_typed else method.parametrize}}):
- {{method.body | indent(8, first=False)}}
-
- {%- endfor %}
- {%- endfor %}
diff --git a/Makefile b/Makefile
index ce78169..3bdc248 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
PYTHON_VERSION = 3.13
-PROBLEM ?= word_break
+PROBLEM ?= min_stack
FORCE ?= 0
COMMA := ,
@@ -46,7 +46,6 @@ lint:
npx prettier --write "**/*.{ts,tsx,css,json,yaml,yml,md}"
$(call lint_target,.)
-
test:
poetry run pytest leetcode/ tests/ \
-v --cov=leetcode --cov=leetcode_py \
@@ -61,7 +60,7 @@ p-test:
echo "Error: Problem '$(PROBLEM)' not found in leetcode/ directory"; \
exit 1; \
fi
- poetry run pytest leetcode/$(PROBLEM)/tests.py -v -s
+ poetry run pytest leetcode/$(PROBLEM)/test_solution.py -v -s
p-lint:
@echo "Linting problem: $(PROBLEM)"
@@ -81,7 +80,9 @@ p-del:
# Generate All Problems - useful for people who fork this repo
gen-all-problems:
@echo "This will DELETE all existing problems and regenerate from JSON templates."
- @read -p "Are you sure? (y/N): " confirm && [ "$$confirm" = "y" ] || exit 1
+ @if [ "$$CI" != "true" ]; then \
+ read -p "Are you sure? (y/N): " confirm && [ "$$confirm" = "y" ] || exit 1; \
+ fi
@echo "Deleting existing problems..."
@rm -rf leetcode/*/
@echo "Generating all problems..."
diff --git a/leetcode/accounts_merge/helpers.py b/leetcode/accounts_merge/helpers.py
new file mode 100644
index 0000000..e163f17
--- /dev/null
+++ b/leetcode/accounts_merge/helpers.py
@@ -0,0 +1,11 @@
+def run_accounts_merge(solution_class: type, accounts: list[list[str]]):
+ implementation = solution_class()
+ return implementation.accounts_merge(accounts)
+
+
+def assert_accounts_merge(result: list[list[str]], expected: list[list[str]]) -> bool:
+ # Sort both result and expected for comparison since order doesn't matter
+ result_sorted = [sorted(account) for account in sorted(result)]
+ expected_sorted = [sorted(account) for account in sorted(expected)]
+ assert result_sorted == expected_sorted
+ return True
diff --git a/leetcode/accounts_merge/playground.ipynb b/leetcode/accounts_merge/playground.ipynb
index 46a7e86..f0dcd43 100644
--- a/leetcode/accounts_merge/playground.ipynb
+++ b/leetcode/accounts_merge/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_accounts_merge, run_accounts_merge\n",
"from solution import Solution"
]
},
@@ -34,7 +35,7 @@
{
"cell_type": "code",
"execution_count": 3,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [
{
@@ -51,21 +52,29 @@
}
],
"source": [
- "result = Solution().accounts_merge(accounts)\n",
+ "result = run_accounts_merge(Solution, accounts)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "test",
+ "id": "assert",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "# Sort for comparison since order doesn't matter\n",
- "result_sorted = [sorted(account) for account in sorted(result)]\n",
- "expected_sorted = [sorted(account) for account in sorted(expected)]\n",
- "assert result_sorted == expected_sorted"
+ "assert_accounts_merge(result, expected)"
]
}
],
diff --git a/leetcode/accounts_merge/solution.py b/leetcode/accounts_merge/solution.py
index dd59d46..b971177 100644
--- a/leetcode/accounts_merge/solution.py
+++ b/leetcode/accounts_merge/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(N * M) where N is accounts, M is max emails per account
# Space: O(N * M)
def accounts_merge(self, accounts: list[list[str]]) -> list[list[str]]:
diff --git a/leetcode/accounts_merge/test_solution.py b/leetcode/accounts_merge/test_solution.py
new file mode 100644
index 0000000..5b98579
--- /dev/null
+++ b/leetcode/accounts_merge/test_solution.py
@@ -0,0 +1,73 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_accounts_merge, run_accounts_merge
+from .solution import Solution
+
+
+class TestAccountsMerge:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "accounts, expected",
+ [
+ (
+ [
+ ["John", "johnsmith@mail.com", "john_newyork@mail.com"],
+ ["John", "johnsmith@mail.com", "john00@mail.com"],
+ ["Mary", "mary@mail.com"],
+ ["John", "johnnybravo@mail.com"],
+ ],
+ [
+ ["John", "john00@mail.com", "john_newyork@mail.com", "johnsmith@mail.com"],
+ ["Mary", "mary@mail.com"],
+ ["John", "johnnybravo@mail.com"],
+ ],
+ ),
+ (
+ [
+ ["Gabe", "Gabe0@m.co", "Gabe3@m.co", "Gabe1@m.co"],
+ ["Kevin", "Kevin3@m.co", "Kevin5@m.co", "Kevin0@m.co"],
+ ["Ethan", "Ethan5@m.co", "Ethan4@m.co", "Ethan0@m.co"],
+ ["Hanzo", "Hanzo3@m.co", "Hanzo1@m.co", "Hanzo0@m.co"],
+ ["Fern", "Fern5@m.co", "Fern1@m.co", "Fern0@m.co"],
+ ],
+ [
+ ["Ethan", "Ethan0@m.co", "Ethan4@m.co", "Ethan5@m.co"],
+ ["Gabe", "Gabe0@m.co", "Gabe1@m.co", "Gabe3@m.co"],
+ ["Hanzo", "Hanzo0@m.co", "Hanzo1@m.co", "Hanzo3@m.co"],
+ ["Kevin", "Kevin0@m.co", "Kevin3@m.co", "Kevin5@m.co"],
+ ["Fern", "Fern0@m.co", "Fern1@m.co", "Fern5@m.co"],
+ ],
+ ),
+ ([["John", "john@mail.com"]], [["John", "john@mail.com"]]),
+ ([["John"]], [["John"]]),
+ (
+ [["John", "john1@mail.com"], ["John", "john2@mail.com"], ["John", "john3@mail.com"]],
+ [["John", "john1@mail.com"], ["John", "john2@mail.com"], ["John", "john3@mail.com"]],
+ ),
+ (
+ [
+ ["John", "a@mail.com", "b@mail.com"],
+ ["John", "b@mail.com", "c@mail.com"],
+ ["John", "d@mail.com"],
+ ],
+ [["John", "a@mail.com", "b@mail.com", "c@mail.com"], ["John", "d@mail.com"]],
+ ),
+ (
+ [
+ ["Alice", "alice@mail.com", "alice1@mail.com"],
+ ["Alice", "alice2@mail.com", "alice3@mail.com"],
+ ["Alice", "alice1@mail.com", "alice2@mail.com"],
+ ],
+ [["Alice", "alice1@mail.com", "alice2@mail.com", "alice3@mail.com", "alice@mail.com"]],
+ ),
+ ([["John", "shared@mail.com"], ["Jane", "shared@mail.com"]], [["John", "shared@mail.com"]]),
+ ],
+ )
+ def test_accounts_merge(self, accounts: list[list[str]], expected: list[list[str]]):
+ result = run_accounts_merge(Solution, accounts)
+ assert_accounts_merge(result, expected)
diff --git a/leetcode/accounts_merge/tests.py b/leetcode/accounts_merge/tests.py
deleted file mode 100644
index a201047..0000000
--- a/leetcode/accounts_merge/tests.py
+++ /dev/null
@@ -1,112 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestAccountsMerge:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "accounts, expected",
- [
- (
- [
- ["John", "johnsmith@mail.com", "john_newyork@mail.com"],
- ["John", "johnsmith@mail.com", "john00@mail.com"],
- ["Mary", "mary@mail.com"],
- ["John", "johnnybravo@mail.com"],
- ],
- [
- ["John", "john00@mail.com", "john_newyork@mail.com", "johnsmith@mail.com"],
- ["Mary", "mary@mail.com"],
- ["John", "johnnybravo@mail.com"],
- ],
- ),
- (
- [
- ["Gabe", "Gabe0@m.co", "Gabe3@m.co", "Gabe1@m.co"],
- ["Kevin", "Kevin3@m.co", "Kevin5@m.co", "Kevin0@m.co"],
- ["Ethan", "Ethan5@m.co", "Ethan4@m.co", "Ethan0@m.co"],
- ["Hanzo", "Hanzo3@m.co", "Hanzo1@m.co", "Hanzo0@m.co"],
- ["Fern", "Fern5@m.co", "Fern1@m.co", "Fern0@m.co"],
- ],
- [
- ["Ethan", "Ethan0@m.co", "Ethan4@m.co", "Ethan5@m.co"],
- ["Gabe", "Gabe0@m.co", "Gabe1@m.co", "Gabe3@m.co"],
- ["Hanzo", "Hanzo0@m.co", "Hanzo1@m.co", "Hanzo3@m.co"],
- ["Kevin", "Kevin0@m.co", "Kevin3@m.co", "Kevin5@m.co"],
- ["Fern", "Fern0@m.co", "Fern1@m.co", "Fern5@m.co"],
- ],
- ),
- # Single account
- (
- [["Alice", "alice@mail.com"]],
- [["Alice", "alice@mail.com"]],
- ),
- # No merging needed - all separate
- (
- [
- ["Alice", "alice@mail.com"],
- ["Bob", "bob@mail.com"],
- ["Charlie", "charlie@mail.com"],
- ],
- [
- ["Alice", "alice@mail.com"],
- ["Bob", "bob@mail.com"],
- ["Charlie", "charlie@mail.com"],
- ],
- ),
- # Chain merging - A->B->C
- (
- [
- ["John", "john1@mail.com", "john2@mail.com"],
- ["John", "john2@mail.com", "john3@mail.com"],
- ["John", "john3@mail.com", "john4@mail.com"],
- ],
- [["John", "john1@mail.com", "john2@mail.com", "john3@mail.com", "john4@mail.com"]],
- ),
- # Multiple emails per account with complex merging
- (
- [
- ["David", "david1@mail.com", "david2@mail.com", "david3@mail.com"],
- ["David", "david3@mail.com", "david4@mail.com"],
- ["Sarah", "sarah@mail.com"],
- ["David", "david5@mail.com", "david1@mail.com"],
- ],
- [
- [
- "David",
- "david1@mail.com",
- "david2@mail.com",
- "david3@mail.com",
- "david4@mail.com",
- "david5@mail.com",
- ],
- ["Sarah", "sarah@mail.com"],
- ],
- ),
- # Same name different people
- (
- [
- ["John", "john1@mail.com"],
- ["John", "john2@mail.com"],
- ["John", "john3@mail.com"],
- ],
- [
- ["John", "john1@mail.com"],
- ["John", "john2@mail.com"],
- ["John", "john3@mail.com"],
- ],
- ),
- ],
- )
- @logged_test
- def test_accounts_merge(self, accounts: list[list[str]], expected: list[list[str]]):
- result = self.solution.accounts_merge(accounts)
- # Sort both result and expected for comparison since order doesn't matter
- result_sorted = [sorted(account) for account in sorted(result)]
- expected_sorted = [sorted(account) for account in sorted(expected)]
- assert result_sorted == expected_sorted
diff --git a/leetcode/add_binary/helpers.py b/leetcode/add_binary/helpers.py
new file mode 100644
index 0000000..b59b32c
--- /dev/null
+++ b/leetcode/add_binary/helpers.py
@@ -0,0 +1,8 @@
+def run_add_binary(solution_class: type, a: str, b: str):
+ implementation = solution_class()
+ return implementation.add_binary(a, b)
+
+
+def assert_add_binary(result: str, expected: str) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/add_binary/playground.ipynb b/leetcode/add_binary/playground.ipynb
index 69c0445..0b2a93d 100644
--- a/leetcode/add_binary/playground.ipynb
+++ b/leetcode/add_binary/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_add_binary, run_add_binary\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,34 +26,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "'100'"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().add_binary(a, b)\n",
+ "result = run_add_binary(Solution, a, b)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_add_binary(result, expected)"
]
}
],
@@ -70,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/add_binary/tests.py b/leetcode/add_binary/test_solution.py
similarity index 59%
rename from leetcode/add_binary/tests.py
rename to leetcode/add_binary/test_solution.py
index d8c772a..585a246 100644
--- a/leetcode/add_binary/tests.py
+++ b/leetcode/add_binary/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_add_binary, run_add_binary
from .solution import Solution
@@ -9,6 +10,7 @@ class TestAddBinary:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"a, b, expected",
[
@@ -17,9 +19,14 @@ def setup_method(self):
("0", "0", "0"),
("1", "1", "10"),
("1111", "1111", "11110"),
+ ("1", "0", "1"),
+ ("0", "1", "1"),
+ ("1", "111", "1000"),
+ ("111", "1", "1000"),
+ ("1010", "1", "1011"),
+ ("1111", "1", "10000"),
],
)
- @logged_test
def test_add_binary(self, a: str, b: str, expected: str):
- result = self.solution.add_binary(a, b)
- assert result == expected
+ result = run_add_binary(Solution, a, b)
+ assert_add_binary(result, expected)
diff --git a/leetcode/balanced_binary_tree/helpers.py b/leetcode/balanced_binary_tree/helpers.py
new file mode 100644
index 0000000..199f407
--- /dev/null
+++ b/leetcode/balanced_binary_tree/helpers.py
@@ -0,0 +1,12 @@
+from leetcode_py import TreeNode
+
+
+def run_is_balanced(solution_class: type, root_list: list[int | None]):
+ implementation = solution_class()
+ root = TreeNode.from_list(root_list)
+ return implementation.is_balanced(root)
+
+
+def assert_is_balanced(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/balanced_binary_tree/playground.ipynb b/leetcode/balanced_binary_tree/playground.ipynb
index 9e4e1e6..e115cdb 100644
--- a/leetcode/balanced_binary_tree/playground.ipynb
+++ b/leetcode/balanced_binary_tree/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_is_balanced, run_is_balanced\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -20,15 +21,36 @@
"outputs": [],
"source": [
"# Example test case\n",
- "root_list = [3, 9, 20, None, None, 15, 7]\n",
- "root = TreeNode.from_list(root_list)\n",
+ "root_list: list[int | None] = [3, 9, 20, None, None, 15, 7]\n",
"expected = True"
]
},
{
"cell_type": "code",
"execution_count": 3,
- "id": "f76011d0",
+ "id": "run",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "result = run_is_balanced(Solution, root_list)\n",
+ "result"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "c01db53b",
"metadata": {},
"outputs": [
{
@@ -105,19 +127,20 @@
"TreeNode([3, 9, 20, None, None, 15, 7])"
]
},
- "execution_count": 3,
+ "execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
+ "root = TreeNode.from_list(root_list)\n",
"root"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "execute",
+ "id": "assert",
"metadata": {},
"outputs": [
{
@@ -132,18 +155,7 @@
}
],
"source": [
- "result = Solution().is_balanced(root)\n",
- "result"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "test",
- "metadata": {},
- "outputs": [],
- "source": [
- "assert result == expected"
+ "assert_is_balanced(result, expected)"
]
}
],
diff --git a/leetcode/balanced_binary_tree/solution.py b/leetcode/balanced_binary_tree/solution.py
index 81a74aa..e7740f4 100644
--- a/leetcode/balanced_binary_tree/solution.py
+++ b/leetcode/balanced_binary_tree/solution.py
@@ -4,8 +4,8 @@
class Solution:
# Time: O(n)
# Space: O(h)
- def is_balanced(self, root: TreeNode | None) -> bool:
- def height(node: TreeNode | None) -> int:
+ def is_balanced(self, root: TreeNode[int] | None) -> bool:
+ def height(node: TreeNode[int] | None) -> int:
if not node:
return 0
diff --git a/leetcode/balanced_binary_tree/tests.py b/leetcode/balanced_binary_tree/test_solution.py
similarity index 61%
rename from leetcode/balanced_binary_tree/tests.py
rename to leetcode/balanced_binary_tree/test_solution.py
index cbc49a0..08eaaa8 100644
--- a/leetcode/balanced_binary_tree/tests.py
+++ b/leetcode/balanced_binary_tree/test_solution.py
@@ -1,8 +1,8 @@
import pytest
-from leetcode_py import TreeNode
from leetcode_py.test_utils import logged_test
+from .helpers import assert_is_balanced, run_is_balanced
from .solution import Solution
@@ -10,6 +10,7 @@ class TestBalancedBinaryTree:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"root_list, expected",
[
@@ -19,15 +20,10 @@ def setup_method(self):
([1], True),
([1, 2], True),
([1, None, 2], True),
- ([1, 2, 3], True),
- ([1, 2, None, 3], False),
- ([1, None, 2, None, 3], False),
- ([1, 2, 3, 4, 5, 6, 7], True),
- ([1, 2, 3, 4, None, None, 7, 8], False),
+ ([1, 2, 3, 4], True),
+ ([1, 2, 2, 3, None, None, 3, 4, None, None, 4], False),
],
)
- @logged_test
def test_is_balanced(self, root_list: list[int | None], expected: bool):
- root = TreeNode.from_list(root_list)
- result = self.solution.is_balanced(root)
- assert result == expected
+ result = run_is_balanced(Solution, root_list)
+ assert_is_balanced(result, expected)
diff --git a/leetcode/basic_calculator/helpers.py b/leetcode/basic_calculator/helpers.py
new file mode 100644
index 0000000..295f681
--- /dev/null
+++ b/leetcode/basic_calculator/helpers.py
@@ -0,0 +1,8 @@
+def run_calculate(solution_class: type, s: str):
+ implementation = solution_class()
+ return implementation.calculate(s)
+
+
+def assert_calculate(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/basic_calculator/playground.ipynb b/leetcode/basic_calculator/playground.ipynb
index 1724458..d7681b7 100644
--- a/leetcode/basic_calculator/playground.ipynb
+++ b/leetcode/basic_calculator/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_calculate, run_calculate\n",
"from solution import Solution"
]
},
@@ -25,7 +26,7 @@
{
"cell_type": "code",
"execution_count": 3,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [
{
@@ -40,18 +41,29 @@
}
],
"source": [
- "result = Solution().calculate(s)\n",
+ "result = run_calculate(Solution, s)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "test",
+ "id": "assert",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "assert result == expected"
+ "assert_calculate(result, expected)"
]
}
],
diff --git a/leetcode/basic_calculator/tests.py b/leetcode/basic_calculator/test_solution.py
similarity index 90%
rename from leetcode/basic_calculator/tests.py
rename to leetcode/basic_calculator/test_solution.py
index 08c96e9..8a67479 100644
--- a/leetcode/basic_calculator/tests.py
+++ b/leetcode/basic_calculator/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_calculate, run_calculate
from .solution import Solution
@@ -9,6 +10,7 @@ class TestBasicCalculator:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"s, expected",
[
@@ -20,11 +22,9 @@ def setup_method(self):
("-(1+2)", -3),
("2147483647", 2147483647),
("1-1+1", 1),
- # Additional edge cases
+ # Additional edge cases from old tests
("0", 0),
("-0", 0),
- ("()", 0),
- ("((()))", 0),
("1+(2+3)", 6),
("(1+2)+3", 6),
("1-(2+3)", -4),
@@ -41,11 +41,11 @@ def setup_method(self):
("-2147483648", -2147483648),
],
)
- @logged_test
def test_calculate(self, s: str, expected: int):
- result = self.solution.calculate(s)
- assert result == expected
+ result = run_calculate(Solution, s)
+ assert_calculate(result, expected)
+ @logged_test
@pytest.mark.parametrize(
"s, error_msg",
[
@@ -62,7 +62,6 @@ def test_calculate(self, s: str, expected: int):
("1+2.5", r"Invalid character: '\.'"),
],
)
- @logged_test
def test_calculate_invalid_input(self, s: str, error_msg: str):
with pytest.raises(ValueError, match=error_msg):
self.solution.calculate(s)
diff --git a/leetcode/best_time_to_buy_and_sell_stock/helpers.py b/leetcode/best_time_to_buy_and_sell_stock/helpers.py
new file mode 100644
index 0000000..90c2b1e
--- /dev/null
+++ b/leetcode/best_time_to_buy_and_sell_stock/helpers.py
@@ -0,0 +1,8 @@
+def run_max_profit(solution_class: type, prices: list[int]):
+ implementation = solution_class()
+ return implementation.max_profit(prices)
+
+
+def assert_max_profit(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/best_time_to_buy_and_sell_stock/playground.ipynb b/leetcode/best_time_to_buy_and_sell_stock/playground.ipynb
index cc80126..81034bf 100644
--- a/leetcode/best_time_to_buy_and_sell_stock/playground.ipynb
+++ b/leetcode/best_time_to_buy_and_sell_stock/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_max_profit, run_max_profit\n",
"from solution import Solution"
]
},
@@ -25,7 +26,7 @@
{
"cell_type": "code",
"execution_count": 3,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [
{
@@ -40,18 +41,29 @@
}
],
"source": [
- "result = Solution().max_profit(prices)\n",
+ "result = run_max_profit(Solution, prices)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "test",
+ "id": "assert",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "assert result == expected"
+ "assert_max_profit(result, expected)"
]
}
],
diff --git a/leetcode/best_time_to_buy_and_sell_stock/tests.py b/leetcode/best_time_to_buy_and_sell_stock/test_solution.py
similarity index 80%
rename from leetcode/best_time_to_buy_and_sell_stock/tests.py
rename to leetcode/best_time_to_buy_and_sell_stock/test_solution.py
index 9b66879..d889ade 100644
--- a/leetcode/best_time_to_buy_and_sell_stock/tests.py
+++ b/leetcode/best_time_to_buy_and_sell_stock/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_max_profit, run_max_profit
from .solution import Solution
@@ -9,6 +10,7 @@ class TestBestTimeToBuyAndSellStock:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"prices, expected",
[
@@ -22,7 +24,6 @@ def setup_method(self):
([3, 2, 6, 5, 0, 3], 4),
],
)
- @logged_test
def test_max_profit(self, prices: list[int], expected: int):
- result = self.solution.max_profit(prices)
- assert result == expected
+ result = run_max_profit(Solution, prices)
+ assert_max_profit(result, expected)
diff --git a/leetcode/binary_search/helpers.py b/leetcode/binary_search/helpers.py
new file mode 100644
index 0000000..70f8aee
--- /dev/null
+++ b/leetcode/binary_search/helpers.py
@@ -0,0 +1,8 @@
+def run_search(solution_class: type, nums: list[int], target: int):
+ implementation = solution_class()
+ return implementation.search(nums, target)
+
+
+def assert_search(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/binary_search/playground.ipynb b/leetcode/binary_search/playground.ipynb
index d7a58c0..9c1a608 100644
--- a/leetcode/binary_search/playground.ipynb
+++ b/leetcode/binary_search/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_search, run_search\n",
"from solution import Solution"
]
},
@@ -26,22 +27,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().search(nums, target)\n",
+ "result = run_search(Solution, nums, target)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_search(result, expected)"
]
}
],
@@ -60,7 +61,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/binary_search/tests.py b/leetcode/binary_search/test_solution.py
similarity index 75%
rename from leetcode/binary_search/tests.py
rename to leetcode/binary_search/test_solution.py
index 0842386..5ed0cee 100644
--- a/leetcode/binary_search/tests.py
+++ b/leetcode/binary_search/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_search, run_search
from .solution import Solution
@@ -9,31 +10,25 @@ class TestBinarySearch:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"nums, target, expected",
[
- # Original examples
([-1, 0, 3, 5, 9, 12], 9, 4),
([-1, 0, 3, 5, 9, 12], 2, -1),
- # Single element
([5], 5, 0),
([5], -5, -1),
- # Target at boundaries
([1, 3, 5, 7, 9], 1, 0),
([1, 3, 5, 7, 9], 9, 4),
- # Target not found
([1, 3, 5, 7, 9], 4, -1),
- # Two elements
([1, 3], 1, 0),
([1, 3], 3, 1),
([1, 3], 2, -1),
- # Negative numbers
([-5, -2, 0, 3, 7], -2, 1),
([-5, -2, 0, 3, 7], 0, 2),
([-5, -2, 0, 3, 7], -1, -1),
],
)
- @logged_test
def test_search(self, nums: list[int], target: int, expected: int):
- result = self.solution.search(nums, target)
- assert result == expected
+ result = run_search(Solution, nums, target)
+ assert_search(result, expected)
diff --git a/leetcode/binary_tree_level_order_traversal/helpers.py b/leetcode/binary_tree_level_order_traversal/helpers.py
new file mode 100644
index 0000000..8da1fe8
--- /dev/null
+++ b/leetcode/binary_tree_level_order_traversal/helpers.py
@@ -0,0 +1,12 @@
+from leetcode_py import TreeNode
+
+
+def run_level_order(solution_class: type, root_list: list[int | None]):
+ implementation = solution_class()
+ root = TreeNode.from_list(root_list) if root_list else None
+ return implementation.level_order(root)
+
+
+def assert_level_order(result: list[list[int]], expected: list[list[int]]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/binary_tree_level_order_traversal/playground.ipynb b/leetcode/binary_tree_level_order_traversal/playground.ipynb
index c0a13d9..9c98b39 100644
--- a/leetcode/binary_tree_level_order_traversal/playground.ipynb
+++ b/leetcode/binary_tree_level_order_traversal/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_level_order, run_level_order\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -20,30 +21,29 @@
"outputs": [],
"source": [
"# Example test case\n",
- "root_list = [3, 9, 20, None, None, 15, 7]\n",
- "root = TreeNode.from_list(root_list)\n",
+ "root_list: list[int | None] = [3, 9, 20, None, None, 15, 7]\n",
"expected = [[3], [9, 20], [15, 7]]"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().level_order(root)\n",
+ "result = run_level_order(Solution, root_list)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_level_order(result, expected)"
]
}
],
@@ -62,7 +62,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/binary_tree_level_order_traversal/solution.py b/leetcode/binary_tree_level_order_traversal/solution.py
index 702c596..0486b29 100644
--- a/leetcode/binary_tree_level_order_traversal/solution.py
+++ b/leetcode/binary_tree_level_order_traversal/solution.py
@@ -6,7 +6,7 @@
class Solution:
# Time: O(n)
# Space: O(w) where w is max width of tree
- def level_order(self, root: TreeNode | None) -> list[list[int]]:
+ def level_order(self, root: TreeNode[int] | None) -> list[list[int]]:
if not root:
return []
diff --git a/leetcode/binary_tree_level_order_traversal/tests.py b/leetcode/binary_tree_level_order_traversal/test_solution.py
similarity index 60%
rename from leetcode/binary_tree_level_order_traversal/tests.py
rename to leetcode/binary_tree_level_order_traversal/test_solution.py
index daddfb4..ccb041a 100644
--- a/leetcode/binary_tree_level_order_traversal/tests.py
+++ b/leetcode/binary_tree_level_order_traversal/test_solution.py
@@ -1,8 +1,8 @@
import pytest
-from leetcode_py import TreeNode
from leetcode_py.test_utils import logged_test
+from .helpers import assert_level_order, run_level_order
from .solution import Solution
@@ -10,6 +10,7 @@ class TestBinaryTreeLevelOrderTraversal:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"root_list, expected",
[
@@ -18,22 +19,19 @@ def setup_method(self):
([], []),
([1, 2, 3, 4, 5, 6, 7], [[1], [2, 3], [4, 5, 6, 7]]),
([1, 2, None, 3, None, 4, None, 5], [[1], [2], [3], [4], [5]]),
- # Edge cases
- ([1, None, 2, None, 3], [[1], [2], [3]]), # Right skewed
- ([1, 2, None, 3, None], [[1], [2], [3]]), # Left skewed
- ([0], [[0]]), # Single zero node
- ([-1, -2, -3], [[-1], [-2, -3]]), # Negative values
- ([1, 2, 3, None, None, None, 4], [[1], [2, 3], [4]]), # Sparse tree
+ ([1, None, 2, None, 3], [[1], [2], [3]]),
+ ([1, 2, None, 3, None], [[1], [2], [3]]),
+ ([0], [[0]]),
+ ([-1, -2, -3], [[-1], [-2, -3]]),
+ ([1, 2, 3, None, None, None, 4], [[1], [2, 3], [4]]),
(
[5, 4, 8, 11, None, 13, 4, 7, 2, None, None, None, 1],
[[5], [4, 8], [11, 13, 4], [7, 2, 1]],
- ), # Complex tree
- ([1, 2, 2, 3, 3, 3, 3], [[1], [2, 2], [3, 3, 3, 3]]), # Duplicate values
- ([1, None, None], [[1]]), # Single node with null children
+ ),
+ ([1, 2, 2, 3, 3, 3, 3], [[1], [2, 2], [3, 3, 3, 3]]),
+ ([1, None, None], [[1]]),
],
)
- @logged_test
def test_level_order(self, root_list: list[int | None], expected: list[list[int]]):
- root = TreeNode.from_list(root_list) if root_list else None
- result = self.solution.level_order(root)
- assert result == expected
+ result = run_level_order(Solution, root_list)
+ assert_level_order(result, expected)
diff --git a/leetcode/binary_tree_right_side_view/helpers.py b/leetcode/binary_tree_right_side_view/helpers.py
new file mode 100644
index 0000000..6b34ffc
--- /dev/null
+++ b/leetcode/binary_tree_right_side_view/helpers.py
@@ -0,0 +1,12 @@
+from leetcode_py import TreeNode
+
+
+def run_right_side_view(solution_class: type, root_list: list[int | None]):
+ implementation = solution_class()
+ root = TreeNode.from_list(root_list) if root_list else None
+ return implementation.right_side_view(root)
+
+
+def assert_right_side_view(result: list[int], expected: list[int]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/binary_tree_right_side_view/playground.ipynb b/leetcode/binary_tree_right_side_view/playground.ipynb
index 1deced0..9187c4f 100644
--- a/leetcode/binary_tree_right_side_view/playground.ipynb
+++ b/leetcode/binary_tree_right_side_view/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_right_side_view, run_right_side_view\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -27,13 +28,81 @@
{
"cell_type": "code",
"execution_count": 3,
- "id": "execute",
+ "id": "6ba11550",
"metadata": {},
"outputs": [
{
"data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n"
+ ],
"text/plain": [
- "[1, 3, 4]"
+ "TreeNode([1, 2, 3, None, 5, None, 4])"
]
},
"execution_count": 3,
@@ -43,18 +112,50 @@
],
"source": [
"root = TreeNode.from_list(root_list)\n",
- "result = Solution().right_side_view(root)\n",
- "result"
+ "root"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "test",
+ "id": "run",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[1, 3, 4]"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "result = run_right_side_view(Solution, root_list)\n",
+ "result"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "assert",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "assert result == expected"
+ "assert_right_side_view(result, expected)"
]
}
],
diff --git a/leetcode/binary_tree_right_side_view/tests.py b/leetcode/binary_tree_right_side_view/test_solution.py
similarity index 55%
rename from leetcode/binary_tree_right_side_view/tests.py
rename to leetcode/binary_tree_right_side_view/test_solution.py
index acfd3ad..1da8582 100644
--- a/leetcode/binary_tree_right_side_view/tests.py
+++ b/leetcode/binary_tree_right_side_view/test_solution.py
@@ -1,13 +1,17 @@
import pytest
-from leetcode_py import TreeNode
from leetcode_py.test_utils import logged_test
+from .helpers import assert_right_side_view, run_right_side_view
from .solution import Solution, SolutionBFS, SolutionDFS
class TestBinaryTreeRightSideView:
- @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS])
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize("solution_class", [Solution, SolutionBFS, SolutionDFS])
@pytest.mark.parametrize(
"root_list, expected",
[
@@ -15,16 +19,13 @@ class TestBinaryTreeRightSideView:
([1, 2, 3, 4, None, None, None, 5], [1, 3, 4, 5]),
([1, None, 3], [1, 3]),
([], []),
+ ([1], [1]),
+ ([1, 2], [1, 2]),
+ ([1, None, 2], [1, 2]),
],
)
- @logged_test
def test_right_side_view(
- self,
- root_list: list[int | None],
- expected: list[int],
- solution_class: type[Solution | SolutionDFS | SolutionBFS],
+ self, solution_class: type, root_list: list[int | None], expected: list[int]
):
- solution = solution_class()
- root = TreeNode.from_list(root_list)
- result = solution.right_side_view(root)
- assert result == expected
+ result = run_right_side_view(solution_class, root_list)
+ assert_right_side_view(result, expected)
diff --git a/leetcode/climbing_stairs/helpers.py b/leetcode/climbing_stairs/helpers.py
new file mode 100644
index 0000000..8730d97
--- /dev/null
+++ b/leetcode/climbing_stairs/helpers.py
@@ -0,0 +1,8 @@
+def run_climb_stairs(solution_class: type, n: int):
+ implementation = solution_class()
+ return implementation.climb_stairs(n)
+
+
+def assert_climb_stairs(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/climbing_stairs/playground.ipynb b/leetcode/climbing_stairs/playground.ipynb
index 2edd00e..6fd5d25 100644
--- a/leetcode/climbing_stairs/playground.ipynb
+++ b/leetcode/climbing_stairs/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_climb_stairs, run_climb_stairs\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -24,34 +25,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "3"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().climb_stairs(n)\n",
+ "result = run_climb_stairs(Solution, n)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_climb_stairs(result, expected)"
]
}
],
@@ -69,8 +59,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/climbing_stairs/solution.py b/leetcode/climbing_stairs/solution.py
index 0a56db6..118c6b4 100644
--- a/leetcode/climbing_stairs/solution.py
+++ b/leetcode/climbing_stairs/solution.py
@@ -1,7 +1,7 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
-
# This follows Fibonacci pattern
# Standard Fib: F(0)=0, F(1)=1, F(2)=1, F(3)=2, F(4)=3, F(5)=5...
def climb_stairs(self, n: int) -> int:
diff --git a/leetcode/climbing_stairs/tests.py b/leetcode/climbing_stairs/test_solution.py
similarity index 73%
rename from leetcode/climbing_stairs/tests.py
rename to leetcode/climbing_stairs/test_solution.py
index f3c338f..7e80934 100644
--- a/leetcode/climbing_stairs/tests.py
+++ b/leetcode/climbing_stairs/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_climb_stairs, run_climb_stairs
from .solution import Solution
@@ -9,11 +10,11 @@ class TestClimbingStairs:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"n, expected",
[(1, 1), (2, 2), (3, 3), (4, 5), (5, 8), (6, 13), (10, 89), (20, 10946), (45, 1836311903)],
)
- @logged_test
def test_climb_stairs(self, n: int, expected: int):
- result = self.solution.climb_stairs(n)
- assert result == expected
+ result = run_climb_stairs(Solution, n)
+ assert_climb_stairs(result, expected)
diff --git a/leetcode/clone_graph/README.md b/leetcode/clone_graph/README.md
index eeda5d5..2e20837 100644
--- a/leetcode/clone_graph/README.md
+++ b/leetcode/clone_graph/README.md
@@ -33,36 +33,39 @@ The given node will always be the first node with `val = 1`. You must return the
### Example 1:
-
+
```
Input: adjList = [[2,4],[1,3],[2,4],[1,3]]
Output: [[2,4],[1,3],[2,4],[1,3]]
-Explanation: There are 4 nodes in the graph.
+```
+
+**Explanation:** There are 4 nodes in the graph.
1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
-```
### Example 2:
-
+
```
Input: adjList = [[]]
Output: [[]]
-Explanation: Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors.
```
+**Explanation:** Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors.
+
### Example 3:
```
Input: adjList = []
Output: []
-Explanation: This an empty graph, it does not have any nodes.
```
+**Explanation:** This an empty graph, it does not have any nodes.
+
## Constraints
- The number of nodes in the graph is in the range `[0, 100]`.
diff --git a/leetcode/clone_graph/helpers.py b/leetcode/clone_graph/helpers.py
new file mode 100644
index 0000000..5b89b44
--- /dev/null
+++ b/leetcode/clone_graph/helpers.py
@@ -0,0 +1,16 @@
+from leetcode_py import GraphNode
+
+
+def run_clone_graph(solution_class: type, adj_list: list[list[int]]):
+ node = GraphNode.from_adjacency_list(adj_list)
+ implementation = solution_class()
+ return implementation.clone_graph(node)
+
+
+def assert_clone_graph(result: GraphNode | None, adj_list: list[list[int]]) -> bool:
+ original = GraphNode.from_adjacency_list(adj_list)
+ if result is None:
+ assert original is None
+ else:
+ assert result.is_clone(original)
+ return True
diff --git a/leetcode/clone_graph/playground.ipynb b/leetcode/clone_graph/playground.ipynb
index 0be0e9e..bb2a422 100644
--- a/leetcode/clone_graph/playground.ipynb
+++ b/leetcode/clone_graph/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_clone_graph, run_clone_graph\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import GraphNode"
@@ -14,174 +15,34 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "GraphNode({1: [2, 4], 2: [1, 3], 3: [2, 4], 4: [1, 3]})"
- ]
- },
- "execution_count": 2,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# Example test case\n",
- "adj_list = [[2, 4], [1, 3], [2, 4], [1, 3]]\n",
- "node = GraphNode.from_adjacency_list(adj_list)\n",
- "node"
+ "adj_list = [[2, 4], [1, 3], [2, 4], [1, 3]]"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "GraphNode({1: [2, 4], 2: [1, 3], 3: [2, 4], 4: [1, 3]})"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().clone_graph(node)\n",
+ "result = run_clone_graph(Solution, adj_list)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result.is_clone(node) if result else node is None"
+ "assert_clone_graph(result, adj_list)"
]
}
],
@@ -199,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/clone_graph/test_solution.py b/leetcode/clone_graph/test_solution.py
new file mode 100644
index 0000000..95bb87f
--- /dev/null
+++ b/leetcode/clone_graph/test_solution.py
@@ -0,0 +1,34 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_clone_graph, run_clone_graph
+from .solution import Solution, SolutionBFS, SolutionDFS
+
+
+class TestCloneGraph:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize("solution_class", [Solution, SolutionBFS, SolutionDFS])
+ @pytest.mark.parametrize(
+ "adj_list",
+ [
+ [[2, 4], [1, 3], [2, 4], [1, 3]],
+ [[]],
+ [],
+ [[2], [1]],
+ [[2, 3], [1], [1]],
+ [[2], [3], [4], []],
+ [[2, 3, 4], [1], [1], [1]],
+ [[2, 3], [1, 3], [1, 2]],
+ [[2, 5], [1, 3], [2, 4], [3, 5], [1, 4]],
+ [[2, 3], [1, 4], [1, 4], [2, 3]],
+ [[2, 3, 4, 5], [1], [1], [1], [1]],
+ [[2], [3], [4], [5], []],
+ ],
+ )
+ def test_clone_graph(self, solution_class: type, adj_list: list[list[int]]):
+ result = run_clone_graph(solution_class, adj_list)
+ assert_clone_graph(result, adj_list)
diff --git a/leetcode/clone_graph/tests.py b/leetcode/clone_graph/tests.py
deleted file mode 100644
index fa0a211..0000000
--- a/leetcode/clone_graph/tests.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import pytest
-
-from leetcode_py import GraphNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution, SolutionBFS, SolutionDFS
-
-
-class TestCloneGraph:
- @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS])
- @pytest.mark.parametrize(
- "adj_list",
- [[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], []],
- )
- @logged_test
- def test_clone_graph(
- self,
- adj_list: list[list[int]],
- solution_class: type[Solution | SolutionDFS | SolutionBFS],
- ):
- solution = solution_class()
- node = GraphNode.from_adjacency_list(adj_list)
- result = solution.clone_graph(node)
-
- assert result.is_clone(node) if result else node is None
diff --git a/leetcode/coin_change/helpers.py b/leetcode/coin_change/helpers.py
new file mode 100644
index 0000000..ce703f9
--- /dev/null
+++ b/leetcode/coin_change/helpers.py
@@ -0,0 +1,8 @@
+def run_coin_change(solution_class: type, coins: list[int], amount: int):
+ implementation = solution_class()
+ return implementation.coin_change(coins, amount)
+
+
+def assert_coin_change(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/coin_change/playground.ipynb b/leetcode/coin_change/playground.ipynb
index 3c2d8e4..580d39c 100644
--- a/leetcode/coin_change/playground.ipynb
+++ b/leetcode/coin_change/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_coin_change, run_coin_change\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,34 +26,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "3"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().coin_change(coins, amount)\n",
+ "result = run_coin_change(Solution, coins, amount)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_coin_change(result, expected)"
]
}
],
@@ -70,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/coin_change/tests.py b/leetcode/coin_change/test_solution.py
similarity index 80%
rename from leetcode/coin_change/tests.py
rename to leetcode/coin_change/test_solution.py
index 50e7c63..a49a846 100644
--- a/leetcode/coin_change/tests.py
+++ b/leetcode/coin_change/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_coin_change, run_coin_change
from .solution import Solution
@@ -9,6 +10,7 @@ class TestCoinChange:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"coins, amount, expected",
[
@@ -17,15 +19,12 @@ def setup_method(self):
([1], 0, 0),
([1, 3, 4], 6, 2),
([2, 5, 10, 1], 27, 4),
- ([10, 1, 2, 5], 27, 4),
([5], 3, -1),
- ([5, 2], 3, -1),
([1], 1, 1),
([1, 2], 2, 1),
([186, 419, 83, 408], 6249, 20),
],
)
- @logged_test
def test_coin_change(self, coins: list[int], amount: int, expected: int):
- result = self.solution.coin_change(coins, amount)
- assert result == expected
+ result = run_coin_change(Solution, coins, amount)
+ assert_coin_change(result, expected)
diff --git a/leetcode/combination_sum/helpers.py b/leetcode/combination_sum/helpers.py
new file mode 100644
index 0000000..15af627
--- /dev/null
+++ b/leetcode/combination_sum/helpers.py
@@ -0,0 +1,13 @@
+def run_combination_sum(solution_class: type, candidates: list[int], target: int):
+ implementation = solution_class()
+ return implementation.combination_sum(candidates, target)
+
+
+def assert_combination_sum(result: list[list[int]], expected: list[list[int]]) -> bool:
+ # Sort both result and expected for comparison
+ result_sorted = [sorted(combo) for combo in result]
+ expected_sorted = [sorted(combo) for combo in expected]
+ result_sorted.sort()
+ expected_sorted.sort()
+ assert result_sorted == expected_sorted
+ return True
diff --git a/leetcode/combination_sum/playground.ipynb b/leetcode/combination_sum/playground.ipynb
index 2bd1081..ac0711f 100644
--- a/leetcode/combination_sum/playground.ipynb
+++ b/leetcode/combination_sum/playground.ipynb
@@ -6,7 +6,10 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": ["from solution import Solution"]
+ "source": [
+ "from helpers import assert_combination_sum, run_combination_sum\n",
+ "from solution import Solution"
+ ]
},
{
"cell_type": "code",
@@ -14,23 +17,33 @@
"id": "setup",
"metadata": {},
"outputs": [],
- "source": ["# Example test case\ncandidates = [2, 3, 6, 7]\ntarget = 7\nexpected = [[2, 2, 3], [7]]"]
+ "source": [
+ "# Example test case\n",
+ "candidates = [2, 3, 6, 7]\n",
+ "target = 7\n",
+ "expected = [[2, 2, 3], [7]]"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
- "source": ["result = Solution().combination_sum(candidates, target)\nresult"]
+ "source": [
+ "result = run_combination_sum(Solution, candidates, target)\n",
+ "result"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
- "source": ["# Sort for comparison\nresult_sorted = [sorted(combo) for combo in result]\nexpected_sorted = [sorted(combo) for combo in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"]
+ "source": [
+ "assert_combination_sum(result, expected)"
+ ]
}
],
"metadata": {
@@ -48,7 +61,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/combination_sum/test_solution.py b/leetcode/combination_sum/test_solution.py
new file mode 100644
index 0000000..3bbe52b
--- /dev/null
+++ b/leetcode/combination_sum/test_solution.py
@@ -0,0 +1,24 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_combination_sum, run_combination_sum
+from .solution import Solution
+
+
+class TestCombinationSum:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "candidates, target, expected",
+ [
+ ([2, 3, 6, 7], 7, [[2, 2, 3], [7]]),
+ ([2, 3, 5], 8, [[2, 2, 2, 2], [2, 3, 3], [3, 5]]),
+ ([2], 1, []),
+ ],
+ )
+ def test_combination_sum(self, candidates: list[int], target: int, expected: list[list[int]]):
+ result = run_combination_sum(Solution, candidates, target)
+ assert_combination_sum(result, expected)
diff --git a/leetcode/combination_sum/tests.py b/leetcode/combination_sum/tests.py
deleted file mode 100644
index 200b2da..0000000
--- a/leetcode/combination_sum/tests.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestCombinationSum:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "candidates, target, expected",
- [
- ([2, 3, 6, 7], 7, [[2, 2, 3], [7]]),
- ([2, 3, 5], 8, [[2, 2, 2, 2], [2, 3, 3], [3, 5]]),
- ([2], 1, []),
- ([1], 1, [[1]]),
- ([1], 2, [[1, 1]]),
- ([2, 3, 4], 6, [[2, 2, 2], [2, 4], [3, 3]]),
- (
- [7, 3, 2],
- 18,
- [
- [2, 2, 2, 2, 2, 2, 2, 2, 2],
- [2, 2, 2, 2, 2, 2, 3, 3],
- [2, 2, 2, 2, 3, 7],
- [2, 2, 2, 3, 3, 3, 3],
- [2, 2, 7, 7],
- [2, 3, 3, 3, 7],
- [3, 3, 3, 3, 3, 3],
- ],
- ),
- ],
- )
- @logged_test
- def test_combination_sum(self, candidates: list[int], target: int, expected: list[list[int]]):
- result = self.solution.combination_sum(candidates, target)
- # Sort both result and expected for comparison
- result_sorted = [sorted(combo) for combo in result]
- expected_sorted = [sorted(combo) for combo in expected]
- result_sorted.sort()
- expected_sorted.sort()
- assert result_sorted == expected_sorted
diff --git a/leetcode/container_with_most_water/README.md b/leetcode/container_with_most_water/README.md
index 78a4c7b..a9ee63f 100644
--- a/leetcode/container_with_most_water/README.md
+++ b/leetcode/container_with_most_water/README.md
@@ -20,14 +20,15 @@ Notice that you may not slant the container.
### Example 1:
-
+
```
Input: height = [1,8,6,2,5,4,8,3,7]
Output: 49
-Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.
```
+**Explanation:** The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.
+
### Example 2:
```
diff --git a/leetcode/container_with_most_water/helpers.py b/leetcode/container_with_most_water/helpers.py
new file mode 100644
index 0000000..0ef5844
--- /dev/null
+++ b/leetcode/container_with_most_water/helpers.py
@@ -0,0 +1,8 @@
+def run_max_area(solution_class: type, height: list[int]):
+ implementation = solution_class()
+ return implementation.max_area(height)
+
+
+def assert_max_area(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/container_with_most_water/playground.ipynb b/leetcode/container_with_most_water/playground.ipynb
index 539853a..a95df0a 100644
--- a/leetcode/container_with_most_water/playground.ipynb
+++ b/leetcode/container_with_most_water/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_max_area, run_max_area\n",
"from solution import Solution"
]
},
@@ -25,22 +26,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().max_area(height)\n",
+ "result = run_max_area(Solution, height)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_max_area(result, expected)"
]
}
],
@@ -59,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/container_with_most_water/test_solution.py b/leetcode/container_with_most_water/test_solution.py
new file mode 100644
index 0000000..4ac5842
--- /dev/null
+++ b/leetcode/container_with_most_water/test_solution.py
@@ -0,0 +1,19 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_max_area, run_max_area
+from .solution import Solution
+
+
+class TestContainerWithMostWater:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "height, expected", [([1, 8, 6, 2, 5, 4, 8, 3, 7], 49), ([1, 1], 1), ([1, 2, 1], 2)]
+ )
+ def test_max_area(self, height: list[int], expected: int):
+ result = run_max_area(Solution, height)
+ assert_max_area(result, expected)
diff --git a/leetcode/container_with_most_water/tests.py b/leetcode/container_with_most_water/tests.py
deleted file mode 100644
index 2713d8d..0000000
--- a/leetcode/container_with_most_water/tests.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestContainerWithMostWater:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "height, expected",
- [
- # Original cases
- ([1, 8, 6, 2, 5, 4, 8, 3, 7], 49),
- ([1, 1], 1),
- ([1, 2, 1], 2),
- # Edge cases
- ([2, 1], 1),
- ([1, 2], 1),
- ([0, 2], 0),
- ([2, 0], 0),
- # Increasing heights
- ([1, 2, 3, 4, 5], 6),
- # Decreasing heights
- ([5, 4, 3, 2, 1], 6),
- # Same heights
- ([3, 3, 3, 3], 9),
- # Large differences
- ([1, 1000, 1], 2),
- ([1000, 1, 1000], 2000),
- # Multiple peaks
- ([2, 3, 4, 5, 18, 17, 6], 17),
- ],
- )
- @logged_test
- def test_max_area(self, height: list[int], expected: int):
- result = self.solution.max_area(height)
- assert result == expected
diff --git a/leetcode/contains_duplicate/helpers.py b/leetcode/contains_duplicate/helpers.py
new file mode 100644
index 0000000..c1e7c5e
--- /dev/null
+++ b/leetcode/contains_duplicate/helpers.py
@@ -0,0 +1,8 @@
+def run_contains_duplicate(solution_class: type, nums: list[int]):
+ implementation = solution_class()
+ return implementation.contains_duplicate(nums)
+
+
+def assert_contains_duplicate(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/contains_duplicate/playground.ipynb b/leetcode/contains_duplicate/playground.ipynb
index 3a84993..f43d0de 100644
--- a/leetcode/contains_duplicate/playground.ipynb
+++ b/leetcode/contains_duplicate/playground.ipynb
@@ -2,35 +2,69 @@
"cells": [
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 1,
"id": "imports",
"metadata": {},
"outputs": [],
- "source": ["from solution import Solution"]
+ "source": [
+ "from helpers import assert_contains_duplicate, run_contains_duplicate\n",
+ "from solution import Solution"
+ ]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 2,
"id": "setup",
"metadata": {},
"outputs": [],
- "source": ["# Example test case\nnums = [1, 2, 3, 1]\nexpected = True"]
+ "source": [
+ "# Example test case\n",
+ "nums = [1, 2, 3, 1]\n",
+ "expected = True"
+ ]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "execute",
+ "execution_count": 3,
+ "id": "run",
"metadata": {},
- "outputs": [],
- "source": ["result = Solution().contains_duplicate(nums)\nresult"]
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "result = run_contains_duplicate(Solution, nums)\n",
+ "result"
+ ]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "test",
+ "execution_count": 4,
+ "id": "assert",
"metadata": {},
- "outputs": [],
- "source": ["assert result == expected"]
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "assert_contains_duplicate(result, expected)"
+ ]
}
],
"metadata": {
@@ -47,7 +81,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python3",
+ "nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
diff --git a/leetcode/contains_duplicate/test_solution.py b/leetcode/contains_duplicate/test_solution.py
new file mode 100644
index 0000000..5eaa35a
--- /dev/null
+++ b/leetcode/contains_duplicate/test_solution.py
@@ -0,0 +1,20 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_contains_duplicate, run_contains_duplicate
+from .solution import Solution
+
+
+class TestContainsDuplicate:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "nums, expected",
+ [([1, 2, 3, 1], True), ([1, 2, 3, 4], False), ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True)],
+ )
+ def test_contains_duplicate(self, nums: list[int], expected: bool):
+ result = run_contains_duplicate(Solution, nums)
+ assert_contains_duplicate(result, expected)
diff --git a/leetcode/contains_duplicate/tests.py b/leetcode/contains_duplicate/tests.py
deleted file mode 100644
index 3a1d6cc..0000000
--- a/leetcode/contains_duplicate/tests.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestContainsDuplicate:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "nums, expected",
- [
- ([1, 2, 3, 1], True),
- ([1, 2, 3, 4], False),
- ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True),
- ([], False),
- ([1], False),
- ([1, 1], True),
- ([-1, -2, -3, -1], True),
- ([-1, -2, -3, -4], False),
- ([0, 0], True),
- ([1000000, 999999, 1000000], True),
- (list(range(1000)), False),
- ([1] * 1000, True),
- ],
- )
- @logged_test
- def test_contains_duplicate(self, nums: list[int], expected: bool):
- result = self.solution.contains_duplicate(nums)
- assert result == expected
diff --git a/leetcode/course_schedule/helpers.py b/leetcode/course_schedule/helpers.py
new file mode 100644
index 0000000..67b7f87
--- /dev/null
+++ b/leetcode/course_schedule/helpers.py
@@ -0,0 +1,8 @@
+def run_can_finish(solution_class: type, num_courses: int, prerequisites: list[list[int]]):
+ implementation = solution_class()
+ return implementation.can_finish(num_courses, prerequisites)
+
+
+def assert_can_finish(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/course_schedule/playground.ipynb b/leetcode/course_schedule/playground.ipynb
index a3fbcc4..2dfeb00 100644
--- a/leetcode/course_schedule/playground.ipynb
+++ b/leetcode/course_schedule/playground.ipynb
@@ -6,7 +6,10 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": ["from solution import Solution"]
+ "source": [
+ "from helpers import assert_can_finish, run_can_finish\n",
+ "from solution import Solution"
+ ]
},
{
"cell_type": "code",
@@ -14,23 +17,33 @@
"id": "setup",
"metadata": {},
"outputs": [],
- "source": ["# Example test case\nnum_courses = 2\nprerequisites = [[1, 0]]\nexpected = True"]
+ "source": [
+ "# Example test case\n",
+ "num_courses = 2\n",
+ "prerequisites = [[1, 0]]\n",
+ "expected = True"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
- "source": ["result = Solution().can_finish(num_courses, prerequisites)\nresult"]
+ "source": [
+ "result = run_can_finish(Solution, num_courses, prerequisites)\n",
+ "result"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
- "source": ["assert result == expected"]
+ "source": [
+ "assert_can_finish(result, expected)"
+ ]
}
],
"metadata": {
@@ -48,7 +61,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/course_schedule/solution.py b/leetcode/course_schedule/solution.py
index 54cf72e..925178c 100644
--- a/leetcode/course_schedule/solution.py
+++ b/leetcode/course_schedule/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(V + E) where V = num_courses, E = prerequisites
# Space: O(V + E) for adjacency list and recursion stack
def can_finish(self, num_courses: int, prerequisites: list[list[int]]) -> bool:
diff --git a/leetcode/course_schedule/test_solution.py b/leetcode/course_schedule/test_solution.py
new file mode 100644
index 0000000..2fc24fc
--- /dev/null
+++ b/leetcode/course_schedule/test_solution.py
@@ -0,0 +1,26 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_can_finish, run_can_finish
+from .solution import Solution
+
+
+class TestCourseSchedule:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "num_courses, prerequisites, expected",
+ [
+ (2, [[1, 0]], True),
+ (2, [[1, 0], [0, 1]], False),
+ (1, [], True),
+ (3, [[1, 0], [2, 1]], True),
+ (4, [[1, 0], [2, 1], [3, 2], [1, 3]], False),
+ ],
+ )
+ def test_can_finish(self, num_courses: int, prerequisites: list[list[int]], expected: bool):
+ result = run_can_finish(Solution, num_courses, prerequisites)
+ assert_can_finish(result, expected)
diff --git a/leetcode/course_schedule/tests.py b/leetcode/course_schedule/tests.py
deleted file mode 100644
index 5eb8d60..0000000
--- a/leetcode/course_schedule/tests.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestCourseSchedule:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "num_courses, prerequisites, expected",
- [
- # Basic cases
- (2, [[1, 0]], True),
- (2, [[1, 0], [0, 1]], False),
- (1, [], True),
- (3, [[1, 0], [2, 1]], True),
- (4, [[1, 0], [2, 1], [3, 2], [1, 3]], False),
- # Edge cases
- (0, [], True),
- (5, [], True),
- (3, [[0, 1], [0, 2], [1, 2]], True),
- # Self-loop
- (2, [[0, 0]], False),
- (3, [[1, 1]], False),
- # Complex valid cases
- (6, [[1, 0], [2, 0], [3, 1], [3, 2], [4, 3], [5, 4]], True),
- (4, [[0, 1], [0, 2], [1, 3], [2, 3]], True),
- # Complex cycles
- (3, [[0, 1], [1, 2], [2, 0]], False),
- (5, [[1, 0], [2, 1], [3, 2], [4, 3], [0, 4]], False),
- (4, [[1, 0], [2, 0], [0, 3], [3, 1]], False),
- ],
- )
- @logged_test
- def test_can_finish(self, num_courses: int, prerequisites: list[list[int]], expected: bool):
- result = self.solution.can_finish(num_courses, prerequisites)
- assert result == expected
diff --git a/leetcode/diameter_of_binary_tree/helpers.py b/leetcode/diameter_of_binary_tree/helpers.py
new file mode 100644
index 0000000..7885029
--- /dev/null
+++ b/leetcode/diameter_of_binary_tree/helpers.py
@@ -0,0 +1,12 @@
+from leetcode_py import TreeNode
+
+
+def run_diameter_of_binary_tree(solution_class: type, root_list: list[int | None]):
+ root = TreeNode[int].from_list(root_list)
+ implementation = solution_class()
+ return implementation.diameter_of_binary_tree(root)
+
+
+def assert_diameter_of_binary_tree(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/diameter_of_binary_tree/playground.ipynb b/leetcode/diameter_of_binary_tree/playground.ipynb
index 4821880..d401bcf 100644
--- a/leetcode/diameter_of_binary_tree/playground.ipynb
+++ b/leetcode/diameter_of_binary_tree/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_diameter_of_binary_tree, run_diameter_of_binary_tree\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -14,7 +15,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -26,124 +27,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "3"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "root = TreeNode.from_list(root_list)\n",
- "result = Solution().diameter_of_binary_tree(root)\n",
+ "result = run_diameter_of_binary_tree(Solution, root_list)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 5,
- "id": "248f3295",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "TreeNode([1, 2, 3, 4, 5])"
- ]
- },
- "execution_count": 5,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "root"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_diameter_of_binary_tree(result, expected)"
]
}
],
@@ -161,8 +61,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/diameter_of_binary_tree/solution.py b/leetcode/diameter_of_binary_tree/solution.py
index bd0cbd2..e54b5ed 100644
--- a/leetcode/diameter_of_binary_tree/solution.py
+++ b/leetcode/diameter_of_binary_tree/solution.py
@@ -2,12 +2,13 @@
class Solution:
+
# Time: O(n)
# Space: O(h)
- def diameter_of_binary_tree(self, root: TreeNode | None) -> int:
+ def diameter_of_binary_tree(self, root: TreeNode[int] | None) -> int:
self.max_diameter = 0
- def dfs(node: TreeNode | None) -> int:
+ def dfs(node: TreeNode[int] | None) -> int:
if not node:
return 0
diff --git a/leetcode/diameter_of_binary_tree/test_solution.py b/leetcode/diameter_of_binary_tree/test_solution.py
new file mode 100644
index 0000000..1abc301
--- /dev/null
+++ b/leetcode/diameter_of_binary_tree/test_solution.py
@@ -0,0 +1,20 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_diameter_of_binary_tree, run_diameter_of_binary_tree
+from .solution import Solution
+
+
+class TestDiameterOfBinaryTree:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "root_list, expected",
+ [([1, 2, 3, 4, 5], 3), ([1, 2], 1), ([], 0), ([1], 0), ([1, 2, 3], 2), ([1, None, 2], 1)],
+ )
+ def test_diameter_of_binary_tree(self, root_list: list[int | None], expected: int):
+ result = run_diameter_of_binary_tree(Solution, root_list)
+ assert_diameter_of_binary_tree(result, expected)
diff --git a/leetcode/diameter_of_binary_tree/tests.py b/leetcode/diameter_of_binary_tree/tests.py
deleted file mode 100644
index 914bab5..0000000
--- a/leetcode/diameter_of_binary_tree/tests.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import pytest
-
-from leetcode_py import TreeNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestDiameterOfBinaryTree:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "root_list, expected",
- [
- ([1, 2, 3, 4, 5], 3),
- ([1, 2], 1),
- ([1], 0),
- ([], 0),
- ([1, 2, 3, 4, 5, None, None, 6, 7], 4),
- ([1, None, 2, None, 3, None, 4], 3),
- ([1, 2, None, 3, None, 4], 3),
- ([1, 2, 3], 2),
- ([1, 2, 3, 4, None, None, 5], 4),
- ],
- )
- @logged_test
- def test_diameter_of_binary_tree(self, root_list: list[int | None], expected: int):
- root = TreeNode.from_list(root_list)
- result = self.solution.diameter_of_binary_tree(root)
- assert result == expected
diff --git a/leetcode/evaluate_reverse_polish_notation/helpers.py b/leetcode/evaluate_reverse_polish_notation/helpers.py
new file mode 100644
index 0000000..540042e
--- /dev/null
+++ b/leetcode/evaluate_reverse_polish_notation/helpers.py
@@ -0,0 +1,8 @@
+def run_eval_rpn(solution_class: type, tokens: list[str]):
+ implementation = solution_class()
+ return implementation.eval_rpn(tokens)
+
+
+def assert_eval_rpn(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/evaluate_reverse_polish_notation/playground.ipynb b/leetcode/evaluate_reverse_polish_notation/playground.ipynb
index fc7ba18..01fd8b8 100644
--- a/leetcode/evaluate_reverse_polish_notation/playground.ipynb
+++ b/leetcode/evaluate_reverse_polish_notation/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_eval_rpn, run_eval_rpn\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -24,34 +25,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "9"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().eval_rpn(tokens)\n",
+ "result = run_eval_rpn(Solution, tokens)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_eval_rpn(result, expected)"
]
}
],
@@ -69,8 +59,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/evaluate_reverse_polish_notation/solution.py b/leetcode/evaluate_reverse_polish_notation/solution.py
index b3b2ea1..d6496e4 100644
--- a/leetcode/evaluate_reverse_polish_notation/solution.py
+++ b/leetcode/evaluate_reverse_polish_notation/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n)
# Space: O(n)
def eval_rpn(self, tokens: list[str]) -> int:
diff --git a/leetcode/evaluate_reverse_polish_notation/test_solution.py b/leetcode/evaluate_reverse_polish_notation/test_solution.py
new file mode 100644
index 0000000..e422248
--- /dev/null
+++ b/leetcode/evaluate_reverse_polish_notation/test_solution.py
@@ -0,0 +1,24 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_eval_rpn, run_eval_rpn
+from .solution import Solution
+
+
+class TestEvaluateReversePolishNotation:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "tokens, expected",
+ [
+ (["2", "1", "+", "3", "*"], 9),
+ (["4", "13", "5", "/", "+"], 6),
+ (["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"], 22),
+ ],
+ )
+ def test_eval_rpn(self, tokens: list[str], expected: int):
+ result = run_eval_rpn(Solution, tokens)
+ assert_eval_rpn(result, expected)
diff --git a/leetcode/evaluate_reverse_polish_notation/tests.py b/leetcode/evaluate_reverse_polish_notation/tests.py
deleted file mode 100644
index f353e1f..0000000
--- a/leetcode/evaluate_reverse_polish_notation/tests.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestEvaluateReversePolishNotation:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "tokens, expected",
- [
- # Original cases
- (["2", "1", "+", "3", "*"], 9),
- (["4", "13", "5", "/", "+"], 6),
- (["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"], 22),
- # Single number
- (["42"], 42),
- # Negative numbers
- (["-1"], -1),
- (["1", "-1", "+"], 0),
- # Basic operations
- (["3", "4", "+"], 7),
- (["5", "2", "-"], 3),
- (["6", "3", "*"], 18),
- (["8", "2", "/"], 4),
- # Division with negatives
- (["-3", "4", "+", "2", "*", "1", "-"], 1),
- # Complex expression
- (["15", "7", "1", "1", "+", "/", "/", "3", "*", "2", "1", "1", "+", "+", "-"], 11),
- ],
- )
- @logged_test
- def test_eval_rpn(self, tokens: list[str], expected: int):
- result = self.solution.eval_rpn(tokens)
- assert result == expected
diff --git a/leetcode/find_median_from_data_stream/helpers.py b/leetcode/find_median_from_data_stream/helpers.py
new file mode 100644
index 0000000..c16a010
--- /dev/null
+++ b/leetcode/find_median_from_data_stream/helpers.py
@@ -0,0 +1,18 @@
+def run_median_finder(solution_class: type, operations: list[str], inputs: list[list[int]]):
+ mf = None
+ results: list[float | None] = []
+ for i, op in enumerate(operations):
+ if op == "MedianFinder":
+ mf = solution_class()
+ results.append(None)
+ elif op == "addNum" and mf is not None:
+ mf.add_num(inputs[i][0])
+ results.append(None)
+ elif op == "findMedian" and mf is not None:
+ results.append(mf.find_median())
+ return results, mf
+
+
+def assert_median_finder(result: list[float | None], expected: list[float | None]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/find_median_from_data_stream/playground.ipynb b/leetcode/find_median_from_data_stream/playground.ipynb
index 3562233..3a72e85 100644
--- a/leetcode/find_median_from_data_stream/playground.ipynb
+++ b/leetcode/find_median_from_data_stream/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_median_finder, run_median_finder\n",
"from solution import MedianFinder"
]
},
@@ -26,32 +27,23 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "mf = None\n",
- "results: list[float | None] = []\n",
- "for i, op in enumerate(operations):\n",
- " if op == \"MedianFinder\":\n",
- " mf = MedianFinder()\n",
- " results.append(None)\n",
- " elif op == \"addNum\" and mf is not None:\n",
- " mf.add_num(inputs[i][0])\n",
- " results.append(None)\n",
- " elif op == \"findMedian\" and mf is not None:\n",
- " results.append(mf.find_median())\n",
- "results"
+ "result, mf = run_median_finder(MedianFinder, operations, inputs)\n",
+ "print(result)\n",
+ "mf"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert results == expected"
+ "assert_median_finder(result, expected)"
]
}
],
@@ -70,7 +62,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/find_median_from_data_stream/test_solution.py b/leetcode/find_median_from_data_stream/test_solution.py
new file mode 100644
index 0000000..25ec573
--- /dev/null
+++ b/leetcode/find_median_from_data_stream/test_solution.py
@@ -0,0 +1,124 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_median_finder, run_median_finder
+from .solution import MedianFinder, MedianFinderHybrid
+
+
+class TestFindMedianFromDataStream:
+
+ @logged_test
+ @pytest.mark.parametrize("solution_class", [MedianFinder, MedianFinderHybrid])
+ @pytest.mark.parametrize(
+ "operations, inputs, expected",
+ [
+ (
+ ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"],
+ [[], [1], [2], [], [3], []],
+ [None, None, None, 1.5, None, 2.0],
+ ),
+ (["MedianFinder", "addNum", "findMedian"], [[], [1], []], [None, None, 1.0]),
+ (
+ ["MedianFinder", "addNum", "addNum", "addNum", "findMedian"],
+ [[], [1], [1], [1], []],
+ [None, None, None, None, 1.0],
+ ),
+ (
+ ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "findMedian"],
+ [[], [1], [2], [3], [4], []],
+ [None, None, None, None, None, 2.5],
+ ),
+ (
+ ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "addNum", "findMedian"],
+ [[], [-1], [0], [], [1], [2], []],
+ [None, None, None, -0.5, None, None, 0.5],
+ ),
+ (
+ ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "addNum", "findMedian"],
+ [[], [5], [1], [3], [2], [4], []],
+ [None, None, None, None, None, None, 3.0],
+ ),
+ (
+ ["MedianFinder", "addNum", "findMedian", "addNum", "findMedian", "addNum", "findMedian"],
+ [[], [100000], [], [-100000], [], [0], []],
+ [None, None, 100000.0, None, 0.0, None, 0.0],
+ ),
+ (
+ [
+ "MedianFinder",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "findMedian",
+ ],
+ [[], [10], [5], [15], [3], [7], [12], [18], []],
+ [None, None, None, None, None, None, None, None, 10.0],
+ ),
+ (
+ [
+ "MedianFinder",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "findMedian",
+ ],
+ [[], [6], [10], [2], [6], [5], [0], []],
+ [None, None, None, None, None, None, None, 5.5],
+ ),
+ (
+ [
+ "MedianFinder",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "findMedian",
+ ],
+ [[], [1], [2], [3], [4], [5], [6], [7], [8], []],
+ [None, None, None, None, None, None, None, None, None, 4.5],
+ ),
+ (
+ [
+ "MedianFinder",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "addNum",
+ "findMedian",
+ ],
+ [[], [9], [8], [7], [6], [5], [4], [3], [2], [1], []],
+ [None, None, None, None, None, None, None, None, None, None, 5.0],
+ ),
+ (
+ ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "addNum", "findMedian"],
+ [[], [0], [0], [0], [0], [0], []],
+ [None, None, None, None, None, None, 0.0],
+ ),
+ ],
+ )
+ def test_median_finder(
+ self,
+ solution_class: type,
+ operations: list[str],
+ inputs: list[list[int]],
+ expected: list[float | None],
+ ):
+ result, _ = run_median_finder(solution_class, operations, inputs)
+ assert_median_finder(result, expected)
diff --git a/leetcode/find_median_from_data_stream/tests.py b/leetcode/find_median_from_data_stream/tests.py
deleted file mode 100644
index 0a07039..0000000
--- a/leetcode/find_median_from_data_stream/tests.py
+++ /dev/null
@@ -1,71 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import MedianFinder, MedianFinderHybrid
-
-
-class TestFindMedianFromDataStream:
- @pytest.mark.parametrize(
- "finder_class, operations, inputs, expected",
- [
- (
- MedianFinder,
- ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"],
- [[], [1], [2], [], [3], []],
- [None, None, None, 1.5, None, 2.0],
- ),
- (
- MedianFinderHybrid,
- ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"],
- [[], [1], [2], [], [3], []],
- [None, None, None, 1.5, None, 2.0],
- ),
- (
- MedianFinder,
- ["MedianFinder", "addNum", "findMedian"],
- [[], [5], []],
- [None, None, 5.0],
- ),
- (
- MedianFinderHybrid,
- ["MedianFinder", "addNum", "findMedian"],
- [[], [5], []],
- [None, None, 5.0],
- ),
- (
- MedianFinder,
- ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "findMedian"],
- [[], [1], [3], [2], [4], []],
- [None, None, None, None, None, 2.5],
- ),
- (
- MedianFinderHybrid,
- ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "findMedian"],
- [[], [1], [3], [2], [4], []],
- [None, None, None, None, None, 2.5],
- ),
- (
- MedianFinderHybrid,
- ["MedianFinder", "addNum", "addNum", "addNum", "findMedian"],
- [[], [-1], [50], [101], []],
- [None, None, None, None, 50.0],
- ),
- ],
- )
- @logged_test
- def test_median_finder(
- self, finder_class, operations: list[str], inputs: list[list[int]], expected: list[float | None]
- ):
- mf = None
- results: list[float | None] = []
- for i, op in enumerate(operations):
- if op == "MedianFinder":
- mf = finder_class()
- results.append(None)
- elif op == "addNum" and mf is not None:
- mf.add_num(inputs[i][0])
- results.append(None)
- elif op == "findMedian" and mf is not None:
- results.append(mf.find_median())
- assert results == expected
diff --git a/leetcode/first_bad_version/helpers.py b/leetcode/first_bad_version/helpers.py
new file mode 100644
index 0000000..a84be2a
--- /dev/null
+++ b/leetcode/first_bad_version/helpers.py
@@ -0,0 +1,8 @@
+def run_first_bad_version(solution_class: type, n: int, bad: int):
+ solution = solution_class(bad)
+ return solution.first_bad_version(n)
+
+
+def assert_first_bad_version(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/first_bad_version/playground.ipynb b/leetcode/first_bad_version/playground.ipynb
index c0d9e87..eff1efe 100644
--- a/leetcode/first_bad_version/playground.ipynb
+++ b/leetcode/first_bad_version/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_first_bad_version, run_first_bad_version\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,35 +26,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "4"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "solution = Solution(first_bad=bad)\n",
- "result = solution.first_bad_version(n)\n",
+ "result = run_first_bad_version(Solution, n, bad)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_first_bad_version(result, expected)"
]
}
],
@@ -72,7 +61,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/first_bad_version/solution.py b/leetcode/first_bad_version/solution.py
index 7fa3f49..0d0fb12 100644
--- a/leetcode/first_bad_version/solution.py
+++ b/leetcode/first_bad_version/solution.py
@@ -1,6 +1,6 @@
class Solution:
- # TODO: template constraint
- def __init__(self, first_bad):
+
+ def __init__(self, first_bad: int = 1) -> None:
self.is_bad_version = lambda version: version >= first_bad
# Time: O(log n)
diff --git a/leetcode/first_bad_version/test_solution.py b/leetcode/first_bad_version/test_solution.py
new file mode 100644
index 0000000..12f25a5
--- /dev/null
+++ b/leetcode/first_bad_version/test_solution.py
@@ -0,0 +1,31 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_first_bad_version, run_first_bad_version
+from .solution import Solution
+
+
+class TestFirstBadVersion:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "n, bad, expected",
+ [
+ (5, 4, 4),
+ (1, 1, 1),
+ (3, 1, 1),
+ (10, 7, 7),
+ (100, 50, 50),
+ (2, 1, 1),
+ (2, 2, 2),
+ (1000, 1, 1),
+ (1000, 999, 999),
+ (1000, 500, 500),
+ ],
+ )
+ def test_first_bad_version(self, n: int, bad: int, expected: int):
+ result = run_first_bad_version(Solution, n, bad)
+ assert_first_bad_version(result, expected)
diff --git a/leetcode/first_bad_version/tests.py b/leetcode/first_bad_version/tests.py
deleted file mode 100644
index 570373b..0000000
--- a/leetcode/first_bad_version/tests.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestFirstBadVersion:
-
- @pytest.mark.parametrize(
- "n, bad, expected", [(5, 4, 4), (1, 1, 1), (3, 1, 1), (10, 7, 7), (5, -1, 1)]
- )
- @logged_test
- def test_first_bad_version(self, n: int, bad: int, expected: int):
- solution = Solution(first_bad=bad)
- result = solution.first_bad_version(n)
- assert result == expected
diff --git a/leetcode/flood_fill/helpers.py b/leetcode/flood_fill/helpers.py
new file mode 100644
index 0000000..5aa521d
--- /dev/null
+++ b/leetcode/flood_fill/helpers.py
@@ -0,0 +1,8 @@
+def run_flood_fill(solution_class: type, image: list[list[int]], sr: int, sc: int, color: int):
+ implementation = solution_class()
+ return implementation.flood_fill(image, sr, sc, color)
+
+
+def assert_flood_fill(result: list[list[int]], expected: list[list[int]]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/flood_fill/playground.ipynb b/leetcode/flood_fill/playground.ipynb
index 53f0ea6..583f7a1 100644
--- a/leetcode/flood_fill/playground.ipynb
+++ b/leetcode/flood_fill/playground.ipynb
@@ -6,7 +6,10 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": ["from solution import Solution"]
+ "source": [
+ "from helpers import assert_flood_fill, run_flood_fill\n",
+ "from solution import Solution"
+ ]
},
{
"cell_type": "code",
@@ -14,23 +17,35 @@
"id": "setup",
"metadata": {},
"outputs": [],
- "source": ["# Example test case\nimage = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\nsr = 1\nsc = 1\ncolor = 2\nexpected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]"]
+ "source": [
+ "# Example test case\n",
+ "image = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\n",
+ "sr = 1\n",
+ "sc = 1\n",
+ "color = 2\n",
+ "expected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
- "source": ["result = Solution().flood_fill(image, sr, sc, color)\nresult"]
+ "source": [
+ "result = run_flood_fill(Solution, image, sr, sc, color)\n",
+ "result"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
- "source": ["assert result == expected"]
+ "source": [
+ "assert_flood_fill(result, expected)"
+ ]
}
],
"metadata": {
@@ -48,7 +63,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/flood_fill/solution.py b/leetcode/flood_fill/solution.py
index 897ac71..ea6885b 100644
--- a/leetcode/flood_fill/solution.py
+++ b/leetcode/flood_fill/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(m*n)
# Space: O(m*n)
def flood_fill(self, image: list[list[int]], sr: int, sc: int, color: int) -> list[list[int]]:
diff --git a/leetcode/flood_fill/tests.py b/leetcode/flood_fill/test_solution.py
similarity index 82%
rename from leetcode/flood_fill/tests.py
rename to leetcode/flood_fill/test_solution.py
index fdb3209..ee4a476 100644
--- a/leetcode/flood_fill/tests.py
+++ b/leetcode/flood_fill/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_flood_fill, run_flood_fill
from .solution import Solution
@@ -9,6 +10,7 @@ class TestFloodFill:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"image, sr, sc, color, expected",
[
@@ -18,9 +20,8 @@ def setup_method(self):
([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 1, [[1, 1, 1], [1, 1, 0], [1, 0, 1]]),
],
)
- @logged_test
def test_flood_fill(
self, image: list[list[int]], sr: int, sc: int, color: int, expected: list[list[int]]
):
- result = self.solution.flood_fill(image, sr, sc, color)
- assert result == expected
+ result = run_flood_fill(Solution, image, sr, sc, color)
+ assert_flood_fill(result, expected)
diff --git a/leetcode/implement_queue_using_stacks/helpers.py b/leetcode/implement_queue_using_stacks/helpers.py
new file mode 100644
index 0000000..504d419
--- /dev/null
+++ b/leetcode/implement_queue_using_stacks/helpers.py
@@ -0,0 +1,22 @@
+def run_my_queue(solution_class: type, operations: list[str], inputs: list[list[int]]):
+ queue = None
+ results: list[int | None | bool] = []
+ for i, op in enumerate(operations):
+ if op == "MyQueue":
+ queue = solution_class()
+ results.append(None)
+ elif op == "push" and queue is not None:
+ queue.push(inputs[i][0])
+ results.append(None)
+ elif op == "pop" and queue is not None:
+ results.append(queue.pop())
+ elif op == "peek" and queue is not None:
+ results.append(queue.peek())
+ elif op == "empty" and queue is not None:
+ results.append(queue.empty())
+ return results, queue
+
+
+def assert_my_queue(result: list[int | None | bool], expected: list[int | None | bool]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/implement_queue_using_stacks/playground.ipynb b/leetcode/implement_queue_using_stacks/playground.ipynb
index 5430378..735a590 100644
--- a/leetcode/implement_queue_using_stacks/playground.ipynb
+++ b/leetcode/implement_queue_using_stacks/playground.ipynb
@@ -2,58 +2,48 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_my_queue, run_my_queue\n",
"from solution import MyQueue"
]
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
"# Example test case\n",
- "queue = MyQueue()\n",
- "queue.push(1)\n",
- "queue.push(2)"
+ "operations = [\"MyQueue\", \"push\", \"push\", \"peek\", \"pop\", \"empty\"]\n",
+ "inputs = [[], [1], [2], [], [], []]\n",
+ "expected = [None, None, None, 1, 1, False]"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "peek: 1, pop: 1, empty: False\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
- "result_peek = queue.peek()\n",
- "result_pop = queue.pop()\n",
- "result_empty = queue.empty()\n",
- "print(f\"peek: {result_peek}, pop: {result_pop}, empty: {result_empty}\")"
+ "result, queue = run_my_queue(MyQueue, operations, inputs)\n",
+ "print(result)\n",
+ "queue"
]
},
{
"cell_type": "code",
- "execution_count": 5,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result_peek == 1\n",
- "assert result_pop == 1\n",
- "assert not result_empty"
+ "assert_my_queue(result, expected)"
]
}
],
@@ -71,8 +61,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/implement_queue_using_stacks/tests.py b/leetcode/implement_queue_using_stacks/test_solution.py
similarity index 59%
rename from leetcode/implement_queue_using_stacks/tests.py
rename to leetcode/implement_queue_using_stacks/test_solution.py
index 519121f..3363ac1 100644
--- a/leetcode/implement_queue_using_stacks/tests.py
+++ b/leetcode/implement_queue_using_stacks/test_solution.py
@@ -2,10 +2,13 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_my_queue, run_my_queue
from .solution import MyQueue
class TestImplementQueueUsingStacks:
+
+ @logged_test
@pytest.mark.parametrize(
"operations, inputs, expected",
[
@@ -26,23 +29,8 @@ class TestImplementQueueUsingStacks:
),
],
)
- @logged_test
def test_queue_operations(
self, operations: list[str], inputs: list[list[int]], expected: list[int | None | bool]
):
- queue = None
- results: list[int | None | bool] = []
- for i, op in enumerate(operations):
- if op == "MyQueue":
- queue = MyQueue()
- results.append(None)
- elif op == "push" and queue is not None:
- queue.push(inputs[i][0])
- results.append(None)
- elif op == "pop" and queue is not None:
- results.append(queue.pop())
- elif op == "peek" and queue is not None:
- results.append(queue.peek())
- elif op == "empty" and queue is not None:
- results.append(queue.empty())
- assert results == expected
+ result, _ = run_my_queue(MyQueue, operations, inputs)
+ assert_my_queue(result, expected)
diff --git a/leetcode/implement_trie_prefix_tree/helpers.py b/leetcode/implement_trie_prefix_tree/helpers.py
new file mode 100644
index 0000000..17ae31a
--- /dev/null
+++ b/leetcode/implement_trie_prefix_tree/helpers.py
@@ -0,0 +1,20 @@
+def run_trie_operations(solution_class: type, operations: list[str], inputs: list[list[str]]):
+ trie = None
+ results: list[bool | None] = []
+ for i, op in enumerate(operations):
+ if op == "Trie":
+ trie = solution_class()
+ results.append(None)
+ elif op == "insert" and trie is not None:
+ trie.insert(inputs[i][0])
+ results.append(None)
+ elif op == "search" and trie is not None:
+ results.append(trie.search(inputs[i][0]))
+ elif op == "starts_with" and trie is not None:
+ results.append(trie.starts_with(inputs[i][0]))
+ return results, trie
+
+
+def assert_trie_operations(result: list[bool | None], expected: list[bool | None]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/implement_trie_prefix_tree/playground.ipynb b/leetcode/implement_trie_prefix_tree/playground.ipynb
index 75d2107..c509af4 100644
--- a/leetcode/implement_trie_prefix_tree/playground.ipynb
+++ b/leetcode/implement_trie_prefix_tree/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_trie_operations, run_trie_operations\n",
"from solution import Trie"
]
},
@@ -26,43 +27,16 @@
{
"cell_type": "code",
"execution_count": 3,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [
{
- "data": {
- "text/plain": [
- "[None, None, True, False, True, None, True]"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "trie = None\n",
- "results: list[bool | None] = []\n",
- "for i, op in enumerate(operations):\n",
- " if op == \"Trie\":\n",
- " trie = Trie()\n",
- " results.append(None)\n",
- " elif op == \"insert\" and trie is not None:\n",
- " trie.insert(inputs[i][0])\n",
- " results.append(None)\n",
- " elif op == \"search\" and trie is not None:\n",
- " results.append(trie.search(inputs[i][0]))\n",
- " elif op == \"starts_with\" and trie is not None:\n",
- " results.append(trie.starts_with(inputs[i][0]))\n",
- "results"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "c8308208",
- "metadata": {},
- "outputs": [
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[None, None, True, False, True, None, True]\n"
+ ]
+ },
{
"data": {
"text/html": [
@@ -170,26 +144,39 @@
"\n"
],
"text/plain": [
- ""
+ ""
]
},
- "execution_count": 5,
+ "execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
+ "result, trie = run_trie_operations(Trie, operations, inputs)\n",
+ "print(result)\n",
"trie"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "test",
+ "id": "assert",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "assert results == expected"
+ "assert_trie_operations(result, expected)"
]
}
],
diff --git a/leetcode/implement_trie_prefix_tree/tests.py b/leetcode/implement_trie_prefix_tree/test_solution.py
similarity index 71%
rename from leetcode/implement_trie_prefix_tree/tests.py
rename to leetcode/implement_trie_prefix_tree/test_solution.py
index 6b078bb..48a4c13 100644
--- a/leetcode/implement_trie_prefix_tree/tests.py
+++ b/leetcode/implement_trie_prefix_tree/test_solution.py
@@ -2,10 +2,13 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_trie_operations, run_trie_operations
from .solution import Trie
class TestImplementTriePrefixTree:
+
+ @logged_test
@pytest.mark.parametrize(
"operations, inputs, expected",
[
@@ -32,21 +35,8 @@ class TestImplementTriePrefixTree:
(["Trie", "search", "starts_with"], [[], ["empty"], ["empty"]], [None, False, False]),
],
)
- @logged_test
def test_trie_operations(
self, operations: list[str], inputs: list[list[str]], expected: list[bool | None]
):
- trie: Trie | None = None
- results: list[bool | None] = []
- for i, op in enumerate(operations):
- if op == "Trie":
- trie = Trie()
- results.append(None)
- elif op == "insert" and trie is not None:
- trie.insert(inputs[i][0])
- results.append(None)
- elif op == "search" and trie is not None:
- results.append(trie.search(inputs[i][0]))
- elif op == "starts_with" and trie is not None:
- results.append(trie.starts_with(inputs[i][0]))
- assert results == expected
+ result, _ = run_trie_operations(Trie, operations, inputs)
+ assert_trie_operations(result, expected)
diff --git a/leetcode/insert_interval/helpers.py b/leetcode/insert_interval/helpers.py
new file mode 100644
index 0000000..19995b9
--- /dev/null
+++ b/leetcode/insert_interval/helpers.py
@@ -0,0 +1,8 @@
+def run_insert(solution_class: type, intervals: list[list[int]], new_interval: list[int]):
+ implementation = solution_class()
+ return implementation.insert(intervals, new_interval)
+
+
+def assert_insert(result: list[list[int]], expected: list[list[int]]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/insert_interval/playground.ipynb b/leetcode/insert_interval/playground.ipynb
index 921d04b..0e8d78f 100644
--- a/leetcode/insert_interval/playground.ipynb
+++ b/leetcode/insert_interval/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_insert, run_insert\n",
"from solution import Solution"
]
},
@@ -26,22 +27,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().insert(intervals, new_interval)\n",
+ "result = run_insert(Solution, intervals, new_interval)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_insert(result, expected)"
]
}
],
@@ -60,7 +61,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/insert_interval/solution.py b/leetcode/insert_interval/solution.py
index 25ce116..7a0b224 100644
--- a/leetcode/insert_interval/solution.py
+++ b/leetcode/insert_interval/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n)
# Space: O(n)
def insert(self, intervals: list[list[int]], new_interval: list[int]) -> list[list[int]]:
diff --git a/leetcode/insert_interval/test_solution.py b/leetcode/insert_interval/test_solution.py
new file mode 100644
index 0000000..7e83e00
--- /dev/null
+++ b/leetcode/insert_interval/test_solution.py
@@ -0,0 +1,30 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_insert, run_insert
+from .solution import Solution
+
+
+class TestInsertInterval:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "intervals, new_interval, expected",
+ [
+ ([[1, 3], [6, 9]], [2, 5], [[1, 5], [6, 9]]),
+ ([[1, 2], [3, 5], [6, 7], [8, 10], [12, 16]], [4, 8], [[1, 2], [3, 10], [12, 16]]),
+ ([], [5, 7], [[5, 7]]),
+ ([[1, 5]], [2, 3], [[1, 5]]),
+ ([[1, 5]], [6, 8], [[1, 5], [6, 8]]),
+ ([[1, 5]], [0, 0], [[0, 0], [1, 5]]),
+ ([[3, 5], [12, 15]], [6, 6], [[3, 5], [6, 6], [12, 15]]),
+ ],
+ )
+ def test_insert(
+ self, intervals: list[list[int]], new_interval: list[int], expected: list[list[int]]
+ ):
+ result = run_insert(Solution, intervals, new_interval)
+ assert_insert(result, expected)
diff --git a/leetcode/insert_interval/tests.py b/leetcode/insert_interval/tests.py
deleted file mode 100644
index ee51e8f..0000000
--- a/leetcode/insert_interval/tests.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestInsertInterval:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "intervals, new_interval, expected",
- [
- # Original cases
- ([[1, 3], [6, 9]], [2, 5], [[1, 5], [6, 9]]),
- ([[1, 2], [3, 5], [6, 7], [8, 10], [12, 16]], [4, 8], [[1, 2], [3, 10], [12, 16]]),
- # Empty intervals
- ([], [5, 7], [[5, 7]]),
- # Insert at beginning
- ([[3, 5], [6, 9]], [1, 2], [[1, 2], [3, 5], [6, 9]]),
- # Insert at end
- ([[1, 3], [6, 9]], [10, 12], [[1, 3], [6, 9], [10, 12]]),
- # No overlap
- ([[1, 2], [4, 5]], [3, 3], [[1, 2], [3, 3], [4, 5]]),
- # Complete overlap
- ([[1, 5]], [2, 3], [[1, 5]]),
- # Merge all intervals
- ([[1, 2], [3, 4], [5, 6]], [0, 7], [[0, 7]]),
- # Adjacent intervals
- ([[1, 3], [6, 9]], [4, 5], [[1, 3], [4, 5], [6, 9]]),
- # Touch boundaries
- ([[1, 3], [6, 9]], [3, 6], [[1, 9]]),
- ],
- )
- @logged_test
- def test_insert(
- self, intervals: list[list[int]], new_interval: list[int], expected: list[list[int]]
- ):
- result = self.solution.insert(intervals, new_interval)
- assert result == expected
diff --git a/leetcode/invert_binary_tree/helpers.py b/leetcode/invert_binary_tree/helpers.py
new file mode 100644
index 0000000..5af07e8
--- /dev/null
+++ b/leetcode/invert_binary_tree/helpers.py
@@ -0,0 +1,13 @@
+from leetcode_py import TreeNode
+
+
+def run_invert_tree(solution_class: type, root_list: list[int | None]):
+ root = TreeNode[int].from_list(root_list)
+ implementation = solution_class()
+ return implementation.invert_tree(root)
+
+
+def assert_invert_tree(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool:
+ expected = TreeNode[int].from_list(expected_list)
+ assert result == expected
+ return True
diff --git a/leetcode/invert_binary_tree/playground.ipynb b/leetcode/invert_binary_tree/playground.ipynb
index c4a7c4c..ba35a6c 100644
--- a/leetcode/invert_binary_tree/playground.ipynb
+++ b/leetcode/invert_binary_tree/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 5,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_invert_tree, run_invert_tree\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -14,36 +15,149 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 6,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
"# Example test case\n",
"root_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\n",
- "root = TreeNode[int].from_list(root_list)\n",
- "expected = TreeNode[int].from_list([4, 7, 2, 9, 6, 3, 1])"
+ "expected_list: list[int | None] = [4, 7, 2, 9, 6, 3, 1]"
]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "execute",
+ "execution_count": 7,
+ "id": "run",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ "TreeNode([4, 7, 2, 9, 6, 3, 1])"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "result = Solution().invert_tree(root)\n",
+ "result = run_invert_tree(Solution, root_list)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "test",
+ "execution_count": 4,
+ "id": "assert",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "assert result == expected"
+ "assert_invert_tree(result, expected_list)"
]
}
],
@@ -61,7 +175,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python3",
+ "nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
diff --git a/leetcode/invert_binary_tree/test_solution.py b/leetcode/invert_binary_tree/test_solution.py
new file mode 100644
index 0000000..9e8cf8e
--- /dev/null
+++ b/leetcode/invert_binary_tree/test_solution.py
@@ -0,0 +1,32 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_invert_tree, run_invert_tree
+from .solution import Solution, SolutionBFS, SolutionDFS
+
+
+class TestInvertBinaryTree:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS])
+ @pytest.mark.parametrize(
+ "root_list, expected_list",
+ [
+ ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]),
+ ([2, 1, 3], [2, 3, 1]),
+ ([], []),
+ ([1], [1]),
+ ([1, 2], [1, None, 2]),
+ ([1, None, 2], [1, 2]),
+ ([1, 2, 3, 4, 5], [1, 3, 2, None, None, 5, 4]),
+ ([1, 2, 3, None, None, 4, 5], [1, 3, 2, 5, 4]),
+ ],
+ )
+ def test_invert_tree(
+ self, root_list: list[int | None], expected_list: list[int | None], solution_class: type
+ ):
+ result = run_invert_tree(solution_class, root_list)
+ assert_invert_tree(result, expected_list)
diff --git a/leetcode/invert_binary_tree/tests.py b/leetcode/invert_binary_tree/tests.py
deleted file mode 100644
index dff6608..0000000
--- a/leetcode/invert_binary_tree/tests.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import pytest
-
-from leetcode_py import TreeNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution, SolutionBFS, SolutionDFS
-
-
-class TestInvertBinaryTree:
- @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS])
- @pytest.mark.parametrize(
- "root_list, expected_list",
- [([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], [])],
- )
- @logged_test
- def test_invert_tree(
- self,
- root_list: list[int | None],
- expected_list: list[int | None],
- solution_class: type[Solution | SolutionDFS | SolutionBFS],
- ):
- solution = solution_class()
- root = TreeNode[int].from_list(root_list)
- expected = TreeNode[int].from_list(expected_list)
- result = solution.invert_tree(root)
- assert result == expected
diff --git a/leetcode/k_closest_points_to_origin/helpers.py b/leetcode/k_closest_points_to_origin/helpers.py
new file mode 100644
index 0000000..9935f6b
--- /dev/null
+++ b/leetcode/k_closest_points_to_origin/helpers.py
@@ -0,0 +1,11 @@
+def run_k_closest(solution_class: type, points: list[list[int]], k: int):
+ implementation = solution_class()
+ return implementation.k_closest(points, k)
+
+
+def assert_k_closest(result: list[list[int]], expected: list[list[int]]) -> bool:
+ # Sort both result and expected for comparison since order doesn't matter
+ result_sorted = sorted(result)
+ expected_sorted = sorted(expected)
+ assert result_sorted == expected_sorted
+ return True
diff --git a/leetcode/k_closest_points_to_origin/playground.ipynb b/leetcode/k_closest_points_to_origin/playground.ipynb
index 90da6e9..fce66ec 100644
--- a/leetcode/k_closest_points_to_origin/playground.ipynb
+++ b/leetcode/k_closest_points_to_origin/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_k_closest, run_k_closest\n",
"from solution import Solution"
]
},
@@ -26,22 +27,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().k_closest(points, k)\n",
+ "result = run_k_closest(Solution, points, k)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert sorted(result) == sorted(expected)"
+ "assert_k_closest(result, expected)"
]
}
],
@@ -60,7 +61,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/k_closest_points_to_origin/test_solution.py b/leetcode/k_closest_points_to_origin/test_solution.py
new file mode 100644
index 0000000..3ef3ad3
--- /dev/null
+++ b/leetcode/k_closest_points_to_origin/test_solution.py
@@ -0,0 +1,27 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_k_closest, run_k_closest
+from .solution import Solution
+
+
+class TestKClosestPointsToOrigin:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "points, k, expected",
+ [
+ ([[1, 3], [-2, 2]], 1, [[-2, 2]]),
+ ([[3, 3], [5, -1], [-2, 4]], 2, [[3, 3], [-2, 4]]),
+ ([[0, 1], [1, 0]], 2, [[0, 1], [1, 0]]),
+ ([[1, 1], [1, 1], [1, 1]], 2, [[1, 1], [1, 1]]),
+ ([[0, 0]], 1, [[0, 0]]),
+ ([[1, 0], [2, 0], [3, 0]], 2, [[1, 0], [2, 0]]),
+ ],
+ )
+ def test_k_closest(self, points: list[list[int]], k: int, expected: list[list[int]]):
+ result = run_k_closest(Solution, points, k)
+ assert_k_closest(result, expected)
diff --git a/leetcode/k_closest_points_to_origin/tests.py b/leetcode/k_closest_points_to_origin/tests.py
deleted file mode 100644
index 296af1b..0000000
--- a/leetcode/k_closest_points_to_origin/tests.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestKClosestPointsToOrigin:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "points, k, expected",
- [
- # Basic examples
- ([[1, 3], [-2, 2]], 1, [[-2, 2]]),
- ([[3, 3], [5, -1], [-2, 4]], 2, [[3, 3], [-2, 4]]),
- ([[0, 1], [1, 0]], 2, [[0, 1], [1, 0]]),
- ([[1, 1], [1, 1], [1, 1]], 2, [[1, 1], [1, 1]]),
- # Edge cases
- ([[0, 0]], 1, [[0, 0]]), # Origin point
- ([[1, 0], [0, 1], [-1, 0], [0, -1]], 1, [[1, 0]]), # Unit circle points
- ([[2, 2], [1, 1], [3, 3]], 3, [[1, 1], [2, 2], [3, 3]]), # All points
- # Negative coordinates
- ([[-1, -1], [-2, -2], [1, 1]], 2, [[-1, -1], [1, 1]]),
- # Large coordinates
- ([[100, 100], [1, 1], [50, 50]], 1, [[1, 1]]),
- # Same distances
- ([[1, 0], [0, 1], [-1, 0], [0, -1]], 2, [[1, 0], [0, 1]]),
- ],
- )
- @logged_test
- def test_k_closest(self, points: list[list[int]], k: int, expected: list[list[int]]):
- result = self.solution.k_closest(points, k)
- # Sort both result and expected for comparison since order doesn't matter
- result_sorted = sorted(result)
- expected_sorted = sorted(expected)
- assert result_sorted == expected_sorted
diff --git a/leetcode/kth_smallest_element_in_a_bst/helpers.py b/leetcode/kth_smallest_element_in_a_bst/helpers.py
new file mode 100644
index 0000000..9ba9875
--- /dev/null
+++ b/leetcode/kth_smallest_element_in_a_bst/helpers.py
@@ -0,0 +1,12 @@
+from leetcode_py import TreeNode
+
+
+def run_kth_smallest(solution_class: type, root_list: list[int | None], k: int):
+ root = TreeNode[int].from_list(root_list)
+ implementation = solution_class()
+ return implementation.kth_smallest(root, k)
+
+
+def assert_kth_smallest(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/kth_smallest_element_in_a_bst/playground.ipynb b/leetcode/kth_smallest_element_in_a_bst/playground.ipynb
index da34058..b65acaf 100644
--- a/leetcode/kth_smallest_element_in_a_bst/playground.ipynb
+++ b/leetcode/kth_smallest_element_in_a_bst/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_kth_smallest, run_kth_smallest\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -14,7 +15,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -27,112 +28,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "1"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "root = TreeNode.from_list(root_list)\n",
- "result = Solution().kth_smallest(root, k)\n",
+ "result = run_kth_smallest(Solution, root_list, k)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "6dc42838",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "TreeNode([3, 1, 4, None, 2])"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "root"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_kth_smallest(result, expected)"
]
}
],
@@ -150,8 +62,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/kth_smallest_element_in_a_bst/solution.py b/leetcode/kth_smallest_element_in_a_bst/solution.py
index 57bd5db..94c91d1 100644
--- a/leetcode/kth_smallest_element_in_a_bst/solution.py
+++ b/leetcode/kth_smallest_element_in_a_bst/solution.py
@@ -2,11 +2,12 @@
class Solution:
+
# Inorder Recursive
# Time: O(k)
# Space: O(h)
- def kth_smallest(self, root: TreeNode | None, k: int) -> int:
- def inorder(node: TreeNode | None):
+ def kth_smallest(self, root: TreeNode[int] | None, k: int) -> int:
+ def inorder(node: TreeNode[int] | None):
if not node:
return
yield from inorder(node.left)
diff --git a/leetcode/kth_smallest_element_in_a_bst/test_solution.py b/leetcode/kth_smallest_element_in_a_bst/test_solution.py
new file mode 100644
index 0000000..9f1e4d8
--- /dev/null
+++ b/leetcode/kth_smallest_element_in_a_bst/test_solution.py
@@ -0,0 +1,27 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_kth_smallest, run_kth_smallest
+from .solution import Solution
+
+
+class TestKthSmallestElementInABst:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "root_list, k, expected",
+ [
+ ([3, 1, 4, None, 2], 1, 1),
+ ([5, 3, 6, 2, 4, None, None, 1], 3, 3),
+ ([1], 1, 1),
+ ([2, 1, 3], 2, 2),
+ ([4, 2, 6, 1, 3, 5, 7], 4, 4),
+ ([1, None, 2], 2, 2),
+ ],
+ )
+ def test_kth_smallest(self, root_list: list[int | None], k: int, expected: int):
+ result = run_kth_smallest(Solution, root_list, k)
+ assert_kth_smallest(result, expected)
diff --git a/leetcode/kth_smallest_element_in_a_bst/tests.py b/leetcode/kth_smallest_element_in_a_bst/tests.py
deleted file mode 100644
index 1e52c52..0000000
--- a/leetcode/kth_smallest_element_in_a_bst/tests.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import pytest
-
-from leetcode_py import TreeNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestKthSmallestElementInABst:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "root_list, k, expected",
- [([3, 1, 4, None, 2], 1, 1), ([5, 3, 6, 2, 4, None, None, 1], 3, 3), ([1], 1, 1)],
- )
- @logged_test
- def test_kth_smallest(self, root_list: list[int | None], k: int, expected: int):
- root = TreeNode.from_list(root_list)
- result = self.solution.kth_smallest(root, k)
- assert result == expected
diff --git a/leetcode/largest_rectangle_in_histogram/helpers.py b/leetcode/largest_rectangle_in_histogram/helpers.py
new file mode 100644
index 0000000..ea578c6
--- /dev/null
+++ b/leetcode/largest_rectangle_in_histogram/helpers.py
@@ -0,0 +1,8 @@
+def run_largest_rectangle_area(solution_class: type, heights: list[int]):
+ implementation = solution_class()
+ return implementation.largest_rectangle_area(heights)
+
+
+def assert_largest_rectangle_area(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/largest_rectangle_in_histogram/playground.ipynb b/leetcode/largest_rectangle_in_histogram/playground.ipynb
index 74de151..8470983 100644
--- a/leetcode/largest_rectangle_in_histogram/playground.ipynb
+++ b/leetcode/largest_rectangle_in_histogram/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_largest_rectangle_area, run_largest_rectangle_area\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -24,34 +25,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "10"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().largest_rectangle_area(heights)\n",
+ "result = run_largest_rectangle_area(Solution, heights)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_largest_rectangle_area(result, expected)"
]
}
],
@@ -69,8 +59,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/largest_rectangle_in_histogram/solution.py b/leetcode/largest_rectangle_in_histogram/solution.py
index c500980..325e627 100644
--- a/leetcode/largest_rectangle_in_histogram/solution.py
+++ b/leetcode/largest_rectangle_in_histogram/solution.py
@@ -1,10 +1,11 @@
class Solution:
+
# Time: O(n)
# Space: O(n)
+ # Monotonic stack approach
+ # Stack stores indices of bars in increasing height order
+ # When we find a shorter bar, we calculate area using previous bars
def largest_rectangle_area(self, heights: list[int]) -> int:
- # Monotonic stack approach
- # Stack stores indices of bars in increasing height order
- # When we find a shorter bar, we calculate area using previous bars
stack: list[int] = [] # Stack of indices
max_area = 0
diff --git a/leetcode/largest_rectangle_in_histogram/tests.py b/leetcode/largest_rectangle_in_histogram/test_solution.py
similarity index 63%
rename from leetcode/largest_rectangle_in_histogram/tests.py
rename to leetcode/largest_rectangle_in_histogram/test_solution.py
index 715712f..2ac5ee6 100644
--- a/leetcode/largest_rectangle_in_histogram/tests.py
+++ b/leetcode/largest_rectangle_in_histogram/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_largest_rectangle_area, run_largest_rectangle_area
from .solution import Solution
@@ -9,38 +10,28 @@ class TestLargestRectangleInHistogram:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"heights, expected",
[
- # Basic examples
([2, 1, 5, 6, 2, 3], 10),
([2, 4], 4),
- # Edge cases
([1], 1),
([0], 0),
([1, 1], 2),
([0, 0, 0], 0),
- # Patterns
([1, 2, 3, 4, 5], 9),
([5, 4, 3, 2, 1], 9),
([3, 3, 3, 3], 12),
([2, 1, 2], 3),
([1, 3, 1], 3),
- # Complex cases
([6, 7, 5, 2, 4, 5, 9, 3], 16),
([4, 2, 0, 3, 2, 5], 6),
([1, 2, 2, 1], 4),
([0, 9], 9),
([9, 0], 9),
- # Large rectangles
- ([2, 1, 5, 6, 2, 3, 1, 5, 6, 2], 10),
- ([1, 8, 6, 2, 5, 4, 8, 3, 7], 16),
- ([50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 50),
- ([1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 1], 50),
- ([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50], 50),
],
)
- @logged_test
def test_largest_rectangle_area(self, heights: list[int], expected: int):
- result = self.solution.largest_rectangle_area(heights)
- assert result == expected
+ result = run_largest_rectangle_area(Solution, heights)
+ assert_largest_rectangle_area(result, expected)
diff --git a/leetcode/linked_list_cycle/helpers.py b/leetcode/linked_list_cycle/helpers.py
new file mode 100644
index 0000000..69a770a
--- /dev/null
+++ b/leetcode/linked_list_cycle/helpers.py
@@ -0,0 +1,32 @@
+from leetcode_py import ListNode
+
+
+def create_cycle_list(values: list[int], pos: int) -> ListNode[int] | None:
+ if not values:
+ return None
+
+ nodes = []
+ head = ListNode(values[0])
+ nodes.append(head)
+ current = head
+
+ for i in range(1, len(values)):
+ current.next = ListNode(values[i])
+ current = current.next
+ nodes.append(current)
+
+ if pos != -1 and pos < len(nodes):
+ current.next = nodes[pos]
+
+ return head
+
+
+def run_has_cycle(solution_class: type, values: list[int], pos: int):
+ head = create_cycle_list(values, pos)
+ implementation = solution_class()
+ return implementation.has_cycle(head)
+
+
+def assert_has_cycle(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/linked_list_cycle/playground.ipynb b/leetcode/linked_list_cycle/playground.ipynb
index 7f74fb3..f4adc95 100644
--- a/leetcode/linked_list_cycle/playground.ipynb
+++ b/leetcode/linked_list_cycle/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_has_cycle, create_cycle_list, run_has_cycle\n",
"from solution import Solution"
]
},
@@ -17,22 +18,101 @@
"metadata": {},
"outputs": [],
"source": [
- "import os\n",
- "import sys\n",
- "\n",
- "sys.path.append(os.path.join(os.getcwd(), \"..\"))\n",
- "from linked_list_cycle.tests import TestLinkedListCycle\n",
- "\n",
"# Example test case\n",
"values = [3, 2, 0, -4]\n",
"pos = 1\n",
"expected = True"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "66f97343",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ "ListNode([3, 2, 0, -4])"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "head = create_cycle_list(values, pos)\n",
+ "head"
+ ]
+ },
{
"cell_type": "code",
"execution_count": 3,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [
{
@@ -47,19 +127,29 @@
}
],
"source": [
- "head = TestLinkedListCycle().create_cycle_list(values, pos)\n",
- "result = Solution().has_cycle(head)\n",
+ "result = run_has_cycle(Solution, values, pos)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "test",
+ "id": "assert",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "assert result == expected"
+ "assert_has_cycle(result, expected)"
]
}
],
diff --git a/leetcode/linked_list_cycle/solution.py b/leetcode/linked_list_cycle/solution.py
index 6c3f838..4bc0686 100644
--- a/leetcode/linked_list_cycle/solution.py
+++ b/leetcode/linked_list_cycle/solution.py
@@ -2,6 +2,7 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
def has_cycle(self, head: ListNode[int] | None) -> bool:
diff --git a/leetcode/linked_list_cycle/test_solution.py b/leetcode/linked_list_cycle/test_solution.py
new file mode 100644
index 0000000..2660c0e
--- /dev/null
+++ b/leetcode/linked_list_cycle/test_solution.py
@@ -0,0 +1,31 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_has_cycle, run_has_cycle
+from .solution import Solution
+
+
+class TestLinkedListCycle:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "values, pos, expected",
+ [
+ ([3, 2, 0, -4], 1, True),
+ ([1, 2], 0, True),
+ ([1], -1, False),
+ ([], -1, False),
+ ([1, 2, 3], -1, False),
+ ([1, 2, 3, 4, 5], 0, True),
+ ([1, 2, 3, 4, 5], 2, True),
+ ([1, 2, 3, 4, 5], 4, True),
+ ([1], 0, True),
+ ([1, 2], 1, True),
+ ],
+ )
+ def test_has_cycle(self, values: list[int], pos: int, expected: bool):
+ result = run_has_cycle(Solution, values, pos)
+ assert_has_cycle(result, expected)
diff --git a/leetcode/linked_list_cycle/tests.py b/leetcode/linked_list_cycle/tests.py
deleted file mode 100644
index 8af1b82..0000000
--- a/leetcode/linked_list_cycle/tests.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import pytest
-
-from leetcode_py import ListNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestLinkedListCycle:
- def setup_method(self):
- self.solution = Solution()
-
- def create_cycle_list(self, values: list[int], pos: int):
- if not values:
- return None
-
- nodes = []
- head = ListNode(values[0])
- nodes.append(head)
- current = head
-
- for i in range(1, len(values)):
- current.next = ListNode(values[i])
- current = current.next
- nodes.append(current)
-
- if pos != -1 and pos < len(nodes):
- current.next = nodes[pos]
-
- return head
-
- @pytest.mark.parametrize(
- "values, pos, expected",
- [
- ([3, 2, 0, -4], 1, True),
- ([1, 2], 0, True),
- ([1], -1, False),
- ([], -1, False),
- ([1, 2, 3], -1, False),
- ([1, 2, 3, 4, 5], 0, True),
- ([1, 2, 3, 4, 5], 2, True),
- ([1, 2, 3, 4, 5], 4, True),
- ([1], 0, True),
- ([1, 2], 1, True),
- ([1, 2, 3, 4, 5, 6, 7, 8], 3, True),
- ([1, 2, 3, 4, 5, 6, 7, 8], -1, False),
- ([1, 2], -1, False),
- ([5, 10], 0, True),
- ([5, 10], 1, True),
- ([0], -1, False),
- ([-1, -2, -3], 1, True),
- ([100, 200, 300], 0, True),
- ],
- )
- @logged_test
- def test_has_cycle(self, values: list[int], pos: int, expected: bool):
- head = self.create_cycle_list(values, pos)
- result = self.solution.has_cycle(head)
- assert result == expected
diff --git a/leetcode/longest_palindrome/helpers.py b/leetcode/longest_palindrome/helpers.py
new file mode 100644
index 0000000..4137bb4
--- /dev/null
+++ b/leetcode/longest_palindrome/helpers.py
@@ -0,0 +1,8 @@
+def run_longest_palindrome(solution_class: type, s: str):
+ implementation = solution_class()
+ return implementation.longest_palindrome(s)
+
+
+def assert_longest_palindrome(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/longest_palindrome/playground.ipynb b/leetcode/longest_palindrome/playground.ipynb
index 5f53c98..31ff59e 100644
--- a/leetcode/longest_palindrome/playground.ipynb
+++ b/leetcode/longest_palindrome/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_longest_palindrome, run_longest_palindrome\n",
"from solution import Solution"
]
},
@@ -25,22 +26,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().longest_palindrome(s)\n",
+ "result = run_longest_palindrome(Solution, s)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_longest_palindrome(result, expected)"
]
}
],
@@ -59,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/longest_palindrome/solution.py b/leetcode/longest_palindrome/solution.py
index baa568f..d5bbba7 100644
--- a/leetcode/longest_palindrome/solution.py
+++ b/leetcode/longest_palindrome/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
def longest_palindrome(self, s: str) -> int:
diff --git a/leetcode/longest_palindrome/tests.py b/leetcode/longest_palindrome/test_solution.py
similarity index 58%
rename from leetcode/longest_palindrome/tests.py
rename to leetcode/longest_palindrome/test_solution.py
index c4a79d1..5c8e279 100644
--- a/leetcode/longest_palindrome/tests.py
+++ b/leetcode/longest_palindrome/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_longest_palindrome, run_longest_palindrome
from .solution import Solution
@@ -9,6 +10,7 @@ class TestLongestPalindrome:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"s, expected",
[
@@ -16,16 +18,17 @@ def setup_method(self):
("a", 1),
("Aa", 1),
("aabbcc", 6),
- ("abc", 1),
+ ("", 0),
+ ("aA", 1),
("abcdef", 1),
- ("aab", 3),
+ ("aabbccdd", 8),
("aaaa", 4),
- ("AaBbCc", 1),
- ("civilwartestingwhetherthatnaptionoranynartionsoconceivedandsodedicatedcanlongendure", 73),
- ("bananas", 5),
+ ("abcdefg", 1),
+ ("AAaa", 4),
+ ("racecar", 7),
+ ("abcABC", 1),
],
)
- @logged_test
def test_longest_palindrome(self, s: str, expected: int):
- result = self.solution.longest_palindrome(s)
- assert result == expected
+ result = run_longest_palindrome(Solution, s)
+ assert_longest_palindrome(result, expected)
diff --git a/leetcode/longest_palindromic_substring/helpers.py b/leetcode/longest_palindromic_substring/helpers.py
new file mode 100644
index 0000000..f3c979c
--- /dev/null
+++ b/leetcode/longest_palindromic_substring/helpers.py
@@ -0,0 +1,8 @@
+def run_longest_palindrome(solution_class: type, s: str):
+ implementation = solution_class()
+ return implementation.longest_palindrome(s)
+
+
+def assert_longest_palindrome(result: str, expected: set[str]) -> bool:
+ assert result in expected
+ return True
diff --git a/leetcode/longest_palindromic_substring/playground.ipynb b/leetcode/longest_palindromic_substring/playground.ipynb
index 8100878..be01046 100644
--- a/leetcode/longest_palindromic_substring/playground.ipynb
+++ b/leetcode/longest_palindromic_substring/playground.ipynb
@@ -2,56 +2,46 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_longest_palindrome, run_longest_palindrome\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
- "# # Example test case\n",
+ "# Example test case\n",
"s = \"babad\"\n",
"expected = {\"bab\", \"aba\"}"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "'bab'"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().longest_palindrome(s)\n",
+ "result = run_longest_palindrome(Solution, s)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result in expected"
+ "assert_longest_palindrome(result, expected)"
]
}
],
@@ -69,8 +59,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/longest_palindromic_substring/solution.py b/leetcode/longest_palindromic_substring/solution.py
index b673d49..defb806 100644
--- a/leetcode/longest_palindromic_substring/solution.py
+++ b/leetcode/longest_palindromic_substring/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n^2)
# Space: O(1)
def longest_palindrome(self, s: str) -> str:
diff --git a/leetcode/longest_palindromic_substring/test_solution.py b/leetcode/longest_palindromic_substring/test_solution.py
new file mode 100644
index 0000000..832e8fa
--- /dev/null
+++ b/leetcode/longest_palindromic_substring/test_solution.py
@@ -0,0 +1,33 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_longest_palindrome, run_longest_palindrome
+from .solution import Solution
+
+
+class TestLongestPalindromicSubstring:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "s, expected",
+ [
+ ("babad", {"bab", "aba"}),
+ ("cbbd", {"bb"}),
+ ("a", {"a"}),
+ ("ac", {"a", "c"}),
+ ("racecar", {"racecar"}),
+ ("aabbaa", {"aabbaa"}),
+ ("abacabad", {"abacaba"}),
+ ("noon", {"noon"}),
+ ("abccba", {"abccba"}),
+ ("aa", {"aa"}),
+ ("aba", {"aba"}),
+ ("abcba", {"abcba"}),
+ ],
+ )
+ def test_longest_palindrome(self, s: str, expected: set[str]):
+ result = run_longest_palindrome(Solution, s)
+ assert_longest_palindrome(result, expected)
diff --git a/leetcode/longest_palindromic_substring/tests.py b/leetcode/longest_palindromic_substring/tests.py
deleted file mode 100644
index 7992302..0000000
--- a/leetcode/longest_palindromic_substring/tests.py
+++ /dev/null
@@ -1,74 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution, SolutionManacher
-
-
-class TestLongestPalindromicSubstring:
- def setup_method(self):
- self.solution = Solution()
- self.solution_manacher = SolutionManacher()
-
- @pytest.mark.parametrize(
- "solution_class",
- [Solution, SolutionManacher],
- )
- @pytest.mark.parametrize(
- "s, expected",
- [
- ("babad", {"bab", "aba"}),
- ("cbbd", {"bb"}),
- ("a", {"a"}),
- ("ac", {"a", "c"}),
- ("racecar", {"racecar"}),
- ("abcdef", {"a", "b", "c", "d", "e", "f"}),
- ("aabbaa", {"aabbaa"}),
- ("abacabad", {"abacaba"}),
- ("aaaaaaaa", {"aaaaaaaa"}),
- ("noon", {"noon"}),
- ("abccba", {"abccba"}),
- ("", {""}),
- ("aa", {"aa"}),
- ("aba", {"aba"}),
- ("abcba", {"abcba"}),
- ("forgeeksskeegfor", {"geeksskeeg"}),
- ("bananas", {"anana"}),
- (
- "abcdefghijklmnopqrstuvwxyz",
- {
- "a",
- "b",
- "c",
- "d",
- "e",
- "f",
- "g",
- "h",
- "i",
- "j",
- "k",
- "l",
- "m",
- "n",
- "o",
- "p",
- "q",
- "r",
- "s",
- "t",
- "u",
- "v",
- "w",
- "x",
- "y",
- "z",
- },
- ),
- ],
- )
- @logged_test
- def test_longest_palindrome(self, solution_class, s: str, expected: set[str]):
- solution = solution_class()
- result = solution.longest_palindrome(s)
- assert result in expected
diff --git a/leetcode/longest_substring_without_repeating_characters/helpers.py b/leetcode/longest_substring_without_repeating_characters/helpers.py
new file mode 100644
index 0000000..86678c6
--- /dev/null
+++ b/leetcode/longest_substring_without_repeating_characters/helpers.py
@@ -0,0 +1,8 @@
+def run_length_of_longest_substring(solution_class: type, s: str):
+ implementation = solution_class()
+ return implementation.length_of_longest_substring(s)
+
+
+def assert_length_of_longest_substring(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/longest_substring_without_repeating_characters/playground.ipynb b/leetcode/longest_substring_without_repeating_characters/playground.ipynb
index c437eba..0c23372 100644
--- a/leetcode/longest_substring_without_repeating_characters/playground.ipynb
+++ b/leetcode/longest_substring_without_repeating_characters/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_length_of_longest_substring, run_length_of_longest_substring\n",
"from solution import Solution"
]
},
@@ -24,34 +25,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "3"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().length_of_longest_substring(s)\n",
+ "result = run_length_of_longest_substring(Solution, s)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_length_of_longest_substring(result, expected)"
]
}
],
@@ -69,8 +59,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/longest_substring_without_repeating_characters/solution.py b/leetcode/longest_substring_without_repeating_characters/solution.py
index 034f4a8..98b8468 100644
--- a/leetcode/longest_substring_without_repeating_characters/solution.py
+++ b/leetcode/longest_substring_without_repeating_characters/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n)
# Space: O(min(m, n)) where m is charset size
def length_of_longest_substring(self, s: str) -> int:
diff --git a/leetcode/longest_substring_without_repeating_characters/test_solution.py b/leetcode/longest_substring_without_repeating_characters/test_solution.py
new file mode 100644
index 0000000..3193ea9
--- /dev/null
+++ b/leetcode/longest_substring_without_repeating_characters/test_solution.py
@@ -0,0 +1,34 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_length_of_longest_substring, run_length_of_longest_substring
+from .solution import Solution
+
+
+class TestLongestSubstringWithoutRepeatingCharacters:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "s, expected",
+ [
+ ("abcabcbb", 3),
+ ("bbbbb", 1),
+ ("pwwkew", 3),
+ ("", 0),
+ ("a", 1),
+ ("au", 2),
+ ("dvdf", 3),
+ ("abcdef", 6),
+ ("aab", 2),
+ ("tmmzuxt", 5),
+ (" ", 1),
+ (" ", 1),
+ ("abba", 2),
+ ],
+ )
+ def test_length_of_longest_substring(self, s: str, expected: int):
+ result = run_length_of_longest_substring(Solution, s)
+ assert_length_of_longest_substring(result, expected)
diff --git a/leetcode/longest_substring_without_repeating_characters/tests.py b/leetcode/longest_substring_without_repeating_characters/tests.py
deleted file mode 100644
index 272cdd4..0000000
--- a/leetcode/longest_substring_without_repeating_characters/tests.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestLongestSubstringWithoutRepeatingCharacters:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "s, expected",
- [
- ("abcabcbb", 3), # "abc"
- ("bbbbb", 1), # "b"
- ("pwwkew", 3), # "wke"
- ("", 0), # empty
- ("a", 1), # single char
- ("au", 2), # "au"
- ("dvdf", 3), # "vdf"
- ("abcdef", 6), # no repeats
- ("aab", 2), # "ab"
- ("cdd", 2), # "cd"
- ("abba", 2), # "ab" or "ba"
- ("tmmzuxt", 5), # "mzuxt"
- (" ", 1), # space char
- ("!@#$%", 5), # symbols
- ("abcabcabcabc", 3), # repeating pattern
- ],
- )
- @logged_test
- def test_length_of_longest_substring(self, s: str, expected: int):
- result = self.solution.length_of_longest_substring(s)
- assert result == expected
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/helpers.py b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/helpers.py
new file mode 100644
index 0000000..101bbb1
--- /dev/null
+++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/helpers.py
@@ -0,0 +1,19 @@
+from leetcode_py import TreeNode
+
+
+def run_lowest_common_ancestor(
+ solution_class: type, root_list: list[int | None], p_val: int, q_val: int
+):
+ root = TreeNode[int].from_list(root_list)
+ assert root is not None
+ p = root.find_node(p_val)
+ q = root.find_node(q_val)
+ assert p is not None and q is not None
+ implementation = solution_class()
+ return implementation.lowest_common_ancestor(root, p, q)
+
+
+def assert_lowest_common_ancestor(result: TreeNode[int] | None, expected_val: int) -> bool:
+ assert result is not None
+ assert result.val == expected_val
+ return True
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb
index 89e233e..6f5c85e 100644
--- a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb
+++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -14,7 +15,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -28,176 +29,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "6"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "root = TreeNode[int].from_list(root_list)\n",
- "assert root is not None\n",
- "p = root.find_node(p_val)\n",
- "q = root.find_node(q_val)\n",
- "assert p is not None and q is not None\n",
- "result = Solution().lowest_common_ancestor(root, p, q)\n",
+ "result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\n",
"result.val if result else None"
]
},
{
"cell_type": "code",
- "execution_count": 5,
- "id": "d84494ee",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "TreeNode([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5])"
- ]
- },
- "execution_count": 5,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "root"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result and result.val == expected_val"
+ "assert_lowest_common_ancestor(result, expected_val)"
]
}
],
@@ -215,8 +63,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py
index f6f5dcd..1d88ec8 100644
--- a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py
+++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py
@@ -2,6 +2,7 @@
class Solution:
+
# Time: O(log n) average, O(n) worst case
# Space: O(1) iterative, O(log n) recursive
def lowest_common_ancestor(
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/tests.py b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py
similarity index 66%
rename from leetcode/lowest_common_ancestor_of_a_binary_search_tree/tests.py
rename to leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py
index b28db6c..d92a905 100644
--- a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/tests.py
+++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py
@@ -1,8 +1,8 @@
import pytest
-from leetcode_py import TreeNode
from leetcode_py.test_utils import logged_test
+from .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor
from .solution import Solution
@@ -10,6 +10,7 @@ class TestLowestCommonAncestorOfABinarySearchTree:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"root_list, p_val, q_val, expected_val",
[
@@ -21,15 +22,8 @@ def setup_method(self):
([6, 2, 8, 0, 4, 7, 9], 7, 9, 8),
],
)
- @logged_test
def test_lowest_common_ancestor(
self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int
):
- root = TreeNode[int].from_list(root_list)
- assert root is not None
- p = root.find_node(p_val)
- q = root.find_node(q_val)
- assert p is not None and q is not None
- result = self.solution.lowest_common_ancestor(root, p, q)
- assert result is not None
- assert result.val == expected_val
+ result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)
+ assert_lowest_common_ancestor(result, expected_val)
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/helpers.py b/leetcode/lowest_common_ancestor_of_a_binary_tree/helpers.py
new file mode 100644
index 0000000..d3c072c
--- /dev/null
+++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/helpers.py
@@ -0,0 +1,18 @@
+from leetcode_py import TreeNode
+
+
+def run_lowest_common_ancestor(
+ solution_class: type, root_list: list[int | None], p_val: int, q_val: int
+):
+ root = TreeNode[int].from_list(root_list)
+ assert root is not None
+ p = root.find_node(p_val)
+ q = root.find_node(q_val)
+ assert p is not None and q is not None
+ implementation = solution_class()
+ return implementation.lowest_common_ancestor(root, p, q)
+
+
+def assert_lowest_common_ancestor(result: TreeNode[int], expected_val: int) -> bool:
+ assert result.val == expected_val
+ return True
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb b/leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb
index 6594efb..0251030 100644
--- a/leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb
+++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -14,187 +15,37 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
"# Example test case\n",
"root_list = [3, 5, 1, 6, 2, 0, 8, None, None, 7, 4]\n",
- "root = TreeNode.from_list(root_list)\n",
- "assert root is not None\n",
- "p = root.find_node(5)\n",
- "q = root.find_node(1)\n",
+ "p_val = 5\n",
+ "q_val = 1\n",
"expected_val = 3"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "6ad16444",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "TreeNode([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4])"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "root"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "id": "execute",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "3"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().lowest_common_ancestor(root, p, q)\n",
+ "result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\n",
"result.val"
]
},
{
"cell_type": "code",
- "execution_count": 5,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result.val == expected_val"
+ "assert_lowest_common_ancestor(result, expected_val)"
]
}
],
@@ -213,7 +64,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py b/leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py
index 33f138a..0075ae4 100644
--- a/leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py
+++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py
@@ -4,12 +4,16 @@
class Solution:
# Time: O(n)
# Space: O(h)
- def lowest_common_ancestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
+ def lowest_common_ancestor(
+ self, root: TreeNode[int], p: TreeNode[int], q: TreeNode[int]
+ ) -> TreeNode[int]:
result = self._lca(root, p, q)
assert result is not None
return result
- def _lca(self, root: TreeNode | None, p: TreeNode, q: TreeNode) -> TreeNode | None:
+ def _lca(
+ self, root: TreeNode[int] | None, p: TreeNode[int], q: TreeNode[int]
+ ) -> TreeNode[int] | None:
if not root or root == p or root == q:
return root
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/tests.py b/leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py
similarity index 52%
rename from leetcode/lowest_common_ancestor_of_a_binary_tree/tests.py
rename to leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py
index d629f35..0f1d86a 100644
--- a/leetcode/lowest_common_ancestor_of_a_binary_tree/tests.py
+++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py
@@ -1,8 +1,8 @@
import pytest
-from leetcode_py import TreeNode
from leetcode_py.test_utils import logged_test
+from .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor
from .solution import Solution
@@ -10,31 +10,20 @@ class TestLowestCommonAncestorOfABinaryTree:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"root_list, p_val, q_val, expected_val",
[
([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 5, 1, 3),
([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 5, 4, 5),
([1, 2], 1, 2, 1),
+ ([2, 1], 2, 1, 2),
([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 6, 7, 5),
- ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 7, 4, 2),
([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 0, 8, 1),
- ([1], 1, 1, 1),
- ([2, 1, 3], 1, 3, 2),
- ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 8, 6),
- ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 3, 5, 4),
- ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 0, 3, 2),
],
)
- @logged_test
def test_lowest_common_ancestor(
self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int
):
- root = TreeNode.from_list(root_list)
- assert root is not None
- p = root.find_node(p_val)
- q = root.find_node(q_val)
- assert p is not None and q is not None
- result = self.solution.lowest_common_ancestor(root, p, q)
- assert result is not None
- assert result.val == expected_val
+ result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)
+ assert_lowest_common_ancestor(result, expected_val)
diff --git a/leetcode/lru_cache/helpers.py b/leetcode/lru_cache/helpers.py
new file mode 100644
index 0000000..3751626
--- /dev/null
+++ b/leetcode/lru_cache/helpers.py
@@ -0,0 +1,18 @@
+def run_lru_cache(solution_class: type, operations: list[str], inputs: list[list[int]]):
+ cache = None
+ results: list[int | None] = []
+ for i, op in enumerate(operations):
+ if op == "LRUCache":
+ cache = solution_class(inputs[i][0])
+ results.append(None)
+ elif op == "get" and cache is not None:
+ results.append(cache.get(inputs[i][0]))
+ elif op == "put" and cache is not None:
+ cache.put(inputs[i][0], inputs[i][1])
+ results.append(None)
+ return results, cache
+
+
+def assert_lru_cache(result: list[int | None], expected: list[int | None]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/lru_cache/playground.ipynb b/leetcode/lru_cache/playground.ipynb
index 3d277f1..687c6f1 100644
--- a/leetcode/lru_cache/playground.ipynb
+++ b/leetcode/lru_cache/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 1,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_lru_cache, run_lru_cache\n",
"from solution import LRUCache"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 2,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,33 +26,53 @@
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "execute",
+ "execution_count": 5,
+ "id": "run",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[None, None, None, 1, None, -1, None, -1, 3, 4]\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "OrderedDict([(3, 3), (4, 4)])"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "cache = None\n",
- "results: list[int | None] = []\n",
- "for i, op in enumerate(operations):\n",
- " if op == \"LRUCache\":\n",
- " cache = LRUCache(inputs[i][0])\n",
- " results.append(None)\n",
- " elif op == \"get\" and cache is not None:\n",
- " results.append(cache.get(inputs[i][0]))\n",
- " elif op == \"put\" and cache is not None:\n",
- " cache.put(inputs[i][0], inputs[i][1])\n",
- " results.append(None)\n",
- "results"
+ "result, cache = run_lru_cache(LRUCache, operations, inputs)\n",
+ "print(result)\n",
+ "cache.cache"
]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "test",
+ "execution_count": 4,
+ "id": "assert",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "assert results == expected"
+ "assert_lru_cache(result, expected)"
]
}
],
@@ -69,7 +90,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python3",
+ "nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
diff --git a/leetcode/lru_cache/tests.py b/leetcode/lru_cache/test_solution.py
similarity index 62%
rename from leetcode/lru_cache/tests.py
rename to leetcode/lru_cache/test_solution.py
index 8d3e6e8..5c709c9 100644
--- a/leetcode/lru_cache/tests.py
+++ b/leetcode/lru_cache/test_solution.py
@@ -2,11 +2,14 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_lru_cache, run_lru_cache
from .solution import LRUCache, LRUCacheWithDoublyList
class TestLRUCache:
- @pytest.mark.parametrize("lru_class", [LRUCache, LRUCacheWithDoublyList])
+
+ @logged_test
+ @pytest.mark.parametrize("solution_class", [LRUCache, LRUCacheWithDoublyList])
@pytest.mark.parametrize(
"operations, inputs, expected",
[
@@ -27,23 +30,12 @@ class TestLRUCache:
),
],
)
- @logged_test
def test_lru_cache(
self,
- lru_class: type[LRUCache | LRUCacheWithDoublyList],
operations: list[str],
inputs: list[list[int]],
expected: list[int | None],
+ solution_class: type,
):
- cache = None
- results: list[int | None] = []
- for i, op in enumerate(operations):
- if op == "LRUCache":
- cache = lru_class(inputs[i][0])
- results.append(None)
- elif op == "get" and cache is not None:
- results.append(cache.get(inputs[i][0]))
- elif op == "put" and cache is not None:
- cache.put(inputs[i][0], inputs[i][1])
- results.append(None)
- assert results == expected
+ result, _ = run_lru_cache(solution_class, operations, inputs)
+ assert_lru_cache(result, expected)
diff --git a/leetcode/majority_element/helpers.py b/leetcode/majority_element/helpers.py
new file mode 100644
index 0000000..c7e6d09
--- /dev/null
+++ b/leetcode/majority_element/helpers.py
@@ -0,0 +1,8 @@
+def run_majority_element(solution_class: type, nums: list[int]):
+ implementation = solution_class()
+ return implementation.majority_element(nums)
+
+
+def assert_majority_element(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/majority_element/playground.ipynb b/leetcode/majority_element/playground.ipynb
index de16ac0..969279e 100644
--- a/leetcode/majority_element/playground.ipynb
+++ b/leetcode/majority_element/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_majority_element, run_majority_element\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -24,34 +25,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "3"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().majority_element(nums)\n",
+ "result = run_majority_element(Solution, nums)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_majority_element(result, expected)"
]
}
],
@@ -69,8 +59,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/majority_element/solution.py b/leetcode/majority_element/solution.py
index 1499c32..cf2f1dc 100644
--- a/leetcode/majority_element/solution.py
+++ b/leetcode/majority_element/solution.py
@@ -1,8 +1,9 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
+ # Boyer-Moore Voting Algorithm
def majority_element(self, nums: list[int]) -> int:
- # Boyer-Moore Voting Algorithm
candidate = 0
count = 0
diff --git a/leetcode/majority_element/test_solution.py b/leetcode/majority_element/test_solution.py
new file mode 100644
index 0000000..d4c439f
--- /dev/null
+++ b/leetcode/majority_element/test_solution.py
@@ -0,0 +1,30 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_majority_element, run_majority_element
+from .solution import Solution
+
+
+class TestMajorityElement:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "nums, expected",
+ [
+ ([3, 2, 3], 3),
+ ([2, 2, 1, 1, 1, 2, 2], 2),
+ ([1], 1),
+ ([1, 1, 2], 1),
+ ([2, 2, 2, 1, 1], 2),
+ ([5, 5, 5, 5, 1, 2, 3], 5),
+ ([1, 2, 3, 4, 4, 4, 4], 4),
+ ([0, 0, 0], 0),
+ ([-1, -1, -1, 1, 1], -1),
+ ],
+ )
+ def test_majority_element(self, nums: list[int], expected: int):
+ result = run_majority_element(Solution, nums)
+ assert_majority_element(result, expected)
diff --git a/leetcode/majority_element/tests.py b/leetcode/majority_element/tests.py
deleted file mode 100644
index 5414f0f..0000000
--- a/leetcode/majority_element/tests.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestMajorityElement:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "nums, expected",
- [([3, 2, 3], 3), ([2, 2, 1, 1, 1, 2, 2], 2), ([1], 1), ([1, 1, 2], 1), ([2, 2, 2, 1, 1], 2)],
- )
- @logged_test
- def test_majority_element(self, nums: list[int], expected: int):
- result = self.solution.majority_element(nums)
- assert result == expected
diff --git a/leetcode/maximum_depth_of_binary_tree/helpers.py b/leetcode/maximum_depth_of_binary_tree/helpers.py
new file mode 100644
index 0000000..eeca53c
--- /dev/null
+++ b/leetcode/maximum_depth_of_binary_tree/helpers.py
@@ -0,0 +1,12 @@
+from leetcode_py import TreeNode
+
+
+def run_max_depth(solution_class: type, root_list: list[int | None]):
+ root = TreeNode[int].from_list(root_list)
+ implementation = solution_class()
+ return implementation.max_depth(root)
+
+
+def assert_max_depth(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/maximum_depth_of_binary_tree/playground.ipynb b/leetcode/maximum_depth_of_binary_tree/playground.ipynb
index 19d1eed..53406b7 100644
--- a/leetcode/maximum_depth_of_binary_tree/playground.ipynb
+++ b/leetcode/maximum_depth_of_binary_tree/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_max_depth, run_max_depth\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -14,7 +15,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -26,124 +27,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "3"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "root = TreeNode.from_list(root_list)\n",
- "result = Solution().max_depth(root)\n",
+ "result = run_max_depth(Solution, root_list)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 5,
- "id": "3d476584",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "TreeNode([3, 9, 20, None, None, 15, 7])"
- ]
- },
- "execution_count": 5,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "root"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_max_depth(result, expected)"
]
}
],
@@ -161,8 +61,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/maximum_depth_of_binary_tree/solution.py b/leetcode/maximum_depth_of_binary_tree/solution.py
index 727e907..d20d39d 100644
--- a/leetcode/maximum_depth_of_binary_tree/solution.py
+++ b/leetcode/maximum_depth_of_binary_tree/solution.py
@@ -2,9 +2,10 @@
class Solution:
+
# Time: O(n)
# Space: O(h)
- def max_depth(self, root: TreeNode | None) -> int:
+ def max_depth(self, root: TreeNode[int] | None) -> int:
if not root:
return 0
diff --git a/leetcode/maximum_depth_of_binary_tree/test_solution.py b/leetcode/maximum_depth_of_binary_tree/test_solution.py
new file mode 100644
index 0000000..229701e
--- /dev/null
+++ b/leetcode/maximum_depth_of_binary_tree/test_solution.py
@@ -0,0 +1,29 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_max_depth, run_max_depth
+from .solution import Solution
+
+
+class TestMaximumDepthOfBinaryTree:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "root_list, expected",
+ [
+ ([3, 9, 20, None, None, 15, 7], 3),
+ ([1, None, 2], 2),
+ ([], 0),
+ ([1], 1),
+ ([1, 2], 2),
+ ([1, 2, 3], 2),
+ ([1, 2, 3, 4], 3),
+ ([1, None, 2, None, 3], 3),
+ ],
+ )
+ def test_max_depth(self, root_list: list[int | None], expected: int):
+ result = run_max_depth(Solution, root_list)
+ assert_max_depth(result, expected)
diff --git a/leetcode/maximum_depth_of_binary_tree/tests.py b/leetcode/maximum_depth_of_binary_tree/tests.py
deleted file mode 100644
index 3bc3acb..0000000
--- a/leetcode/maximum_depth_of_binary_tree/tests.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import pytest
-
-from leetcode_py import TreeNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestMaximumDepthOfBinaryTree:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "root_list, expected",
- [
- # Original examples
- ([3, 9, 20, None, None, 15, 7], 3),
- ([1, None, 2], 2),
- ([], 0),
- # Single node
- ([1], 1),
- # Left skewed tree (depth 3)
- ([1, 2, None, 3], 3),
- # Right skewed tree (depth 2)
- ([1, None, 2], 2),
- # Balanced tree
- ([1, 2, 3, 4, 5, 6, 7], 3),
- # Unbalanced tree (left heavy)
- ([1, 2, 3, 4, 5, None, None, 6, 7], 4),
- # Two nodes
- ([1, 2], 2),
- ([1, None, 2], 2),
- ],
- )
- @logged_test
- def test_max_depth(self, root_list: list[int | None], expected: int):
- root = TreeNode.from_list(root_list)
- result = self.solution.max_depth(root)
- assert result == expected
diff --git a/leetcode/maximum_profit_in_job_scheduling/helpers.py b/leetcode/maximum_profit_in_job_scheduling/helpers.py
new file mode 100644
index 0000000..06e90fe
--- /dev/null
+++ b/leetcode/maximum_profit_in_job_scheduling/helpers.py
@@ -0,0 +1,10 @@
+def run_job_scheduling(
+ solution_class: type, start_time: list[int], end_time: list[int], profit: list[int]
+):
+ implementation = solution_class()
+ return implementation.job_scheduling(start_time, end_time, profit)
+
+
+def assert_job_scheduling(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/maximum_profit_in_job_scheduling/playground.ipynb b/leetcode/maximum_profit_in_job_scheduling/playground.ipynb
index 9499ca9..cf2e659 100644
--- a/leetcode/maximum_profit_in_job_scheduling/playground.ipynb
+++ b/leetcode/maximum_profit_in_job_scheduling/playground.ipynb
@@ -2,19 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
- "import bisect\n",
- "\n",
+ "from helpers import assert_job_scheduling, run_job_scheduling\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -28,125 +27,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "120"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().job_scheduling(start_time, end_time, profit)\n",
+ "result = run_job_scheduling(Solution, start_time, end_time, profit)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "dd4ebe51",
- "metadata": {},
- "source": [
- "# Bisect"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "3596ff83",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Original array: [1, 3, 3, 5, 7]\n",
- "Indices: [0, 1, 2, 3, 4]\n",
- "\n"
- ]
- }
- ],
- "source": [
- "# Demo array\n",
- "arr = [1, 3, 3, 5, 7]\n",
- "print(f\"Original array: {arr}\")\n",
- "print(f\"Indices: {list(range(len(arr)))}\")\n",
- "print()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "id": "508a52e9",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "=== bisect_left vs bisect_right ===\n",
- "x=0: left=0, right=0\n",
- "x=3: left=1, right=3\n",
- "x=4: left=3, right=3\n",
- "x=8: left=5, right=5\n",
- "\n"
- ]
- }
- ],
- "source": [
- "# bisect_left vs bisect_right\n",
- "print(\"=== bisect_left vs bisect_right ===\")\n",
- "for x in [0, 3, 4, 8]:\n",
- " left = bisect.bisect_left(arr, x)\n",
- " right = bisect.bisect_right(arr, x)\n",
- " print(f\"x={x}: left={left}, right={right}\")\n",
- "print()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "id": "b5dfa28a",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "=== insort demonstration ===\n",
- "Before: [1, 3, 5]\n",
- "After insort(4): [1, 3, 4, 5]\n",
- "After insort(3): [1, 3, 3, 4, 5]\n",
- "\n"
- ]
- }
- ],
- "source": [
- "# insort demonstration\n",
- "print(\"=== insort demonstration ===\")\n",
- "test_arr = [1, 3, 5]\n",
- "print(f\"Before: {test_arr}\")\n",
- "bisect.insort(test_arr, 4)\n",
- "print(f\"After insort(4): {test_arr}\")\n",
- "bisect.insort(test_arr, 3)\n",
- "print(f\"After insort(3): {test_arr}\")\n",
- "print()"
+ "assert_job_scheduling(result, expected)"
]
}
],
@@ -164,8 +61,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/maximum_profit_in_job_scheduling/solution.py b/leetcode/maximum_profit_in_job_scheduling/solution.py
index 6fc5cfc..c1653df 100644
--- a/leetcode/maximum_profit_in_job_scheduling/solution.py
+++ b/leetcode/maximum_profit_in_job_scheduling/solution.py
@@ -5,7 +5,6 @@ class Solution:
# Time: O(n log n)
# Space: O(n)
def job_scheduling(self, start_time: list[int], end_time: list[int], profit: list[int]) -> int:
-
jobs = sorted(zip(end_time, start_time, profit))
dp = [0] * len(jobs)
diff --git a/leetcode/maximum_profit_in_job_scheduling/test_solution.py b/leetcode/maximum_profit_in_job_scheduling/test_solution.py
new file mode 100644
index 0000000..3cfe330
--- /dev/null
+++ b/leetcode/maximum_profit_in_job_scheduling/test_solution.py
@@ -0,0 +1,27 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_job_scheduling, run_job_scheduling
+from .solution import Solution
+
+
+class TestMaximumProfitInJobScheduling:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "start_time, end_time, profit, expected",
+ [
+ ([1, 2, 3, 3], [3, 4, 5, 6], [50, 10, 40, 70], 120),
+ ([1, 2, 3, 4, 6], [3, 5, 10, 6, 9], [20, 20, 100, 70, 60], 150),
+ ([1, 1, 1], [2, 3, 4], [5, 6, 4], 6),
+ ([1], [2], [100], 100),
+ ],
+ )
+ def test_job_scheduling(
+ self, start_time: list[int], end_time: list[int], profit: list[int], expected: int
+ ):
+ result = run_job_scheduling(Solution, start_time, end_time, profit)
+ assert_job_scheduling(result, expected)
diff --git a/leetcode/maximum_profit_in_job_scheduling/tests.py b/leetcode/maximum_profit_in_job_scheduling/tests.py
deleted file mode 100644
index 21da4c0..0000000
--- a/leetcode/maximum_profit_in_job_scheduling/tests.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestMaximumProfitInJobScheduling:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "start_time, end_time, profit, expected",
- [
- # Original examples
- ([1, 2, 3, 3], [3, 4, 5, 6], [50, 10, 40, 70], 120),
- ([1, 2, 3, 4, 6], [3, 5, 10, 6, 9], [20, 20, 100, 70, 60], 150),
- ([1, 1, 1], [2, 3, 4], [5, 6, 4], 6),
- # Edge cases
- ([1], [2], [100], 100), # Single job
- ([1, 2], [2, 3], [50, 100], 150), # Adjacent jobs
- ([1, 3], [2, 4], [50, 100], 150), # Overlapping jobs - can take both
- ([1, 3, 5], [2, 4, 6], [10, 20, 30], 60), # No overlaps - take all
- ([1, 1, 1], [2, 2, 2], [10, 20, 30], 30), # Same time slots - take best
- ([1, 2, 3, 4], [2, 3, 4, 5], [1, 1, 1, 1], 4), # All same profit - take all
- ([1, 5, 10], [3, 7, 12], [100, 1, 1], 102), # High profit + non-overlapping
- ],
- )
- @logged_test
- def test_job_scheduling(
- self, start_time: list[int], end_time: list[int], profit: list[int], expected: int
- ):
- result = self.solution.job_scheduling(start_time, end_time, profit)
- assert result == expected
diff --git a/leetcode/maximum_subarray/helpers.py b/leetcode/maximum_subarray/helpers.py
new file mode 100644
index 0000000..e455a38
--- /dev/null
+++ b/leetcode/maximum_subarray/helpers.py
@@ -0,0 +1,8 @@
+def run_max_sub_array(solution_class: type, nums: list[int]):
+ implementation = solution_class()
+ return implementation.max_sub_array(nums)
+
+
+def assert_max_sub_array(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/maximum_subarray/playground.ipynb b/leetcode/maximum_subarray/playground.ipynb
index a34773e..bd4f64e 100644
--- a/leetcode/maximum_subarray/playground.ipynb
+++ b/leetcode/maximum_subarray/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_max_sub_array, run_max_sub_array\n",
"from solution import Solution"
]
},
@@ -25,22 +26,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().max_sub_array(nums)\n",
+ "result = run_max_sub_array(Solution, nums)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_max_sub_array(result, expected)"
]
}
],
@@ -59,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/maximum_subarray/solution.py b/leetcode/maximum_subarray/solution.py
index 2176165..0458a8c 100644
--- a/leetcode/maximum_subarray/solution.py
+++ b/leetcode/maximum_subarray/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
def max_sub_array(self, nums: list[int]) -> int:
diff --git a/leetcode/maximum_subarray/tests.py b/leetcode/maximum_subarray/test_solution.py
similarity index 78%
rename from leetcode/maximum_subarray/tests.py
rename to leetcode/maximum_subarray/test_solution.py
index f186921..1690628 100644
--- a/leetcode/maximum_subarray/tests.py
+++ b/leetcode/maximum_subarray/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_max_sub_array, run_max_sub_array
from .solution import Solution
@@ -9,6 +10,7 @@ class TestMaximumSubarray:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"nums, expected",
[
@@ -21,7 +23,6 @@ def setup_method(self):
([-5, -2, -8, -1], -1),
],
)
- @logged_test
def test_max_sub_array(self, nums: list[int], expected: int):
- result = self.solution.max_sub_array(nums)
- assert result == expected
+ result = run_max_sub_array(Solution, nums)
+ assert_max_sub_array(result, expected)
diff --git a/leetcode/merge_intervals/helpers.py b/leetcode/merge_intervals/helpers.py
new file mode 100644
index 0000000..a8d5049
--- /dev/null
+++ b/leetcode/merge_intervals/helpers.py
@@ -0,0 +1,8 @@
+def run_merge(solution_class: type, intervals: list[list[int]]):
+ implementation = solution_class()
+ return implementation.merge(intervals)
+
+
+def assert_merge(result: list[list[int]], expected: list[list[int]]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/merge_intervals/playground.ipynb b/leetcode/merge_intervals/playground.ipynb
index 99158ad..f5c7c1e 100644
--- a/leetcode/merge_intervals/playground.ipynb
+++ b/leetcode/merge_intervals/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_merge, run_merge\n",
"from solution import Solution"
]
},
@@ -25,22 +26,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().merge(intervals)\n",
+ "result = run_merge(Solution, intervals)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_merge(result, expected)"
]
}
],
@@ -59,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/merge_intervals/solution.py b/leetcode/merge_intervals/solution.py
index 55ca082..a1ea4dd 100644
--- a/leetcode/merge_intervals/solution.py
+++ b/leetcode/merge_intervals/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n log n)
# Space: O(1)
def merge(self, intervals: list[list[int]]) -> list[list[int]]:
diff --git a/leetcode/merge_intervals/test_solution.py b/leetcode/merge_intervals/test_solution.py
new file mode 100644
index 0000000..045bc7f
--- /dev/null
+++ b/leetcode/merge_intervals/test_solution.py
@@ -0,0 +1,27 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_merge, run_merge
+from .solution import Solution
+
+
+class TestMergeIntervals:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "intervals, expected",
+ [
+ ([[1, 3], [2, 6], [8, 10], [15, 18]], [[1, 6], [8, 10], [15, 18]]),
+ ([[1, 4], [4, 5]], [[1, 5]]),
+ ([[4, 7], [1, 4]], [[1, 7]]),
+ ([[1, 3]], [[1, 3]]),
+ ([[1, 4], [2, 3]], [[1, 4]]),
+ ([[1, 2], [3, 4], [5, 6]], [[1, 2], [3, 4], [5, 6]]),
+ ],
+ )
+ def test_merge(self, intervals: list[list[int]], expected: list[list[int]]):
+ result = run_merge(Solution, intervals)
+ assert_merge(result, expected)
diff --git a/leetcode/merge_intervals/tests.py b/leetcode/merge_intervals/tests.py
deleted file mode 100644
index 812653e..0000000
--- a/leetcode/merge_intervals/tests.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestMergeIntervals:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "intervals, expected",
- [
- # Original test cases
- ([[1, 3], [2, 6], [8, 10], [15, 18]], [[1, 6], [8, 10], [15, 18]]),
- ([[1, 4], [4, 5]], [[1, 5]]),
- ([[4, 7], [1, 4]], [[1, 7]]),
- # Edge cases
- ([[1, 1]], [[1, 1]]), # Single point interval
- ([[1, 2], [3, 4]], [[1, 2], [3, 4]]), # No overlap
- ([[1, 4], [2, 3]], [[1, 4]]), # Complete overlap
- ([[1, 10], [2, 6], [8, 10], [15, 18]], [[1, 10], [15, 18]]), # Multiple merges
- ([[0, 0], [1, 1], [2, 2]], [[0, 0], [1, 1], [2, 2]]), # All single points
- ([[1, 3], [2, 6], [8, 10], [9, 12], [15, 18]], [[1, 6], [8, 12], [15, 18]]), # Chain merge
- ([[4, 5], [4, 6]], [[4, 6]]), # Same start time
- ],
- )
- @logged_test
- def test_merge(self, intervals: list[list[int]], expected: list[list[int]]):
- result = self.solution.merge(intervals)
- assert result == expected
diff --git a/leetcode/merge_k_sorted_lists/helpers.py b/leetcode/merge_k_sorted_lists/helpers.py
new file mode 100644
index 0000000..b76a2b7
--- /dev/null
+++ b/leetcode/merge_k_sorted_lists/helpers.py
@@ -0,0 +1,13 @@
+from leetcode_py import ListNode
+
+
+def run_merge_k_lists(solution_class: type, lists_data: list[list[int]]):
+ lists = [ListNode[int].from_list(lst) for lst in lists_data]
+ implementation = solution_class()
+ return implementation.merge_k_lists(lists)
+
+
+def assert_merge_k_lists(result: ListNode[int] | None, expected_data: list[int]) -> bool:
+ expected = ListNode[int].from_list(expected_data)
+ assert result == expected
+ return True
diff --git a/leetcode/merge_k_sorted_lists/playground.ipynb b/leetcode/merge_k_sorted_lists/playground.ipynb
index f374726..44dff96 100644
--- a/leetcode/merge_k_sorted_lists/playground.ipynb
+++ b/leetcode/merge_k_sorted_lists/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_merge_k_lists, run_merge_k_lists\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import ListNode"
@@ -14,48 +15,35 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
"# Example test case\n",
"lists_data = [[1, 4, 5], [1, 3, 4], [2, 6]]\n",
- "lists = [ListNode.from_list(lst) for lst in lists_data]\n",
"expected_data = [1, 1, 2, 3, 4, 4, 5, 6]"
]
},
{
"cell_type": "code",
- "execution_count": 9,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[1, 1, 2, 3, 4, 4, 5, 6]"
- ]
- },
- "execution_count": 9,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().merge_k_lists(lists)\n",
- "ListNode.to_list(result) if result else []"
+ "result = run_merge_k_lists(Solution, lists_data)\n",
+ "ListNode[int].to_list(result) if result else []"
]
},
{
"cell_type": "code",
- "execution_count": 10,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "expected = ListNode.from_list(expected_data)\n",
- "assert result == expected"
+ "assert_merge_k_lists(result, expected_data)"
]
}
],
@@ -74,7 +62,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/merge_k_sorted_lists/solution.py b/leetcode/merge_k_sorted_lists/solution.py
index d6ed124..7de08d9 100644
--- a/leetcode/merge_k_sorted_lists/solution.py
+++ b/leetcode/merge_k_sorted_lists/solution.py
@@ -2,14 +2,17 @@
class Solution:
+
# Time: O(n log k) where n is total nodes, k is number of lists
# Space: O(log k) for recursion stack
- def merge_k_lists(self, lists: list[ListNode | None]) -> ListNode | None:
+ def merge_k_lists(self, lists: list[ListNode[int] | None]) -> ListNode[int] | None:
if not lists:
return None
return self._divide_conquer(lists, 0, len(lists) - 1)
- def _divide_conquer(self, lists: list[ListNode | None], left: int, right: int) -> ListNode | None:
+ def _divide_conquer(
+ self, lists: list[ListNode[int] | None], left: int, right: int
+ ) -> ListNode[int] | None:
if left == right:
return lists[left]
@@ -18,7 +21,7 @@ def _divide_conquer(self, lists: list[ListNode | None], left: int, right: int) -
l2 = self._divide_conquer(lists, mid + 1, right)
return self._merge_two(l1, l2)
- def _merge_two(self, l1: ListNode | None, l2: ListNode | None) -> ListNode | None:
+ def _merge_two(self, l1: ListNode[int] | None, l2: ListNode[int] | None) -> ListNode[int] | None:
dummy = ListNode(0)
curr = dummy
diff --git a/leetcode/merge_k_sorted_lists/test_solution.py b/leetcode/merge_k_sorted_lists/test_solution.py
new file mode 100644
index 0000000..ea7d200
--- /dev/null
+++ b/leetcode/merge_k_sorted_lists/test_solution.py
@@ -0,0 +1,30 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_merge_k_lists, run_merge_k_lists
+from .solution import Solution
+
+
+class TestMergeKSortedLists:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "lists_data, expected_data",
+ [
+ ([[1, 4, 5], [1, 3, 4], [2, 6]], [1, 1, 2, 3, 4, 4, 5, 6]),
+ ([], []),
+ ([[]], []),
+ ([[1]], [1]),
+ ([[1, 2], [3, 4]], [1, 2, 3, 4]),
+ ([[5], [1, 3], [2, 4, 6]], [1, 2, 3, 4, 5, 6]),
+ ([[-1, 0, 1], [-2, 2]], [-2, -1, 0, 1, 2]),
+ ([[1, 1, 1], [2, 2, 2]], [1, 1, 1, 2, 2, 2]),
+ ([[], [1], []], [1]),
+ ],
+ )
+ def test_merge_k_lists(self, lists_data: list[list[int]], expected_data: list[int]):
+ result = run_merge_k_lists(Solution, lists_data)
+ assert_merge_k_lists(result, expected_data)
diff --git a/leetcode/merge_k_sorted_lists/tests.py b/leetcode/merge_k_sorted_lists/tests.py
deleted file mode 100644
index 7249fc0..0000000
--- a/leetcode/merge_k_sorted_lists/tests.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import pytest
-
-from leetcode_py import ListNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestMergeKSortedLists:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "lists_data, expected_data",
- [
- ([[1, 4, 5], [1, 3, 4], [2, 6]], [1, 1, 2, 3, 4, 4, 5, 6]),
- ([], []),
- ([[]], []),
- ([[1]], [1]),
- ([[1, 2], [3, 4]], [1, 2, 3, 4]),
- ([[5], [1, 3], [2, 4, 6]], [1, 2, 3, 4, 5, 6]),
- ([[-1, 0, 1], [-2, 2]], [-2, -1, 0, 1, 2]),
- ([[1, 1, 1], [2, 2, 2]], [1, 1, 1, 2, 2, 2]),
- ([[]], []),
- ([[], [1], []], [1]),
- ([[0]], [0]),
- ([[-10, -5, -1], [-8, -3], [-7, -2, 0]], [-10, -8, -7, -5, -3, -2, -1, 0]),
- ([[1, 2, 3, 4, 5]], [1, 2, 3, 4, 5]),
- ([[10], [9], [8], [7]], [7, 8, 9, 10]),
- ([[1, 100], [2, 99], [3, 98]], [1, 2, 3, 98, 99, 100]),
- ([[], [], []], []),
- ([[0, 0, 0], [0, 0]], [0, 0, 0, 0, 0]),
- ([[1, 3, 5, 7], [2, 4, 6, 8]], [1, 2, 3, 4, 5, 6, 7, 8]),
- (
- [[100, 200, 300], [50, 150, 250], [75, 125, 175]],
- [50, 75, 100, 125, 150, 175, 200, 250, 300],
- ),
- ],
- )
- @logged_test
- def test_merge_k_lists(self, lists_data: list[list[int]], expected_data: list[int]):
- lists = [ListNode.from_list(lst) for lst in lists_data]
- result = self.solution.merge_k_lists(lists)
- expected = ListNode.from_list(expected_data)
- assert result == expected
diff --git a/leetcode/merge_two_sorted_lists/helpers.py b/leetcode/merge_two_sorted_lists/helpers.py
new file mode 100644
index 0000000..1951bed
--- /dev/null
+++ b/leetcode/merge_two_sorted_lists/helpers.py
@@ -0,0 +1,14 @@
+from leetcode_py import ListNode
+
+
+def run_merge_two_lists(solution_class: type, list1_vals: list[int], list2_vals: list[int]):
+ list1 = ListNode[int].from_list(list1_vals)
+ list2 = ListNode[int].from_list(list2_vals)
+ implementation = solution_class()
+ return implementation.merge_two_lists(list1, list2)
+
+
+def assert_merge_two_lists(result: ListNode[int] | None, expected_vals: list[int]) -> bool:
+ expected = ListNode[int].from_list(expected_vals)
+ assert result == expected
+ return True
diff --git a/leetcode/merge_two_sorted_lists/playground.ipynb b/leetcode/merge_two_sorted_lists/playground.ipynb
index 492ef37..0ec2f71 100644
--- a/leetcode/merge_two_sorted_lists/playground.ipynb
+++ b/leetcode/merge_two_sorted_lists/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_merge_two_lists, run_merge_two_lists\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import ListNode"
@@ -28,25 +29,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "list1 = ListNode.from_list(list1_vals)\n",
- "list2 = ListNode.from_list(list2_vals)\n",
- "result = Solution().merge_two_lists(list1, list2)\n",
- "result"
+ "result = run_merge_two_lists(Solution, list1_vals, list2_vals)\n",
+ "ListNode[int].to_list(result) if result else []"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "expected = ListNode.from_list(expected_vals)\n",
- "assert result == expected"
+ "assert_merge_two_lists(result, expected_vals)"
]
}
],
@@ -65,7 +63,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/merge_two_sorted_lists/solution.py b/leetcode/merge_two_sorted_lists/solution.py
index 07ebb03..5c0535e 100644
--- a/leetcode/merge_two_sorted_lists/solution.py
+++ b/leetcode/merge_two_sorted_lists/solution.py
@@ -2,10 +2,12 @@
class Solution:
+
# Time: O(m + n)
# Space: O(1)
- def merge_two_lists(self, list1: ListNode | None, list2: ListNode | None) -> ListNode | None:
-
+ def merge_two_lists(
+ self, list1: ListNode[int] | None, list2: ListNode[int] | None
+ ) -> ListNode[int] | None:
dummy = ListNode(0)
current = dummy
diff --git a/leetcode/merge_two_sorted_lists/tests.py b/leetcode/merge_two_sorted_lists/test_solution.py
similarity index 60%
rename from leetcode/merge_two_sorted_lists/tests.py
rename to leetcode/merge_two_sorted_lists/test_solution.py
index 5cb715d..c0deaf5 100644
--- a/leetcode/merge_two_sorted_lists/tests.py
+++ b/leetcode/merge_two_sorted_lists/test_solution.py
@@ -1,8 +1,8 @@
import pytest
-from leetcode_py import ListNode
from leetcode_py.test_utils import logged_test
+from .helpers import assert_merge_two_lists, run_merge_two_lists
from .solution import Solution
@@ -10,28 +10,21 @@ class TestMergeTwoSortedLists:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"list1_vals, list2_vals, expected_vals",
[
([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]),
([], [], []),
([], [0], [0]),
- ([0], [], [0]),
([1], [2], [1, 2]),
([2], [1], [1, 2]),
- ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2]),
([1, 3, 5], [2, 4, 6], [1, 2, 3, 4, 5, 6]),
- ([-10, -5, 0], [-8, -3, 1], [-10, -8, -5, -3, 0, 1]),
- ([5], [1, 2, 3, 4], [1, 2, 3, 4, 5]),
- ([1, 2, 3, 4], [5], [1, 2, 3, 4, 5]),
+ ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2]),
],
)
- @logged_test
def test_merge_two_lists(
self, list1_vals: list[int], list2_vals: list[int], expected_vals: list[int]
):
- list1 = ListNode.from_list(list1_vals)
- list2 = ListNode.from_list(list2_vals)
- expected = ListNode.from_list(expected_vals)
- result = self.solution.merge_two_lists(list1, list2)
- assert result == expected
+ result = run_merge_two_lists(Solution, list1_vals, list2_vals)
+ assert_merge_two_lists(result, expected_vals)
diff --git a/leetcode/middle_of_the_linked_list/helpers.py b/leetcode/middle_of_the_linked_list/helpers.py
new file mode 100644
index 0000000..f630738
--- /dev/null
+++ b/leetcode/middle_of_the_linked_list/helpers.py
@@ -0,0 +1,13 @@
+from leetcode_py import ListNode
+
+
+def run_middle_node(solution_class: type, head_list: list[int]):
+ head = ListNode[int].from_list(head_list)
+ implementation = solution_class()
+ return implementation.middle_node(head)
+
+
+def assert_middle_node(result: ListNode[int] | None, expected_list: list[int]) -> bool:
+ expected = ListNode[int].from_list(expected_list)
+ assert result == expected
+ return True
diff --git a/leetcode/middle_of_the_linked_list/playground.ipynb b/leetcode/middle_of_the_linked_list/playground.ipynb
index 921ff0f..d078f51 100644
--- a/leetcode/middle_of_the_linked_list/playground.ipynb
+++ b/leetcode/middle_of_the_linked_list/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_middle_node, run_middle_node\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import ListNode"
@@ -14,7 +15,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -26,80 +27,23 @@
},
{
"cell_type": "code",
- "execution_count": 7,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "ListNode([3, 4, 5])"
- ]
- },
- "execution_count": 7,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "head = ListNode.from_list(head_list)\n",
- "result = Solution().middle_node(head)\n",
- "result"
+ "result = run_middle_node(Solution, head_list)\n",
+ "ListNode[int].to_list(result) if result else []"
]
},
{
"cell_type": "code",
- "execution_count": 8,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "expected = ListNode.from_list(expected_list)\n",
- "assert result == expected"
+ "assert_middle_node(result, expected_list)"
]
}
],
@@ -118,7 +62,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/middle_of_the_linked_list/solution.py b/leetcode/middle_of_the_linked_list/solution.py
index bec661f..eeb11fd 100644
--- a/leetcode/middle_of_the_linked_list/solution.py
+++ b/leetcode/middle_of_the_linked_list/solution.py
@@ -2,9 +2,10 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
- def middle_node(self, head: ListNode | None) -> ListNode | None:
+ def middle_node(self, head: ListNode[int] | None) -> ListNode[int] | None:
slow = fast = head
while fast and fast.next:
assert slow is not None
diff --git a/leetcode/middle_of_the_linked_list/test_solution.py b/leetcode/middle_of_the_linked_list/test_solution.py
new file mode 100644
index 0000000..5bf4440
--- /dev/null
+++ b/leetcode/middle_of_the_linked_list/test_solution.py
@@ -0,0 +1,28 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_middle_node, run_middle_node
+from .solution import Solution
+
+
+class TestMiddleOfTheLinkedList:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "head_list, expected_list",
+ [
+ ([1, 2, 3, 4, 5], [3, 4, 5]),
+ ([1, 2, 3, 4, 5, 6], [4, 5, 6]),
+ ([1], [1]),
+ ([1, 2], [2]),
+ ([1, 2, 3], [2, 3]),
+ ([1, 2, 3, 4], [3, 4]),
+ ([10, 20, 30, 40, 50, 60, 70], [40, 50, 60, 70]),
+ ],
+ )
+ def test_middle_node(self, head_list: list[int], expected_list: list[int]):
+ result = run_middle_node(Solution, head_list)
+ assert_middle_node(result, expected_list)
diff --git a/leetcode/middle_of_the_linked_list/tests.py b/leetcode/middle_of_the_linked_list/tests.py
deleted file mode 100644
index 7454186..0000000
--- a/leetcode/middle_of_the_linked_list/tests.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import pytest
-
-from leetcode_py import ListNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestMiddleOfTheLinkedList:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "head_list, expected_list",
- [
- ([1, 2, 3, 4, 5], [3, 4, 5]), # odd length
- ([1, 2, 3, 4, 5, 6], [4, 5, 6]), # even length
- ([1], [1]), # single node
- ([1, 2], [2]), # two nodes
- ([1, 2, 3], [2, 3]), # three nodes
- ([1, 2, 3, 4], [3, 4]), # four nodes
- ([10, 20, 30, 40, 50, 60, 70], [40, 50, 60, 70]), # larger odd
- ([5, 15, 25, 35], [25, 35]), # larger even
- ],
- )
- @logged_test
- def test_middle_node(self, head_list: list[int], expected_list: list[int]):
- head = ListNode.from_list(head_list)
- expected = ListNode.from_list(expected_list)
- result = self.solution.middle_node(head)
- assert result == expected
diff --git a/leetcode/min_stack/helpers.py b/leetcode/min_stack/helpers.py
new file mode 100644
index 0000000..858f516
--- /dev/null
+++ b/leetcode/min_stack/helpers.py
@@ -0,0 +1,26 @@
+from typing import Any
+
+
+def run_min_stack_operations(solution_class: type, operations: list[str], inputs: list[list[int]]):
+ stack: Any = None
+ results: list[int | None] = []
+ for i, op in enumerate(operations):
+ if op == "MinStack":
+ stack = solution_class()
+ results.append(None)
+ elif op == "push" and stack is not None:
+ stack.push(inputs[i][0])
+ results.append(None)
+ elif op == "pop" and stack is not None:
+ stack.pop()
+ results.append(None)
+ elif op == "top" and stack is not None:
+ results.append(stack.top())
+ elif op == "getMin" and stack is not None:
+ results.append(stack.get_min())
+ return results
+
+
+def assert_min_stack_operations(result: list[int | None], expected: list[int | None]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/min_stack/playground.ipynb b/leetcode/min_stack/playground.ipynb
index 72b888c..0d24cc7 100644
--- a/leetcode/min_stack/playground.ipynb
+++ b/leetcode/min_stack/playground.ipynb
@@ -2,72 +2,47 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_min_stack_operations, run_min_stack_operations\n",
"from solution import MinStack"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
"# Example test case\n",
"operations = [\"MinStack\", \"push\", \"push\", \"push\", \"getMin\", \"pop\", \"top\", \"getMin\"]\n",
- "inputs = [[], [-2], [0], [-3], [], [], [], []]"
+ "inputs = [[], [-2], [0], [-3], [], [], [], []]\n",
+ "expected = [None, None, None, None, -3, None, 0, -2]"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[None, None, None, None, -3, None, 0, -2]"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "stack = None\n",
- "results: list[int | None] = []\n",
- "for i, op in enumerate(operations):\n",
- " if op == \"MinStack\":\n",
- " stack = MinStack()\n",
- " results.append(None)\n",
- " elif op == \"push\" and stack is not None:\n",
- " stack.push(inputs[i][0])\n",
- " results.append(None)\n",
- " elif op == \"pop\" and stack is not None:\n",
- " stack.pop()\n",
- " results.append(None)\n",
- " elif op == \"top\" and stack is not None:\n",
- " results.append(stack.top())\n",
- " elif op == \"getMin\" and stack is not None:\n",
- " results.append(stack.get_min())\n",
- "results"
+ "result = run_min_stack_operations(MinStack, operations, inputs)\n",
+ "result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "expected = [None, None, None, None, -3, None, 0, -2]\n",
- "assert results == expected"
+ "assert_min_stack_operations(result, expected)"
]
}
],
@@ -85,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/min_stack/tests.py b/leetcode/min_stack/test_solution.py
similarity index 50%
rename from leetcode/min_stack/tests.py
rename to leetcode/min_stack/test_solution.py
index af73df4..0604301 100644
--- a/leetcode/min_stack/tests.py
+++ b/leetcode/min_stack/test_solution.py
@@ -2,10 +2,13 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_min_stack_operations, run_min_stack_operations
from .solution import MinStack
-class TestMinStack:
+class TestTestMinStack:
+
+ @logged_test
@pytest.mark.parametrize(
"operations, inputs, expected",
[
@@ -24,29 +27,8 @@ class TestMinStack:
[[], [1], [1], [2], [], [], [], [], []],
[None, None, None, None, 1, None, 1, None, 1],
),
- (
- ["MinStack", "push", "push", "getMin", "push", "getMin", "pop", "getMin"],
- [[], [3], [1], [], [0], [], [], []],
- [None, None, None, 1, None, 0, None, 1],
- ),
],
)
- @logged_test
def test_min_stack(self, operations: list[str], inputs: list[list[int]], expected: list[int | None]):
- stack: MinStack | None = None
- results: list[int | None] = []
- for i, op in enumerate(operations):
- if op == "MinStack":
- stack = MinStack()
- results.append(None)
- elif op == "push" and stack is not None:
- stack.push(inputs[i][0])
- results.append(None)
- elif op == "pop" and stack is not None:
- stack.pop()
- results.append(None)
- elif op == "top" and stack is not None:
- results.append(stack.top())
- elif op == "getMin" and stack is not None:
- results.append(stack.get_min())
- assert results == expected
+ result = run_min_stack_operations(MinStack, operations, inputs)
+ assert_min_stack_operations(result, expected)
diff --git a/leetcode/minimum_height_trees/helpers.py b/leetcode/minimum_height_trees/helpers.py
new file mode 100644
index 0000000..c6ebe38
--- /dev/null
+++ b/leetcode/minimum_height_trees/helpers.py
@@ -0,0 +1,8 @@
+def run_find_min_height_trees(solution_class: type, n: int, edges: list[list[int]]):
+ implementation = solution_class()
+ return implementation.find_min_height_trees(n, edges)
+
+
+def assert_find_min_height_trees(result: list[int], expected: list[int]) -> bool:
+ assert sorted(result) == sorted(expected)
+ return True
diff --git a/leetcode/minimum_height_trees/playground.ipynb b/leetcode/minimum_height_trees/playground.ipynb
index 51e0605..8cf7734 100644
--- a/leetcode/minimum_height_trees/playground.ipynb
+++ b/leetcode/minimum_height_trees/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_find_min_height_trees, run_find_min_height_trees\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,34 +26,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[1]"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().find_min_height_trees(n, edges)\n",
+ "result = run_find_min_height_trees(Solution, n, edges)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert sorted(result) == sorted(expected)"
+ "assert_find_min_height_trees(result, expected)"
]
}
],
@@ -70,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/minimum_height_trees/solution.py b/leetcode/minimum_height_trees/solution.py
index 5b9a6d2..7ecb55e 100644
--- a/leetcode/minimum_height_trees/solution.py
+++ b/leetcode/minimum_height_trees/solution.py
@@ -2,6 +2,7 @@
class Solution:
+
# Time: O(V)
# Space: O(V)
def find_min_height_trees(self, n: int, edges: list[list[int]]) -> list[int]:
diff --git a/leetcode/minimum_height_trees/tests.py b/leetcode/minimum_height_trees/test_solution.py
similarity index 66%
rename from leetcode/minimum_height_trees/tests.py
rename to leetcode/minimum_height_trees/test_solution.py
index 11fd934..d9a27c4 100644
--- a/leetcode/minimum_height_trees/tests.py
+++ b/leetcode/minimum_height_trees/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_find_min_height_trees, run_find_min_height_trees
from .solution import Solution
@@ -9,15 +10,17 @@ class TestMinimumHeightTrees:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"n, edges, expected",
[
(4, [[1, 0], [1, 2], [1, 3]], [1]),
(6, [[3, 0], [3, 1], [3, 2], [3, 4], [5, 4]], [3, 4]),
(1, [], [0]),
+ (2, [[0, 1]], [0, 1]),
+ (3, [[0, 1], [1, 2]], [1]),
],
)
- @logged_test
def test_find_min_height_trees(self, n: int, edges: list[list[int]], expected: list[int]):
- result = self.solution.find_min_height_trees(n, edges)
- assert sorted(result) == sorted(expected)
+ result = run_find_min_height_trees(Solution, n, edges)
+ assert_find_min_height_trees(result, expected)
diff --git a/leetcode/minimum_window_substring/helpers.py b/leetcode/minimum_window_substring/helpers.py
new file mode 100644
index 0000000..ac1ecc5
--- /dev/null
+++ b/leetcode/minimum_window_substring/helpers.py
@@ -0,0 +1,8 @@
+def run_min_window(solution_class: type, s: str, t: str):
+ implementation = solution_class()
+ return implementation.min_window(s, t)
+
+
+def assert_min_window(result: str, expected: str) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/minimum_window_substring/playground.ipynb b/leetcode/minimum_window_substring/playground.ipynb
index 1f99f98..c35e737 100644
--- a/leetcode/minimum_window_substring/playground.ipynb
+++ b/leetcode/minimum_window_substring/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_min_window, run_min_window\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,34 +26,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "'BANC'"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().min_window(s, t)\n",
+ "result = run_min_window(Solution, s, t)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_min_window(result, expected)"
]
}
],
@@ -70,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/minimum_window_substring/solution.py b/leetcode/minimum_window_substring/solution.py
index 60bcb89..db9711a 100644
--- a/leetcode/minimum_window_substring/solution.py
+++ b/leetcode/minimum_window_substring/solution.py
@@ -6,6 +6,7 @@ class Solution:
# Time: O(m + n) where m = len(s), n = len(t)
# Space: O(k) where k is unique chars in t
def min_window(self, s: str, t: str) -> str:
+
if not t or len(t) > len(s):
return ""
diff --git a/leetcode/minimum_window_substring/test_solution.py b/leetcode/minimum_window_substring/test_solution.py
new file mode 100644
index 0000000..2be62d0
--- /dev/null
+++ b/leetcode/minimum_window_substring/test_solution.py
@@ -0,0 +1,26 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_min_window, run_min_window
+from .solution import Solution
+
+
+class TestMinimumWindowSubstring:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "s, t, expected",
+ [
+ ("ADOBECODEBANC", "ABC", "BANC"),
+ ("a", "a", "a"),
+ ("a", "aa", ""),
+ ("ab", "b", "b"),
+ ("abc", "cba", "abc"),
+ ],
+ )
+ def test_min_window(self, s: str, t: str, expected: str):
+ result = run_min_window(Solution, s, t)
+ assert_min_window(result, expected)
diff --git a/leetcode/minimum_window_substring/tests.py b/leetcode/minimum_window_substring/tests.py
deleted file mode 100644
index 8a3e16a..0000000
--- a/leetcode/minimum_window_substring/tests.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestMinimumWindowSubstring:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "s, t, expected",
- [
- # Basic cases
- ("ADOBECODEBANC", "ABC", "BANC"),
- ("a", "a", "a"),
- ("a", "aa", ""),
- # Edge cases
- ("", "a", ""), # Empty s
- ("a", "", ""), # Empty t
- ("", "", ""), # Both empty
- ("ab", "ba", "ab"), # Same length
- ("abc", "cba", "abc"), # Entire string needed
- # Duplicates
- ("ADOBECODEBANC", "AABC", "ADOBECODEBA"), # Correct: needs 2 A's, 1 B, 1 C
- ("aa", "aa", "aa"),
- # No solution
- ("abc", "def", ""),
- ("a", "b", ""),
- # Multiple valid windows
- ("ADOBECODEBANC", "AB", "BA"), # Correct: "BA" is shorter than "ADOB"
- ("abcdef", "cf", "cdef"),
- ],
- )
- @logged_test
- def test_min_window(self, s: str, t: str, expected: str):
- result = self.solution.min_window(s, t)
- assert result == expected
diff --git a/leetcode/number_of_islands/helpers.py b/leetcode/number_of_islands/helpers.py
new file mode 100644
index 0000000..291c636
--- /dev/null
+++ b/leetcode/number_of_islands/helpers.py
@@ -0,0 +1,8 @@
+def run_num_islands(solution_class: type, grid: list[list[str]]):
+ implementation = solution_class()
+ return implementation.num_islands(grid)
+
+
+def assert_num_islands(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/number_of_islands/playground.ipynb b/leetcode/number_of_islands/playground.ipynb
index 6b0ed5f..57ebc3f 100644
--- a/leetcode/number_of_islands/playground.ipynb
+++ b/leetcode/number_of_islands/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_num_islands, run_num_islands\n",
"from solution import Solution"
]
},
@@ -30,21 +31,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().num_islands(grid)\nresult"
+ "result = run_num_islands(Solution, grid)\n",
+ "result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_num_islands(result, expected)"
]
}
],
@@ -63,7 +65,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/number_of_islands/solution.py b/leetcode/number_of_islands/solution.py
index 62368c8..6327f54 100644
--- a/leetcode/number_of_islands/solution.py
+++ b/leetcode/number_of_islands/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(m * n) where m = rows, n = cols
# Space: O(m * n) for recursion stack in worst case
def num_islands(self, grid: list[list[str]]) -> int:
diff --git a/leetcode/number_of_islands/test_solution.py b/leetcode/number_of_islands/test_solution.py
new file mode 100644
index 0000000..2428a04
--- /dev/null
+++ b/leetcode/number_of_islands/test_solution.py
@@ -0,0 +1,42 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_num_islands, run_num_islands
+from .solution import Solution
+
+
+class TestNumberOfIslands:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "grid, expected",
+ [
+ (
+ [
+ ["1", "1", "1", "1", "0"],
+ ["1", "1", "0", "1", "0"],
+ ["1", "1", "0", "0", "0"],
+ ["0", "0", "0", "0", "0"],
+ ],
+ 1,
+ ),
+ (
+ [
+ ["1", "1", "0", "0", "0"],
+ ["1", "1", "0", "0", "0"],
+ ["0", "0", "1", "0", "0"],
+ ["0", "0", "0", "1", "1"],
+ ],
+ 3,
+ ),
+ ([["1"]], 1),
+ ([["0"]], 0),
+ ([["1", "0", "1"], ["0", "1", "0"], ["1", "0", "1"]], 5),
+ ],
+ )
+ def test_num_islands(self, grid: list[list[str]], expected: int):
+ result = run_num_islands(Solution, grid)
+ assert_num_islands(result, expected)
diff --git a/leetcode/number_of_islands/tests.py b/leetcode/number_of_islands/tests.py
deleted file mode 100644
index c3eb4e3..0000000
--- a/leetcode/number_of_islands/tests.py
+++ /dev/null
@@ -1,67 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestNumberOfIslands:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "grid, expected",
- [
- # Basic examples
- (
- [
- ["1", "1", "1", "1", "0"],
- ["1", "1", "0", "1", "0"],
- ["1", "1", "0", "0", "0"],
- ["0", "0", "0", "0", "0"],
- ],
- 1,
- ),
- (
- [
- ["1", "1", "0", "0", "0"],
- ["1", "1", "0", "0", "0"],
- ["0", "0", "1", "0", "0"],
- ["0", "0", "0", "1", "1"],
- ],
- 3,
- ),
- # Edge cases
- ([["1"]], 1),
- ([["0"]], 0),
- ([["1", "0", "1"], ["0", "1", "0"], ["1", "0", "1"]], 5),
- # All water
- ([["0", "0", "0"], ["0", "0", "0"]], 0),
- # All land
- ([["1", "1", "1"], ["1", "1", "1"]], 1),
- # Single row
- ([["1", "0", "1", "0", "1"]], 3),
- # Single column
- ([["1"], ["0"], ["1"], ["0"], ["1"]], 3),
- # L-shaped island
- ([["1", "1", "0"], ["1", "0", "0"], ["1", "1", "1"]], 1),
- # Diagonal pattern (no connections)
- ([["1", "0", "0"], ["0", "1", "0"], ["0", "0", "1"]], 3),
- # Large grid with multiple islands
- (
- [
- ["1", "0", "0", "1", "1", "0"],
- ["0", "0", "0", "0", "1", "0"],
- ["0", "0", "1", "0", "0", "0"],
- ["1", "1", "0", "0", "0", "1"],
- ],
- 5,
- ),
- # Snake-like island
- ([["1", "1", "0"], ["0", "1", "0"], ["0", "1", "1"]], 1),
- ],
- )
- @logged_test
- def test_num_islands(self, grid: list[list[str]], expected: int):
- result = self.solution.num_islands(grid)
- assert result == expected
diff --git a/leetcode/partition_equal_subset_sum/README.md b/leetcode/partition_equal_subset_sum/README.md
new file mode 100644
index 0000000..82f48a5
--- /dev/null
+++ b/leetcode/partition_equal_subset_sum/README.md
@@ -0,0 +1,36 @@
+# Partition Equal Subset Sum
+
+**Difficulty:** Medium
+**Topics:** Array, Dynamic Programming
+**Tags:** grind-75
+
+**LeetCode:** [Problem 416](https://leetcode.com/problems/partition-equal-subset-sum/description/)
+
+## Problem Description
+
+Given an integer array `nums`, return `true` if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or `false` otherwise.
+
+## Examples
+
+### Example 1:
+
+```
+Input: nums = [1,5,11,5]
+Output: true
+```
+
+**Explanation:** The array can be partitioned as [1, 5, 5] and [11].
+
+### Example 2:
+
+```
+Input: nums = [1,2,3,5]
+Output: false
+```
+
+**Explanation:** The array cannot be partitioned into equal sum subsets.
+
+## Constraints
+
+- 1 <= nums.length <= 200
+- 1 <= nums[i] <= 100
diff --git a/leetcode/__init__.py b/leetcode/partition_equal_subset_sum/__init__.py
similarity index 100%
rename from leetcode/__init__.py
rename to leetcode/partition_equal_subset_sum/__init__.py
diff --git a/leetcode/partition_equal_subset_sum/helpers.py b/leetcode/partition_equal_subset_sum/helpers.py
new file mode 100644
index 0000000..20a018a
--- /dev/null
+++ b/leetcode/partition_equal_subset_sum/helpers.py
@@ -0,0 +1,8 @@
+def run_can_partition(solution_class: type, nums: list[int]):
+ implementation = solution_class()
+ return implementation.can_partition(nums)
+
+
+def assert_can_partition(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/partition_equal_subset_sum/playground.ipynb b/leetcode/partition_equal_subset_sum/playground.ipynb
new file mode 100644
index 0000000..5597f66
--- /dev/null
+++ b/leetcode/partition_equal_subset_sum/playground.ipynb
@@ -0,0 +1,68 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "imports",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from helpers import assert_can_partition, run_can_partition\n",
+ "from solution import Solution"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "setup",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Example test case\n",
+ "nums = [1, 5, 11, 5]\n",
+ "expected = True"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "run",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "result = run_can_partition(Solution, nums)\n",
+ "result"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "assert",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "assert_can_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/partition_equal_subset_sum/solution.py b/leetcode/partition_equal_subset_sum/solution.py
new file mode 100644
index 0000000..c3bcdfe
--- /dev/null
+++ b/leetcode/partition_equal_subset_sum/solution.py
@@ -0,0 +1,93 @@
+class Solution:
+
+ # Time: O(n * sum)
+ # Space: O(sum)
+ def can_partition(self, nums: list[int]) -> bool:
+ """
+ Example: nums = [1, 5, 11, 5], target = 11
+
+ Initial: dp = [T, F, F, F, F, F, F, F, F, F, F, F]
+ 0 1 2 3 4 5 6 7 8 9 10 11
+
+ After num=1: [T, T, F, F, F, F, F, F, F, F, F, F]
+ └─┘ (can make sum 1)
+
+ After num=5: [T, T, F, F, F, T, T, F, F, F, F, F]
+ └─┘ └─┘ └─┘ (can make sums 5,6)
+
+ After num=11:[T, T, F, F, F, T, T, F, F, F, F, T]
+ └─┘ (target!)
+
+ Backward iteration prevents using same number twice
+ """
+ total = sum(nums)
+ if total % 2:
+ return False
+
+ target = total // 2
+ dp = [False] * (target + 1)
+ dp[0] = True
+
+ for num in nums:
+ for j in range(target, num - 1, -1):
+ dp[j] = dp[j] or dp[j - num]
+
+ # Early termination: found target sum!
+ if dp[target]:
+ return True
+
+ return False
+
+
+class SolutionBitset:
+ # Time: O(n * sum)
+ # Space: O(1)
+ def can_partition(self, nums: list[int]) -> bool:
+ """
+ Example: nums = [1, 5, 11, 5], target = 11
+
+ Bitset representation (bit position = achievable sum):
+
+ Initial: dp = 1 (binary: 1)
+ Bits: ...0001
+ Sums: {0}
+
+ After num=1: dp |= dp << 1
+ a = dp = 1 (bin: 0001)
+ b = dp << 1 = 2 (bin: 0010)
+ c = a | b = 3 (bin: 0011)
+ Sums: {0, 1}
+
+ After num=5: dp |= dp << 5
+ a = dp = 3 (bin: 0000011)
+ b = dp << 5 = 96 (bin: 1100000)
+ c = a | b = 99 (bin: 1100011)
+ Sums: {0, 1, 5, 6}
+
+ After num=11: dp |= dp << 11
+ a = dp = 99 (bin: 00000001100011)
+ b = dp << 11 = 202752 (bin: 110001100000000)
+ c = a | b = 202851 (bin: 110001101100011)
+ Sums: {0, 1, 5, 6, 11, 12, 16, 17}
+
+ Check: (dp & (1 << 11)) != 0
+ a = dp = 202851 (bin: 110001101100011)
+ b = 1 << 11 = 2048 (bin: 100000000000)
+ c = a & b = 2048 (bin: 100000000000)
+ c != 0 → bit 11 is set → True!
+ """
+ total = sum(nums)
+ if total % 2 != 0:
+ return False
+
+ target = total // 2
+ dp = 1
+
+ for num in nums:
+ dp |= dp << num
+
+ # Early termination: found target sum!
+ if (dp & (1 << target)) != 0:
+ return True
+
+ return False
diff --git a/leetcode/partition_equal_subset_sum/test_solution.py b/leetcode/partition_equal_subset_sum/test_solution.py
new file mode 100644
index 0000000..a04af23
--- /dev/null
+++ b/leetcode/partition_equal_subset_sum/test_solution.py
@@ -0,0 +1,27 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_can_partition, run_can_partition
+from .solution import Solution, SolutionBitset
+
+
+class TestPartitionEqualSubsetSum:
+
+ @logged_test
+ @pytest.mark.parametrize("solution_class", [Solution, SolutionBitset])
+ @pytest.mark.parametrize(
+ "nums, expected",
+ [
+ ([1, 5, 11, 5], True),
+ ([1, 2, 3, 5], False),
+ ([1, 1], True),
+ ([1], False),
+ ([2, 2, 1, 1], True),
+ ([100], False),
+ ([1, 2, 5], False),
+ ],
+ )
+ def test_can_partition(self, nums: list[int], expected: bool, solution_class: type):
+ result = run_can_partition(solution_class, nums)
+ assert_can_partition(result, expected)
diff --git a/leetcode/permutations/helpers.py b/leetcode/permutations/helpers.py
new file mode 100644
index 0000000..6114fa1
--- /dev/null
+++ b/leetcode/permutations/helpers.py
@@ -0,0 +1,14 @@
+def run_permute(solution_class: type, nums: list[int]):
+ implementation = solution_class()
+ return implementation.permute(nums)
+
+
+def assert_permute(result: list[list[int]], expected: list[list[int]]) -> bool:
+ # Sort both result and expected for comparison since order doesn't matter
+ result_sorted = [sorted(perm) for perm in result]
+ expected_sorted = [sorted(perm) for perm in expected]
+ result_sorted.sort()
+ expected_sorted.sort()
+ assert len(result) == len(expected)
+ assert result_sorted == expected_sorted
+ return True
diff --git a/leetcode/permutations/playground.ipynb b/leetcode/permutations/playground.ipynb
index a90984e..3df2f29 100644
--- a/leetcode/permutations/playground.ipynb
+++ b/leetcode/permutations/playground.ipynb
@@ -6,7 +6,10 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": ["from solution import Solution"]
+ "source": [
+ "from helpers import assert_permute, run_permute\n",
+ "from solution import Solution"
+ ]
},
{
"cell_type": "code",
@@ -14,23 +17,32 @@
"id": "setup",
"metadata": {},
"outputs": [],
- "source": ["# Example test case\nnums = [1, 2, 3]\nexpected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]"]
+ "source": [
+ "# Example test case\n",
+ "nums = [1, 2, 3]\n",
+ "expected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
- "source": ["result = Solution().permute(nums)\nresult"]
+ "source": [
+ "result = run_permute(Solution, nums)\n",
+ "result"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
- "source": ["# Check that we have the right number of permutations\nassert len(result) == len(expected)\n# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(perm) for perm in result]\nexpected_sorted = [sorted(perm) for perm in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"]
+ "source": [
+ "assert_permute(result, expected)"
+ ]
}
],
"metadata": {
@@ -48,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/permutations/solution.py b/leetcode/permutations/solution.py
index 1bdf59f..c71dcd0 100644
--- a/leetcode/permutations/solution.py
+++ b/leetcode/permutations/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n! * n)
# Space: O(n! * n) output + O(n) recursion
def permute(self, nums: list[int]) -> list[list[int]]:
diff --git a/leetcode/permutations/test_solution.py b/leetcode/permutations/test_solution.py
new file mode 100644
index 0000000..da684e9
--- /dev/null
+++ b/leetcode/permutations/test_solution.py
@@ -0,0 +1,25 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_permute, run_permute
+from .solution import Solution
+
+
+class TestPermutations:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "nums, expected",
+ [
+ ([1, 2, 3], [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]),
+ ([0, 1], [[0, 1], [1, 0]]),
+ ([1], [[1]]),
+ ([2, 1], [[2, 1], [1, 2]]),
+ ],
+ )
+ def test_permute(self, nums: list[int], expected: list[list[int]]):
+ result = run_permute(Solution, nums)
+ assert_permute(result, expected)
diff --git a/leetcode/permutations/tests.py b/leetcode/permutations/tests.py
deleted file mode 100644
index d455d72..0000000
--- a/leetcode/permutations/tests.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestPermutations:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "nums, expected",
- [
- ([], [[]]),
- ([1, 2, 3], [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]),
- ([0, 1], [[0, 1], [1, 0]]),
- ([1], [[1]]),
- ([-1, 0, 1], [[-1, 0, 1], [-1, 1, 0], [0, -1, 1], [0, 1, -1], [1, -1, 0], [1, 0, -1]]),
- ([4, 5], [[4, 5], [5, 4]]),
- ([1, 2, 3, 4], 24), # Test count only for larger input
- ([10], [[10]]),
- ([-5, -3], [[-5, -3], [-3, -5]]),
- ],
- )
- @logged_test
- def test_permute(self, nums: list[int], expected):
- result = self.solution.permute(nums)
-
- # If expected is int, just check count (for larger inputs)
- if isinstance(expected, int):
- assert len(result) == expected
- # Verify all permutations are unique
- assert len(set(tuple(perm) for perm in result)) == expected
- return
-
- # Sort both result and expected for comparison since order doesn't matter
- result_sorted = [sorted(perm) for perm in result]
- expected_sorted = [sorted(perm) for perm in expected]
- result_sorted.sort()
- expected_sorted.sort()
- assert len(result) == len(expected)
- assert result_sorted == expected_sorted
diff --git a/leetcode/product_of_array_except_self/helpers.py b/leetcode/product_of_array_except_self/helpers.py
new file mode 100644
index 0000000..c43546a
--- /dev/null
+++ b/leetcode/product_of_array_except_self/helpers.py
@@ -0,0 +1,8 @@
+def run_product_except_self(solution_class: type, nums: list[int]):
+ implementation = solution_class()
+ return implementation.product_except_self(nums)
+
+
+def assert_product_except_self(result: list[int], expected: list[int]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/product_of_array_except_self/playground.ipynb b/leetcode/product_of_array_except_self/playground.ipynb
index ebfb399..c96f1f3 100644
--- a/leetcode/product_of_array_except_self/playground.ipynb
+++ b/leetcode/product_of_array_except_self/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_product_except_self, run_product_except_self\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -24,34 +25,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[24, 12, 8, 6]"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().product_except_self(nums)\n",
+ "result = run_product_except_self(Solution, nums)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_product_except_self(result, expected)"
]
}
],
@@ -69,8 +59,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/product_of_array_except_self/solution.py b/leetcode/product_of_array_except_self/solution.py
index 821e548..0265649 100644
--- a/leetcode/product_of_array_except_self/solution.py
+++ b/leetcode/product_of_array_except_self/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
def product_except_self(self, nums: list[int]) -> list[int]:
diff --git a/leetcode/product_of_array_except_self/test_solution.py b/leetcode/product_of_array_except_self/test_solution.py
new file mode 100644
index 0000000..2645aff
--- /dev/null
+++ b/leetcode/product_of_array_except_self/test_solution.py
@@ -0,0 +1,28 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_product_except_self, run_product_except_self
+from .solution import Solution
+
+
+class TestProductOfArrayExceptSelf:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "nums, expected",
+ [
+ ([1, 2, 3, 4], [24, 12, 8, 6]),
+ ([-1, 1, 0, -3, 3], [0, 0, 9, 0, 0]),
+ ([2, 3, 4, 5], [60, 40, 30, 24]),
+ ([1, 1], [1, 1]),
+ ([5, 2], [2, 5]),
+ ([0, 1, 2, 3], [6, 0, 0, 0]),
+ ([1, 0, 3, 4], [0, 12, 0, 0]),
+ ],
+ )
+ def test_product_except_self(self, nums: list[int], expected: list[int]):
+ result = run_product_except_self(Solution, nums)
+ assert_product_except_self(result, expected)
diff --git a/leetcode/product_of_array_except_self/tests.py b/leetcode/product_of_array_except_self/tests.py
deleted file mode 100644
index e3532e4..0000000
--- a/leetcode/product_of_array_except_self/tests.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestProductOfArrayExceptSelf:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "nums, expected",
- [
- ([1, 2, 3, 4], [24, 12, 8, 6]),
- ([-1, 1, 0, -3, 3], [0, 0, 9, 0, 0]),
- ([2, 3, 4, 5], [60, 40, 30, 24]),
- # Edge cases
- ([1, 1], [1, 1]), # Minimum length
- ([5, 2], [2, 5]), # Two elements
- ([0, 0], [0, 0]), # All zeros
- ([1, 0], [0, 1]), # Single zero
- ([0, 1, 2], [2, 0, 0]), # Zero at start
- ([1, 2, 0], [0, 0, 2]), # Zero at end
- # Negative numbers
- ([-1, -2], [-2, -1]),
- ([-1, -2, -3], [6, 3, 2]),
- ([1, -2, 3], [-6, 3, -2]),
- # All ones
- ([1, 1, 1, 1], [1, 1, 1, 1]),
- # Large numbers
- ([10, 3, 5, 6, 2], [180, 600, 360, 300, 900]),
- ],
- )
- @logged_test
- def test_product_except_self(self, nums: list[int], expected: list[int]):
- result = self.solution.product_except_self(nums)
- assert result == expected
diff --git a/leetcode/ransom_note/helpers.py b/leetcode/ransom_note/helpers.py
new file mode 100644
index 0000000..b150ae5
--- /dev/null
+++ b/leetcode/ransom_note/helpers.py
@@ -0,0 +1,8 @@
+def run_can_construct(solution_class: type, ransom_note: str, magazine: str):
+ implementation = solution_class()
+ return implementation.can_construct(ransom_note, magazine)
+
+
+def assert_can_construct(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/ransom_note/playground.ipynb b/leetcode/ransom_note/playground.ipynb
index 864b296..5982636 100644
--- a/leetcode/ransom_note/playground.ipynb
+++ b/leetcode/ransom_note/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_can_construct, run_can_construct\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,34 +26,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "True"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().can_construct(ransom_note, magazine)\n",
+ "result = run_can_construct(Solution, ransom_note, magazine)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_can_construct(result, expected)"
]
}
],
@@ -70,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/ransom_note/test_solution.py b/leetcode/ransom_note/test_solution.py
new file mode 100644
index 0000000..429913a
--- /dev/null
+++ b/leetcode/ransom_note/test_solution.py
@@ -0,0 +1,32 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_can_construct, run_can_construct
+from .solution import Solution
+
+
+class TestRansomNote:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "ransom_note, magazine, expected",
+ [
+ ("a", "b", False),
+ ("aa", "ab", False),
+ ("aa", "aab", True),
+ ("aab", "baa", True),
+ ("", "", True),
+ ("", "abc", True),
+ ("abc", "", False),
+ ("abc", "abc", True),
+ ("abc", "cba", True),
+ ("aaa", "aa", False),
+ ("ab", "ba", True),
+ ],
+ )
+ def test_can_construct(self, ransom_note: str, magazine: str, expected: bool):
+ result = run_can_construct(Solution, ransom_note, magazine)
+ assert_can_construct(result, expected)
diff --git a/leetcode/ransom_note/tests.py b/leetcode/ransom_note/tests.py
deleted file mode 100644
index 90881af..0000000
--- a/leetcode/ransom_note/tests.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestRansomNote:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "ransom_note, magazine, expected",
- [
- # Original test cases
- ("a", "b", False),
- ("aa", "ab", False),
- ("aa", "aab", True),
- ("aab", "baa", True),
- # Edge cases
- ("", "", True), # Both empty
- ("", "abc", True), # Empty ransom note
- ("a", "", False), # Empty magazine
- ("a", "a", True), # Single char match
- ("ab", "a", False), # Ransom longer than magazine
- # More complex cases
- ("abc", "aabbcc", True), # Multiple of each char
- ("aab", "baa", True), # Same chars, different order
- ("aaa", "aa", False), # Not enough of same char
- ("abcd", "dcba", True), # All different chars
- ("hello", "helloworld", True), # Ransom is substring
- ("world", "hello", False), # Missing chars
- ],
- )
- @logged_test
- def test_can_construct(self, ransom_note: str, magazine: str, expected: bool):
- result = self.solution.can_construct(ransom_note, magazine)
- assert result == expected
diff --git a/leetcode/reverse_linked_list/helpers.py b/leetcode/reverse_linked_list/helpers.py
new file mode 100644
index 0000000..a78b0ed
--- /dev/null
+++ b/leetcode/reverse_linked_list/helpers.py
@@ -0,0 +1,13 @@
+from leetcode_py import ListNode
+
+
+def run_reverse_list(solution_class: type, head_list: list[int]):
+ head = ListNode[int].from_list(head_list)
+ implementation = solution_class()
+ return implementation.reverse_list(head)
+
+
+def assert_reverse_list(result: ListNode[int] | None, expected_list: list[int]) -> bool:
+ expected = ListNode[int].from_list(expected_list)
+ assert result == expected
+ return True
diff --git a/leetcode/reverse_linked_list/playground.ipynb b/leetcode/reverse_linked_list/playground.ipynb
index 140f09b..b6a4171 100644
--- a/leetcode/reverse_linked_list/playground.ipynb
+++ b/leetcode/reverse_linked_list/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_reverse_list, run_reverse_list\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import ListNode"
@@ -14,139 +15,35 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Original: 1 -> 2 -> 3 -> 4 -> 5\n",
- "Expected: 5 -> 4 -> 3 -> 2 -> 1\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
- "# Example test case: reverse [1,2,3,4,5] to [5,4,3,2,1]\n",
+ "# Example test case\n",
"head_list = [1, 2, 3, 4, 5]\n",
- "expected_list = [5, 4, 3, 2, 1]\n",
- "\n",
- "# Convert lists to linked lists\n",
- "head = ListNode.from_list(head_list)\n",
- "expected = ListNode.from_list(expected_list)\n",
- "print(f\"Original: {head}\")\n",
- "print(f\"Expected: {expected}\")"
+ "expected_list = [5, 4, 3, 2, 1]"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Result: 5 -> 4 -> 3 -> 2 -> 1\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "ListNode([5, 4, 3, 2, 1])"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "# Reverse the linked list: 1->2->3->4->5 becomes 5->4->3->2->1\n",
- "result = Solution().reverse_list(head)\n",
- "print(f\"Result: {result}\")\n",
+ "result = run_reverse_list(Solution, head_list)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "# Verify the result matches expected output\n",
- "assert result == expected"
+ "assert_reverse_list(result, expected_list)"
]
}
],
@@ -164,8 +61,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/reverse_linked_list/solution.py b/leetcode/reverse_linked_list/solution.py
index 58a0cbe..ed4d433 100644
--- a/leetcode/reverse_linked_list/solution.py
+++ b/leetcode/reverse_linked_list/solution.py
@@ -2,6 +2,7 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
def reverse_list(self, head: ListNode[int] | None) -> ListNode[int] | None:
diff --git a/leetcode/reverse_linked_list/test_solution.py b/leetcode/reverse_linked_list/test_solution.py
new file mode 100644
index 0000000..38404d5
--- /dev/null
+++ b/leetcode/reverse_linked_list/test_solution.py
@@ -0,0 +1,31 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_reverse_list, run_reverse_list
+from .solution import Solution
+
+
+class TestReverseLinkedList:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "head_list, expected_list",
+ [
+ ([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]),
+ ([1, 2], [2, 1]),
+ ([1], [1]),
+ ([], []),
+ ([1, 2, 3], [3, 2, 1]),
+ ([1, 2, 3, 4], [4, 3, 2, 1]),
+ ([-1, -2, -3], [-3, -2, -1]),
+ ([0], [0]),
+ ([5000, -5000], [-5000, 5000]),
+ ([1, 1, 1], [1, 1, 1]),
+ ],
+ )
+ def test_reverse_list(self, head_list: list[int], expected_list: list[int]):
+ result = run_reverse_list(Solution, head_list)
+ assert_reverse_list(result, expected_list)
diff --git a/leetcode/reverse_linked_list/tests.py b/leetcode/reverse_linked_list/tests.py
deleted file mode 100644
index c8f51a9..0000000
--- a/leetcode/reverse_linked_list/tests.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import pytest
-
-from leetcode_py import ListNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestReverseLinkedList:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "head_list, expected_list",
- [
- ([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), # Basic case
- ([1, 2], [2, 1]), # Two nodes
- ([1], [1]), # Single node
- ([], []), # Empty list
- ([1, 2, 3], [3, 2, 1]), # Odd length
- ([1, 2, 3, 4], [4, 3, 2, 1]), # Even length
- ([-1, -2, -3], [-3, -2, -1]), # Negative values
- ([0], [0]), # Zero value
- ([5000, -5000], [-5000, 5000]), # Boundary values
- ([1, 1, 1], [1, 1, 1]), # Duplicate values
- ],
- )
- @logged_test
- def test_reverse_list(self, head_list: list[int], expected_list: list[int]):
- # Convert input list to linked list structure
- # e.g., [1,2,3] -> 1->2->3->None
- head = ListNode.from_list(head_list)
-
- # Convert expected result list to linked list for comparison
- # e.g., [3,2,1] -> 3->2->1->None
- expected = ListNode.from_list(expected_list)
-
- # Call the reverse_list method to reverse the linked list
- # This should transform 1->2->3->None into 3->2->1->None
- result = self.solution.reverse_list(head)
-
- # Verify that the reversed linked list matches expected output
- # ListNode supports direct equality comparison
- assert result == expected
diff --git a/leetcode/reverse_linked_list_ii/helpers.py b/leetcode/reverse_linked_list_ii/helpers.py
new file mode 100644
index 0000000..c518e6d
--- /dev/null
+++ b/leetcode/reverse_linked_list_ii/helpers.py
@@ -0,0 +1,13 @@
+from leetcode_py import ListNode
+
+
+def run_reverse_between(solution_class: type, head_list: list[int], left: int, right: int):
+ head = ListNode[int].from_list(head_list)
+ implementation = solution_class()
+ return implementation.reverse_between(head, left, right)
+
+
+def assert_reverse_between(result: ListNode[int] | None, expected_list: list[int]) -> bool:
+ expected = ListNode[int].from_list(expected_list)
+ assert result == expected
+ return True
diff --git a/leetcode/reverse_linked_list_ii/playground.ipynb b/leetcode/reverse_linked_list_ii/playground.ipynb
index bfdc6d9..0cd88a4 100644
--- a/leetcode/reverse_linked_list_ii/playground.ipynb
+++ b/leetcode/reverse_linked_list_ii/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_reverse_between, run_reverse_between\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import ListNode"
@@ -21,30 +22,29 @@
"source": [
"# Example test case\n",
"head_list = [1, 2, 3, 4, 5]\n",
- "head = ListNode[int].from_list(head_list)\n",
"left, right = 2, 4\n",
- "expected = ListNode[int].from_list([1, 4, 3, 2, 5])"
+ "expected_list = [1, 4, 3, 2, 5]"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().reverse_between(head, left, right)\n",
+ "result = run_reverse_between(Solution, head_list, left, right)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_reverse_between(result, expected_list)"
]
}
],
@@ -63,7 +63,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/reverse_linked_list_ii/solution.py b/leetcode/reverse_linked_list_ii/solution.py
index db9a3a0..59280dd 100644
--- a/leetcode/reverse_linked_list_ii/solution.py
+++ b/leetcode/reverse_linked_list_ii/solution.py
@@ -2,6 +2,7 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
def reverse_between(self, head: ListNode[int] | None, left: int, right: int) -> ListNode[int] | None:
diff --git a/leetcode/reverse_linked_list_ii/test_solution.py b/leetcode/reverse_linked_list_ii/test_solution.py
new file mode 100644
index 0000000..daecb86
--- /dev/null
+++ b/leetcode/reverse_linked_list_ii/test_solution.py
@@ -0,0 +1,29 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_reverse_between, run_reverse_between
+from .solution import Solution
+
+
+class TestReverseLinkedListII:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "head_list, left, right, expected_list",
+ [
+ ([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]),
+ ([5], 1, 1, [5]),
+ ([1, 2], 1, 2, [2, 1]),
+ ([1, 2, 3], 1, 3, [3, 2, 1]),
+ ([1, 2, 3, 4, 5], 1, 5, [5, 4, 3, 2, 1]),
+ ([1, 2, 3, 4, 5], 3, 3, [1, 2, 3, 4, 5]),
+ ],
+ )
+ def test_reverse_between(
+ self, head_list: list[int], left: int, right: int, expected_list: list[int]
+ ):
+ result = run_reverse_between(Solution, head_list, left, right)
+ assert_reverse_between(result, expected_list)
diff --git a/leetcode/reverse_linked_list_ii/tests.py b/leetcode/reverse_linked_list_ii/tests.py
deleted file mode 100644
index e3ea5b7..0000000
--- a/leetcode/reverse_linked_list_ii/tests.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import pytest
-
-from leetcode_py import ListNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestReverseLinkedListII:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "head_list, left, right, expected_list",
- [([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), ([5], 1, 1, [5])],
- )
- @logged_test
- def test_reverse_between(
- self, head_list: list[int], left: int, right: int, expected_list: list[int]
- ):
- head = ListNode[int].from_list(head_list)
- expected = ListNode[int].from_list(expected_list)
- result = self.solution.reverse_between(head, left, right)
- assert result == expected
diff --git a/leetcode/rotting_oranges/helpers.py b/leetcode/rotting_oranges/helpers.py
new file mode 100644
index 0000000..0395657
--- /dev/null
+++ b/leetcode/rotting_oranges/helpers.py
@@ -0,0 +1,8 @@
+def run_oranges_rotting(solution_class: type, grid: list[list[int]]):
+ implementation = solution_class()
+ return implementation.oranges_rotting(grid)
+
+
+def assert_oranges_rotting(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/rotting_oranges/playground.ipynb b/leetcode/rotting_oranges/playground.ipynb
index 077b70a..fde0cf1 100644
--- a/leetcode/rotting_oranges/playground.ipynb
+++ b/leetcode/rotting_oranges/playground.ipynb
@@ -6,7 +6,10 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": ["from solution import Solution"]
+ "source": [
+ "from helpers import assert_oranges_rotting, run_oranges_rotting\n",
+ "from solution import Solution"
+ ]
},
{
"cell_type": "code",
@@ -14,23 +17,32 @@
"id": "setup",
"metadata": {},
"outputs": [],
- "source": ["# Example test case\ngrid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\nexpected = 4"]
+ "source": [
+ "# Example test case\n",
+ "grid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\n",
+ "expected = 4"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
- "source": ["result = Solution().oranges_rotting(grid)\nresult"]
+ "source": [
+ "result = run_oranges_rotting(Solution, grid)\n",
+ "result"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
- "source": ["assert result == expected"]
+ "source": [
+ "assert_oranges_rotting(result, expected)"
+ ]
}
],
"metadata": {
@@ -48,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/rotting_oranges/solution.py b/leetcode/rotting_oranges/solution.py
index 747d2c9..bab56fd 100644
--- a/leetcode/rotting_oranges/solution.py
+++ b/leetcode/rotting_oranges/solution.py
@@ -2,6 +2,7 @@
class Solution:
+
# Time: O(m*n)
# Space: O(m*n)
def oranges_rotting(self, grid: list[list[int]]) -> int:
diff --git a/leetcode/rotting_oranges/test_solution.py b/leetcode/rotting_oranges/test_solution.py
new file mode 100644
index 0000000..9710fb8
--- /dev/null
+++ b/leetcode/rotting_oranges/test_solution.py
@@ -0,0 +1,32 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_oranges_rotting, run_oranges_rotting
+from .solution import Solution
+
+
+class TestRottingOranges:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "grid, expected",
+ [
+ ([[2, 1, 1], [1, 1, 0], [0, 1, 1]], 4),
+ ([[2, 1, 1], [0, 1, 1], [1, 0, 1]], -1),
+ ([[0, 2]], 0),
+ ([[0]], 0),
+ ([[1]], -1),
+ ([[2]], 0),
+ ([[1, 2]], 1),
+ ([[2, 1]], 1),
+ ([[0, 1, 2]], 1),
+ ([[2, 2], [1, 1], [0, 0]], 1),
+ ([[2, 1, 1], [1, 1, 1], [0, 1, 2]], 2),
+ ],
+ )
+ def test_oranges_rotting(self, grid: list[list[int]], expected: int):
+ result = run_oranges_rotting(Solution, grid)
+ assert_oranges_rotting(result, expected)
diff --git a/leetcode/rotting_oranges/tests.py b/leetcode/rotting_oranges/tests.py
deleted file mode 100644
index cbf8011..0000000
--- a/leetcode/rotting_oranges/tests.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestTestRottingOranges:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "grid, expected",
- [
- # Original examples
- ([[2, 1, 1], [1, 1, 0], [0, 1, 1]], 4),
- ([[2, 1, 1], [0, 1, 1], [1, 0, 1]], -1),
- ([[0, 2]], 0),
- # Single cell cases
- ([[0]], 0),
- ([[1]], -1),
- ([[2]], 0),
- # Two cell cases
- ([[1, 2]], 1),
- ([[2, 1]], 1),
- ([[0, 1, 2]], 1),
- # Multiple rotten sources
- ([[2, 2], [1, 1], [0, 0]], 1),
- ([[2, 1, 1], [1, 1, 1], [0, 1, 2]], 2),
- # All empty grid
- ([[0, 0, 0], [0, 0, 0]], 0),
- # All rotten grid
- ([[2, 2, 2], [2, 2, 2]], 0),
- # All fresh grid (impossible)
- ([[1, 1, 1], [1, 1, 1]], -1),
- # Large grid with barriers
- ([[2, 1, 0, 0, 1], [1, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 2]], 3),
- # Cross pattern
- ([[0, 1, 0], [1, 2, 1], [0, 1, 0]], 1),
- # Diagonal (no spread)
- ([[2, 0, 1], [0, 0, 0], [1, 0, 1]], -1),
- # Ring pattern
- ([[1, 1, 1], [1, 0, 1], [1, 1, 1]], -1),
- # Multiple disconnected fresh groups
- ([[2, 1, 0, 1], [0, 0, 0, 0], [1, 0, 0, 2]], -1),
- # Linear spread
- ([[2, 1, 1, 1, 1]], 4),
- # Vertical spread
- ([[2], [1], [1], [1]], 3),
- # Corner cases
- ([[2, 0, 0], [0, 0, 0], [0, 0, 1]], -1),
- ([[1, 0, 0], [0, 0, 0], [0, 0, 2]], -1),
- # Complex maze-like
- ([[2, 1, 1, 0, 1], [1, 0, 1, 0, 1], [1, 1, 1, 0, 2]], 4),
- ],
- )
- @logged_test
- def test_oranges_rotting(self, grid: list[list[int]], expected: int):
- result = self.solution.oranges_rotting(grid)
- assert result == expected
diff --git a/leetcode/search_in_rotated_sorted_array/helpers.py b/leetcode/search_in_rotated_sorted_array/helpers.py
new file mode 100644
index 0000000..70f8aee
--- /dev/null
+++ b/leetcode/search_in_rotated_sorted_array/helpers.py
@@ -0,0 +1,8 @@
+def run_search(solution_class: type, nums: list[int], target: int):
+ implementation = solution_class()
+ return implementation.search(nums, target)
+
+
+def assert_search(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/search_in_rotated_sorted_array/playground.ipynb b/leetcode/search_in_rotated_sorted_array/playground.ipynb
index 357fb33..6dadf29 100644
--- a/leetcode/search_in_rotated_sorted_array/playground.ipynb
+++ b/leetcode/search_in_rotated_sorted_array/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_search, run_search\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,34 +26,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "4"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().search(nums, target)\n",
+ "result = run_search(Solution, nums, target)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_search(result, expected)"
]
}
],
@@ -70,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/search_in_rotated_sorted_array/solution.py b/leetcode/search_in_rotated_sorted_array/solution.py
index d8af000..8de19ca 100644
--- a/leetcode/search_in_rotated_sorted_array/solution.py
+++ b/leetcode/search_in_rotated_sorted_array/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(log n)
# Space: O(1)
def search(self, nums: list[int], target: int) -> int:
diff --git a/leetcode/search_in_rotated_sorted_array/test_solution.py b/leetcode/search_in_rotated_sorted_array/test_solution.py
new file mode 100644
index 0000000..c63c101
--- /dev/null
+++ b/leetcode/search_in_rotated_sorted_array/test_solution.py
@@ -0,0 +1,30 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_search, run_search
+from .solution import Solution
+
+
+class TestSearchInRotatedSortedArray:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "nums, target, expected",
+ [
+ ([4, 5, 6, 7, 0, 1, 2], 0, 4),
+ ([4, 5, 6, 7, 0, 1, 2], 3, -1),
+ ([1], 0, -1),
+ ([1], 1, 0),
+ ([3, 1], 1, 1),
+ ([1, 3], 3, 1),
+ ([2, 1], 2, 0),
+ ([5, 1, 3], 3, 2),
+ ([4, 5, 6, 7, 8, 1, 2, 3], 8, 4),
+ ],
+ )
+ def test_search(self, nums: list[int], target: int, expected: int):
+ result = run_search(Solution, nums, target)
+ assert_search(result, expected)
diff --git a/leetcode/search_in_rotated_sorted_array/tests.py b/leetcode/search_in_rotated_sorted_array/tests.py
deleted file mode 100644
index 90cc27f..0000000
--- a/leetcode/search_in_rotated_sorted_array/tests.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestSearchInRotatedSortedArray:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "nums, target, expected",
- [
- # Original test cases
- ([4, 5, 6, 7, 0, 1, 2], 0, 4),
- ([4, 5, 6, 7, 0, 1, 2], 3, -1),
- ([1], 0, -1),
- ([1], 1, 0),
- ([3, 1], 1, 1),
- # No rotation (sorted array)
- ([1, 2, 3, 4, 5], 3, 2),
- ([1, 2, 3, 4, 5], 6, -1),
- # Different rotation points
- ([6, 7, 0, 1, 2, 4, 5], 0, 2),
- ([6, 7, 0, 1, 2, 4, 5], 4, 5),
- ([6, 7, 0, 1, 2, 4, 5], 7, 1),
- ([2, 3, 4, 5, 6, 7, 0, 1], 0, 6),
- # Target at boundaries
- ([4, 5, 6, 7, 0, 1, 2], 4, 0),
- ([4, 5, 6, 7, 0, 1, 2], 2, 6),
- # Two elements
- ([2, 1], 1, 1),
- ([2, 1], 2, 0),
- ([1, 3], 3, 1),
- ],
- )
- @logged_test
- def test_search(self, nums: list[int], target: int, expected: int):
- result = self.solution.search(nums, target)
- assert result == expected
diff --git a/leetcode/serialize_and_deserialize_binary_tree/helpers.py b/leetcode/serialize_and_deserialize_binary_tree/helpers.py
new file mode 100644
index 0000000..62809b6
--- /dev/null
+++ b/leetcode/serialize_and_deserialize_binary_tree/helpers.py
@@ -0,0 +1,19 @@
+from leetcode_py import TreeNode
+
+
+def run_serialize_deserialize(solution_class: type, root_list: list[int | None]):
+ root = TreeNode[int].from_list(root_list) if root_list else None
+ codec = solution_class()
+ serialized = codec.serialize(root)
+ deserialized = codec.deserialize(serialized)
+ return deserialized
+
+
+def assert_serialize_deserialize(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool:
+ expected = TreeNode[int].from_list(expected_list) if expected_list else None
+ if expected is None:
+ assert result is None
+ else:
+ assert result is not None
+ assert result.to_list() == expected.to_list()
+ return True
diff --git a/leetcode/serialize_and_deserialize_binary_tree/playground.ipynb b/leetcode/serialize_and_deserialize_binary_tree/playground.ipynb
index 36ca758..a1b0bf6 100644
--- a/leetcode/serialize_and_deserialize_binary_tree/playground.ipynb
+++ b/leetcode/serialize_and_deserialize_binary_tree/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_serialize_deserialize, run_serialize_deserialize\n",
"from solution import Codec\n",
"\n",
"from leetcode_py import TreeNode"
@@ -14,132 +15,34 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
"# Example test case\n",
- "root_list = [1, 2, 3, None, None, 4, 5]\n",
- "root = TreeNode.from_list(root_list) if root_list else None"
+ "root_list = [1, 2, 3, None, None, 4, 5]"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Original: [1, 2, 3, None, None, 4, 5]\n",
- "Serialized: 1,2,#,#,3,4,#,#,5,#,#\n",
- "Deserialized: [1, 2, 3, None, None, 4, 5]\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "\n",
- "\n",
- "\n"
- ],
- "text/plain": [
- "TreeNode([1, 2, 3, None, None, 4, 5])"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "codec = Codec()\n",
- "serialized = codec.serialize(root)\n",
- "deserialized = codec.deserialize(serialized)\n",
- "print(f\"Original: {root.to_list() if root else None}\")\n",
- "print(f\"Serialized: {serialized}\")\n",
- "print(f\"Deserialized: {deserialized.to_list() if deserialized else None}\")\n",
- "deserialized"
+ "result = run_serialize_deserialize(Codec, root_list)\n",
+ "result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "if root is None:\n",
- " assert deserialized is None\n",
- "else:\n",
- " assert deserialized is not None\n",
- " assert deserialized.to_list() == root.to_list()"
+ "assert_serialize_deserialize(result, root_list)"
]
}
],
@@ -157,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/serialize_and_deserialize_binary_tree/solution.py b/leetcode/serialize_and_deserialize_binary_tree/solution.py
index aeb3177..14c613c 100644
--- a/leetcode/serialize_and_deserialize_binary_tree/solution.py
+++ b/leetcode/serialize_and_deserialize_binary_tree/solution.py
@@ -5,10 +5,15 @@ class Codec:
# Preorder with Null Markers
# Time: O(n)
# Space: O(n)
- def serialize(self, root: TreeNode | None) -> str:
+ def __init__(self) -> None:
+ pass
+
+ # Time: O(n)
+ # Space: O(n)
+ def serialize(self, root: TreeNode[int] | None) -> str:
vals = []
- def dfs(node: TreeNode | None):
+ def dfs(node: TreeNode[int] | None):
if not node:
vals.append("#")
return
@@ -21,14 +26,14 @@ def dfs(node: TreeNode | None):
# Time: O(n)
# Space: O(n)
- def deserialize(self, data: str) -> TreeNode | None:
+ def deserialize(self, data: str) -> TreeNode[int] | None:
vals = iter(data.split(","))
def dfs():
val = next(vals)
if val == "#":
return None
- node = TreeNode(int(val))
+ node = TreeNode[int](int(val))
node.left = dfs()
node.right = dfs()
return node
diff --git a/leetcode/serialize_and_deserialize_binary_tree/test_solution.py b/leetcode/serialize_and_deserialize_binary_tree/test_solution.py
new file mode 100644
index 0000000..4e523e9
--- /dev/null
+++ b/leetcode/serialize_and_deserialize_binary_tree/test_solution.py
@@ -0,0 +1,26 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_serialize_deserialize, run_serialize_deserialize
+from .solution import Codec
+
+
+class TestSerializeAndDeserializeBinaryTree:
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "root_list",
+ [
+ ([1, 2, 3, None, None, 4, 5]),
+ ([]),
+ ([1]),
+ ([1, 2]),
+ ([1, None, 2]),
+ ([1, 2, 3, 4, 5, 6, 7]),
+ ([5, 2, 3, None, None, 2, 4, 3, 1]),
+ ],
+ )
+ def test_serialize_deserialize(self, root_list: list[int | None]):
+ result = run_serialize_deserialize(Codec, root_list)
+ assert_serialize_deserialize(result, root_list)
diff --git a/leetcode/serialize_and_deserialize_binary_tree/tests.py b/leetcode/serialize_and_deserialize_binary_tree/tests.py
deleted file mode 100644
index f1fb387..0000000
--- a/leetcode/serialize_and_deserialize_binary_tree/tests.py
+++ /dev/null
@@ -1,100 +0,0 @@
-import pytest
-
-from leetcode_py import TreeNode
-from leetcode_py.test_utils import logged_test
-
-from .solution import Codec
-
-
-class TestSerializeAndDeserializeBinaryTree:
- def setup_method(self):
- self.codec = Codec()
-
- @pytest.mark.parametrize(
- "root_list",
- [
- # Original test cases
- ([1, 2, 3, None, None, 4, 5]),
- ([]),
- ([1]),
- ([1, 2]),
- ([1, None, 2]),
- ([1, 2, 3, 4, 5, 6, 7]),
- ([5, 2, 3, None, None, 2, 4, 3, 1]),
- # Edge cases
- ([0]), # Single node with value 0
- ([-1]), # Single node with negative value
- ([1000]), # Single node with max value
- ([-1000]), # Single node with min value
- # Skewed trees
- ([1, 2, None, 3, None, 4, None]), # Left skewed
- ([1, None, 2, None, 3, None, 4]), # Right skewed
- # Trees with negative values
- ([-5, -3, -8, -2, -1, -7, -9]),
- ([0, -1, 1, -2, None, None, 2]),
- # Larger trees
- ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
- # Trees with mixed null patterns
- ([1, None, 2, None, 3, None, 4, None, 5]),
- ([1, 2, None, 3, None, 4, None, 5]),
- ([5, 4, 7, 3, None, 2, None, -1, None, 9]),
- # Duplicate values
- ([1, 1, 1, 1, 1, 1, 1]),
- ([2, 2, None, 2, None]),
- # Complex asymmetric trees
- ([10, 5, 15, None, 6, 12, 20, None, None, None, 13, 18, 25]),
- ([50, 30, 70, 20, 40, 60, 80, 10, 25, 35, 45]),
- ],
- )
- @logged_test
- def test_serialize_deserialize(self, root_list: list[int | None]):
- root = TreeNode.from_list(root_list) if root_list else None
- serialized = self.codec.serialize(root)
- deserialized = self.codec.deserialize(serialized)
- if root is None:
- assert deserialized is None
- else:
- assert deserialized is not None
- assert deserialized.to_list() == root.to_list()
-
- @logged_test
- def test_multiple_serialize_deserialize_cycles(self):
- """Test that multiple serialize/deserialize cycles preserve the tree"""
- root_list = [1, 2, 3, None, None, 4, 5]
- root = TreeNode.from_list(root_list)
-
- # Perform multiple cycles
- current = root
- for _ in range(3):
- serialized = self.codec.serialize(current)
- current = self.codec.deserialize(serialized)
-
- assert current is not None
- assert current.to_list() == root_list
-
- @logged_test
- def test_serialization_format(self):
- """Test that serialization produces expected string format"""
- # Simple tree: [1, 2, 3]
- root = TreeNode.from_list([1, 2, 3])
- serialized = self.codec.serialize(root)
-
- # Should be preorder: root, left, right with # for null
- assert serialized == "1,2,#,#,3,#,#"
-
- # Empty tree
- serialized_empty = self.codec.serialize(None)
- assert serialized_empty == "#"
-
- @logged_test
- def test_deserialization_edge_cases(self):
- """Test deserialization with various string inputs"""
- # Single null
- assert self.codec.deserialize("#") is None
-
- # Single node
- single = self.codec.deserialize("42,#,#")
- assert single is not None
- assert single.val == 42
- assert single.left is None
- assert single.right is None
diff --git a/leetcode/sort_colors/helpers.py b/leetcode/sort_colors/helpers.py
new file mode 100644
index 0000000..5dcd8e9
--- /dev/null
+++ b/leetcode/sort_colors/helpers.py
@@ -0,0 +1,10 @@
+def run_sort_colors(solution_class: type, nums: list[int]):
+ nums_copy = nums.copy()
+ implementation = solution_class()
+ implementation.sort_colors(nums_copy)
+ return nums_copy
+
+
+def assert_sort_colors(result: list[int], expected: list[int]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/sort_colors/playground.ipynb b/leetcode/sort_colors/playground.ipynb
index 33d81fa..b2f099f 100644
--- a/leetcode/sort_colors/playground.ipynb
+++ b/leetcode/sort_colors/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_sort_colors, run_sort_colors\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -24,35 +25,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[0, 0, 1, 1, 2, 2]"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "nums_copy = nums.copy()\n",
- "Solution().sort_colors(nums_copy)\n",
- "nums_copy"
+ "result = run_sort_colors(Solution, nums)\n",
+ "result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert nums_copy == expected"
+ "assert_sort_colors(result, expected)"
]
}
],
@@ -70,8 +59,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/sort_colors/solution.py b/leetcode/sort_colors/solution.py
index 003cf4a..9e21708 100644
--- a/leetcode/sort_colors/solution.py
+++ b/leetcode/sort_colors/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Dutch National Flag Algorithm - partitions array into 3 regions using 2 pointers
# Creates: [0s][1s][2s] with left/right boundaries, mid processes unvisited elements
# Time: O(n)
diff --git a/leetcode/sort_colors/tests.py b/leetcode/sort_colors/test_solution.py
similarity index 67%
rename from leetcode/sort_colors/tests.py
rename to leetcode/sort_colors/test_solution.py
index a500d9d..df38daf 100644
--- a/leetcode/sort_colors/tests.py
+++ b/leetcode/sort_colors/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_sort_colors, run_sort_colors
from .solution import Solution
@@ -9,6 +10,7 @@ class TestSortColors:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"nums, expected",
[
@@ -18,10 +20,11 @@ def setup_method(self):
([1], [1]),
([2], [2]),
([0, 1, 2], [0, 1, 2]),
+ ([2, 2, 2], [2, 2, 2]),
+ ([0, 0, 0], [0, 0, 0]),
+ ([1, 1, 1], [1, 1, 1]),
],
)
- @logged_test
def test_sort_colors(self, nums: list[int], expected: list[int]):
- nums_copy = nums.copy()
- self.solution.sort_colors(nums_copy)
- assert nums_copy == expected
+ result = run_sort_colors(Solution, nums)
+ assert_sort_colors(result, expected)
diff --git a/leetcode/spiral_matrix/helpers.py b/leetcode/spiral_matrix/helpers.py
new file mode 100644
index 0000000..5b6456f
--- /dev/null
+++ b/leetcode/spiral_matrix/helpers.py
@@ -0,0 +1,8 @@
+def run_spiral_order(solution_class: type, matrix: list[list[int]]):
+ implementation = solution_class()
+ return implementation.spiral_order(matrix)
+
+
+def assert_spiral_order(result: list[int], expected: list[int]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/spiral_matrix/playground.ipynb b/leetcode/spiral_matrix/playground.ipynb
index c219f95..0668a7e 100644
--- a/leetcode/spiral_matrix/playground.ipynb
+++ b/leetcode/spiral_matrix/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_spiral_order, run_spiral_order\n",
"from solution import Solution"
]
},
@@ -25,22 +26,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().spiral_order(matrix)\n",
+ "result = run_spiral_order(Solution, matrix)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_spiral_order(result, expected)"
]
}
],
@@ -59,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/spiral_matrix/solution.py b/leetcode/spiral_matrix/solution.py
index 65e1113..276d25e 100644
--- a/leetcode/spiral_matrix/solution.py
+++ b/leetcode/spiral_matrix/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(m*n)
# Space: O(1)
def spiral_order(self, matrix: list[list[int]]) -> list[int]:
diff --git a/leetcode/spiral_matrix/tests.py b/leetcode/spiral_matrix/test_solution.py
similarity index 53%
rename from leetcode/spiral_matrix/tests.py
rename to leetcode/spiral_matrix/test_solution.py
index 8e3233c..6d36a02 100644
--- a/leetcode/spiral_matrix/tests.py
+++ b/leetcode/spiral_matrix/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_spiral_order, run_spiral_order
from .solution import Solution
@@ -9,32 +10,19 @@ class TestSpiralMatrix:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"matrix, expected",
[
- # Original cases
([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [1, 2, 3, 6, 9, 8, 7, 4, 5]),
([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]], [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]),
- # Single element
([[1]], [1]),
- # Single row
- ([[1, 2, 3, 4]], [1, 2, 3, 4]),
- # Single column
- ([[1], [2], [3]], [1, 2, 3]),
- # 2x2 matrix
- ([[1, 2], [3, 4]], [1, 2, 4, 3]),
- # 1x2 matrix
([[1, 2]], [1, 2]),
- # 2x1 matrix
([[1], [2]], [1, 2]),
- # Larger square matrix
- (
- [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]],
- [1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10],
- ),
+ ([[1, 2, 3]], [1, 2, 3]),
+ ([[1], [2], [3]], [1, 2, 3]),
],
)
- @logged_test
def test_spiral_order(self, matrix: list[list[int]], expected: list[int]):
- result = self.solution.spiral_order(matrix)
- assert result == expected
+ result = run_spiral_order(Solution, matrix)
+ assert_spiral_order(result, expected)
diff --git a/leetcode/string_to_integer_atoi/helpers.py b/leetcode/string_to_integer_atoi/helpers.py
new file mode 100644
index 0000000..623972c
--- /dev/null
+++ b/leetcode/string_to_integer_atoi/helpers.py
@@ -0,0 +1,8 @@
+def run_my_atoi(solution_class: type, s: str):
+ implementation = solution_class()
+ return implementation.my_atoi(s)
+
+
+def assert_my_atoi(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/string_to_integer_atoi/playground.ipynb b/leetcode/string_to_integer_atoi/playground.ipynb
index 012d088..82ecfb7 100644
--- a/leetcode/string_to_integer_atoi/playground.ipynb
+++ b/leetcode/string_to_integer_atoi/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_my_atoi, run_my_atoi\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -24,34 +25,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "42"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().my_atoi(s)\n",
+ "result = run_my_atoi(Solution, s)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_my_atoi(result, expected)"
]
}
],
@@ -69,8 +59,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/string_to_integer_atoi/solution.py b/leetcode/string_to_integer_atoi/solution.py
index 42c5c62..7f69674 100644
--- a/leetcode/string_to_integer_atoi/solution.py
+++ b/leetcode/string_to_integer_atoi/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
def my_atoi(self, s: str) -> int:
diff --git a/leetcode/string_to_integer_atoi/tests.py b/leetcode/string_to_integer_atoi/test_solution.py
similarity index 56%
rename from leetcode/string_to_integer_atoi/tests.py
rename to leetcode/string_to_integer_atoi/test_solution.py
index b3c2a54..d09898c 100644
--- a/leetcode/string_to_integer_atoi/tests.py
+++ b/leetcode/string_to_integer_atoi/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_my_atoi, run_my_atoi
from .solution import Solution
@@ -9,6 +10,7 @@ class TestStringToIntegerAtoi:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"s, expected",
[
@@ -25,25 +27,8 @@ def setup_method(self):
("-2147483648", -2147483648),
("2147483648", 2147483647),
("-2147483649", -2147483648),
- # Edge cases
- ("+-12", 0),
- ("-+12", 0),
- ("++1", 0),
- ("--1", 0),
- (" +0 123", 0),
- ("0000000000012345678", 12345678),
- ("-000000000000001", -1),
- (" +000", 0),
- ("123-", 123),
- (" 13 5", 13),
- (".1", 0),
- ("1a33", 1),
- (" -0012a42", -12),
- ("21474836460", 2147483647),
- ("-21474836480", -2147483648),
],
)
- @logged_test
def test_my_atoi(self, s: str, expected: int):
- result = self.solution.my_atoi(s)
- assert result == expected
+ result = run_my_atoi(Solution, s)
+ assert_my_atoi(result, expected)
diff --git a/leetcode/task_scheduler/helpers.py b/leetcode/task_scheduler/helpers.py
new file mode 100644
index 0000000..78a421a
--- /dev/null
+++ b/leetcode/task_scheduler/helpers.py
@@ -0,0 +1,8 @@
+def run_least_interval(solution_class: type, tasks: list[str], n: int):
+ implementation = solution_class()
+ return implementation.least_interval(tasks, n)
+
+
+def assert_least_interval(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/task_scheduler/playground.ipynb b/leetcode/task_scheduler/playground.ipynb
index 7f76881..3ad7d83 100644
--- a/leetcode/task_scheduler/playground.ipynb
+++ b/leetcode/task_scheduler/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_least_interval, run_least_interval\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,42 +26,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Counter({'A': 3, 'B': 3})\n",
- "[-3, -3]\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "8"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().least_interval(tasks, n)\n",
+ "result = run_least_interval(Solution, tasks, n)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_least_interval(result, expected)"
]
}
],
@@ -78,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/task_scheduler/tests.py b/leetcode/task_scheduler/test_solution.py
similarity index 53%
rename from leetcode/task_scheduler/tests.py
rename to leetcode/task_scheduler/test_solution.py
index c04842f..b2db53e 100644
--- a/leetcode/task_scheduler/tests.py
+++ b/leetcode/task_scheduler/test_solution.py
@@ -2,10 +2,13 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_least_interval, run_least_interval
from .solution import Solution, SolutionGreedy
class TestTaskScheduler:
+
+ @logged_test
@pytest.mark.parametrize("solution_class", [Solution, SolutionGreedy])
@pytest.mark.parametrize(
"tasks, n, expected",
@@ -13,18 +16,11 @@ class TestTaskScheduler:
(["A", "A", "A", "B", "B", "B"], 2, 8),
(["A", "C", "A", "B", "D", "B"], 1, 6),
(["A", "A", "A", "B", "B", "B"], 3, 10),
- (["A", "A", "A", "A", "A", "A", "B", "C", "D", "E", "F", "G"], 2, 16),
- (["A"], 2, 1),
+ (["A"], 0, 1),
+ (["A", "A"], 1, 3),
+ (["A", "B"], 0, 2),
],
)
- @logged_test
- def test_least_interval(
- self,
- tasks: list[str],
- n: int,
- expected: int,
- solution_class: type[Solution | SolutionGreedy],
- ):
- solution = solution_class()
- result = solution.least_interval(tasks, n)
- assert result == expected
+ def test_least_interval(self, tasks: list[str], n: int, expected: int, solution_class: type):
+ result = run_least_interval(solution_class, tasks, n)
+ assert_least_interval(result, expected)
diff --git a/leetcode/three_sum/helpers.py b/leetcode/three_sum/helpers.py
new file mode 100644
index 0000000..e85c19f
--- /dev/null
+++ b/leetcode/three_sum/helpers.py
@@ -0,0 +1,13 @@
+def run_three_sum(solution_class: type, nums: list[int]):
+ implementation = solution_class()
+ return implementation.three_sum(nums)
+
+
+def assert_three_sum(result: list[list[int]], expected: list[list[int]]) -> bool:
+ # Sort both result and expected for comparison since order doesn't matter
+ result_sorted = [sorted(triplet) for triplet in result]
+ expected_sorted = [sorted(triplet) for triplet in expected]
+ result_sorted.sort()
+ expected_sorted.sort()
+ assert result_sorted == expected_sorted
+ return True
diff --git a/leetcode/three_sum/playground.ipynb b/leetcode/three_sum/playground.ipynb
index 007270c..ddbc41b 100644
--- a/leetcode/three_sum/playground.ipynb
+++ b/leetcode/three_sum/playground.ipynb
@@ -6,7 +6,10 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": ["from solution import Solution"]
+ "source": [
+ "from helpers import assert_three_sum, run_three_sum\n",
+ "from solution import Solution"
+ ]
},
{
"cell_type": "code",
@@ -14,23 +17,32 @@
"id": "setup",
"metadata": {},
"outputs": [],
- "source": ["# Example test case\nnums = [-1, 0, 1, 2, -1, -4]\nexpected = [[-1, -1, 2], [-1, 0, 1]]"]
+ "source": [
+ "# Example test case\n",
+ "nums = [-1, 0, 1, 2, -1, -4]\n",
+ "expected = [[-1, -1, 2], [-1, 0, 1]]"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
- "source": ["result = Solution().three_sum(nums)\nresult"]
+ "source": [
+ "result = run_three_sum(Solution, nums)\n",
+ "result"
+ ]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
- "source": ["# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(triplet) for triplet in result]\nexpected_sorted = [sorted(triplet) for triplet in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"]
+ "source": [
+ "assert_three_sum(result, expected)"
+ ]
}
],
"metadata": {
@@ -48,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/three_sum/solution.py b/leetcode/three_sum/solution.py
index 6846e00..7e44a03 100644
--- a/leetcode/three_sum/solution.py
+++ b/leetcode/three_sum/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n^2)
# Space: O(k) where k is number of unique triplets
def three_sum(self, nums: list[int]) -> list[list[int]]:
diff --git a/leetcode/three_sum/test_solution.py b/leetcode/three_sum/test_solution.py
new file mode 100644
index 0000000..9ec9f76
--- /dev/null
+++ b/leetcode/three_sum/test_solution.py
@@ -0,0 +1,27 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_three_sum, run_three_sum
+from .solution import Solution
+
+
+class TestThreeSum:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "nums, expected",
+ [
+ ([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]),
+ ([0, 1, 1], []),
+ ([0, 0, 0], [[0, 0, 0]]),
+ ([-1, 0, 1], [[-1, 0, 1]]),
+ ([1, 2, -2, -1], []),
+ ([-2, 0, 1, 1, 2], [[-2, 0, 2], [-2, 1, 1]]),
+ ],
+ )
+ def test_three_sum(self, nums: list[int], expected: list[list[int]]):
+ result = run_three_sum(Solution, nums)
+ assert_three_sum(result, expected)
diff --git a/leetcode/three_sum/tests.py b/leetcode/three_sum/tests.py
deleted file mode 100644
index 9448085..0000000
--- a/leetcode/three_sum/tests.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestThreeSum:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "nums, expected",
- [
- ([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]),
- ([0, 1, 1], []),
- ([0, 0, 0], [[0, 0, 0]]),
- ([-1, 0, 1], [[-1, 0, 1]]),
- ([1, 2, -2, -1], []),
- ([-2, 0, 1, 1, 2], [[-2, 0, 2], [-2, 1, 1]]),
- # Edge cases
- ([1, 2, 3], []), # All positive
- ([-3, -2, -1], []), # All negative
- ([0, 0, 0, 0], [[0, 0, 0]]), # Multiple zeros
- ([-1, -1, 2, 2], [[-1, -1, 2]]), # Duplicate pairs
- ([3, 0, -2, -1, 1, 2], [[-2, -1, 3], [-2, 0, 2], [-1, 0, 1]]), # Multiple solutions
- (
- [-4, -2, -2, -2, 0, 1, 2, 2, 2, 3, 3, 4, 4, 6, 6],
- [[-4, -2, 6], [-4, 0, 4], [-4, 1, 3], [-4, 2, 2], [-2, -2, 4], [-2, 0, 2]],
- ), # Many duplicates
- ([1, -1, 0], [[-1, 0, 1]]), # Simple case
- ([2, -1, -1], [[-1, -1, 2]]), # Solution with duplicates
- ],
- )
- @logged_test
- def test_three_sum(self, nums: list[int], expected: list[list[int]]):
- result = self.solution.three_sum(nums)
- # Sort both result and expected for comparison since order doesn't matter
- result_sorted = [sorted(triplet) for triplet in result]
- expected_sorted = [sorted(triplet) for triplet in expected]
- result_sorted.sort()
- expected_sorted.sort()
- assert result_sorted == expected_sorted
diff --git a/leetcode/time_based_key_value_store/helpers.py b/leetcode/time_based_key_value_store/helpers.py
new file mode 100644
index 0000000..05b7e89
--- /dev/null
+++ b/leetcode/time_based_key_value_store/helpers.py
@@ -0,0 +1,19 @@
+def run_time_map_operations(solution_class: type, operations: list[str], inputs: list[list]):
+ time_map = None
+ results: list[str | None] = []
+ for i, op in enumerate(operations):
+ if op == "TimeMap":
+ time_map = solution_class()
+ results.append(None)
+ elif op == "set" and time_map is not None:
+ time_map.set(*inputs[i])
+ results.append(None)
+ elif op == "get" and time_map is not None:
+ results.append(time_map.get(*inputs[i]))
+ return results, time_map
+
+
+def assert_time_map_operations(result: list, expected: list) -> bool:
+ results, _ = result
+ assert results == expected
+ return True
diff --git a/leetcode/time_based_key_value_store/playground.ipynb b/leetcode/time_based_key_value_store/playground.ipynb
index d0567d4..28f407a 100644
--- a/leetcode/time_based_key_value_store/playground.ipynb
+++ b/leetcode/time_based_key_value_store/playground.ipynb
@@ -2,66 +2,47 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_time_map_operations, run_time_map_operations\n",
"from solution import TimeMap"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
"# Example test case\n",
- "time_map = TimeMap()\n",
- "time_map.set(\"foo\", \"bar\", 1)\n",
- "result1 = time_map.get(\"foo\", 1)\n",
- "result2 = time_map.get(\"foo\", 3)\n",
- "time_map.set(\"foo\", \"bar2\", 4)\n",
- "result3 = time_map.get(\"foo\", 4)\n",
- "result4 = time_map.get(\"foo\", 5)"
+ "operations = [\"TimeMap\", \"set\", \"get\", \"get\", \"set\", \"get\", \"get\"]\n",
+ "inputs = [[], [\"foo\", \"bar\", 1], [\"foo\", 1], [\"foo\", 3], [\"foo\", \"bar2\", 4], [\"foo\", 4], [\"foo\", 5]]\n",
+ "expected = [None, None, \"bar\", \"bar\", None, \"bar2\", \"bar2\"]"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "get(foo, 1): bar\n",
- "get(foo, 3): bar\n",
- "get(foo, 4): bar2\n",
- "get(foo, 5): bar2\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
- "print(f\"get(foo, 1): {result1}\")\n",
- "print(f\"get(foo, 3): {result2}\")\n",
- "print(f\"get(foo, 4): {result3}\")\n",
- "print(f\"get(foo, 5): {result4}\")"
+ "result = run_time_map_operations(TimeMap, operations, inputs)\n",
+ "result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result1 == \"bar\"\n",
- "assert result2 == \"bar\"\n",
- "assert result3 == \"bar2\"\n",
- "assert result4 == \"bar2\""
+ "assert_time_map_operations(result, expected)"
]
}
],
@@ -79,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/time_based_key_value_store/solution.py b/leetcode/time_based_key_value_store/solution.py
index fcb11cd..cb51d7a 100644
--- a/leetcode/time_based_key_value_store/solution.py
+++ b/leetcode/time_based_key_value_store/solution.py
@@ -1,9 +1,7 @@
class TimeMap:
# Time: O(1)
# Space: O(n)
- def __init__(
- self,
- ) -> None:
+ def __init__(self) -> None:
self.store: dict[str, list[tuple[int, str]]] = {}
# Time: O(1)
diff --git a/leetcode/time_based_key_value_store/test_solution.py b/leetcode/time_based_key_value_store/test_solution.py
new file mode 100644
index 0000000..c0c16ff
--- /dev/null
+++ b/leetcode/time_based_key_value_store/test_solution.py
@@ -0,0 +1,102 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_time_map_operations, run_time_map_operations
+from .solution import TimeMap
+
+
+class TestTimeBasedKeyValueStore:
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "operations, inputs, expected",
+ [
+ (
+ ["TimeMap", "set", "get", "get", "set", "get", "get"],
+ [
+ [],
+ ["foo", "bar", 1],
+ ["foo", 1],
+ ["foo", 3],
+ ["foo", "bar2", 4],
+ ["foo", 4],
+ ["foo", 5],
+ ],
+ [None, None, "bar", "bar", None, "bar2", "bar2"],
+ ),
+ (["TimeMap", "get"], [[], ["key", 1]], [None, ""]),
+ (["TimeMap", "set", "get"], [[], ["a", "val", 1], ["a", 1]], [None, None, "val"]),
+ (
+ ["TimeMap", "set", "get", "get"],
+ [[], ["key", "value", 5], ["key", 3], ["key", 7]],
+ [None, None, "", "value"],
+ ),
+ (
+ ["TimeMap", "set", "set", "get", "get", "get"],
+ [[], ["x", "v1", 1], ["x", "v2", 2], ["x", 1], ["x", 2], ["x", 3]],
+ [None, None, None, "v1", "v2", "v2"],
+ ),
+ (
+ ["TimeMap", "set", "set", "set", "get", "get", "get"],
+ [[], ["k", "a", 10], ["k", "b", 20], ["k", "c", 30], ["k", 15], ["k", 25], ["k", 35]],
+ [None, None, None, None, "a", "b", "c"],
+ ),
+ (
+ ["TimeMap", "set", "set", "get", "get"],
+ [[], ["key1", "val1", 1], ["key2", "val2", 2], ["key1", 1], ["key2", 2]],
+ [None, None, None, "val1", "val2"],
+ ),
+ (
+ ["TimeMap", "set", "set", "set", "get", "get", "get"],
+ [[], ["a", "x", 1], ["b", "y", 2], ["c", "z", 3], ["a", 1], ["b", 2], ["c", 3]],
+ [None, None, None, None, "x", "y", "z"],
+ ),
+ (
+ ["TimeMap", "set", "get", "set", "get", "set", "get"],
+ [
+ [],
+ ["test", "first", 1],
+ ["test", 1],
+ ["test", "second", 100],
+ ["test", 50],
+ ["test", "third", 1000],
+ ["test", 500],
+ ],
+ [None, None, "first", None, "first", None, "second"],
+ ),
+ (
+ ["TimeMap", "set", "set", "set", "set", "get"],
+ [
+ [],
+ ["data", "v1", 1],
+ ["data", "v2", 10],
+ ["data", "v3", 100],
+ ["data", "v4", 1000],
+ ["data", 555],
+ ],
+ [None, None, None, None, None, "v3"],
+ ),
+ (
+ ["TimeMap", "set", "get", "get", "get"],
+ [[], ["single", "value", 42], ["single", 1], ["single", 42], ["single", 100]],
+ [None, None, "", "value", "value"],
+ ),
+ (
+ ["TimeMap", "set", "set", "get", "get", "get", "get"],
+ [
+ [],
+ ["boundary", "min", 1],
+ ["boundary", "max", 10000000],
+ ["boundary", 0],
+ ["boundary", 1],
+ ["boundary", 5000000],
+ ["boundary", 10000000],
+ ],
+ [None, None, None, "", "min", "min", "max"],
+ ),
+ ],
+ )
+ def test_time_map_operations(self, operations: list[str], inputs: list[list], expected: list):
+ result = run_time_map_operations(TimeMap, operations, inputs)
+ assert_time_map_operations(result, expected)
diff --git a/leetcode/time_based_key_value_store/tests.py b/leetcode/time_based_key_value_store/tests.py
deleted file mode 100644
index f4c23a0..0000000
--- a/leetcode/time_based_key_value_store/tests.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import TimeMap
-
-
-class TestTimeBasedKeyValueStore:
- @pytest.mark.parametrize(
- "operations, inputs, expected",
- [
- (
- ["TimeMap", "set", "get", "get", "set", "get", "get"],
- [
- [],
- ["foo", "bar", 1],
- ["foo", 1],
- ["foo", 3],
- ["foo", "bar2", 4],
- ["foo", 4],
- ["foo", 5],
- ],
- [None, None, "bar", "bar", None, "bar2", "bar2"],
- )
- ],
- )
- @logged_test
- def test_time_map_operations(self, operations: list[str], inputs: list[list], expected: list):
- time_map: TimeMap | None = None
- result: list[str | None] = []
- for i, op in enumerate(operations):
- if op == "TimeMap":
- time_map = TimeMap()
- result.append(None)
- elif op == "set" and time_map is not None:
- time_map.set(*inputs[i])
- result.append(None)
- elif op == "get" and time_map is not None:
- result.append(time_map.get(*inputs[i]))
- assert result == expected
diff --git a/leetcode/trapping_rain_water/helpers.py b/leetcode/trapping_rain_water/helpers.py
new file mode 100644
index 0000000..0fc4b5e
--- /dev/null
+++ b/leetcode/trapping_rain_water/helpers.py
@@ -0,0 +1,8 @@
+def run_trap(solution_class: type, height: list[int]):
+ implementation = solution_class()
+ return implementation.trap(height)
+
+
+def assert_trap(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/trapping_rain_water/playground.ipynb b/leetcode/trapping_rain_water/playground.ipynb
index be2bf9a..b4db37d 100644
--- a/leetcode/trapping_rain_water/playground.ipynb
+++ b/leetcode/trapping_rain_water/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_trap, run_trap\n",
"from solution import Solution"
]
},
@@ -25,7 +26,7 @@
{
"cell_type": "code",
"execution_count": 3,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [
{
@@ -40,18 +41,29 @@
}
],
"source": [
- "result = Solution().trap(height)\n",
+ "result = run_trap(Solution, height)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "test",
+ "id": "assert",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "assert result == expected"
+ "assert_trap(result, expected)"
]
}
],
diff --git a/leetcode/trapping_rain_water/solution.py b/leetcode/trapping_rain_water/solution.py
index dd84800..2596f30 100644
--- a/leetcode/trapping_rain_water/solution.py
+++ b/leetcode/trapping_rain_water/solution.py
@@ -1,31 +1,5 @@
-# class Solution:
-# # Time: O(n)
-# # Space: O(1)
-# def trap(self, height: list[int]) -> int:
-# if not height:
-# return 0
-
-# left, right = 0, len(height) - 1
-# left_max = right_max = water = 0
-
-# while left < right:
-# if height[left] < height[right]:
-# if height[left] >= left_max:
-# left_max = height[left]
-# else:
-# water += left_max - height[left]
-# left += 1
-# else:
-# if height[right] >= right_max:
-# right_max = height[right]
-# else:
-# water += right_max - height[right]
-# right -= 1
-
-# return water
-
-
class Solution:
+
# Time: O(n)
# Space: O(1)
def trap(self, height: list[int]) -> int:
diff --git a/leetcode/trapping_rain_water/test_solution.py b/leetcode/trapping_rain_water/test_solution.py
new file mode 100644
index 0000000..fb1fc4c
--- /dev/null
+++ b/leetcode/trapping_rain_water/test_solution.py
@@ -0,0 +1,28 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_trap, run_trap
+from .solution import Solution
+
+
+class TestTrappingRainWater:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "height, expected",
+ [
+ ([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6),
+ ([4, 2, 0, 3, 2, 5], 9),
+ ([3, 0, 2, 0, 4], 7),
+ ([0], 0),
+ ([1], 0),
+ ([1, 2], 0),
+ ([2, 1], 0),
+ ],
+ )
+ def test_trap(self, height: list[int], expected: int):
+ result = run_trap(Solution, height)
+ assert_trap(result, expected)
diff --git a/leetcode/trapping_rain_water/tests.py b/leetcode/trapping_rain_water/tests.py
deleted file mode 100644
index 2a3b912..0000000
--- a/leetcode/trapping_rain_water/tests.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestTrappingRainWater:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "height, expected",
- [
- # Original examples
- ([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6),
- ([4, 2, 0, 3, 2, 5], 9),
- ([3, 0, 2, 0, 4], 7),
- # Edge cases
- ([], 0),
- ([1], 0),
- ([1, 2], 0),
- ([2, 1], 0),
- # No water trapped
- ([1, 2, 3, 4, 5], 0),
- ([5, 4, 3, 2, 1], 0),
- # Simple cases
- ([3, 0, 3], 3),
- ([2, 1, 2], 1),
- ([5, 2, 7, 2, 6, 1, 5, 3, 2, 4], 14),
- # All same height
- ([3, 3, 3, 3], 0),
- # Valley pattern
- ([3, 2, 1, 2, 3], 4),
- # Multiple peaks
- ([0, 2, 0, 4, 0, 3, 0, 4, 0, 2, 0], 13),
- ],
- )
- @logged_test
- def test_trap(self, height: list[int], expected: int):
- result = self.solution.trap(height)
- assert result == expected
diff --git a/leetcode/valid_anagram/helpers.py b/leetcode/valid_anagram/helpers.py
new file mode 100644
index 0000000..6c4f5fe
--- /dev/null
+++ b/leetcode/valid_anagram/helpers.py
@@ -0,0 +1,8 @@
+def run_is_anagram(solution_class: type, s: str, t: str):
+ implementation = solution_class()
+ return implementation.is_anagram(s, t)
+
+
+def assert_is_anagram(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/valid_anagram/playground.ipynb b/leetcode/valid_anagram/playground.ipynb
index d312663..c178ebe 100644
--- a/leetcode/valid_anagram/playground.ipynb
+++ b/leetcode/valid_anagram/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_is_anagram, run_is_anagram\n",
"from solution import Solution"
]
},
@@ -26,7 +27,7 @@
{
"cell_type": "code",
"execution_count": 3,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [
{
@@ -41,18 +42,29 @@
}
],
"source": [
- "result = Solution().is_anagram(s, t)\n",
+ "result = run_is_anagram(Solution, s, t)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "test",
+ "id": "assert",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "assert result == expected"
+ "assert_is_anagram(result, expected)"
]
}
],
diff --git a/leetcode/valid_anagram/tests.py b/leetcode/valid_anagram/test_solution.py
similarity index 87%
rename from leetcode/valid_anagram/tests.py
rename to leetcode/valid_anagram/test_solution.py
index aea8280..8a46646 100644
--- a/leetcode/valid_anagram/tests.py
+++ b/leetcode/valid_anagram/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_is_anagram, run_is_anagram
from .solution import Solution
@@ -9,6 +10,7 @@ class TestValidAnagram:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"s, t, expected",
[
@@ -32,7 +34,6 @@ def setup_method(self):
("stressed", "desserts", True),
],
)
- @logged_test
def test_is_anagram(self, s: str, t: str, expected: bool):
- result = self.solution.is_anagram(s, t)
- assert result == expected
+ result = run_is_anagram(Solution, s, t)
+ assert_is_anagram(result, expected)
diff --git a/leetcode/valid_palindrome/helpers.py b/leetcode/valid_palindrome/helpers.py
new file mode 100644
index 0000000..7f7713b
--- /dev/null
+++ b/leetcode/valid_palindrome/helpers.py
@@ -0,0 +1,8 @@
+def run_is_palindrome(solution_class: type, s: str):
+ implementation = solution_class()
+ return implementation.is_palindrome(s)
+
+
+def assert_is_palindrome(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/valid_palindrome/playground.ipynb b/leetcode/valid_palindrome/playground.ipynb
index 86aeb30..c4c6bbc 100644
--- a/leetcode/valid_palindrome/playground.ipynb
+++ b/leetcode/valid_palindrome/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_is_palindrome, run_is_palindrome\n",
"from solution import Solution"
]
},
@@ -25,22 +26,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().is_palindrome(s)\n",
+ "result = run_is_palindrome(Solution, s)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_is_palindrome(result, expected)"
]
}
],
@@ -59,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/valid_palindrome/solution.py b/leetcode/valid_palindrome/solution.py
index e2eba71..daefb2e 100644
--- a/leetcode/valid_palindrome/solution.py
+++ b/leetcode/valid_palindrome/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n)
# Space: O(1)
def is_palindrome(self, s: str) -> bool:
diff --git a/leetcode/valid_palindrome/tests.py b/leetcode/valid_palindrome/test_solution.py
similarity index 79%
rename from leetcode/valid_palindrome/tests.py
rename to leetcode/valid_palindrome/test_solution.py
index a029400..f1fedb9 100644
--- a/leetcode/valid_palindrome/tests.py
+++ b/leetcode/valid_palindrome/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_is_palindrome, run_is_palindrome
from .solution import Solution
@@ -9,6 +10,7 @@ class TestValidPalindrome:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"s, expected",
[
@@ -22,7 +24,6 @@ def setup_method(self):
("Mr. Owl ate my metal worm", True),
],
)
- @logged_test
def test_is_palindrome(self, s: str, expected: bool):
- result = self.solution.is_palindrome(s)
- assert result == expected
+ result = run_is_palindrome(Solution, s)
+ assert_is_palindrome(result, expected)
diff --git a/leetcode/valid_parentheses/helpers.py b/leetcode/valid_parentheses/helpers.py
new file mode 100644
index 0000000..e1fec52
--- /dev/null
+++ b/leetcode/valid_parentheses/helpers.py
@@ -0,0 +1,8 @@
+def run_is_valid(solution_class: type, s: str):
+ implementation = solution_class()
+ return implementation.is_valid(s)
+
+
+def assert_is_valid(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/valid_parentheses/playground.ipynb b/leetcode/valid_parentheses/playground.ipynb
index b5b4f34..e5324f0 100644
--- a/leetcode/valid_parentheses/playground.ipynb
+++ b/leetcode/valid_parentheses/playground.ipynb
@@ -7,6 +7,7 @@
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_is_valid, run_is_valid\n",
"from solution import Solution"
]
},
@@ -25,21 +26,22 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "execute",
+ "id": "run",
"metadata": {},
"outputs": [],
"source": [
- "result = Solution().is_valid(s)\nresult"
+ "result = run_is_valid(Solution, s)\n",
+ "result"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "test",
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_is_valid(result, expected)"
]
}
],
@@ -58,7 +60,6 @@
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python3",
- "pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
diff --git a/leetcode/valid_parentheses/solution.py b/leetcode/valid_parentheses/solution.py
index 779f734..a776a9d 100644
--- a/leetcode/valid_parentheses/solution.py
+++ b/leetcode/valid_parentheses/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n)
# Space: O(n)
def is_valid(self, s: str) -> bool:
diff --git a/leetcode/valid_parentheses/tests.py b/leetcode/valid_parentheses/test_solution.py
similarity index 51%
rename from leetcode/valid_parentheses/tests.py
rename to leetcode/valid_parentheses/test_solution.py
index 528dacb..88b32d6 100644
--- a/leetcode/valid_parentheses/tests.py
+++ b/leetcode/valid_parentheses/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_is_valid, run_is_valid
from .solution import Solution
@@ -9,6 +10,7 @@ class TestValidParentheses:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"s, expected",
[
@@ -22,25 +24,8 @@ def setup_method(self):
(")", False),
("{[()]}", True),
("{[(])}", False),
- ("(((", False),
- (")))", False),
- ("()()()", True),
- ("({[]})", True),
- ("({[}])", False),
- ("[[[[[]]]]]", True),
- ("[[[[[]]]]", False),
- ("{{{{{}}}}}", True),
- ("((((((((((", False),
- ("))))))))))", False),
- ("(){}[]", True),
- ("([{}])", True),
- ("([{]})", False),
- ("(())", True),
- ("(()", False),
- ("())", False),
],
)
- @logged_test
def test_is_valid(self, s: str, expected: bool):
- result = self.solution.is_valid(s)
- assert result == expected
+ result = run_is_valid(Solution, s)
+ assert_is_valid(result, expected)
diff --git a/leetcode/validate_binary_search_tree/helpers.py b/leetcode/validate_binary_search_tree/helpers.py
new file mode 100644
index 0000000..b476545
--- /dev/null
+++ b/leetcode/validate_binary_search_tree/helpers.py
@@ -0,0 +1,12 @@
+from leetcode_py import TreeNode
+
+
+def run_is_valid_bst(solution_class: type, root_list: list[int | None]):
+ root = TreeNode[int].from_list(root_list) if root_list else None
+ implementation = solution_class()
+ return implementation.is_valid_bst(root)
+
+
+def assert_is_valid_bst(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/validate_binary_search_tree/playground.ipynb b/leetcode/validate_binary_search_tree/playground.ipynb
index 4b972d5..a53e0f9 100644
--- a/leetcode/validate_binary_search_tree/playground.ipynb
+++ b/leetcode/validate_binary_search_tree/playground.ipynb
@@ -2,11 +2,12 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_is_valid_bst, run_is_valid_bst\n",
"from solution import Solution\n",
"\n",
"from leetcode_py import TreeNode"
@@ -14,47 +15,35 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
"# Example test case\n",
- "root_list: list[int | None] = [2, 1, 3]\n",
+ "root_list = [2, 1, 3]\n",
"expected = True"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "True"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "root = TreeNode.from_list(root_list)\n",
- "result = Solution().is_valid_bst(root)\n",
+ "result = run_is_valid_bst(Solution, root_list)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 5,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_is_valid_bst(result, expected)"
]
}
],
@@ -72,8 +61,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/validate_binary_search_tree/solution.py b/leetcode/validate_binary_search_tree/solution.py
index 3b4dcbf..6d531db 100644
--- a/leetcode/validate_binary_search_tree/solution.py
+++ b/leetcode/validate_binary_search_tree/solution.py
@@ -4,6 +4,7 @@
class Solution:
+
@classmethod
def validate(cls, node: TreeNode[int] | None, min_val: float, max_val: float) -> bool:
if not node:
diff --git a/leetcode/validate_binary_search_tree/tests.py b/leetcode/validate_binary_search_tree/test_solution.py
similarity index 56%
rename from leetcode/validate_binary_search_tree/tests.py
rename to leetcode/validate_binary_search_tree/test_solution.py
index b03d47e..e7af72f 100644
--- a/leetcode/validate_binary_search_tree/tests.py
+++ b/leetcode/validate_binary_search_tree/test_solution.py
@@ -1,12 +1,14 @@
import pytest
-from leetcode_py import TreeNode
from leetcode_py.test_utils import logged_test
+from .helpers import assert_is_valid_bst, run_is_valid_bst
from .solution import Solution, SolutionBFS, SolutionDFS
class TestValidateBinarySearchTree:
+
+ @logged_test
@pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS])
@pytest.mark.parametrize(
"root_list, expected",
@@ -15,16 +17,10 @@ class TestValidateBinarySearchTree:
([5, 1, 4, None, None, 3, 6], False),
([1], True),
([1, 1], False),
+ ([10, 5, 15, None, None, 6, 20], False),
+ ([2, 1, 3, None, None, None, 4], True),
],
)
- @logged_test
- def test_is_valid_bst(
- self,
- root_list: list[int | None],
- expected: bool,
- solution_class: type[Solution | SolutionDFS | SolutionBFS],
- ):
- solution = solution_class()
- root = TreeNode.from_list(root_list)
- result = solution.is_valid_bst(root)
- assert result == expected
+ def test_is_valid_bst(self, root_list: list[int | None], expected: bool, solution_class: type):
+ result = run_is_valid_bst(solution_class, root_list)
+ assert_is_valid_bst(result, expected)
diff --git a/leetcode/word_break/helpers.py b/leetcode/word_break/helpers.py
new file mode 100644
index 0000000..45c39e9
--- /dev/null
+++ b/leetcode/word_break/helpers.py
@@ -0,0 +1,8 @@
+def run_word_break(solution_class: type, s: str, word_dict: list[str]):
+ implementation = solution_class()
+ return implementation.word_break(s, word_dict)
+
+
+def assert_word_break(result: bool, expected: bool) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/word_break/playground.ipynb b/leetcode/word_break/playground.ipynb
index 3d5b07d..b606647 100644
--- a/leetcode/word_break/playground.ipynb
+++ b/leetcode/word_break/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_word_break, run_word_break\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,34 +26,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "True"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().word_break(s, word_dict)\n",
+ "result = run_word_break(Solution, s, word_dict)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_word_break(result, expected)"
]
}
],
@@ -70,8 +60,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/word_break/solution.py b/leetcode/word_break/solution.py
index ed8dc30..24efa49 100644
--- a/leetcode/word_break/solution.py
+++ b/leetcode/word_break/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(n^2)
# Space: O(n)
def word_break(self, s: str, word_dict: list[str]) -> bool:
diff --git a/leetcode/word_break/tests.py b/leetcode/word_break/test_solution.py
similarity index 81%
rename from leetcode/word_break/tests.py
rename to leetcode/word_break/test_solution.py
index f250fa1..fe2f52e 100644
--- a/leetcode/word_break/tests.py
+++ b/leetcode/word_break/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_word_break, run_word_break
from .solution import Solution
@@ -9,6 +10,7 @@ class TestWordBreak:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"s, word_dict, expected",
[
@@ -21,7 +23,6 @@ def setup_method(self):
("abcd", ["a", "abc", "d"], True),
],
)
- @logged_test
def test_word_break(self, s: str, word_dict: list[str], expected: bool):
- result = self.solution.word_break(s, word_dict)
- assert result == expected
+ result = run_word_break(Solution, s, word_dict)
+ assert_word_break(result, expected)
diff --git a/leetcode/word_ladder/helpers.py b/leetcode/word_ladder/helpers.py
new file mode 100644
index 0000000..615221a
--- /dev/null
+++ b/leetcode/word_ladder/helpers.py
@@ -0,0 +1,8 @@
+def run_ladder_length(solution_class: type, begin_word: str, end_word: str, word_list: list[str]):
+ implementation = solution_class()
+ return implementation.ladder_length(begin_word, end_word, word_list)
+
+
+def assert_ladder_length(result: int, expected: int) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/word_ladder/playground.ipynb b/leetcode/word_ladder/playground.ipynb
index da0260d..863eedf 100644
--- a/leetcode/word_ladder/playground.ipynb
+++ b/leetcode/word_ladder/playground.ipynb
@@ -2,17 +2,18 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
+ "from helpers import assert_ladder_length, run_ladder_length\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -26,34 +27,23 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "id": "execute",
+ "execution_count": null,
+ "id": "run",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "5"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().ladder_length(begin_word, end_word, word_list)\n",
+ "result = run_ladder_length(Solution, begin_word, end_word, word_list)\n",
"result"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "id": "test",
+ "execution_count": null,
+ "id": "assert",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert_ladder_length(result, expected)"
]
}
],
@@ -71,8 +61,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
+ "nbconvert_exporter": "python3",
"version": "3.13.7"
}
},
diff --git a/leetcode/word_ladder/solution.py b/leetcode/word_ladder/solution.py
index 7b74aa4..2356300 100644
--- a/leetcode/word_ladder/solution.py
+++ b/leetcode/word_ladder/solution.py
@@ -1,4 +1,5 @@
class Solution:
+
# Time: O(M^2 * N) where M is length of each word, N is total number of words
# Space: O(M * N) for the visited sets
def ladder_length(self, begin_word: str, end_word: str, word_list: list[str]) -> int:
diff --git a/leetcode/word_ladder/test_solution.py b/leetcode/word_ladder/test_solution.py
new file mode 100644
index 0000000..ed83464
--- /dev/null
+++ b/leetcode/word_ladder/test_solution.py
@@ -0,0 +1,26 @@
+import pytest
+
+from leetcode_py.test_utils import logged_test
+
+from .helpers import assert_ladder_length, run_ladder_length
+from .solution import Solution
+
+
+class TestWordLadder:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "begin_word, end_word, word_list, expected",
+ [
+ ("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"], 5),
+ ("hit", "cog", ["hot", "dot", "dog", "lot", "log"], 0),
+ ("a", "c", ["a", "b", "c"], 2),
+ ("hot", "dog", ["hot", "dog"], 0),
+ ("hot", "dog", ["hot", "hog", "dog"], 3),
+ ],
+ )
+ def test_ladder_length(self, begin_word: str, end_word: str, word_list: list[str], expected: int):
+ result = run_ladder_length(Solution, begin_word, end_word, word_list)
+ assert_ladder_length(result, expected)
diff --git a/leetcode/word_ladder/tests.py b/leetcode/word_ladder/tests.py
deleted file mode 100644
index 0b7682f..0000000
--- a/leetcode/word_ladder/tests.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import pytest
-
-from leetcode_py.test_utils import logged_test
-
-from .solution import Solution
-
-
-class TestWordLadder:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "begin_word, end_word, word_list, expected",
- [
- # Basic cases
- ("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"], 5),
- ("hit", "cog", ["hot", "dot", "dog", "lot", "log"], 0),
- ("a", "c", ["a", "b", "c"], 2),
- # Edge cases
- ("hot", "dog", ["hot", "dog"], 0), # No intermediate words
- ("hot", "hot", ["hot"], 1), # Same word
- ("cat", "dog", [], 0), # Empty word list
- ("cat", "dog", ["cat"], 0), # End word not in list
- # Single character changes
- ("a", "b", ["a", "b"], 2),
- ("ab", "cd", ["ab", "ad", "cd"], 3),
- # Longer paths
- ("red", "tax", ["ted", "tex", "red", "tax", "tad", "den", "rex", "pee"], 4),
- # Multiple possible paths (should return shortest)
- ("cat", "dog", ["cat", "bat", "bag", "dag", "dog", "cag", "cog"], 4),
- # No path exists
- ("abc", "def", ["abc", "def", "ghi"], 0),
- # Direct transformation
- ("cat", "bat", ["cat", "bat"], 2),
- # Longer word length
- ("word", "form", ["word", "worm", "form", "foam", "flam", "flab"], 3),
- ],
- )
- @logged_test
- def test_ladder_length(self, begin_word: str, end_word: str, word_list: list[str], expected: int):
- result = self.solution.ladder_length(begin_word, end_word, word_list)
- assert result == expected
diff --git a/leetcode/zero_one_matrix/helpers.py b/leetcode/zero_one_matrix/helpers.py
new file mode 100644
index 0000000..46b900c
--- /dev/null
+++ b/leetcode/zero_one_matrix/helpers.py
@@ -0,0 +1,8 @@
+def run_update_matrix(solution_class: type, mat: list[list[int]]):
+ implementation = solution_class()
+ return implementation.update_matrix(mat)
+
+
+def assert_update_matrix(result: list[list[int]], expected: list[list[int]]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/zero_one_matrix/playground.ipynb b/leetcode/zero_one_matrix/playground.ipynb
index 659bd5e..6b3b709 100644
--- a/leetcode/zero_one_matrix/playground.ipynb
+++ b/leetcode/zero_one_matrix/playground.ipynb
@@ -2,35 +2,69 @@
"cells": [
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 1,
"id": "imports",
"metadata": {},
"outputs": [],
- "source": ["from solution import Solution"]
+ "source": [
+ "from helpers import assert_update_matrix, run_update_matrix\n",
+ "from solution import Solution"
+ ]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 2,
"id": "setup",
"metadata": {},
"outputs": [],
- "source": ["# Example test case\nmat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\nexpected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]"]
+ "source": [
+ "# Example test case\n",
+ "mat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\n",
+ "expected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]"
+ ]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "execute",
+ "execution_count": 3,
+ "id": "run",
"metadata": {},
- "outputs": [],
- "source": ["result = Solution().update_matrix(mat)\nresult"]
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[[0, 0, 0], [0, 1, 0], [1, 2, 1]]"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "result = run_update_matrix(Solution, mat)\n",
+ "result"
+ ]
},
{
"cell_type": "code",
- "execution_count": null,
- "id": "test",
+ "execution_count": 4,
+ "id": "assert",
"metadata": {},
- "outputs": [],
- "source": ["assert result == expected"]
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "assert_update_matrix(result, expected)"
+ ]
}
],
"metadata": {
@@ -47,7 +81,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python3",
+ "nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
diff --git a/leetcode/zero_one_matrix/solution.py b/leetcode/zero_one_matrix/solution.py
index b70c86c..11fdf12 100644
--- a/leetcode/zero_one_matrix/solution.py
+++ b/leetcode/zero_one_matrix/solution.py
@@ -2,6 +2,7 @@
class Solution:
+
# Time: O(m * n)
# Space: O(m * n)
def update_matrix(self, mat: list[list[int]]) -> list[list[int]]:
diff --git a/leetcode/zero_one_matrix/tests.py b/leetcode/zero_one_matrix/test_solution.py
similarity index 54%
rename from leetcode/zero_one_matrix/tests.py
rename to leetcode/zero_one_matrix/test_solution.py
index 6b5a4e4..ea195cb 100644
--- a/leetcode/zero_one_matrix/tests.py
+++ b/leetcode/zero_one_matrix/test_solution.py
@@ -2,6 +2,7 @@
from leetcode_py.test_utils import logged_test
+from .helpers import assert_update_matrix, run_update_matrix
from .solution import Solution
@@ -9,21 +10,16 @@ class TestZeroOneMatrix:
def setup_method(self):
self.solution = Solution()
+ @logged_test
@pytest.mark.parametrize(
"mat, expected",
[
([[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]]),
([[0, 0, 0], [0, 1, 0], [1, 1, 1]], [[0, 0, 0], [0, 1, 0], [1, 2, 1]]),
([[0]], [[0]]),
- ([[1, 1, 1], [1, 1, 1], [1, 1, 0]], [[4, 3, 2], [3, 2, 1], [2, 1, 0]]),
- ([[0, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], [[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]),
- (
- [[1, 0, 1, 1, 0, 0, 1, 0, 0, 1], [0, 1, 1, 0, 1, 0, 1, 0, 1, 1]],
- [[1, 0, 1, 1, 0, 0, 1, 0, 0, 1], [0, 1, 1, 0, 1, 0, 1, 0, 1, 2]],
- ),
+ ([[1, 0]], [[1, 0]]),
],
)
- @logged_test
def test_update_matrix(self, mat: list[list[int]], expected: list[list[int]]):
- result = self.solution.update_matrix(mat)
- assert result == expected
+ result = run_update_matrix(Solution, mat)
+ assert_update_matrix(result, expected)
diff --git a/poetry.lock b/poetry.lock
index 3cc5f73..1ee7322 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -150,84 +150,101 @@ files = [
[[package]]
name = "cffi"
-version = "1.17.1"
+version = "2.0.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["dev"]
markers = "implementation_name == \"pypy\""
files = [
- {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
- {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"},
- {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"},
- {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"},
- {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"},
- {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"},
- {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"},
- {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"},
- {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"},
- {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"},
- {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"},
- {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"},
- {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"},
- {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"},
- {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"},
- {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"},
- {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
- {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
- {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
- {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
- {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
- {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
- {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
- {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
- {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
- {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
- {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
- {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
- {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
- {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
- {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"},
- {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"},
- {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"},
- {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"},
- {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"},
- {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"},
- {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"},
- {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"},
- {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"},
- {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"},
- {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"},
- {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
- {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
+ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"},
+ {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"},
+ {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"},
+ {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"},
+ {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"},
+ {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"},
+ {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"},
+ {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"},
+ {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"},
+ {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"},
+ {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"},
+ {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"},
+ {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"},
+ {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"},
+ {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"},
+ {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"},
+ {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"},
+ {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"},
+ {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"},
+ {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"},
+ {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"},
+ {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"},
+ {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"},
+ {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"},
+ {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"},
+ {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"},
+ {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"},
+ {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"},
+ {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"},
+ {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"},
+ {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"},
+ {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"},
+ {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"},
+ {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"},
+ {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"},
+ {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"},
+ {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"},
+ {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"},
+ {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"},
+ {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"},
+ {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"},
+ {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"},
+ {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"},
+ {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"},
+ {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"},
+ {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"},
+ {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"},
+ {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"},
+ {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"},
+ {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"},
+ {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"},
+ {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"},
+ {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"},
+ {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"},
+ {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"},
+ {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"},
+ {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"},
+ {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"},
+ {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"},
+ {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"},
+ {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"},
+ {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"},
+ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"},
+ {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"},
]
[package.dependencies]
-pycparser = "*"
+pycparser = {version = "*", markers = "implementation_name != \"PyPy\""}
[[package]]
name = "cfgv"
@@ -614,14 +631,14 @@ test = ["coverage", "pytest (>=7,<8.1)", "pytest-cov", "pytest-mock (>=3)"]
[[package]]
name = "identify"
-version = "2.6.13"
+version = "2.6.14"
description = "File identification library for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"},
- {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"},
+ {file = "identify-2.6.14-py2.py3-none-any.whl", hash = "sha256:11a073da82212c6646b1f39bb20d4483bfb9543bd5566fec60053c4bb309bf2e"},
+ {file = "identify-2.6.14.tar.gz", hash = "sha256:663494103b4f717cb26921c52f8751363dc89db64364cd836a9bf1535f53cd6a"},
]
[package.extras]
@@ -1280,15 +1297,15 @@ files = [
[[package]]
name = "pycparser"
-version = "2.22"
+version = "2.23"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "implementation_name == \"pypy\""
files = [
- {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
- {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
+ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"},
+ {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"},
]
[[package]]
@@ -1330,14 +1347,14 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests
[[package]]
name = "pytest-cov"
-version = "6.2.1"
+version = "6.3.0"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"},
- {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"},
+ {file = "pytest_cov-6.3.0-py3-none-any.whl", hash = "sha256:440db28156d2468cafc0415b4f8e50856a0d11faefa38f30906048fe490f1749"},
+ {file = "pytest_cov-6.3.0.tar.gz", hash = "sha256:35c580e7800f87ce892e687461166e1ac2bcb8fb9e13aea79032518d6e503ff2"},
]
[package.dependencies]
@@ -1477,104 +1494,104 @@ files = [
[[package]]
name = "pyzmq"
-version = "27.0.2"
+version = "27.1.0"
description = "Python bindings for 0MQ"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "pyzmq-27.0.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:8b32c4636ced87dce0ac3d671e578b3400215efab372f1b4be242e8cf0b11384"},
- {file = "pyzmq-27.0.2-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9528a4b3e24189cb333a9850fddbbafaa81df187297cfbddee50447cdb042cf"},
- {file = "pyzmq-27.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b02ba0c0b2b9ebe74688002e6c56c903429924a25630804b9ede1f178aa5a3f"},
- {file = "pyzmq-27.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4dc5c9a6167617251dea0d024d67559795761aabb4b7ea015518be898be076"},
- {file = "pyzmq-27.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1151b33aaf3b4fa9da26f4d696e38eebab67d1b43c446184d733c700b3ff8ce"},
- {file = "pyzmq-27.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4ecfc7999ac44c9ef92b5ae8f0b44fb935297977df54d8756b195a3cd12f38f0"},
- {file = "pyzmq-27.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:31c26a5d0b00befcaeeb600d8b15ad09f5604b6f44e2057ec5e521a9e18dcd9a"},
- {file = "pyzmq-27.0.2-cp310-cp310-win32.whl", hash = "sha256:25a100d2de2ac0c644ecf4ce0b509a720d12e559c77aff7e7e73aa684f0375bc"},
- {file = "pyzmq-27.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a1acf091f53bb406e9e5e7383e467d1dd1b94488b8415b890917d30111a1fef3"},
- {file = "pyzmq-27.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:b38e01f11e9e95f6668dc8a62dccf9483f454fed78a77447507a0e8dcbd19a63"},
- {file = "pyzmq-27.0.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:063845960df76599ad4fad69fa4d884b3ba38304272104fdcd7e3af33faeeb1d"},
- {file = "pyzmq-27.0.2-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:845a35fb21b88786aeb38af8b271d41ab0967985410f35411a27eebdc578a076"},
- {file = "pyzmq-27.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:515d20b5c3c86db95503faa989853a8ab692aab1e5336db011cd6d35626c4cb1"},
- {file = "pyzmq-27.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:862aedec0b0684a5050cdb5ec13c2da96d2f8dffda48657ed35e312a4e31553b"},
- {file = "pyzmq-27.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cb5bcfc51c7a4fce335d3bc974fd1d6a916abbcdd2b25f6e89d37b8def25f57"},
- {file = "pyzmq-27.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:38ff75b2a36e3a032e9fef29a5871e3e1301a37464e09ba364e3c3193f62982a"},
- {file = "pyzmq-27.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a5709abe8d23ca158a9d0a18c037f4193f5b6afeb53be37173a41e9fb885792"},
- {file = "pyzmq-27.0.2-cp311-cp311-win32.whl", hash = "sha256:47c5dda2018c35d87be9b83de0890cb92ac0791fd59498847fc4eca6ff56671d"},
- {file = "pyzmq-27.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:f54ca3e98f8f4d23e989c7d0edcf9da7a514ff261edaf64d1d8653dd5feb0a8b"},
- {file = "pyzmq-27.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:2ef3067cb5b51b090fb853f423ad7ed63836ec154374282780a62eb866bf5768"},
- {file = "pyzmq-27.0.2-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:5da05e3c22c95e23bfc4afeee6ff7d4be9ff2233ad6cb171a0e8257cd46b169a"},
- {file = "pyzmq-27.0.2-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e4520577971d01d47e2559bb3175fce1be9103b18621bf0b241abe0a933d040"},
- {file = "pyzmq-27.0.2-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d7de7bf73165b90bd25a8668659ccb134dd28449116bf3c7e9bab5cf8a8ec9"},
- {file = "pyzmq-27.0.2-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340e7cddc32f147c6c00d116a3f284ab07ee63dbd26c52be13b590520434533c"},
- {file = "pyzmq-27.0.2-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba95693f9df8bb4a9826464fb0fe89033936f35fd4a8ff1edff09a473570afa0"},
- {file = "pyzmq-27.0.2-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:ca42a6ce2d697537da34f77a1960d21476c6a4af3e539eddb2b114c3cf65a78c"},
- {file = "pyzmq-27.0.2-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e44e665d78a07214b2772ccbd4b9bcc6d848d7895f1b2d7653f047b6318a4f6"},
- {file = "pyzmq-27.0.2-cp312-abi3-win32.whl", hash = "sha256:272d772d116615397d2be2b1417b3b8c8bc8671f93728c2f2c25002a4530e8f6"},
- {file = "pyzmq-27.0.2-cp312-abi3-win_amd64.whl", hash = "sha256:734be4f44efba0aa69bf5f015ed13eb69ff29bf0d17ea1e21588b095a3147b8e"},
- {file = "pyzmq-27.0.2-cp312-abi3-win_arm64.whl", hash = "sha256:41f0bd56d9279392810950feb2785a419c2920bbf007fdaaa7f4a07332ae492d"},
- {file = "pyzmq-27.0.2-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:7f01118133427cd7f34ee133b5098e2af5f70303fa7519785c007bca5aa6f96a"},
- {file = "pyzmq-27.0.2-cp313-cp313-android_24_x86_64.whl", hash = "sha256:e4b860edf6379a7234ccbb19b4ed2c57e3ff569c3414fadfb49ae72b61a8ef07"},
- {file = "pyzmq-27.0.2-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:cb77923ea163156da14295c941930bd525df0d29c96c1ec2fe3c3806b1e17cb3"},
- {file = "pyzmq-27.0.2-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:61678b7407b04df8f9423f188156355dc94d0fb52d360ae79d02ed7e0d431eea"},
- {file = "pyzmq-27.0.2-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3c824b70925963bdc8e39a642672c15ffaa67e7d4b491f64662dd56d6271263"},
- {file = "pyzmq-27.0.2-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4833e02fcf2751975457be1dfa2f744d4d09901a8cc106acaa519d868232175"},
- {file = "pyzmq-27.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b18045668d09cf0faa44918af2a67f0dbbef738c96f61c2f1b975b1ddb92ccfc"},
- {file = "pyzmq-27.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bbbb7e2f3ac5a22901324e7b086f398b8e16d343879a77b15ca3312e8cd8e6d5"},
- {file = "pyzmq-27.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b751914a73604d40d88a061bab042a11d4511b3ddbb7624cd83c39c8a498564c"},
- {file = "pyzmq-27.0.2-cp313-cp313t-win32.whl", hash = "sha256:3e8f833dd82af11db5321c414638045c70f61009f72dd61c88db4a713c1fb1d2"},
- {file = "pyzmq-27.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5b45153cb8eadcab14139970643a84f7a7b08dda541fbc1f6f4855c49334b549"},
- {file = "pyzmq-27.0.2-cp313-cp313t-win_arm64.whl", hash = "sha256:86898f5c9730df23427c1ee0097d8aa41aa5f89539a79e48cd0d2c22d059f1b7"},
- {file = "pyzmq-27.0.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d2b4b261dce10762be5c116b6ad1f267a9429765b493c454f049f33791dd8b8a"},
- {file = "pyzmq-27.0.2-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e4d88b6cff156fed468903006b24bbd85322612f9c2f7b96e72d5016fd3f543"},
- {file = "pyzmq-27.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8426c0ebbc11ed8416a6e9409c194142d677c2c5c688595f2743664e356d9e9b"},
- {file = "pyzmq-27.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565bee96a155fe6452caed5fb5f60c9862038e6b51a59f4f632562081cdb4004"},
- {file = "pyzmq-27.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5de735c745ca5cefe9c2d1547d8f28cfe1b1926aecb7483ab1102fd0a746c093"},
- {file = "pyzmq-27.0.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ea4f498f8115fd90d7bf03a3e83ae3e9898e43362f8e8e8faec93597206e15cc"},
- {file = "pyzmq-27.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d00e81cb0afd672915257a3927124ee2ad117ace3c256d39cd97ca3f190152ad"},
- {file = "pyzmq-27.0.2-cp314-cp314t-win32.whl", hash = "sha256:0f6e9b00d81b58f859fffc112365d50413954e02aefe36c5b4c8fb4af79f8cc3"},
- {file = "pyzmq-27.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2e73cf3b127a437fef4100eb3ac2ebe6b49e655bb721329f667f59eca0a26221"},
- {file = "pyzmq-27.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:4108785f2e5ac865d06f678a07a1901e3465611356df21a545eeea8b45f56265"},
- {file = "pyzmq-27.0.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:59a50f5eedf8ed20b7dbd57f1c29b2de003940dea3eedfbf0fbfea05ee7f9f61"},
- {file = "pyzmq-27.0.2-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:a00e6390e52770ba1ec753b2610f90b4f00e74c71cfc5405b917adf3cc39565e"},
- {file = "pyzmq-27.0.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49d8d05d9844d83cddfbc86a82ac0cafe7ab694fcc9c9618de8d015c318347c3"},
- {file = "pyzmq-27.0.2-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3660d85e2b6a28eb2d586dedab9c61a7b7c64ab0d89a35d2973c7be336f12b0d"},
- {file = "pyzmq-27.0.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:bccfee44b392f4d13bbf05aa88d8f7709271b940a8c398d4216fde6b717624ae"},
- {file = "pyzmq-27.0.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:989066d51686415f1da646d6e2c5364a9b084777c29d9d1720aa5baf192366ef"},
- {file = "pyzmq-27.0.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc283595b82f0db155a52f6462945c7b6b47ecaae2f681746eeea537c95cf8c9"},
- {file = "pyzmq-27.0.2-cp38-cp38-win32.whl", hash = "sha256:ad38daf57495beadc0d929e8901b2aa46ff474239b5a8a46ccc7f67dc01d2335"},
- {file = "pyzmq-27.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:36508466a266cf78bba2f56529ad06eb38ba827f443b47388d420bec14d331ba"},
- {file = "pyzmq-27.0.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:aa9c1c208c263b84386ac25bed6af5672397dc3c232638114fc09bca5c7addf9"},
- {file = "pyzmq-27.0.2-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:795c4884cfe7ea59f2b67d82b417e899afab889d332bfda13b02f8e0c155b2e4"},
- {file = "pyzmq-27.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47eb65bb25478358ba3113dd9a08344f616f417ad3ffcbb190cd874fae72b1b1"},
- {file = "pyzmq-27.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6fc24f00293f10aff04d55ca37029b280474c91f4de2cad5e911e5e10d733b7"},
- {file = "pyzmq-27.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58d4cc9b6b768478adfc40a5cbee545303db8dbc81ba688474e0f499cc581028"},
- {file = "pyzmq-27.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea2f26c5972796e02b222968a21a378d09eb4ff590eb3c5fafa8913f8c2bdf5"},
- {file = "pyzmq-27.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a0621ec020c49fc1b6e31304f1a820900d54e7d9afa03ea1634264bf9387519e"},
- {file = "pyzmq-27.0.2-cp39-cp39-win32.whl", hash = "sha256:1326500792a9cb0992db06bbaf5d0098459133868932b81a6e90d45c39eca99d"},
- {file = "pyzmq-27.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:5ee9560cb1e3094ef01fc071b361121a57ebb8d4232912b6607a6d7d2d0a97b4"},
- {file = "pyzmq-27.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:85e3c6fb0d25ea046ebcfdc2bcb9683d663dc0280645c79a616ff5077962a15b"},
- {file = "pyzmq-27.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d67a0960803a37b60f51b460c58444bc7033a804c662f5735172e21e74ee4902"},
- {file = "pyzmq-27.0.2-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:dd4d3e6a567ffd0d232cfc667c49d0852d0ee7481458a2a1593b9b1bc5acba88"},
- {file = "pyzmq-27.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e558be423631704803bc6a642e2caa96083df759e25fe6eb01f2d28725f80bd"},
- {file = "pyzmq-27.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4c20ba8389f495c7b4f6b896bb1ca1e109a157d4f189267a902079699aaf787"},
- {file = "pyzmq-27.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c5be232f7219414ff672ff7ab8c5a7e8632177735186d8a42b57b491fafdd64e"},
- {file = "pyzmq-27.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e297784aea724294fe95e442e39a4376c2f08aa4fae4161c669f047051e31b02"},
- {file = "pyzmq-27.0.2-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e3659a79ded9745bc9c2aef5b444ac8805606e7bc50d2d2eb16dc3ab5483d91f"},
- {file = "pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3dba49ff037d02373a9306b58d6c1e0be031438f822044e8767afccfdac4c6b"},
- {file = "pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de84e1694f9507b29e7b263453a2255a73e3d099d258db0f14539bad258abe41"},
- {file = "pyzmq-27.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f0944d65ba2b872b9fcece08411d6347f15a874c775b4c3baae7f278550da0fb"},
- {file = "pyzmq-27.0.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:05288947797dcd6724702db2056972dceef9963a83041eb734aea504416094ec"},
- {file = "pyzmq-27.0.2-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:dff9198adbb6810ad857f3bfa59b4859c45acb02b0d198b39abeafb9148474f3"},
- {file = "pyzmq-27.0.2-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849123fd9982c7f63911fdceba9870f203f0f32c953a3bab48e7f27803a0e3ec"},
- {file = "pyzmq-27.0.2-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5ee06945f3069e3609819890a01958c4bbfea7a2b31ae87107c6478838d309e"},
- {file = "pyzmq-27.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6156ad5e8bbe8a78a3f5b5757c9a883b0012325c83f98ce6d58fcec81e8b3d06"},
- {file = "pyzmq-27.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:400f34321e3bd89b1165b91ea6b18ad26042ba9ad0dfed8b35049e2e24eeab9b"},
- {file = "pyzmq-27.0.2-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9cbad4ef12e4c15c94d2c24ecd15a8ed56bf091c62f121a2b0c618ddd4b7402b"},
- {file = "pyzmq-27.0.2-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6b2b74aac3392b8cf508ccb68c980a8555298cd378434a2d065d6ce0f4211dff"},
- {file = "pyzmq-27.0.2-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7db5db88c24cf9253065d69229a148ff60821e5d6f8ff72579b1f80f8f348bab"},
- {file = "pyzmq-27.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8ffe40c216c41756ca05188c3e24a23142334b304f7aebd75c24210385e35573"},
- {file = "pyzmq-27.0.2.tar.gz", hash = "sha256:b398dd713b18de89730447347e96a0240225e154db56e35b6bb8447ffdb07798"},
+ {file = "pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4"},
+ {file = "pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556"},
+ {file = "pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b"},
+ {file = "pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e"},
+ {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526"},
+ {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1"},
+ {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386"},
+ {file = "pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda"},
+ {file = "pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f"},
+ {file = "pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32"},
+ {file = "pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86"},
+ {file = "pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581"},
+ {file = "pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f"},
+ {file = "pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e"},
+ {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e"},
+ {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2"},
+ {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394"},
+ {file = "pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f"},
+ {file = "pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97"},
+ {file = "pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07"},
+ {file = "pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc"},
+ {file = "pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113"},
+ {file = "pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233"},
+ {file = "pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31"},
+ {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28"},
+ {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856"},
+ {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496"},
+ {file = "pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd"},
+ {file = "pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf"},
+ {file = "pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f"},
+ {file = "pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5"},
+ {file = "pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2"},
+ {file = "pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0"},
+ {file = "pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7"},
+ {file = "pyzmq-27.1.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:18339186c0ed0ce5835f2656cdfb32203125917711af64da64dbaa3d949e5a1b"},
+ {file = "pyzmq-27.1.0-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:753d56fba8f70962cd8295fb3edb40b9b16deaa882dd2b5a3a2039f9ff7625aa"},
+ {file = "pyzmq-27.1.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b721c05d932e5ad9ff9344f708c96b9e1a485418c6618d765fca95d4daacfbef"},
+ {file = "pyzmq-27.1.0-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be883ff3d722e6085ee3f4afc057a50f7f2e0c72d289fd54df5706b4e3d3a50"},
+ {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b2e592db3a93128daf567de9650a2f3859017b3f7a66bc4ed6e4779d6034976f"},
+ {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad68808a61cbfbbae7ba26d6233f2a4aa3b221de379ce9ee468aa7a83b9c36b0"},
+ {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e2687c2d230e8d8584fbea433c24382edfeda0c60627aca3446aa5e58d5d1831"},
+ {file = "pyzmq-27.1.0-cp38-cp38-win32.whl", hash = "sha256:a1aa0ee920fb3825d6c825ae3f6c508403b905b698b6460408ebd5bb04bbb312"},
+ {file = "pyzmq-27.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:df7cd397ece96cf20a76fae705d40efbab217d217897a5053267cd88a700c266"},
+ {file = "pyzmq-27.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:96c71c32fff75957db6ae33cd961439f386505c6e6b377370af9b24a1ef9eafb"},
+ {file = "pyzmq-27.1.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:49d3980544447f6bd2968b6ac913ab963a49dcaa2d4a2990041f16057b04c429"},
+ {file = "pyzmq-27.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849ca054d81aa1c175c49484afaaa5db0622092b5eccb2055f9f3bb8f703782d"},
+ {file = "pyzmq-27.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3970778e74cb7f85934d2b926b9900e92bfe597e62267d7499acc39c9c28e345"},
+ {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da96ecdcf7d3919c3be2de91a8c513c186f6762aa6cf7c01087ed74fad7f0968"},
+ {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9541c444cfe1b1c0156c5c86ece2bb926c7079a18e7b47b0b1b3b1b875e5d098"},
+ {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e30a74a39b93e2e1591b58eb1acef4902be27c957a8720b0e368f579b82dc22f"},
+ {file = "pyzmq-27.1.0-cp39-cp39-win32.whl", hash = "sha256:b1267823d72d1e40701dcba7edc45fd17f71be1285557b7fe668887150a14b78"},
+ {file = "pyzmq-27.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c996ded912812a2fcd7ab6574f4ad3edc27cb6510349431e4930d4196ade7db"},
+ {file = "pyzmq-27.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:346e9ba4198177a07e7706050f35d733e08c1c1f8ceacd5eb6389d653579ffbc"},
+ {file = "pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6"},
+ {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90"},
+ {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62"},
+ {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74"},
+ {file = "pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba"},
+ {file = "pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066"},
+ {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604"},
+ {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c"},
+ {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271"},
+ {file = "pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355"},
+ {file = "pyzmq-27.1.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:50081a4e98472ba9f5a02850014b4c9b629da6710f8f14f3b15897c666a28f1b"},
+ {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:510869f9df36ab97f89f4cff9d002a89ac554c7ac9cadd87d444aa4cf66abd27"},
+ {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f8426a01b1c4098a750973c37131cf585f61c7911d735f729935a0c701b68d3"},
+ {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726b6a502f2e34c6d2ada5e702929586d3ac948a4dbbb7fed9854ec8c0466027"},
+ {file = "pyzmq-27.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bd67e7c8f4654bef471c0b1ca6614af0b5202a790723a58b79d9584dc8022a78"},
+ {file = "pyzmq-27.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:722ea791aa233ac0a819fc2c475e1292c76930b31f1d828cb61073e2fe5e208f"},
+ {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:01f9437501886d3a1dd4b02ef59fb8cc384fa718ce066d52f175ee49dd5b7ed8"},
+ {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4a19387a3dddcc762bfd2f570d14e2395b2c9701329b266f83dd87a2b3cbd381"},
+ {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c618fbcd069e3a29dcd221739cacde52edcc681f041907867e0f5cc7e85f172"},
+ {file = "pyzmq-27.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff8d114d14ac671d88c89b9224c63d6c4e5a613fe8acd5594ce53d752a3aafe9"},
+ {file = "pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540"},
]
[package.dependencies]