From 9080a8210eb63efcd9ad23d842c0bbbf24ad0729 Mon Sep 17 00:00:00 2001
From: Wisaroot <66859294+wisarootl@users.noreply.github.com>
Date: Tue, 16 Sep 2025 21:47:52 +0700
Subject: [PATCH 01/42] feat: use tags.json5 when generate problems (#46)
- use tags.json5 when generate problems
- move logged_test
- add sort_tags.py
---
.amazonq/rules/problem-creation.md | 9 +-
.github/workflows/ci-test.yml | 13 +-
Makefile | 3 +-
leetcode/accounts_merge/test_solution.py | 2 +-
leetcode/add_binary/test_solution.py | 2 +-
.../balanced_binary_tree/test_solution.py | 2 +-
leetcode/basic_calculator/test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/binary_search/test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/climbing_stairs/test_solution.py | 2 +-
leetcode/clone_graph/test_solution.py | 2 +-
leetcode/coin_change/test_solution.py | 2 +-
leetcode/combination_sum/test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/contains_duplicate/test_solution.py | 2 +-
leetcode/course_schedule/test_solution.py | 2 +-
leetcode/daily_temperatures/README.md | 46 +++
leetcode/daily_temperatures/__init__.py | 0
leetcode/daily_temperatures/helpers.py | 8 +
leetcode/daily_temperatures/playground.py | 29 ++
leetcode/daily_temperatures/solution.py | 15 +
leetcode/daily_temperatures/test_solution.py | 56 +++
leetcode/diagonal_traverse/test_solution.py | 2 +-
.../diameter_of_binary_tree/test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/first_bad_version/test_solution.py | 2 +-
leetcode/flood_fill/test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/insert_interval/test_solution.py | 2 +-
leetcode/invert_binary_tree/test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/linked_list_cycle/test_solution.py | 2 +-
leetcode/longest_palindrome/test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/lru_cache/test_solution.py | 2 +-
leetcode/majority_element/test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/maximum_subarray/test_solution.py | 2 +-
leetcode/merge_intervals/test_solution.py | 2 +-
.../merge_k_sorted_lists/test_solution.py | 2 +-
.../merge_two_sorted_lists/test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/min_stack/test_solution.py | 2 +-
.../minimum_height_trees/test_solution.py | 2 +-
.../minimum_window_substring/test_solution.py | 2 +-
leetcode/number_of_islands/test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/permutations/test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/ransom_note/test_solution.py | 2 +-
leetcode/reverse_linked_list/test_solution.py | 2 +-
.../reverse_linked_list_ii/test_solution.py | 2 +-
leetcode/rotting_oranges/test_solution.py | 2 +-
.../test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/sort_colors/test_solution.py | 2 +-
leetcode/spiral_matrix/test_solution.py | 2 +-
.../string_to_integer_atoi/test_solution.py | 2 +-
leetcode/subsets/test_solution.py | 2 +-
leetcode/task_scheduler/test_solution.py | 2 +-
leetcode/three_sum/test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/trapping_rain_water/test_solution.py | 2 +-
leetcode/two_sum/test_solution.py | 2 +-
leetcode/unique_paths/test_solution.py | 2 +-
leetcode/valid_anagram/test_solution.py | 2 +-
leetcode/valid_palindrome/test_solution.py | 2 +-
leetcode/valid_parentheses/test_solution.py | 2 +-
.../test_solution.py | 2 +-
leetcode/word_break/test_solution.py | 2 +-
leetcode/word_ladder/test_solution.py | 2 +-
leetcode/word_search/test_solution.py | 2 +-
leetcode/zero_one_matrix/test_solution.py | 2 +-
leetcode_py/__init__.py | 3 +-
.../cli/resources/leetcode/cookiecutter.json | 4 +-
.../resources/leetcode/examples/example.json5 | 368 +++++++++---------
.../json/problems/accounts_merge.json | 2 +-
.../leetcode/json/problems/add_binary.json | 2 +-
.../json/problems/balanced_binary_tree.json | 2 +-
.../json/problems/basic_calculator.json | 2 +-
.../best_time_to_buy_and_sell_stock.json | 2 +-
.../leetcode/json/problems/binary_search.json | 2 +-
.../binary_tree_level_order_traversal.json | 2 +-
.../problems/binary_tree_right_side_view.json | 2 +-
.../json/problems/climbing_stairs.json | 2 +-
.../leetcode/json/problems/clone_graph.json | 2 +-
.../leetcode/json/problems/coin_change.json | 2 +-
.../json/problems/combination_sum.json | 2 +-
...e_from_preorder_and_inorder_traversal.json | 2 +-
.../problems/container_with_most_water.json | 2 +-
.../json/problems/contains_duplicate.json | 2 +-
.../json/problems/course_schedule.json | 2 +-
.../json/problems/daily_temperatures.json | 63 +++
.../json/problems/diagonal_traverse.json | 2 +-
.../problems/diameter_of_binary_tree.json | 2 +-
.../evaluate_reverse_polish_notation.json | 2 +-
.../find_all_anagrams_in_a_string.json | 2 +-
.../find_median_from_data_stream.json | 2 +-
.../json/problems/first_bad_version.json | 2 +-
.../leetcode/json/problems/flood_fill.json | 2 +-
.../implement_queue_using_stacks.json | 2 +-
.../problems/implement_trie_prefix_tree.json | 2 +-
.../json/problems/insert_interval.json | 2 +-
.../json/problems/invert_binary_tree.json | 2 +-
.../problems/k_closest_points_to_origin.json | 2 +-
.../kth_smallest_element_in_a_bst.json | 2 +-
.../largest_rectangle_in_histogram.json | 2 +-
...letter_combinations_of_a_phone_number.json | 2 +-
.../json/problems/linked_list_cycle.json | 2 +-
.../json/problems/longest_palindrome.json | 2 +-
.../longest_palindromic_substring.json | 2 +-
...ubstring_without_repeating_characters.json | 2 +-
...mmon_ancestor_of_a_binary_search_tree.json | 2 +-
...west_common_ancestor_of_a_binary_tree.json | 2 +-
.../leetcode/json/problems/lru_cache.json | 2 +-
.../json/problems/majority_element.json | 2 +-
.../maximum_depth_of_binary_tree.json | 2 +-
.../maximum_profit_in_job_scheduling.json | 2 +-
.../json/problems/maximum_subarray.json | 2 +-
.../json/problems/merge_intervals.json | 2 +-
.../json/problems/merge_k_sorted_lists.json | 2 +-
.../json/problems/merge_two_sorted_lists.json | 2 +-
.../problems/middle_of_the_linked_list.json | 2 +-
.../leetcode/json/problems/min_stack.json | 2 +-
.../json/problems/minimum_height_trees.json | 2 +-
.../problems/minimum_window_substring.json | 2 +-
.../json/problems/number_of_islands.json | 2 +-
.../problems/partition_equal_subset_sum.json | 2 +-
.../leetcode/json/problems/permutations.json | 2 +-
.../product_of_array_except_self.json | 2 +-
.../leetcode/json/problems/ransom_note.json | 2 +-
.../json/problems/reverse_linked_list.json | 2 +-
.../json/problems/reverse_linked_list_ii.json | 2 +-
.../json/problems/rotting_oranges.json | 2 +-
.../search_in_rotated_sorted_array.json | 2 +-
...serialize_and_deserialize_binary_tree.json | 2 +-
.../leetcode/json/problems/sort_colors.json | 2 +-
.../leetcode/json/problems/spiral_matrix.json | 2 +-
.../json/problems/string_to_integer_atoi.json | 2 +-
.../leetcode/json/problems/subsets.json | 2 +-
.../json/problems/task_scheduler.json | 2 +-
.../leetcode/json/problems/three_sum.json | 2 +-
.../problems/time_based_key_value_store.json | 2 +-
.../json/problems/trapping_rain_water.json | 2 +-
.../leetcode/json/problems/two_sum.json | 2 +-
.../leetcode/json/problems/unique_paths.json | 2 +-
.../leetcode/json/problems/valid_anagram.json | 2 +-
.../json/problems/valid_palindrome.json | 2 +-
.../json/problems/valid_parentheses.json | 2 +-
.../problems/validate_binary_search_tree.json | 2 +-
.../leetcode/json/problems/word_break.json | 2 +-
.../leetcode/json/problems/word_ladder.json | 2 +-
.../leetcode/json/problems/word_search.json | 2 +-
.../json/problems/zero_one_matrix.json | 2 +-
.../cli/resources/leetcode/json/tags.json5 | 10 +-
leetcode_py/cli/utils/problem_finder.py | 39 +-
leetcode_py/tools/generator.py | 27 ++
.../{test_utils.py => tools/logged_test.py} | 0
poetry.lock | 78 ++--
pyproject.toml | 9 +-
scripts/sort_tags.py | 30 ++
tests/test_problem_finder.py | 25 ++
tests/test_test_utils.py | 2 +-
176 files changed, 753 insertions(+), 392 deletions(-)
create mode 100644 leetcode/daily_temperatures/README.md
create mode 100644 leetcode/daily_temperatures/__init__.py
create mode 100644 leetcode/daily_temperatures/helpers.py
create mode 100644 leetcode/daily_temperatures/playground.py
create mode 100644 leetcode/daily_temperatures/solution.py
create mode 100644 leetcode/daily_temperatures/test_solution.py
create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/daily_temperatures.json
rename leetcode_py/{test_utils.py => tools/logged_test.py} (100%)
create mode 100644 scripts/sort_tags.py
create mode 100644 tests/test_problem_finder.py
diff --git a/.amazonq/rules/problem-creation.md b/.amazonq/rules/problem-creation.md
index a262244..9249475 100644
--- a/.amazonq/rules/problem-creation.md
+++ b/.amazonq/rules/problem-creation.md
@@ -13,10 +13,11 @@ When user requests a problem by **number** or **name/slug**, the assistant will:
- Images provide crucial visual context, especially for tree and graph problems
- Always verify images are included in `readme_examples` and accessible
4. **Create** JSON file in `leetcode_py/cli/resources/leetcode/json/problems/{problem_name}.json`
-5. **Update** Makefile with `PROBLEM ?= {problem_name}`
-6. **Generate** problem structure using `make p-gen`
-7. **Verify** with `make p-lint` - fix template issues in JSON if possible, or manually fix generated files if template limitations
-8. **Iterate** if JSON fixes: re-run `make p-gen PROBLEM={problem_name} FORCE=1` and `make p-lint` until passes to ensure reproducibility
+5. **Update tags.json5** - If user specifies tags, manually add problem name to corresponding tag arrays in `leetcode_py/cli/resources/leetcode/json/tags.json5`
+6. **Update** Makefile with `PROBLEM ?= {problem_name}`
+7. **Generate** problem structure using `make p-gen`
+8. **Verify** with `make p-lint` - fix template issues in JSON if possible, or manually fix generated files if template limitations
+9. **Iterate** if JSON fixes: re-run `make p-gen PROBLEM={problem_name} FORCE=1` and `make p-lint` until passes to ensure reproducibility
## Scraping Commands
diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml
index 6365946..7116769 100644
--- a/.github/workflows/ci-test.yml
+++ b/.github/workflows/ci-test.yml
@@ -6,9 +6,16 @@ on:
pull_request:
types: [opened, synchronize, reopened]
+env:
+ TARGET_PYTHON_VERSION: "3.13"
+
jobs:
test:
runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -16,7 +23,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
- python-version: "3.13"
+ python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1
@@ -30,7 +37,7 @@ jobs:
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: .venv
- key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
+ key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
run: |
@@ -67,11 +74,13 @@ jobs:
run: make test
- name: SonarQube Scan
+ if: matrix.python-version == env.TARGET_PYTHON_VERSION
uses: SonarSource/sonarqube-scan-action@1a6d90ebcb0e6a6b1d87e37ba693fe453195ae25 # v5.3.1
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Upload coverage reports to Codecov
+ if: matrix.python-version == env.TARGET_PYTHON_VERSION
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
with:
fail_ci_if_error: true
diff --git a/Makefile b/Makefile
index 1849e62..83be5a8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
PYTHON_VERSION = 3.13
-PROBLEM ?= find_all_anagrams_in_a_string
+PROBLEM ?= daily_temperatures
FORCE ?= 0
COMMA := ,
@@ -42,6 +42,7 @@ define lint_target
endef
lint:
+ poetry run python scripts/sort_tags.py
poetry sort
npx prettier --write "**/*.{ts,tsx,css,json,yaml,yml,md}"
$(call lint_target,.)
diff --git a/leetcode/accounts_merge/test_solution.py b/leetcode/accounts_merge/test_solution.py
index 8f18c66..0667cab 100644
--- a/leetcode/accounts_merge/test_solution.py
+++ b/leetcode/accounts_merge/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_accounts_merge, run_accounts_merge
from .solution import Solution
diff --git a/leetcode/add_binary/test_solution.py b/leetcode/add_binary/test_solution.py
index 585a246..bfdf6a7 100644
--- a/leetcode/add_binary/test_solution.py
+++ b/leetcode/add_binary/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_add_binary, run_add_binary
from .solution import Solution
diff --git a/leetcode/balanced_binary_tree/test_solution.py b/leetcode/balanced_binary_tree/test_solution.py
index 97000d1..9d7a061 100644
--- a/leetcode/balanced_binary_tree/test_solution.py
+++ b/leetcode/balanced_binary_tree/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_is_balanced, run_is_balanced
from .solution import Solution
diff --git a/leetcode/basic_calculator/test_solution.py b/leetcode/basic_calculator/test_solution.py
index 8a67479..6bbc871 100644
--- a/leetcode/basic_calculator/test_solution.py
+++ b/leetcode/basic_calculator/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_calculate, run_calculate
from .solution import Solution
diff --git a/leetcode/best_time_to_buy_and_sell_stock/test_solution.py b/leetcode/best_time_to_buy_and_sell_stock/test_solution.py
index 4ed9be5..4100e9f 100644
--- a/leetcode/best_time_to_buy_and_sell_stock/test_solution.py
+++ b/leetcode/best_time_to_buy_and_sell_stock/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_max_profit, run_max_profit
from .solution import Solution
diff --git a/leetcode/binary_search/test_solution.py b/leetcode/binary_search/test_solution.py
index 5ed0cee..f56195c 100644
--- a/leetcode/binary_search/test_solution.py
+++ b/leetcode/binary_search/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_search, run_search
from .solution import Solution
diff --git a/leetcode/binary_tree_level_order_traversal/test_solution.py b/leetcode/binary_tree_level_order_traversal/test_solution.py
index ccb041a..ee7f511 100644
--- a/leetcode/binary_tree_level_order_traversal/test_solution.py
+++ b/leetcode/binary_tree_level_order_traversal/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_level_order, run_level_order
from .solution import Solution
diff --git a/leetcode/binary_tree_right_side_view/test_solution.py b/leetcode/binary_tree_right_side_view/test_solution.py
index 871ecef..260e71b 100644
--- a/leetcode/binary_tree_right_side_view/test_solution.py
+++ b/leetcode/binary_tree_right_side_view/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_right_side_view, run_right_side_view
from .solution import Solution, SolutionBFS, SolutionDFS
diff --git a/leetcode/climbing_stairs/test_solution.py b/leetcode/climbing_stairs/test_solution.py
index a141b4e..a41eff7 100644
--- a/leetcode/climbing_stairs/test_solution.py
+++ b/leetcode/climbing_stairs/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_climb_stairs, run_climb_stairs
from .solution import Solution
diff --git a/leetcode/clone_graph/test_solution.py b/leetcode/clone_graph/test_solution.py
index 95bb87f..4bc8818 100644
--- a/leetcode/clone_graph/test_solution.py
+++ b/leetcode/clone_graph/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_clone_graph, run_clone_graph
from .solution import Solution, SolutionBFS, SolutionDFS
diff --git a/leetcode/coin_change/test_solution.py b/leetcode/coin_change/test_solution.py
index 62eede4..b5013d4 100644
--- a/leetcode/coin_change/test_solution.py
+++ b/leetcode/coin_change/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_coin_change, run_coin_change
from .solution import Solution
diff --git a/leetcode/combination_sum/test_solution.py b/leetcode/combination_sum/test_solution.py
index de34df9..e168a02 100644
--- a/leetcode/combination_sum/test_solution.py
+++ b/leetcode/combination_sum/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_combination_sum, run_combination_sum
from .solution import Solution
diff --git a/leetcode/construct_binary_tree_from_preorder_and_inorder_traversal/test_solution.py b/leetcode/construct_binary_tree_from_preorder_and_inorder_traversal/test_solution.py
index 5f08fe6..88f7534 100644
--- a/leetcode/construct_binary_tree_from_preorder_and_inorder_traversal/test_solution.py
+++ b/leetcode/construct_binary_tree_from_preorder_and_inorder_traversal/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_build_tree, run_build_tree
from .solution import Solution
diff --git a/leetcode/container_with_most_water/test_solution.py b/leetcode/container_with_most_water/test_solution.py
index c2bf0e1..edee77b 100644
--- a/leetcode/container_with_most_water/test_solution.py
+++ b/leetcode/container_with_most_water/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_max_area, run_max_area
from .solution import Solution
diff --git a/leetcode/contains_duplicate/test_solution.py b/leetcode/contains_duplicate/test_solution.py
index e8c2c7e..02178b9 100644
--- a/leetcode/contains_duplicate/test_solution.py
+++ b/leetcode/contains_duplicate/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_contains_duplicate, run_contains_duplicate
from .solution import Solution
diff --git a/leetcode/course_schedule/test_solution.py b/leetcode/course_schedule/test_solution.py
index 35aae35..85e5235 100644
--- a/leetcode/course_schedule/test_solution.py
+++ b/leetcode/course_schedule/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_can_finish, run_can_finish
from .solution import Solution
diff --git a/leetcode/daily_temperatures/README.md b/leetcode/daily_temperatures/README.md
new file mode 100644
index 0000000..ce4e11f
--- /dev/null
+++ b/leetcode/daily_temperatures/README.md
@@ -0,0 +1,46 @@
+# Daily Temperatures
+
+**Difficulty:** Medium
+**Topics:** Array, Stack, Monotonic Stack
+**Tags:** grind
+
+**LeetCode:** [Problem 739](https://leetcode.com/problems/daily-temperatures/description/)
+
+## Problem Description
+
+Given an array of integers `temperatures` represents the daily temperatures, return an array `answer` such that `answer[i]` is the number of days you have to wait after the `ith` day to get a warmer temperature. If there is no future day for which this is possible, keep `answer[i] == 0` instead.
+
+## Examples
+
+### Example 1:
+
+```
+Input: temperatures = [73,74,75,71,69,72,76,73]
+Output: [1,1,4,2,1,1,0,0]
+```
+
+**Explanation:**
+
+- For input `[73,74,75,71,69,72,76,73]`, the output should be `[1,1,4,2,1,1,0,0]`.
+- For example, the first temperature is 73. The next warmer temperature is 74, which is 1 day later, so we put 1.
+- The second temperature is 74. The next warmer temperature is 75, which is 1 day later, so we put 1.
+- The third temperature is 75. The next warmer temperature is 76, which is 4 days later, so we put 4.
+
+### Example 2:
+
+```
+Input: temperatures = [30,40,50,60]
+Output: [1,1,1,0]
+```
+
+### Example 3:
+
+```
+Input: temperatures = [30,60,90]
+Output: [1,1,0]
+```
+
+## Constraints
+
+- `1 <= temperatures.length <= 10^5`
+- `30 <= temperatures[i] <= 100`
diff --git a/leetcode/daily_temperatures/__init__.py b/leetcode/daily_temperatures/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/leetcode/daily_temperatures/helpers.py b/leetcode/daily_temperatures/helpers.py
new file mode 100644
index 0000000..dfef547
--- /dev/null
+++ b/leetcode/daily_temperatures/helpers.py
@@ -0,0 +1,8 @@
+def run_daily_temperatures(solution_class: type, temperatures: list[int]):
+ implementation = solution_class()
+ return implementation.daily_temperatures(temperatures)
+
+
+def assert_daily_temperatures(result: list[int], expected: list[int]) -> bool:
+ assert result == expected
+ return True
diff --git a/leetcode/daily_temperatures/playground.py b/leetcode/daily_temperatures/playground.py
new file mode 100644
index 0000000..18c07bf
--- /dev/null
+++ b/leetcode/daily_temperatures/playground.py
@@ -0,0 +1,29 @@
+# ---
+# jupyter:
+# jupytext:
+# text_representation:
+# extension: .py
+# format_name: percent
+# format_version: '1.3'
+# jupytext_version: 1.17.3
+# kernelspec:
+# display_name: leetcode-py-py3.13
+# language: python
+# name: python3
+# ---
+
+# %%
+from helpers import assert_daily_temperatures, run_daily_temperatures
+from solution import Solution
+
+# %%
+# Example test case
+temperatures = [73, 74, 75, 71, 69, 72, 76, 73]
+expected = [1, 1, 4, 2, 1, 1, 0, 0]
+
+# %%
+result = run_daily_temperatures(Solution, temperatures)
+result
+
+# %%
+assert_daily_temperatures(result, expected)
diff --git a/leetcode/daily_temperatures/solution.py b/leetcode/daily_temperatures/solution.py
new file mode 100644
index 0000000..93002d9
--- /dev/null
+++ b/leetcode/daily_temperatures/solution.py
@@ -0,0 +1,15 @@
+class Solution:
+
+ # Time: O(n)
+ # Space: O(n)
+ def daily_temperatures(self, temperatures: list[int]) -> list[int]:
+ result = [0] * len(temperatures)
+ stack: list[int] = []
+
+ for i, temp in enumerate(temperatures):
+ while stack and temperatures[stack[-1]] < temp:
+ prev_index = stack.pop()
+ result[prev_index] = i - prev_index
+ stack.append(i)
+
+ return result
diff --git a/leetcode/daily_temperatures/test_solution.py b/leetcode/daily_temperatures/test_solution.py
new file mode 100644
index 0000000..3a50fc7
--- /dev/null
+++ b/leetcode/daily_temperatures/test_solution.py
@@ -0,0 +1,56 @@
+import pytest
+
+from leetcode_py import logged_test
+
+from .helpers import assert_daily_temperatures, run_daily_temperatures
+from .solution import Solution
+
+
+class TestDailyTemperatures:
+ def setup_method(self):
+ self.solution = Solution()
+
+ @logged_test
+ @pytest.mark.parametrize(
+ "temperatures, expected",
+ [
+ ([73, 74, 75, 71, 69, 72, 76, 73], [1, 1, 4, 2, 1, 1, 0, 0]),
+ ([30, 40, 50, 60], [1, 1, 1, 0]),
+ ([30, 60, 90], [1, 1, 0]),
+ ([89, 62, 70, 58, 47, 47, 46, 76, 100, 70], [8, 1, 5, 4, 3, 2, 1, 1, 0, 0]),
+ ([55, 38, 53, 81, 61, 93, 97, 32, 43, 78], [3, 1, 1, 2, 1, 1, 0, 1, 1, 0]),
+ ([34, 80, 80, 34, 34, 80, 80, 80, 80, 34], [1, 0, 0, 2, 1, 0, 0, 0, 0, 0]),
+ ([73], [0]),
+ ([100], [0]),
+ ([30, 31, 32, 33, 34], [1, 1, 1, 1, 0]),
+ ([90, 80, 70, 60, 50], [0, 0, 0, 0, 0]),
+ ([50, 50, 50, 50], [0, 0, 0, 0]),
+ ([30, 100, 30, 100], [1, 0, 1, 0]),
+ ([75, 71, 69, 72, 76], [4, 2, 1, 1, 0]),
+ ([40, 35, 32, 37, 50], [4, 2, 1, 1, 0]),
+ ([30, 40, 50, 60, 70, 80, 90, 100], [1, 1, 1, 1, 1, 1, 1, 0]),
+ ([30, 30], [0, 0]),
+ ([100, 100], [0, 0]),
+ ([30, 100], [1, 0]),
+ ([100, 30], [0, 0]),
+ ([30, 31, 100], [1, 1, 0]),
+ ([30, 99, 100], [1, 1, 0]),
+ ([50, 40, 60, 30, 70], [2, 1, 2, 1, 0]),
+ ([60, 50, 70, 40, 80], [2, 1, 2, 1, 0]),
+ ([40, 50, 60, 50, 40], [1, 1, 0, 0, 0]),
+ ([30, 40, 50, 40, 30], [1, 1, 0, 0, 0]),
+ ([60, 50, 40, 50, 60], [0, 3, 1, 1, 0]),
+ ([70, 60, 50, 60, 70], [0, 3, 1, 1, 0]),
+ ([45, 50, 40, 60, 55, 65], [1, 2, 1, 2, 1, 0]),
+ ([35, 45, 30, 50, 40, 60], [1, 2, 1, 2, 1, 0]),
+ ([30, 31, 32, 33, 34, 35, 36, 37, 38, 39], [1, 1, 1, 1, 1, 1, 1, 1, 1, 0]),
+ ([50, 49, 48, 47, 46, 45, 44, 43, 42, 41], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
+ ([40, 40, 50, 50, 60, 60], [2, 1, 2, 1, 0, 0]),
+ ([60, 60, 50, 50, 40, 40], [0, 0, 0, 0, 0, 0]),
+ ([30, 31, 30, 32], [1, 2, 1, 0]),
+ ([99, 100, 99, 100], [1, 0, 1, 0]),
+ ],
+ )
+ def test_daily_temperatures(self, temperatures: list[int], expected: list[int]):
+ result = run_daily_temperatures(Solution, temperatures)
+ assert_daily_temperatures(result, expected)
diff --git a/leetcode/diagonal_traverse/test_solution.py b/leetcode/diagonal_traverse/test_solution.py
index a57c110..2cec862 100644
--- a/leetcode/diagonal_traverse/test_solution.py
+++ b/leetcode/diagonal_traverse/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_find_diagonal_order, run_find_diagonal_order
from .solution import Solution, SolutionRowShift
diff --git a/leetcode/diameter_of_binary_tree/test_solution.py b/leetcode/diameter_of_binary_tree/test_solution.py
index 11468a0..8a3bffc 100644
--- a/leetcode/diameter_of_binary_tree/test_solution.py
+++ b/leetcode/diameter_of_binary_tree/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_diameter_of_binary_tree, run_diameter_of_binary_tree
from .solution import Solution
diff --git a/leetcode/evaluate_reverse_polish_notation/test_solution.py b/leetcode/evaluate_reverse_polish_notation/test_solution.py
index 244730f..e11cc5b 100644
--- a/leetcode/evaluate_reverse_polish_notation/test_solution.py
+++ b/leetcode/evaluate_reverse_polish_notation/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_eval_rpn, run_eval_rpn
from .solution import Solution
diff --git a/leetcode/find_all_anagrams_in_a_string/test_solution.py b/leetcode/find_all_anagrams_in_a_string/test_solution.py
index 3782888..d16ca19 100644
--- a/leetcode/find_all_anagrams_in_a_string/test_solution.py
+++ b/leetcode/find_all_anagrams_in_a_string/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_find_anagrams, run_find_anagrams
from .solution import Solution
diff --git a/leetcode/find_median_from_data_stream/test_solution.py b/leetcode/find_median_from_data_stream/test_solution.py
index 25ec573..823bd07 100644
--- a/leetcode/find_median_from_data_stream/test_solution.py
+++ b/leetcode/find_median_from_data_stream/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_median_finder, run_median_finder
from .solution import MedianFinder, MedianFinderHybrid
diff --git a/leetcode/first_bad_version/test_solution.py b/leetcode/first_bad_version/test_solution.py
index 3ea5446..916fb2c 100644
--- a/leetcode/first_bad_version/test_solution.py
+++ b/leetcode/first_bad_version/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_first_bad_version, run_first_bad_version
from .solution import Solution
diff --git a/leetcode/flood_fill/test_solution.py b/leetcode/flood_fill/test_solution.py
index c9703c3..cf1b8ab 100644
--- a/leetcode/flood_fill/test_solution.py
+++ b/leetcode/flood_fill/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_flood_fill, run_flood_fill
from .solution import Solution
diff --git a/leetcode/implement_queue_using_stacks/test_solution.py b/leetcode/implement_queue_using_stacks/test_solution.py
index 24520ea..8094d30 100644
--- a/leetcode/implement_queue_using_stacks/test_solution.py
+++ b/leetcode/implement_queue_using_stacks/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_my_queue, run_my_queue
from .solution import MyQueue
diff --git a/leetcode/implement_trie_prefix_tree/test_solution.py b/leetcode/implement_trie_prefix_tree/test_solution.py
index 272e9bd..8a53b20 100644
--- a/leetcode/implement_trie_prefix_tree/test_solution.py
+++ b/leetcode/implement_trie_prefix_tree/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_trie_operations, run_trie_operations
from .solution import Trie
diff --git a/leetcode/insert_interval/test_solution.py b/leetcode/insert_interval/test_solution.py
index a7ab73d..0901983 100644
--- a/leetcode/insert_interval/test_solution.py
+++ b/leetcode/insert_interval/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_insert, run_insert
from .solution import Solution
diff --git a/leetcode/invert_binary_tree/test_solution.py b/leetcode/invert_binary_tree/test_solution.py
index 9e40afb..c86cc48 100644
--- a/leetcode/invert_binary_tree/test_solution.py
+++ b/leetcode/invert_binary_tree/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_invert_tree, run_invert_tree
from .solution import Solution, SolutionBFS, SolutionDFS
diff --git a/leetcode/k_closest_points_to_origin/test_solution.py b/leetcode/k_closest_points_to_origin/test_solution.py
index df391c0..7faa99d 100644
--- a/leetcode/k_closest_points_to_origin/test_solution.py
+++ b/leetcode/k_closest_points_to_origin/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_k_closest, run_k_closest
from .solution import Solution
diff --git a/leetcode/kth_smallest_element_in_a_bst/test_solution.py b/leetcode/kth_smallest_element_in_a_bst/test_solution.py
index be3683e..f12abdb 100644
--- a/leetcode/kth_smallest_element_in_a_bst/test_solution.py
+++ b/leetcode/kth_smallest_element_in_a_bst/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_kth_smallest, run_kth_smallest
from .solution import Solution
diff --git a/leetcode/largest_rectangle_in_histogram/test_solution.py b/leetcode/largest_rectangle_in_histogram/test_solution.py
index 2ac5ee6..66965c5 100644
--- a/leetcode/largest_rectangle_in_histogram/test_solution.py
+++ b/leetcode/largest_rectangle_in_histogram/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_largest_rectangle_area, run_largest_rectangle_area
from .solution import Solution
diff --git a/leetcode/letter_combinations_of_a_phone_number/test_solution.py b/leetcode/letter_combinations_of_a_phone_number/test_solution.py
index c28498c..28c1e2b 100644
--- a/leetcode/letter_combinations_of_a_phone_number/test_solution.py
+++ b/leetcode/letter_combinations_of_a_phone_number/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_letter_combinations, run_letter_combinations
from .solution import Solution
diff --git a/leetcode/linked_list_cycle/test_solution.py b/leetcode/linked_list_cycle/test_solution.py
index 25f3780..c302c6d 100644
--- a/leetcode/linked_list_cycle/test_solution.py
+++ b/leetcode/linked_list_cycle/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_has_cycle, run_has_cycle
from .solution import Solution
diff --git a/leetcode/longest_palindrome/test_solution.py b/leetcode/longest_palindrome/test_solution.py
index 5c8e279..dfdfcce 100644
--- a/leetcode/longest_palindrome/test_solution.py
+++ b/leetcode/longest_palindrome/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_longest_palindrome, run_longest_palindrome
from .solution import Solution
diff --git a/leetcode/longest_palindromic_substring/test_solution.py b/leetcode/longest_palindromic_substring/test_solution.py
index 0cd1994..5751e25 100644
--- a/leetcode/longest_palindromic_substring/test_solution.py
+++ b/leetcode/longest_palindromic_substring/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_longest_palindrome, run_longest_palindrome
from .solution import Solution, SolutionManacher
diff --git a/leetcode/longest_substring_without_repeating_characters/test_solution.py b/leetcode/longest_substring_without_repeating_characters/test_solution.py
index 3193ea9..289a132 100644
--- a/leetcode/longest_substring_without_repeating_characters/test_solution.py
+++ b/leetcode/longest_substring_without_repeating_characters/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_length_of_longest_substring, run_length_of_longest_substring
from .solution import Solution
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py
index fd2cede..47dc07b 100644
--- a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py
+++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor
from .solution import Solution
diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py b/leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py
index 3d28a96..6b01c21 100644
--- a/leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py
+++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor
from .solution import Solution
diff --git a/leetcode/lru_cache/test_solution.py b/leetcode/lru_cache/test_solution.py
index db70723..3a3c15f 100644
--- a/leetcode/lru_cache/test_solution.py
+++ b/leetcode/lru_cache/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_lru_cache, run_lru_cache
from .solution import LRUCache, LRUCacheWithDoublyList
diff --git a/leetcode/majority_element/test_solution.py b/leetcode/majority_element/test_solution.py
index ef73961..eaa2b55 100644
--- a/leetcode/majority_element/test_solution.py
+++ b/leetcode/majority_element/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_majority_element, run_majority_element
from .solution import Solution
diff --git a/leetcode/maximum_depth_of_binary_tree/test_solution.py b/leetcode/maximum_depth_of_binary_tree/test_solution.py
index e86979c..285a10a 100644
--- a/leetcode/maximum_depth_of_binary_tree/test_solution.py
+++ b/leetcode/maximum_depth_of_binary_tree/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_max_depth, run_max_depth
from .solution import Solution
diff --git a/leetcode/maximum_profit_in_job_scheduling/test_solution.py b/leetcode/maximum_profit_in_job_scheduling/test_solution.py
index 8e17451..ec132a6 100644
--- a/leetcode/maximum_profit_in_job_scheduling/test_solution.py
+++ b/leetcode/maximum_profit_in_job_scheduling/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_job_scheduling, run_job_scheduling
from .solution import Solution
diff --git a/leetcode/maximum_subarray/test_solution.py b/leetcode/maximum_subarray/test_solution.py
index ddb4d89..841b14b 100644
--- a/leetcode/maximum_subarray/test_solution.py
+++ b/leetcode/maximum_subarray/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_max_sub_array, run_max_sub_array
from .solution import Solution
diff --git a/leetcode/merge_intervals/test_solution.py b/leetcode/merge_intervals/test_solution.py
index 195cced..b8d86f6 100644
--- a/leetcode/merge_intervals/test_solution.py
+++ b/leetcode/merge_intervals/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_merge, run_merge
from .solution import Solution
diff --git a/leetcode/merge_k_sorted_lists/test_solution.py b/leetcode/merge_k_sorted_lists/test_solution.py
index e9d9cfc..15d491c 100644
--- a/leetcode/merge_k_sorted_lists/test_solution.py
+++ b/leetcode/merge_k_sorted_lists/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_merge_k_lists, run_merge_k_lists
from .solution import Solution
diff --git a/leetcode/merge_two_sorted_lists/test_solution.py b/leetcode/merge_two_sorted_lists/test_solution.py
index 58b2887..1f04409 100644
--- a/leetcode/merge_two_sorted_lists/test_solution.py
+++ b/leetcode/merge_two_sorted_lists/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_merge_two_lists, run_merge_two_lists
from .solution import Solution
diff --git a/leetcode/middle_of_the_linked_list/test_solution.py b/leetcode/middle_of_the_linked_list/test_solution.py
index 0cdfdd0..ae85cfb 100644
--- a/leetcode/middle_of_the_linked_list/test_solution.py
+++ b/leetcode/middle_of_the_linked_list/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_middle_node, run_middle_node
from .solution import Solution
diff --git a/leetcode/min_stack/test_solution.py b/leetcode/min_stack/test_solution.py
index fd75d51..4e781e2 100644
--- a/leetcode/min_stack/test_solution.py
+++ b/leetcode/min_stack/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_min_stack_operations, run_min_stack_operations
from .solution import MinStack
diff --git a/leetcode/minimum_height_trees/test_solution.py b/leetcode/minimum_height_trees/test_solution.py
index 74cd63c..b03370a 100644
--- a/leetcode/minimum_height_trees/test_solution.py
+++ b/leetcode/minimum_height_trees/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_find_min_height_trees, run_find_min_height_trees
from .solution import Solution
diff --git a/leetcode/minimum_window_substring/test_solution.py b/leetcode/minimum_window_substring/test_solution.py
index a76fe7a..2e33210 100644
--- a/leetcode/minimum_window_substring/test_solution.py
+++ b/leetcode/minimum_window_substring/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_min_window, run_min_window
from .solution import Solution
diff --git a/leetcode/number_of_islands/test_solution.py b/leetcode/number_of_islands/test_solution.py
index 2b37d2d..44a247e 100644
--- a/leetcode/number_of_islands/test_solution.py
+++ b/leetcode/number_of_islands/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_num_islands, run_num_islands
from .solution import Solution
diff --git a/leetcode/partition_equal_subset_sum/test_solution.py b/leetcode/partition_equal_subset_sum/test_solution.py
index a480df5..0a983dc 100644
--- a/leetcode/partition_equal_subset_sum/test_solution.py
+++ b/leetcode/partition_equal_subset_sum/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_can_partition, run_can_partition
from .solution import Solution, SolutionBitset
diff --git a/leetcode/permutations/test_solution.py b/leetcode/permutations/test_solution.py
index d8d1bce..92237b9 100644
--- a/leetcode/permutations/test_solution.py
+++ b/leetcode/permutations/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_permute, run_permute
from .solution import Solution
diff --git a/leetcode/product_of_array_except_self/test_solution.py b/leetcode/product_of_array_except_self/test_solution.py
index 84013ac..fe69a3c 100644
--- a/leetcode/product_of_array_except_self/test_solution.py
+++ b/leetcode/product_of_array_except_self/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_product_except_self, run_product_except_self
from .solution import Solution
diff --git a/leetcode/ransom_note/test_solution.py b/leetcode/ransom_note/test_solution.py
index 429913a..d5613e0 100644
--- a/leetcode/ransom_note/test_solution.py
+++ b/leetcode/ransom_note/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_can_construct, run_can_construct
from .solution import Solution
diff --git a/leetcode/reverse_linked_list/test_solution.py b/leetcode/reverse_linked_list/test_solution.py
index 8f8198e..8a8c4d0 100644
--- a/leetcode/reverse_linked_list/test_solution.py
+++ b/leetcode/reverse_linked_list/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_reverse_list, run_reverse_list
from .solution import Solution
diff --git a/leetcode/reverse_linked_list_ii/test_solution.py b/leetcode/reverse_linked_list_ii/test_solution.py
index 5964711..56770a1 100644
--- a/leetcode/reverse_linked_list_ii/test_solution.py
+++ b/leetcode/reverse_linked_list_ii/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_reverse_between, run_reverse_between
from .solution import Solution
diff --git a/leetcode/rotting_oranges/test_solution.py b/leetcode/rotting_oranges/test_solution.py
index 9710fb8..571852a 100644
--- a/leetcode/rotting_oranges/test_solution.py
+++ b/leetcode/rotting_oranges/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_oranges_rotting, run_oranges_rotting
from .solution import Solution
diff --git a/leetcode/search_in_rotated_sorted_array/test_solution.py b/leetcode/search_in_rotated_sorted_array/test_solution.py
index a227711..29303eb 100644
--- a/leetcode/search_in_rotated_sorted_array/test_solution.py
+++ b/leetcode/search_in_rotated_sorted_array/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_search, run_search
from .solution import Solution
diff --git a/leetcode/serialize_and_deserialize_binary_tree/test_solution.py b/leetcode/serialize_and_deserialize_binary_tree/test_solution.py
index 8f67b6e..056ca2f 100644
--- a/leetcode/serialize_and_deserialize_binary_tree/test_solution.py
+++ b/leetcode/serialize_and_deserialize_binary_tree/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_serialize_deserialize, run_serialize_deserialize
from .solution import Codec
diff --git a/leetcode/sort_colors/test_solution.py b/leetcode/sort_colors/test_solution.py
index 43f6085..a9aaa03 100644
--- a/leetcode/sort_colors/test_solution.py
+++ b/leetcode/sort_colors/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_sort_colors, run_sort_colors
from .solution import Solution
diff --git a/leetcode/spiral_matrix/test_solution.py b/leetcode/spiral_matrix/test_solution.py
index 22bda84..d79d8ab 100644
--- a/leetcode/spiral_matrix/test_solution.py
+++ b/leetcode/spiral_matrix/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_spiral_order, run_spiral_order
from .solution import Solution
diff --git a/leetcode/string_to_integer_atoi/test_solution.py b/leetcode/string_to_integer_atoi/test_solution.py
index d09898c..e73c34a 100644
--- a/leetcode/string_to_integer_atoi/test_solution.py
+++ b/leetcode/string_to_integer_atoi/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_my_atoi, run_my_atoi
from .solution import Solution
diff --git a/leetcode/subsets/test_solution.py b/leetcode/subsets/test_solution.py
index 6efd136..e16ab8c 100644
--- a/leetcode/subsets/test_solution.py
+++ b/leetcode/subsets/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_subsets, run_subsets
from .solution import Solution
diff --git a/leetcode/task_scheduler/test_solution.py b/leetcode/task_scheduler/test_solution.py
index 85794ce..441fa58 100644
--- a/leetcode/task_scheduler/test_solution.py
+++ b/leetcode/task_scheduler/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_least_interval, run_least_interval
from .solution import Solution
diff --git a/leetcode/three_sum/test_solution.py b/leetcode/three_sum/test_solution.py
index 2bba573..46d0263 100644
--- a/leetcode/three_sum/test_solution.py
+++ b/leetcode/three_sum/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_three_sum, run_three_sum
from .solution import Solution
diff --git a/leetcode/time_based_key_value_store/test_solution.py b/leetcode/time_based_key_value_store/test_solution.py
index c0c16ff..fb955f7 100644
--- a/leetcode/time_based_key_value_store/test_solution.py
+++ b/leetcode/time_based_key_value_store/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_time_map_operations, run_time_map_operations
from .solution import TimeMap
diff --git a/leetcode/trapping_rain_water/test_solution.py b/leetcode/trapping_rain_water/test_solution.py
index c35bb3f..ab8cd90 100644
--- a/leetcode/trapping_rain_water/test_solution.py
+++ b/leetcode/trapping_rain_water/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_trap, run_trap
from .solution import Solution
diff --git a/leetcode/two_sum/test_solution.py b/leetcode/two_sum/test_solution.py
index 75cb3f3..a251146 100644
--- a/leetcode/two_sum/test_solution.py
+++ b/leetcode/two_sum/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_two_sum, run_two_sum
from .solution import Solution
diff --git a/leetcode/unique_paths/test_solution.py b/leetcode/unique_paths/test_solution.py
index b66ad67..1e9be3c 100644
--- a/leetcode/unique_paths/test_solution.py
+++ b/leetcode/unique_paths/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_unique_paths, run_unique_paths
from .solution import Solution, SolutionMath
diff --git a/leetcode/valid_anagram/test_solution.py b/leetcode/valid_anagram/test_solution.py
index 8a46646..0acd896 100644
--- a/leetcode/valid_anagram/test_solution.py
+++ b/leetcode/valid_anagram/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_is_anagram, run_is_anagram
from .solution import Solution
diff --git a/leetcode/valid_palindrome/test_solution.py b/leetcode/valid_palindrome/test_solution.py
index 83b4362..7804ca5 100644
--- a/leetcode/valid_palindrome/test_solution.py
+++ b/leetcode/valid_palindrome/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_is_palindrome, run_is_palindrome
from .solution import Solution
diff --git a/leetcode/valid_parentheses/test_solution.py b/leetcode/valid_parentheses/test_solution.py
index 7e4c8b2..78f33d3 100644
--- a/leetcode/valid_parentheses/test_solution.py
+++ b/leetcode/valid_parentheses/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_is_valid, run_is_valid
from .solution import Solution
diff --git a/leetcode/validate_binary_search_tree/test_solution.py b/leetcode/validate_binary_search_tree/test_solution.py
index 2491911..e71dd32 100644
--- a/leetcode/validate_binary_search_tree/test_solution.py
+++ b/leetcode/validate_binary_search_tree/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_is_valid_bst, run_is_valid_bst
from .solution import Solution, SolutionBFS, SolutionDFS
diff --git a/leetcode/word_break/test_solution.py b/leetcode/word_break/test_solution.py
index 97a2619..7463d00 100644
--- a/leetcode/word_break/test_solution.py
+++ b/leetcode/word_break/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_word_break, run_word_break
from .solution import Solution
diff --git a/leetcode/word_ladder/test_solution.py b/leetcode/word_ladder/test_solution.py
index 94008a8..2239150 100644
--- a/leetcode/word_ladder/test_solution.py
+++ b/leetcode/word_ladder/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_ladder_length, run_ladder_length
from .solution import Solution
diff --git a/leetcode/word_search/test_solution.py b/leetcode/word_search/test_solution.py
index 5e1151d..32f041c 100644
--- a/leetcode/word_search/test_solution.py
+++ b/leetcode/word_search/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_exist, run_exist
from .solution import Solution
diff --git a/leetcode/zero_one_matrix/test_solution.py b/leetcode/zero_one_matrix/test_solution.py
index 4129b47..d7823c0 100644
--- a/leetcode/zero_one_matrix/test_solution.py
+++ b/leetcode/zero_one_matrix/test_solution.py
@@ -1,6 +1,6 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
from .helpers import assert_update_matrix, run_update_matrix
from .solution import Solution
diff --git a/leetcode_py/__init__.py b/leetcode_py/__init__.py
index 0300fc0..8134f11 100644
--- a/leetcode_py/__init__.py
+++ b/leetcode_py/__init__.py
@@ -1,5 +1,6 @@
from leetcode_py.data_structures.graph_node import GraphNode
from leetcode_py.data_structures.list_node import ListNode
from leetcode_py.data_structures.tree_node import TreeNode
+from leetcode_py.tools.logged_test import logged_test
-__all__ = ["GraphNode", "ListNode", "TreeNode"]
+__all__ = ["GraphNode", "ListNode", "TreeNode", "logged_test"]
diff --git a/leetcode_py/cli/resources/leetcode/cookiecutter.json b/leetcode_py/cli/resources/leetcode/cookiecutter.json
index 4287ea1..e8175f0 100644
--- a/leetcode_py/cli/resources/leetcode/cookiecutter.json
+++ b/leetcode_py/cli/resources/leetcode/cookiecutter.json
@@ -18,7 +18,7 @@
"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_imports": "import pytest\nfrom leetcode_py 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)",
@@ -31,7 +31,7 @@
"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_imports": "import pytest\nfrom leetcode_py 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()",
diff --git a/leetcode_py/cli/resources/leetcode/examples/example.json5 b/leetcode_py/cli/resources/leetcode/examples/example.json5
index 09c1c24..57d4159 100644
--- a/leetcode_py/cli/resources/leetcode/examples/example.json5
+++ b/leetcode_py/cli/resources/leetcode/examples/example.json5
@@ -1,196 +1,200 @@
{
- // ============================================================================
- // 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
- // ============================================================================
+ // ============================================================================
+ // 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
+ // === 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 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_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?",
+ 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"
+ // === 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)
+ // === 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: ""
+ // === TEST CONFIGURATION ===
+ test_imports: "import pytest\nfrom leetcode_py 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" }
- }
- ]
- },
+ // === 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 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)"
- }
- ]
- },
+ // === 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)"
+ // === 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
- // ============================================================================
+ // ============================================================================
+ // 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/leetcode_py/cli/resources/leetcode/json/problems/accounts_merge.json b/leetcode_py/cli/resources/leetcode/json/problems/accounts_merge.json
index ec92c0d..08b14e0 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/accounts_merge.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/accounts_merge.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"solution_contents": "",
"solution_class_content": "",
- "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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_accounts_merge, run_accounts_merge\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "AccountsMerge",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/add_binary.json b/leetcode_py/cli/resources/leetcode/json/problems/add_binary.json
index 5b2e293..34caf68 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/add_binary.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/add_binary.json
@@ -26,7 +26,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_add_binary, run_add_binary\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "AddBinary",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/balanced_binary_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/balanced_binary_tree.json
index c413c49..2d5db13 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/balanced_binary_tree.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/balanced_binary_tree.json
@@ -31,7 +31,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_is_balanced, run_is_balanced\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "BalancedBinaryTree",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/basic_calculator.json b/leetcode_py/cli/resources/leetcode/json/problems/basic_calculator.json
index 6644911..74d4066 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/basic_calculator.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/basic_calculator.json
@@ -27,7 +27,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_calculate, run_calculate\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "BasicCalculator",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/best_time_to_buy_and_sell_stock.json b/leetcode_py/cli/resources/leetcode/json/problems/best_time_to_buy_and_sell_stock.json
index 0c86cdb..7e4ca68 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/best_time_to_buy_and_sell_stock.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/best_time_to_buy_and_sell_stock.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_max_profit, run_max_profit\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "BestTimeToBuyAndSellStock",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/binary_search.json b/leetcode_py/cli/resources/leetcode/json/problems/binary_search.json
index 8a15f0d..3166d10 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/binary_search.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/binary_search.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_search, run_search\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "BinarySearch",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/binary_tree_level_order_traversal.json b/leetcode_py/cli/resources/leetcode/json/problems/binary_tree_level_order_traversal.json
index ba006e5..e1cde47 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/binary_tree_level_order_traversal.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/binary_tree_level_order_traversal.json
@@ -29,7 +29,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_level_order, run_level_order\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "BinaryTreeLevelOrderTraversal",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/binary_tree_right_side_view.json b/leetcode_py/cli/resources/leetcode/json/problems/binary_tree_right_side_view.json
index 2607777..8852d72 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/binary_tree_right_side_view.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/binary_tree_right_side_view.json
@@ -32,7 +32,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/climbing_stairs.json b/leetcode_py/cli/resources/leetcode/json/problems/climbing_stairs.json
index c5a1375..fd3db16 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/climbing_stairs.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/climbing_stairs.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_climb_stairs, run_climb_stairs\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ClimbingStairs",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/clone_graph.json b/leetcode_py/cli/resources/leetcode/json/problems/clone_graph.json
index 7165af5..75f6244 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/clone_graph.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/clone_graph.json
@@ -33,7 +33,7 @@
"solution_imports": "from leetcode_py import GraphNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_clone_graph, run_clone_graph\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "CloneGraph",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/coin_change.json b/leetcode_py/cli/resources/leetcode/json/problems/coin_change.json
index 480f745..a668904 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/coin_change.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/coin_change.json
@@ -29,7 +29,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_coin_change, run_coin_change\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "CoinChange",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/combination_sum.json b/leetcode_py/cli/resources/leetcode/json/problems/combination_sum.json
index 44610a1..74786a8 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/combination_sum.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/combination_sum.json
@@ -31,7 +31,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_combination_sum, run_combination_sum\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "CombinationSum",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/construct_binary_tree_from_preorder_and_inorder_traversal.json b/leetcode_py/cli/resources/leetcode/json/problems/construct_binary_tree_from_preorder_and_inorder_traversal.json
index e7e7716..6546085 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/construct_binary_tree_from_preorder_and_inorder_traversal.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/construct_binary_tree_from_preorder_and_inorder_traversal.json
@@ -28,7 +28,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"solution_contents": "",
"solution_class_content": "",
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_build_tree, run_build_tree\nfrom .solution import Solution",
+ "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_build_tree, run_build_tree\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ConstructBinaryTreeFromPreorderAndInorderTraversal",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/container_with_most_water.json b/leetcode_py/cli/resources/leetcode/json/problems/container_with_most_water.json
index f07668b..f7c23cb 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/container_with_most_water.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/container_with_most_water.json
@@ -28,7 +28,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_max_area, run_max_area\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ContainerWithMostWater",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/contains_duplicate.json b/leetcode_py/cli/resources/leetcode/json/problems/contains_duplicate.json
index 0601beb..b623280 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/contains_duplicate.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/contains_duplicate.json
@@ -31,7 +31,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_contains_duplicate, run_contains_duplicate\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ContainsDuplicate",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/course_schedule.json b/leetcode_py/cli/resources/leetcode/json/problems/course_schedule.json
index f02e646..44fc4db 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/course_schedule.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/course_schedule.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_can_finish, run_can_finish\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "CourseSchedule",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/daily_temperatures.json b/leetcode_py/cli/resources/leetcode/json/problems/daily_temperatures.json
new file mode 100644
index 0000000..6dc3945
--- /dev/null
+++ b/leetcode_py/cli/resources/leetcode/json/problems/daily_temperatures.json
@@ -0,0 +1,63 @@
+{
+ "problem_name": "daily_temperatures",
+ "solution_class_name": "Solution",
+ "problem_number": "739",
+ "problem_title": "Daily Temperatures",
+ "difficulty": "Medium",
+ "topics": "Array, Stack, Monotonic Stack",
+ "_tags": { "list": [] },
+ "readme_description": "Given an array of integers `temperatures` represents the daily temperatures, return an array `answer` such that `answer[i]` is the number of days you have to wait after the `ith` day to get a warmer temperature. If there is no future day for which this is possible, keep `answer[i] == 0` instead.",
+ "_readme_examples": {
+ "list": [
+ {
+ "content": "```\nInput: temperatures = [73,74,75,71,69,72,76,73]\nOutput: [1,1,4,2,1,1,0,0]\n```\n**Explanation:**\n- For input `[73,74,75,71,69,72,76,73]`, the output should be `[1,1,4,2,1,1,0,0]`.\n- For example, the first temperature is 73. The next warmer temperature is 74, which is 1 day later, so we put 1.\n- The second temperature is 74. The next warmer temperature is 75, which is 1 day later, so we put 1.\n- The third temperature is 75. The next warmer temperature is 76, which is 4 days later, so we put 4."
+ },
+ { "content": "```\nInput: temperatures = [30,40,50,60]\nOutput: [1,1,1,0]\n```" },
+ { "content": "```\nInput: temperatures = [30,60,90]\nOutput: [1,1,0]\n```" }
+ ]
+ },
+ "readme_constraints": "- `1 <= temperatures.length <= 10^5`\n- `30 <= temperatures[i] <= 100`",
+ "readme_additional": "",
+ "helpers_imports": "",
+ "helpers_content": "",
+ "helpers_run_name": "daily_temperatures",
+ "helpers_run_signature": "(solution_class: type, temperatures: list[int])",
+ "helpers_run_body": " implementation = solution_class()\n return implementation.daily_temperatures(temperatures)",
+ "helpers_assert_name": "daily_temperatures",
+ "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool",
+ "helpers_assert_body": " assert result == expected\n return True",
+ "solution_imports": "",
+ "solution_contents": "",
+ "solution_class_content": "",
+ "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_daily_temperatures, run_daily_temperatures\nfrom .solution import Solution",
+ "test_content": "",
+ "test_class_name": "DailyTemperatures",
+ "test_class_content": " def setup_method(self):\n self.solution = Solution()",
+ "_solution_methods": {
+ "list": [
+ {
+ "name": "daily_temperatures",
+ "signature": "(self, temperatures: list[int]) -> list[int]",
+ "body": " # TODO: Implement daily_temperatures\n return []"
+ }
+ ]
+ },
+ "_test_helper_methods": {
+ "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_daily_temperatures",
+ "signature": "(self, temperatures: list[int], expected: list[int])",
+ "parametrize": "temperatures, expected",
+ "test_cases": "[([73,74,75,71,69,72,76,73], [1,1,4,2,1,1,0,0]), ([30,40,50,60], [1,1,1,0]), ([30,60,90], [1,1,0]), ([89,62,70,58,47,47,46,76,100,70], [8,1,5,4,3,2,1,1,0,0]), ([55,38,53,81,61,93,97,32,43,78], [3,1,1,2,1,1,0,1,1,0]), ([34,80,80,34,34,80,80,80,80,34], [1,0,0,2,1,0,0,0,0,0]), ([73], [0]), ([100], [0]), ([30,31,32,33,34], [1,1,1,1,0]), ([90,80,70,60,50], [0,0,0,0,0]), ([50,50,50,50], [0,0,0,0]), ([30,100,30,100], [1,0,1,0]), ([75,71,69,72,76], [4,2,1,1,0]), ([40,35,32,37,50], [4,2,1,1,0]), ([30,40,50,60,70,80,90,100], [1,1,1,1,1,1,1,0]), ([30,30], [0,0]), ([100,100], [0,0]), ([30,100], [1,0]), ([100,30], [0,0]), ([30,31,100], [1,1,0]), ([30,99,100], [1,1,0]), ([50,40,60,30,70], [2,1,2,1,0]), ([60,50,70,40,80], [2,1,2,1,0]), ([40,50,60,50,40], [1,1,0,0,0]), ([30,40,50,40,30], [1,1,0,0,0]), ([60,50,40,50,60], [0,3,1,1,0]), ([70,60,50,60,70], [0,3,1,1,0]), ([45,50,40,60,55,65], [1,2,1,2,1,0]), ([35,45,30,50,40,60], [1,2,1,2,1,0]), ([30,31,32,33,34,35,36,37,38,39], [1,1,1,1,1,1,1,1,1,0]), ([50,49,48,47,46,45,44,43,42,41], [0,0,0,0,0,0,0,0,0,0]), ([40,40,50,50,60,60], [2,1,2,1,0,0]), ([60,60,50,50,40,40], [0,0,0,0,0,0]), ([30,31,30,32], [1,2,1,0]), ([99,100,99,100], [1,0,1,0])]",
+ "body": " result = run_daily_temperatures(Solution, temperatures)\n assert_daily_temperatures(result, expected)"
+ }
+ ]
+ },
+ "playground_imports": "from helpers import run_daily_temperatures, assert_daily_temperatures\nfrom solution import Solution",
+ "playground_setup": "# Example test case\ntemperatures = [73,74,75,71,69,72,76,73]\nexpected = [1,1,4,2,1,1,0,0]",
+ "playground_run": "result = run_daily_temperatures(Solution, temperatures)\nresult",
+ "playground_assert": "assert_daily_temperatures(result, expected)"
+}
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/diagonal_traverse.json b/leetcode_py/cli/resources/leetcode/json/problems/diagonal_traverse.json
index df17575..6a06a56 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/diagonal_traverse.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/diagonal_traverse.json
@@ -28,7 +28,7 @@
"solution_imports": "",
"solution_contents": "",
"solution_class_content": "",
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_find_diagonal_order, run_find_diagonal_order\nfrom .solution import Solution",
+ "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_find_diagonal_order, run_find_diagonal_order\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "DiagonalTraverse",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/diameter_of_binary_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/diameter_of_binary_tree.json
index 0eec11f..2415601 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/diameter_of_binary_tree.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/diameter_of_binary_tree.json
@@ -28,7 +28,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/evaluate_reverse_polish_notation.json b/leetcode_py/cli/resources/leetcode/json/problems/evaluate_reverse_polish_notation.json
index 7d4e83d..b2f5048 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/evaluate_reverse_polish_notation.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/evaluate_reverse_polish_notation.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_eval_rpn, run_eval_rpn\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "EvaluateReversePolishNotation",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/find_all_anagrams_in_a_string.json b/leetcode_py/cli/resources/leetcode/json/problems/find_all_anagrams_in_a_string.json
index fa9cd1a..8eba9a3 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/find_all_anagrams_in_a_string.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/find_all_anagrams_in_a_string.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"solution_contents": "",
"solution_class_content": "",
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_find_anagrams, run_find_anagrams\nfrom .solution import Solution",
+ "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_find_anagrams, run_find_anagrams\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "FindAllAnagramsInAString",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/find_median_from_data_stream.json b/leetcode_py/cli/resources/leetcode/json/problems/find_median_from_data_stream.json
index 3a85fa4..11b2961 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/find_median_from_data_stream.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/find_median_from_data_stream.json
@@ -27,7 +27,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_median_finder, run_median_finder\nfrom .solution import MedianFinder",
"test_content": "",
"test_class_name": "FindMedianFromDataStream",
"test_class_content": "",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/first_bad_version.json b/leetcode_py/cli/resources/leetcode/json/problems/first_bad_version.json
index 0b30e0b..d8b8699 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/first_bad_version.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/first_bad_version.json
@@ -28,7 +28,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/flood_fill.json b/leetcode_py/cli/resources/leetcode/json/problems/flood_fill.json
index a889a3d..b63f274 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/flood_fill.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/flood_fill.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_flood_fill, run_flood_fill\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "FloodFill",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/implement_queue_using_stacks.json b/leetcode_py/cli/resources/leetcode/json/problems/implement_queue_using_stacks.json
index f2adeb5..bb35f25 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/implement_queue_using_stacks.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/implement_queue_using_stacks.json
@@ -27,7 +27,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_my_queue, run_my_queue\nfrom .solution import MyQueue",
"test_content": "",
"test_class_name": "ImplementQueueUsingStacks",
"test_class_content": "",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/implement_trie_prefix_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/implement_trie_prefix_tree.json
index c800080..c4af8b9 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/implement_trie_prefix_tree.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/implement_trie_prefix_tree.json
@@ -27,7 +27,7 @@
"solution_imports": "from leetcode_py.data_structures import DictTree, RecursiveDict",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_trie_operations, run_trie_operations\nfrom .solution import Trie",
"test_content": "",
"test_class_name": "ImplementTriePrefixTree",
"test_class_content": "",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/insert_interval.json b/leetcode_py/cli/resources/leetcode/json/problems/insert_interval.json
index 44280d1..5429660 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/insert_interval.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/insert_interval.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_insert, run_insert\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "InsertInterval",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/invert_binary_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/invert_binary_tree.json
index b0d5f66..4d8af88 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/invert_binary_tree.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/invert_binary_tree.json
@@ -27,7 +27,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_invert_tree, run_invert_tree\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "InvertBinaryTree",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/k_closest_points_to_origin.json b/leetcode_py/cli/resources/leetcode/json/problems/k_closest_points_to_origin.json
index d5ebb8f..4c0523b 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/k_closest_points_to_origin.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/k_closest_points_to_origin.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_k_closest, run_k_closest\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "KClosestPointsToOrigin",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/kth_smallest_element_in_a_bst.json b/leetcode_py/cli/resources/leetcode/json/problems/kth_smallest_element_in_a_bst.json
index 0b4f072..0c10237 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/kth_smallest_element_in_a_bst.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/kth_smallest_element_in_a_bst.json
@@ -30,7 +30,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_kth_smallest, run_kth_smallest\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "KthSmallestElementInABst",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/largest_rectangle_in_histogram.json b/leetcode_py/cli/resources/leetcode/json/problems/largest_rectangle_in_histogram.json
index 2fe5780..e76b075 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/largest_rectangle_in_histogram.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/largest_rectangle_in_histogram.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/letter_combinations_of_a_phone_number.json b/leetcode_py/cli/resources/leetcode/json/problems/letter_combinations_of_a_phone_number.json
index 990afaf..0fb1f5f 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/letter_combinations_of_a_phone_number.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/letter_combinations_of_a_phone_number.json
@@ -29,7 +29,7 @@
"solution_imports": "",
"solution_contents": "",
"solution_class_content": "",
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_letter_combinations, run_letter_combinations\nfrom .solution import Solution",
+ "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_letter_combinations, run_letter_combinations\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "LetterCombinationsOfAPhoneNumber",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/linked_list_cycle.json b/leetcode_py/cli/resources/leetcode/json/problems/linked_list_cycle.json
index 53aefe2..be053cb 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/linked_list_cycle.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/linked_list_cycle.json
@@ -33,7 +33,7 @@
"solution_imports": "from leetcode_py import ListNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_has_cycle, run_has_cycle\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "LinkedListCycle",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/longest_palindrome.json b/leetcode_py/cli/resources/leetcode/json/problems/longest_palindrome.json
index 8208c5d..0307637 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/longest_palindrome.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/longest_palindrome.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_longest_palindrome, run_longest_palindrome\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "LongestPalindrome",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/longest_palindromic_substring.json b/leetcode_py/cli/resources/leetcode/json/problems/longest_palindromic_substring.json
index de0e3fc..81a4263 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/longest_palindromic_substring.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/longest_palindromic_substring.json
@@ -28,7 +28,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_longest_palindrome, run_longest_palindrome\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "LongestPalindromicSubstring",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/longest_substring_without_repeating_characters.json b/leetcode_py/cli/resources/leetcode/json/problems/longest_substring_without_repeating_characters.json
index 50c04c2..5c4c6b0 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/longest_substring_without_repeating_characters.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/longest_substring_without_repeating_characters.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/lowest_common_ancestor_of_a_binary_search_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/lowest_common_ancestor_of_a_binary_search_tree.json
index e310ef7..3eb7da0 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/lowest_common_ancestor_of_a_binary_search_tree.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/lowest_common_ancestor_of_a_binary_search_tree.json
@@ -31,7 +31,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/lowest_common_ancestor_of_a_binary_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/lowest_common_ancestor_of_a_binary_tree.json
index 44c77e2..f5f5e36 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/lowest_common_ancestor_of_a_binary_tree.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/lowest_common_ancestor_of_a_binary_tree.json
@@ -31,7 +31,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/lru_cache.json b/leetcode_py/cli/resources/leetcode/json/problems/lru_cache.json
index 2765e1f..2ecd230 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/lru_cache.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/lru_cache.json
@@ -27,7 +27,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_lru_cache, run_lru_cache\nfrom .solution import LRUCache",
"test_content": "",
"test_class_name": "LRUCache",
"test_class_content": "",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/majority_element.json b/leetcode_py/cli/resources/leetcode/json/problems/majority_element.json
index 36f438e..01c8606 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/majority_element.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/majority_element.json
@@ -26,7 +26,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_majority_element, run_majority_element\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "MajorityElement",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/maximum_depth_of_binary_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/maximum_depth_of_binary_tree.json
index 1d62f00..217a983 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/maximum_depth_of_binary_tree.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/maximum_depth_of_binary_tree.json
@@ -28,7 +28,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_max_depth, run_max_depth\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "MaximumDepthOfBinaryTree",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/maximum_profit_in_job_scheduling.json b/leetcode_py/cli/resources/leetcode/json/problems/maximum_profit_in_job_scheduling.json
index 54c0cd2..4a2f47d 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/maximum_profit_in_job_scheduling.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/maximum_profit_in_job_scheduling.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_job_scheduling, run_job_scheduling\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "MaximumProfitInJobScheduling",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/maximum_subarray.json b/leetcode_py/cli/resources/leetcode/json/problems/maximum_subarray.json
index 8c9bd1b..9e0611a 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/maximum_subarray.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/maximum_subarray.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/merge_intervals.json b/leetcode_py/cli/resources/leetcode/json/problems/merge_intervals.json
index 606741c..244f3e7 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/merge_intervals.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/merge_intervals.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_merge, run_merge\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "MergeIntervals",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/merge_k_sorted_lists.json b/leetcode_py/cli/resources/leetcode/json/problems/merge_k_sorted_lists.json
index d85adc6..4c58547 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/merge_k_sorted_lists.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/merge_k_sorted_lists.json
@@ -29,7 +29,7 @@
"solution_imports": "from leetcode_py import ListNode",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/merge_two_sorted_lists.json b/leetcode_py/cli/resources/leetcode/json/problems/merge_two_sorted_lists.json
index a6f7231..45a89ce 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/merge_two_sorted_lists.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/merge_two_sorted_lists.json
@@ -29,7 +29,7 @@
"solution_imports": "from leetcode_py import ListNode",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/middle_of_the_linked_list.json b/leetcode_py/cli/resources/leetcode/json/problems/middle_of_the_linked_list.json
index f64df0a..4e333f7 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/middle_of_the_linked_list.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/middle_of_the_linked_list.json
@@ -30,7 +30,7 @@
"solution_imports": "from leetcode_py import ListNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_middle_node, run_middle_node\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "MiddleOfTheLinkedList",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/min_stack.json b/leetcode_py/cli/resources/leetcode/json/problems/min_stack.json
index 7ed6490..739f13e 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/min_stack.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/min_stack.json
@@ -27,7 +27,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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": "",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/minimum_height_trees.json b/leetcode_py/cli/resources/leetcode/json/problems/minimum_height_trees.json
index 3378d91..c4d3133 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/minimum_height_trees.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/minimum_height_trees.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/minimum_window_substring.json b/leetcode_py/cli/resources/leetcode/json/problems/minimum_window_substring.json
index 7032f45..efaef29 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/minimum_window_substring.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/minimum_window_substring.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_min_window, run_min_window\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "MinimumWindowSubstring",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/number_of_islands.json b/leetcode_py/cli/resources/leetcode/json/problems/number_of_islands.json
index 66f120d..edf3ea2 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/number_of_islands.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/number_of_islands.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_num_islands, run_num_islands\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "NumberOfIslands",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/partition_equal_subset_sum.json b/leetcode_py/cli/resources/leetcode/json/problems/partition_equal_subset_sum.json
index 64fa8db..dd0becc 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/partition_equal_subset_sum.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/partition_equal_subset_sum.json
@@ -30,7 +30,7 @@
"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_imports": "import pytest\nfrom leetcode_py 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()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/permutations.json b/leetcode_py/cli/resources/leetcode/json/problems/permutations.json
index cb974c4..10960fe 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/permutations.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/permutations.json
@@ -29,7 +29,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/product_of_array_except_self.json b/leetcode_py/cli/resources/leetcode/json/problems/product_of_array_except_self.json
index d698e0c..7497565 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/product_of_array_except_self.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/product_of_array_except_self.json
@@ -26,7 +26,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/ransom_note.json b/leetcode_py/cli/resources/leetcode/json/problems/ransom_note.json
index 6354724..9425e00 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/ransom_note.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/ransom_note.json
@@ -27,7 +27,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_can_construct, run_can_construct\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "RansomNote",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/reverse_linked_list.json b/leetcode_py/cli/resources/leetcode/json/problems/reverse_linked_list.json
index 9ea3b51..5c01cfb 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/reverse_linked_list.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/reverse_linked_list.json
@@ -31,7 +31,7 @@
"solution_imports": "from leetcode_py import ListNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_reverse_list, run_reverse_list\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ReverseLinkedList",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/reverse_linked_list_ii.json b/leetcode_py/cli/resources/leetcode/json/problems/reverse_linked_list_ii.json
index 75d9318..0fd9061 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/reverse_linked_list_ii.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/reverse_linked_list_ii.json
@@ -28,7 +28,7 @@
"solution_imports": "from leetcode_py import ListNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_reverse_between, run_reverse_between\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ReverseLinkedListII",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/rotting_oranges.json b/leetcode_py/cli/resources/leetcode/json/problems/rotting_oranges.json
index 6081ed4..f4eb7a5 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/rotting_oranges.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/rotting_oranges.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/search_in_rotated_sorted_array.json b/leetcode_py/cli/resources/leetcode/json/problems/search_in_rotated_sorted_array.json
index b20a54c..aae6977 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/search_in_rotated_sorted_array.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/search_in_rotated_sorted_array.json
@@ -27,7 +27,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_search, run_search\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "SearchInRotatedSortedArray",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/serialize_and_deserialize_binary_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/serialize_and_deserialize_binary_tree.json
index e45c6e4..4b4b99c 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/serialize_and_deserialize_binary_tree.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/serialize_and_deserialize_binary_tree.json
@@ -28,7 +28,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_serialize_deserialize, run_serialize_deserialize\nfrom .solution import Codec",
"test_content": "",
"test_class_name": "SerializeAndDeserializeBinaryTree",
"test_class_content": "",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/sort_colors.json b/leetcode_py/cli/resources/leetcode/json/problems/sort_colors.json
index 6633cb0..1628617 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/sort_colors.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/sort_colors.json
@@ -26,7 +26,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_sort_colors, run_sort_colors\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "SortColors",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/spiral_matrix.json b/leetcode_py/cli/resources/leetcode/json/problems/spiral_matrix.json
index 355427b..561dd75 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/spiral_matrix.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/spiral_matrix.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_spiral_order, run_spiral_order\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "SpiralMatrix",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/string_to_integer_atoi.json b/leetcode_py/cli/resources/leetcode/json/problems/string_to_integer_atoi.json
index 781b2c8..2cb7afa 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/string_to_integer_atoi.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/string_to_integer_atoi.json
@@ -39,7 +39,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_my_atoi, run_my_atoi\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "StringToIntegerAtoi",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/subsets.json b/leetcode_py/cli/resources/leetcode/json/problems/subsets.json
index a30f10a..fe72687 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/subsets.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/subsets.json
@@ -28,7 +28,7 @@
"solution_imports": "",
"solution_contents": "",
"solution_class_content": "",
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_subsets, run_subsets\nfrom .solution import Solution",
+ "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_subsets, run_subsets\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "Subsets",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/task_scheduler.json b/leetcode_py/cli/resources/leetcode/json/problems/task_scheduler.json
index a416607..963ebfb 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/task_scheduler.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/task_scheduler.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_least_interval, run_least_interval\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "TaskScheduler",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/three_sum.json b/leetcode_py/cli/resources/leetcode/json/problems/three_sum.json
index 2f099e1..ed261d7 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/three_sum.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/three_sum.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_three_sum, run_three_sum\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ThreeSum",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/time_based_key_value_store.json b/leetcode_py/cli/resources/leetcode/json/problems/time_based_key_value_store.json
index 529ce24..1b4be43 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/time_based_key_value_store.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/time_based_key_value_store.json
@@ -27,7 +27,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": "",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/trapping_rain_water.json b/leetcode_py/cli/resources/leetcode/json/problems/trapping_rain_water.json
index 4a21e57..d72b4ae 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/trapping_rain_water.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/trapping_rain_water.json
@@ -28,7 +28,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_trap, run_trap\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "TrappingRainWater",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/two_sum.json b/leetcode_py/cli/resources/leetcode/json/problems/two_sum.json
index a1c69c3..c79d490 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/two_sum.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/two_sum.json
@@ -29,7 +29,7 @@
"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_imports": "import pytest\nfrom leetcode_py 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()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/unique_paths.json b/leetcode_py/cli/resources/leetcode/json/problems/unique_paths.json
index 001900d..afca70e 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/unique_paths.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/unique_paths.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"solution_contents": "",
"solution_class_content": "",
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_unique_paths, run_unique_paths\nfrom .solution import Solution",
+ "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_unique_paths, run_unique_paths\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "UniquePaths",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/valid_anagram.json b/leetcode_py/cli/resources/leetcode/json/problems/valid_anagram.json
index 2a0c3ce..36e12bc 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/valid_anagram.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/valid_anagram.json
@@ -26,7 +26,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_is_anagram, run_is_anagram\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ValidAnagram",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/valid_palindrome.json b/leetcode_py/cli/resources/leetcode/json/problems/valid_palindrome.json
index 4f84dc9..69ad19e 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/valid_palindrome.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/valid_palindrome.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_is_palindrome, run_is_palindrome\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ValidPalindrome",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/valid_parentheses.json b/leetcode_py/cli/resources/leetcode/json/problems/valid_parentheses.json
index 554ca10..81b406c 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/valid_parentheses.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/valid_parentheses.json
@@ -29,7 +29,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_is_valid, run_is_valid\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ValidParentheses",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/validate_binary_search_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/validate_binary_search_tree.json
index b0202c1..24095c4 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/validate_binary_search_tree.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/validate_binary_search_tree.json
@@ -30,7 +30,7 @@
"solution_imports": "from leetcode_py import TreeNode",
"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_imports": "import pytest\nfrom leetcode_py 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_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/word_break.json b/leetcode_py/cli/resources/leetcode/json/problems/word_break.json
index 28a1de4..89d9d1b 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/word_break.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/word_break.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_word_break, run_word_break\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "WordBreak",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/word_ladder.json b/leetcode_py/cli/resources/leetcode/json/problems/word_ladder.json
index 216257a..102223a 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/word_ladder.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/word_ladder.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_ladder_length, run_ladder_length\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "WordLadder",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/word_search.json b/leetcode_py/cli/resources/leetcode/json/problems/word_search.json
index 776a490..ea30fc4 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/word_search.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/word_search.json
@@ -33,7 +33,7 @@
"solution_imports": "",
"solution_contents": "",
"solution_class_content": "",
- "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_exist, run_exist\nfrom .solution import Solution",
+ "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_exist, run_exist\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "WordSearch",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/problems/zero_one_matrix.json b/leetcode_py/cli/resources/leetcode/json/problems/zero_one_matrix.json
index f0bac0c..21aca93 100644
--- a/leetcode_py/cli/resources/leetcode/json/problems/zero_one_matrix.json
+++ b/leetcode_py/cli/resources/leetcode/json/problems/zero_one_matrix.json
@@ -30,7 +30,7 @@
"solution_imports": "",
"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_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_update_matrix, run_update_matrix\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "ZeroOneMatrix",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
diff --git a/leetcode_py/cli/resources/leetcode/json/tags.json5 b/leetcode_py/cli/resources/leetcode/json/tags.json5
index ac0d614..f09def5 100644
--- a/leetcode_py/cli/resources/leetcode/json/tags.json5
+++ b/leetcode_py/cli/resources/leetcode/json/tags.json5
@@ -75,13 +75,11 @@
"word_break",
"word_ladder",
"word_search",
- "zero_one_matrix"
+ "zero_one_matrix",
],
+ grind: [{ tag: "grind-75" }, "daily_temperatures"],
+
// Test tag for development and testing
- "test": [
- "two_sum",
- "valid_palindrome",
- "binary_search"
- ]
+ test: ["binary_search", "two_sum", "valid_palindrome"],
}
diff --git a/leetcode_py/cli/utils/problem_finder.py b/leetcode_py/cli/utils/problem_finder.py
index 5943b5c..9a12ba2 100644
--- a/leetcode_py/cli/utils/problem_finder.py
+++ b/leetcode_py/cli/utils/problem_finder.py
@@ -51,22 +51,43 @@ def get_all_problems() -> list[str]:
return [json_file.stem for json_file in json_path.glob("*.json")]
+def _add_problem_to_tag_map(
+ problem_tags_map: dict[str, list[str]], problem_name: str, tag_name: str
+) -> None:
+ if problem_name not in problem_tags_map:
+ problem_tags_map[problem_name] = []
+ problem_tags_map[problem_name].append(tag_name)
+
+
+def _process_tag_reference(
+ tags_data: dict, item: dict, tag_name: str, problem_tags_map: dict[str, list[str]]
+) -> None:
+ for problem_name in tags_data.get(item["tag"], []):
+ if isinstance(problem_name, str):
+ _add_problem_to_tag_map(problem_tags_map, problem_name, tag_name)
+
+
+def _process_tag_item(
+ tags_data: dict, item: str | dict, tag_name: str, problem_tags_map: dict[str, list[str]]
+) -> None:
+ if isinstance(item, dict) and "tag" in item:
+ _process_tag_reference(tags_data, item, tag_name, problem_tags_map)
+ elif isinstance(item, str):
+ _add_problem_to_tag_map(problem_tags_map, item, tag_name)
+
+
@lru_cache(maxsize=1)
def _build_problem_tags_cache() -> dict[str, list[str]]:
- tags_file = get_tags_path()
- problem_tags_map: dict[str, list[str]] = {}
-
try:
- with open(tags_file) as f:
+ with open(get_tags_path()) as f:
tags_data = json5.load(f)
- # Build reverse mapping: problem -> list of tags
+ problem_tags_map: dict[str, list[str]] = {}
+
for tag_name, problems in tags_data.items():
if isinstance(problems, list):
- for problem_name in problems:
- if problem_name not in problem_tags_map:
- problem_tags_map[problem_name] = []
- problem_tags_map[problem_name].append(tag_name)
+ for item in problems:
+ _process_tag_item(tags_data, item, tag_name, problem_tags_map)
return problem_tags_map
except (ValueError, OSError, KeyError):
diff --git a/leetcode_py/tools/generator.py b/leetcode_py/tools/generator.py
index 4474cb9..257491c 100644
--- a/leetcode_py/tools/generator.py
+++ b/leetcode_py/tools/generator.py
@@ -5,6 +5,8 @@
import typer
from cookiecutter.main import cookiecutter
+from leetcode_py.cli.utils.problem_finder import get_tags_for_problem
+
def load_json_data(json_path: Path) -> dict:
if not json_path.exists():
@@ -44,9 +46,34 @@ def format_python_files(problem_dir: Path) -> None:
pass
+def merge_tags(data: dict) -> dict:
+ """Merge tags from get_tags_for_problem with existing JSON tags."""
+ problem_name = data.get("problem_name", "")
+ if not problem_name:
+ return data
+
+ # Get tags from tag system
+ system_tags = get_tags_for_problem(problem_name)
+
+ # Get existing tags from JSON
+ existing_tags = data.get("_tags", {}).get("list", [])
+
+ # Merge and deduplicate tags
+ all_tags = list(set(system_tags + existing_tags))
+
+ # Update data with merged tags
+ if all_tags:
+ data["_tags"] = {"list": all_tags}
+
+ return data
+
+
def generate_from_template(data: dict, template_dir: Path, output_dir: Path) -> None:
"""Generate problem files using cookiecutter template."""
try:
+ # Merge tags before generating
+ data = merge_tags(data)
+
cookiecutter(
str(template_dir),
extra_context=data,
diff --git a/leetcode_py/test_utils.py b/leetcode_py/tools/logged_test.py
similarity index 100%
rename from leetcode_py/test_utils.py
rename to leetcode_py/tools/logged_test.py
diff --git a/poetry.lock b/poetry.lock
index c88ef01..b6650a4 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -95,6 +95,7 @@ files = [
[package.dependencies]
pycodestyle = ">=2.12.0"
+tomli = {version = "*", markers = "python_version < \"3.11\""}
[[package]]
name = "binaryornot"
@@ -149,6 +150,8 @@ mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
@@ -542,6 +545,9 @@ files = [
{file = "coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90"},
]
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
+
[package.extras]
toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
@@ -605,6 +611,25 @@ files = [
{file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"},
]
+[[package]]
+name = "exceptiongroup"
+version = "1.3.0"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+markers = "python_version == \"3.10\""
+files = [
+ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
+ {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
+
+[package.extras]
+test = ["pytest (>=6)"]
+
[[package]]
name = "executing"
version = "2.2.1"
@@ -742,20 +767,20 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0,<9)", "pytest-async
[[package]]
name = "ipython"
-version = "9.5.0"
+version = "8.37.0"
description = "IPython: Productive Interactive Computing"
optional = false
-python-versions = ">=3.11"
+python-versions = ">=3.10"
groups = ["dev"]
files = [
- {file = "ipython-9.5.0-py3-none-any.whl", hash = "sha256:88369ffa1d5817d609120daa523a6da06d02518e582347c29f8451732a9c5e72"},
- {file = "ipython-9.5.0.tar.gz", hash = "sha256:129c44b941fe6d9b82d36fc7a7c18127ddb1d6f02f78f867f402e2e3adde3113"},
+ {file = "ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2"},
+ {file = "ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
decorator = "*"
-ipython-pygments-lexers = "*"
+exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
jedi = ">=0.16"
matplotlib-inline = "*"
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""}
@@ -763,29 +788,21 @@ prompt_toolkit = ">=3.0.41,<3.1.0"
pygments = ">=2.4.0"
stack_data = "*"
traitlets = ">=5.13.0"
+typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""}
[package.extras]
-all = ["ipython[doc,matplotlib,test,test-extra]"]
+all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"]
black = ["black"]
-doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinx_toml (==0.0.4)", "typing_extensions"]
+doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"]
+kernel = ["ipykernel"]
matplotlib = ["matplotlib"]
-test = ["packaging", "pytest", "pytest-asyncio", "testpath"]
-test-extra = ["curio", "ipykernel", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbclient", "nbformat", "numpy (>=1.23)", "pandas", "trio"]
-
-[[package]]
-name = "ipython-pygments-lexers"
-version = "1.1.1"
-description = "Defines a variety of Pygments lexers for highlighting IPython code."
-optional = false
-python-versions = ">=3.8"
-groups = ["dev"]
-files = [
- {file = "ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c"},
- {file = "ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81"},
-]
-
-[package.dependencies]
-pygments = "*"
+nbconvert = ["nbconvert"]
+nbformat = ["nbformat"]
+notebook = ["ipywidgets", "notebook"]
+parallel = ["ipyparallel"]
+qtconsole = ["qtconsole"]
+test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"]
+test-extra = ["curio", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"]
[[package]]
name = "isort"
@@ -955,6 +972,7 @@ mdit-py-plugins = "*"
nbformat = "*"
packaging = "*"
pyyaml = "*"
+tomli = {version = "*", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["autopep8", "black", "flake8", "gitpython", "ipykernel", "isort", "jupyter-fs[fs] (>=1.0)", "jupyter-server (!=2.11)", "nbconvert", "pre-commit", "pytest", "pytest-asyncio", "pytest-cov (>=2.6.1)", "pytest-randomly", "pytest-xdist", "sphinx", "sphinx-gallery (>=0.8)"]
@@ -1178,6 +1196,7 @@ files = [
[package.dependencies]
mypy_extensions = ">=1.0.0"
pathspec = ">=0.9.0"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing_extensions = ">=4.6.0"
[package.extras]
@@ -1495,10 +1514,12 @@ files = [
[package.dependencies]
colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
iniconfig = ">=1"
packaging = ">=20"
pluggy = ">=1.5,<2"
pygments = ">=2.7.2"
+tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
@@ -1770,6 +1791,7 @@ files = [
[package.dependencies]
attrs = ">=22.2.0"
rpds-py = ">=0.7.0"
+typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""}
[[package]]
name = "requests"
@@ -2080,7 +2102,7 @@ version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
-groups = ["dev"]
+groups = ["main", "dev"]
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
@@ -2115,6 +2137,7 @@ files = [
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
]
+markers = {main = "python_version == \"3.10\""}
[[package]]
name = "tornado"
@@ -2230,6 +2253,7 @@ files = [
distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<5"
+typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""}
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
@@ -2265,5 +2289,5 @@ dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"]
[metadata]
lock-version = "2.1"
-python-versions = "^3.13"
-content-hash = "11af98f38dd5768ebe54804d96677bc209cd0927a64f14c2110dde92d9496301"
+python-versions = ">=3.10,<4.0"
+content-hash = "36c5064361f5582ade3b260efc37e6c576cf1fd3d97f2608b1b7d333d19caee7"
diff --git a/pyproject.toml b/pyproject.toml
index 28663d1..1a07d33 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -16,6 +16,9 @@ classifiers = [
"Topic :: Education",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
packages = [{include = "leetcode_py"}]
@@ -25,7 +28,7 @@ include = ["leetcode_py/cli/resources/**/*"]
lcpy = "leetcode_py.cli.main:app"
[tool.poetry.dependencies]
-python = "^3.13"
+python = ">=3.10,<4.0"
anytree = "^2.13.0"
black = "^25.1.0"
cookiecutter = "^2.6.0"
@@ -58,7 +61,7 @@ poetry-plugin-sort = { version = ">=0.2.2", extras = ["plugin"] }
[tool.black]
line-length = 105
-target-version = ['py312']
+target-version = ['py310']
include = '.*\.(py|ipynb)$' # All .py and .ipynb files
extend-exclude = '''
/(
@@ -78,7 +81,7 @@ extend_skip_glob = ["leetcode_py/cli/resources/*"]
[tool.ruff]
line-length = 105
-target-version = 'py312'
+target-version = 'py310'
extend-exclude = ["leetcode_py/cli/resources"]
[tool.ruff.lint.pydocstyle]
diff --git a/scripts/sort_tags.py b/scripts/sort_tags.py
new file mode 100644
index 0000000..94aa364
--- /dev/null
+++ b/scripts/sort_tags.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+import sys
+
+import json5
+
+tags_file = "leetcode_py/cli/resources/leetcode/json/tags.json5"
+
+with open(tags_file) as f:
+ data = json5.load(f)
+
+unsorted_tags = []
+for tag_name, problems in data.items():
+ if isinstance(problems, list):
+ dicts = [item for item in problems if isinstance(item, dict)]
+ strings = [item for item in problems if isinstance(item, str)]
+ expected_order = dicts + sorted(strings)
+
+ if problems != expected_order:
+ unsorted_tags.append((tag_name, problems, expected_order))
+
+if unsorted_tags:
+ print("❌ Found unsorted problem lists:")
+ for tag_name, current, expected in unsorted_tags:
+ print(f"\n{tag_name}:")
+ print(f" Current: {current}")
+ print(f" Expected: {expected}")
+ sys.exit(1)
+else:
+ print("✅ All problem lists are sorted!")
+ sys.exit(0)
diff --git a/tests/test_problem_finder.py b/tests/test_problem_finder.py
new file mode 100644
index 0000000..1d0aac6
--- /dev/null
+++ b/tests/test_problem_finder.py
@@ -0,0 +1,25 @@
+from leetcode_py.cli.utils.problem_finder import _build_problem_tags_cache, get_tags_for_problem
+
+
+def test_build_problem_tags_cache_with_real_tags():
+ result = _build_problem_tags_cache()
+
+ # Test that grind tag includes both grind-75 problems and daily_temperatures
+ assert "daily_temperatures" in result
+ assert "grind" in result["daily_temperatures"]
+
+ # Test that grind-75 problems also get grind tag
+ assert "two_sum" in result
+ assert "grind-75" in result["two_sum"]
+ assert "grind" in result["two_sum"]
+
+
+def test_get_tags_for_problem():
+ # Test daily_temperatures has grind tag
+ tags = get_tags_for_problem("daily_temperatures")
+ assert "grind" in tags
+
+ # Test grind-75 problem has both tags
+ tags = get_tags_for_problem("two_sum")
+ assert "grind-75" in tags
+ assert "grind" in tags
diff --git a/tests/test_test_utils.py b/tests/test_test_utils.py
index 31533cd..3e4eb3c 100644
--- a/tests/test_test_utils.py
+++ b/tests/test_test_utils.py
@@ -2,7 +2,7 @@
import pytest
-from leetcode_py.test_utils import logged_test
+from leetcode_py import logged_test
class TestLoggedTest:
From 85d763c14360daad83a74a4e7a9bfdc7d5bf9cae Mon Sep 17 00:00:00 2001
From: Wisaroot <66859294+wisarootl@users.noreply.github.com>
Date: Wed, 17 Sep 2025 00:28:45 +0700
Subject: [PATCH 02/42] docs: update README.md (#47)
- update README.md
- update agent rules
- add llm-assisted-problem-creation.md
---
.amazonq/rules/problem-creation.md | 6 +-
.amazonq/rules/test-quality-assurance.md | 4 +-
Makefile | 2 +-
README.md | 147 ++++++++++++------
docs/images/generated-solution.png | Bin 0 -> 58317 bytes
docs/images/generated-test.png | Bin 0 -> 207839 bytes
docs/images/linkedlist-str-viz.png | Bin 0 -> 21740 bytes
docs/images/logs-in-test-solution.png | Bin 0 -> 156936 bytes
docs/images/problems-are-generated.png | Bin 0 -> 124050 bytes
docs/images/problems-generation-2.png | Bin 0 -> 35479 bytes
docs/images/problems-generation.png | Bin 0 -> 73623 bytes
docs/images/prompt-with-context.png | Bin 0 -> 84211 bytes
docs/images/readme-example.png | Bin 0 -> 83805 bytes
docs/images/solution-boilerplate.png | Bin 0 -> 29520 bytes
docs/images/test-example.png | Bin 0 -> 110123 bytes
docs/images/tree-str-viz.png | Bin 0 -> 15613 bytes
docs/llm-assisted-problem-creation.md | 144 +++++++++++++++++
leetcode/house_robber/README.md | 38 +++++
leetcode/house_robber/__init__.py | 0
leetcode/house_robber/helpers.py | 8 +
leetcode/house_robber/playground.py | 29 ++++
leetcode/house_robber/solution.py | 21 +++
leetcode/house_robber/test_solution.py | 36 +++++
.../leetcode/json/problems/house_robber.json | 73 +++++++++
.../cli/resources/leetcode/json/tags.json5 | 2 +-
leetcode_py/cli/utils/problem_finder.py | 32 ++--
poetry.lock | 10 +-
pyproject.toml | 4 +-
tests/test_problem_finder.py | 2 +
29 files changed, 480 insertions(+), 78 deletions(-)
create mode 100644 docs/images/generated-solution.png
create mode 100644 docs/images/generated-test.png
create mode 100644 docs/images/linkedlist-str-viz.png
create mode 100644 docs/images/logs-in-test-solution.png
create mode 100644 docs/images/problems-are-generated.png
create mode 100644 docs/images/problems-generation-2.png
create mode 100644 docs/images/problems-generation.png
create mode 100644 docs/images/prompt-with-context.png
create mode 100644 docs/images/readme-example.png
create mode 100644 docs/images/solution-boilerplate.png
create mode 100644 docs/images/test-example.png
create mode 100644 docs/images/tree-str-viz.png
create mode 100644 docs/llm-assisted-problem-creation.md
create mode 100644 leetcode/house_robber/README.md
create mode 100644 leetcode/house_robber/__init__.py
create mode 100644 leetcode/house_robber/helpers.py
create mode 100644 leetcode/house_robber/playground.py
create mode 100644 leetcode/house_robber/solution.py
create mode 100644 leetcode/house_robber/test_solution.py
create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/house_robber.json
diff --git a/.amazonq/rules/problem-creation.md b/.amazonq/rules/problem-creation.md
index 9249475..8159741 100644
--- a/.amazonq/rules/problem-creation.md
+++ b/.amazonq/rules/problem-creation.md
@@ -4,7 +4,7 @@
When user requests a problem by **number** or **name/slug**, the assistant will:
-1. **Scrape** problem data using `lcpy scrape`
+1. **Scrape** problem data using `poetry run lcpy scrape`
2. **Transform** data into proper JSON template format
3. **CRITICAL: Include images** - Extract image URLs from scraped data and add to readme_examples with format: `\n\n` before code blocks
- Check scraped data for image URLs in the `raw_content` field
@@ -23,10 +23,10 @@ When user requests a problem by **number** or **name/slug**, the assistant will:
```bash
# Fetch by number
-lcpy scrape -n 1
+poetry run lcpy scrape -n 1
# Fetch by slug
-lcpy scrape -s "two-sum"
+poetry run lcpy scrape -s "two-sum"
```
## JSON Template Format
diff --git a/.amazonq/rules/test-quality-assurance.md b/.amazonq/rules/test-quality-assurance.md
index dd68f27..cff96ad 100644
--- a/.amazonq/rules/test-quality-assurance.md
+++ b/.amazonq/rules/test-quality-assurance.md
@@ -59,7 +59,7 @@ mv .cache/leetcode/{problem_name} leetcode/{problem_name}
```bash
# Generate enhanced problem
-lcpy gen -s {problem_name} -o leetcode --force
+poetry run lcpy gen -s {problem_name} -o leetcode --force
# Test specific problem
make p-test PROBLEM={problem_name}
@@ -80,7 +80,7 @@ poetry run python -m leetcode_py.tools.check_test_cases --threshold=10 --max=non
# Check with custom threshold
poetry run python -m leetcode_py.tools.check_test_cases --threshold=12
-# Generate from JSON template (uses lcpy internally)
+# Generate from JSON template (uses poetry run lcpy internally)
make p-gen PROBLEM={problem_name} FORCE=1
```
diff --git a/Makefile b/Makefile
index 83be5a8..7ce4dcd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
PYTHON_VERSION = 3.13
-PROBLEM ?= daily_temperatures
+PROBLEM ?= house_robber
FORCE ?= 0
COMMA := ,
diff --git a/README.md b/README.md
index bf5c4a9..e4c610a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# LeetCode Practice Repository 🚀
+# LeetCode Practice Environment Generator 🚀
[](https://sonarcloud.io/summary/new_code?id=wisarootl_leetcode-py)
[](https://sonarcloud.io/summary/new_code?id=wisarootl_leetcode-py)
@@ -7,11 +7,11 @@
[](https://github.com/wisarootl/zerv/actions/workflows/ci-test.yml)
[](https://github.com/wisarootl/zerv/actions/workflows/cd.yml)
-A modern Python LeetCode practice environment that goes beyond basic problem solving. Features automated problem generation from LeetCode URLs, beautiful data structure visualizations (TreeNode, ListNode, GraphNode), and comprehensive testing with 12+ test cases per problem. Built with professional development practices including CI/CD, type hints, and quality gates.
+A Python package to generate professional LeetCode practice environments. Features automated problem generation from LeetCode URLs, beautiful data structure visualizations (TreeNode, ListNode, GraphNode), and comprehensive testing with 10+ test cases per problem. Built with professional development practices including CI/CD, type hints, and quality gates.
**What makes this different:**
-- 🤖 **LLM-Assisted Workflow**: Generate new problems instantly with AI assistance
+- 🤖 **[LLM-Assisted Workflow](#llm-assisted-problem-creation)**: Generate new problems instantly with AI assistance
- 🎨 **Visual Debugging**: Interactive tree/graph rendering with Graphviz and anytree
- 🧪 **Production Testing**: Comprehensive test suites with edge cases and reproducibility verification
- 🚀 **Modern Python**: PEP 585/604 type hints, Poetry, and professional tooling
@@ -21,14 +21,20 @@ A modern Python LeetCode practice environment that goes beyond basic problem sol
**Current**: All 75 problems from [Grind 75](https://www.techinterviewhandbook.org/grind75/) - the most essential coding interview questions curated by the creator of Blind 75.
-**Future**: Planned expansion to all 169 Grind problems for comprehensive interview preparation.
+**Future**: Planned expansion to all free Grind problems for comprehensive interview preparation.
## 🚀 Quick Start
-### CLI Installation (Recommended)
+### System Requirements
+
+- **Python 3.13+** - Modern Python runtime with latest type system features
+- **Poetry** - Dependency management and packaging
+- **Make** - Build automation (development workflows)
+- **Git** - Version control system
+- **Graphviz** - Graph visualization library (for data structure rendering)
```bash
-# Install globally via pip
+# Install the package
pip install leetcode-py
# Generate problems anywhere
@@ -36,31 +42,26 @@ lcpy gen -n 1 # Generate Two Sum
lcpy gen -t grind-75 # Generate all Grind 75 problems
lcpy list -t grind-75 # List available problems
lcpy scrape -n 1 # Fetch problem data
+
+# Start practicing
+cd leetcode/two_sum
+python -m pytest test_solution.py # Run tests
+# Edit solution.py, then rerun tests
```
-### Development Setup
+### Example
```bash
-# Clone and setup for development
-git clone https://github.com/wisarootl/leetcode-py.git
-cd leetcode-py
-poetry install
-
-# Start with existing Grind 75 problems
-make gen-all-problems # Regenerates all problems with TODO placeholders
+lcpy gen --problem-tag grind-75 --output leetcode # Generate all Grind 75 problems
+```
-# Practice a specific problem
-make p-test PROBLEM=two_sum
-# Edit leetcode/two_sum/solution.py, then rerun tests
+
-# Run all tests
-make test
-```
+_Bulk generation output showing "Generated problem:" messages for all 75 Grind problems_
-## 📋 Prerequisites
+
-- Python 3.13+
-- Poetry, Make, Git, Graphviz
+_Generated folder structure showing all 75 problem directories after command execution_
## 📁 Problem Structure
@@ -70,42 +71,77 @@ Each problem follows a consistent, production-ready template:
leetcode/two_sum/
├── README.md # Problem description with examples and constraints
├── solution.py # Implementation with type hints and TODO placeholder
-├── test_solution.py # Comprehensive parametrized tests (12+ test cases)
+├── test_solution.py # Comprehensive parametrized tests (10+ test cases)
├── helpers.py # Test helper functions
├── playground.py # Interactive debugging environment (converted from .ipynb)
└── __init__.py # Package marker
```
+
+
+_README format that mirrors LeetCode's problem description layout_
+
+
+
+_Solution boilerplate with type hints and TODO placeholder_
+
+
+
+_Comprehensive parametrized tests with 10+ test cases - executable and debuggable in local development environment_
+
+
+
+_Beautiful colorful test output with loguru integration for enhanced debugging and test result visualization_
+
## ✨ Key Features
### Production-Grade Development Environment
- **Modern Python**: PEP 585/604 type hints, snake_case conventions
- **Comprehensive Linting**: black, isort, ruff, mypy with nbqa for notebooks
-- **High Test Coverage**: 12+ test cases per problem including edge cases
+- **High Test Coverage**: 10+ test cases per problem including edge cases
- **Beautiful Logging**: loguru integration for enhanced test debugging
- **CI/CD Pipeline**: Automated testing, security scanning, and quality gates
### Enhanced Data Structure Visualization
-- **TreeNode**: Beautiful tree rendering with anytree and Graphviz
-- **ListNode**: Clean arrow-based visualization (`1 -> 2 -> 3`)
-- **Interactive Debugging**: Multi-cell playground environment
+Professional-grade visualization for debugging complex data structures with dual rendering modes:
+
+- **TreeNode**: Beautiful tree rendering with anytree and Graphviz integration
+- **ListNode**: Clean arrow-based visualization with cycle detection
+- **GraphNode**: Interactive graph rendering for adjacency list problems
+- **DictTree**: Box-drawing character trees perfect for Trie implementations
+
+#### Jupyter Notebook Integration (HTML Rendering)
-
-_Beautiful tree rendering with anytree and Graphviz_
+
-
-_Clean arrow-based list visualization_
+_Interactive tree visualization using Graphviz SVG rendering in Jupyter notebooks_
+
+
+
+_Professional linked list visualization with Graphviz in Jupyter environment_
+
+#### Terminal/Console Output (String Rendering)
+
+
+
+_Clean ASCII tree rendering using anytree for terminal debugging and logging_
+
+
+
+_Simple arrow-based list representation for console output and test debugging_
### Flexible Notebook Support
-- **Template Generation**: Creates Jupyter notebooks (`.ipynb`) by default
-- **Repository State**: This repo uses Python files (`.py`) for better version control
-- **User Choice**: Use `make nb-to-py` to convert notebooks to Python files, or keep as `.ipynb` for interactive development
+- **Template Generation**: Creates Jupyter notebooks (`.ipynb`) by default with rich data structure rendering
+- **User Choice**: Use `jupytext` to convert notebooks to Python files, or keep as `.ipynb` for interactive exploration
+- **Repository State**: This repo converts them to Python files (`.py`) for better version control
+- **Dual Rendering**: Automatic HTML visualization in notebooks, clean string output in terminals
-
-_Interactive multi-cell playground for each problem_
+
+
+_Interactive multi-cell playground with rich data structure visualization for each problem_
## 🔄 Usage Patterns
@@ -131,31 +167,38 @@ lcpy list -d Medium # Filter by difficulty
lcpy scrape -n 1 > two_sum.json # Save problem data
```
-### Development Workflow
+## 🛠️ Development Setup
-For repository development and customization:
+For working within this repository to generate additional LeetCode problems using LLM assistance:
```bash
-# Regenerate all 75 problems with fresh TODO placeholders
-make gen-all-problems
+# Clone repository for development
+git clone https://github.com/wisarootl/leetcode-py.git
+cd leetcode-py
+poetry install
+
+# Generate problems from JSON templates
+make p-gen PROBLEM=problem_name
+make p-test PROBLEM=problem_name
-# Work through problems systematically
-make p-test PROBLEM=two_sum
-make p-test PROBLEM=valid_palindrome
-make p-test PROBLEM=merge_two_sorted_lists
+# Regenerate all existing problems
+make gen-all-problems
```
### LLM-Assisted Problem Creation
-If you need more problems beyond Grind 75, use an LLM assistant in your IDE (Cursor, GitHub Copilot Chat, Amazon Q, etc.):
+To extend the problem collection beyond the current catalog, leverage an LLM assistant within your IDE (Cursor, GitHub Copilot Chat, Amazon Q, etc.).
+
+📖 **[Complete LLM-Assisted Problem Creation Guide](docs/llm-assisted-problem-creation.md)** - Comprehensive guide with screenshots and detailed workflow.
+
+**Quick Start:**
```bash
-# Example commands to give your LLM assistant:
-"Create LeetCode problem 146 (LRU Cache)"
-"Add problem 'Word Ladder' by number 127"
-"Generate problem 'Serialize and Deserialize Binary Tree'"
+# Problem generation commands:
+"Add problem 198. House Robber"
+"Add problem 198. House Robber. tag: grind"
-# For test enhancement (when you need more comprehensive test coverage):
+# Test enhancement commands:
"Enhance test cases for two_sum problem"
"Fix test reproducibility for binary_tree_inorder_traversal"
```
@@ -201,6 +244,8 @@ poetry run python -m leetcode_py.tools.check_test_cases --threshold=10
### CLI Commands (Global)
+📖 **[Complete CLI Usage Guide](docs/cli-usage.md)** - Detailed documentation with all options and examples.
+
```bash
# Generate problems
lcpy gen -n 1 # Single problem by number
diff --git a/docs/images/generated-solution.png b/docs/images/generated-solution.png
new file mode 100644
index 0000000000000000000000000000000000000000..40ac8ef7905af816a271b10e7d56ac757d79293e
GIT binary patch
literal 58317
zcmZU*1zZ(d_dkq;bPFg*cY{cGDcv2?ol=KJkOpZE9U|S`A>E;LHwYZM`yKAR>hphp
z=QG3E%I==Ui
zBq!?Vth~fnfl=vz`li@kXN3|@a#P9FSL`KD2bR5ikH>rXfg0Z|UO%#c2+lqy8mng-3)g5J|L2
zxXD@J`dXhE?@^G3ccSp|wH%V+Y>%uy&NM@IJWfv7Gu
zRQfd|DAy=(DDf!bC`l+gD6dc`QILcd;e--9qmYa}-fXZ^u>E5F1vUE)<++eG5|Ytb
zxoGev%)N0%n6EGQb5iic!1wYNUteL7m!!ETX}-RXFBLrXgbfwR_{3hdRlLZ1MIb!0
zF#qbMM&AA7BX_E=uWqdG>f!n0qmSL=aMO1HPX=QNa1s=HG9a
zm_R5%hfr9+>6!`syEcqVCd}{hvm4+Zl(4d>q$KdIY~)~KV(Vyb=R|^{whmlCw3pCy
zgo47Qd^(>=Dp4E({ZCn_XgF!e%J3N3*)SOx+ZmcLx!Tx2^#jG{$^#T_Oq>izTy3mv
z9eG^&$^NOq1C*aGGn0}0Q^m=OpG-qmfkf2K!Gwf^iG_)UOaO_5goMw**px>}?A?FO
zf&ci)%$=Okt116M{{NAiC=`Q4A0
ziKCH&g}sx7oh`{zzXpbO&QAPfWKRSA`TR?#iL1rmk!&6RV+&xA`RNWbD-#RzpT2>n
zd{0+-6f9g#tTn|fYyg=7V+gRbaPa+8|Nq?iJL3Pe)cD(yi|hYs`9F95|1DJ=O&mn+
zY=9x11pcb)zhVFP=6?hMW#fS4ROfEvIXF?=u$^J&26
zet2n7;I{QtuHl`mb_OOD7RfU#1Tx=hWol=e=*>a+3N(UU$_vh*kdC`LH&2UfPRM5B=Tv1PBOC?
zJF}dAPaSGB16JbGKSyRhqQVRg`$wWk*a0Y@-kY(0KJni@`$mxo8Q>``w)aE{?T`o=
zsPo)Jn+g9WO2Gj^VJ1Gc)lgb8vkL?0m{qqsFy{BDaaaH$IoafH_!JmYurv^n0?rxS
zKOI*Jed;J>ID0c14I7#W6`+d=v%UHsMW0{?_?`&yLBxPUgv|hM9wk-2``z*x){)q!
zjuC7^TXLWn2Jo#nQ~y8AUdaM??VJ{(VI{s30)he=KgsO3AOl2!VeRWH)v##B0h$J!
zE>AZk{}se1*ic_!ELNdyQpwEfCpqo&51j&o{3~Jvc7UC)Q{>j@h;h(QoMmFWA^qu>
zG8AYUvdhE)hLw5_5FhhH-kS2a284D<0nxeN#F&XdlL7=V88`Z&lK$?PhWv>TiT9^(
zB{ONBbU|rD6!u#?pJ2skAg(xoDq+)r5yqk`MgB(+5&)~@M79)wAc6ov2u{?a{~i+x
z@k$=J>*KNz3kU)g5Co@wqU`?&LM&6mUsp_oJj7N&DDCH~^Mr599qW6N7e5Fc-kGfO
z+YB~~@R+DcLsKZs0Q4cHuy5O7HS08>P?2^=}CfyD2z6$TJq6qJYAvMOXHUnZQ%R>gsN$$t#r6kfij
z&4VRcvVmrnpD`fpVDg&xQwmJW6NS!60s7cE&`T%$&7IIMOd2HvyjTP)T`$S&dvRH^
z++KDpOg%kDdc}lwnqLfIA52e}By~Bs&-yCF0WH}Af^87*ME#>M3I!V2P?Z@~x#kbZ
zO#l(`tZ2AR0yizGl~V?~Nf49-nUo?^wG&%z%}{^cfO>NaR~P!lq$V>vurj>-3#s15
z!ndWG&KBNo)ZDe3<<_ayL#mit(LxHq69ETDPX9-{B+tqapV-}TE5UWKuW%w#hBq)n
zxwc@srP#
z2uMRq<Hh6Vv}%wKO6jlVD)k{n9Lm6;v2rJ5zE6M=32%$6%md@y^7;Tik5eym!Jm
zrt(@r;6rY?_RYhxgZ)mar%B=}p|7jSy|t??fi4&QeRc7JXX`wn_J>K;ENsKG3m9pa
zzE-&TMew
KDZvUmtCCZ
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