From bd698424e4dd2ab63ad5a83c067b7a21e569ea2a Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 19 Sep 2025 07:59:17 +0700 Subject: [PATCH 1/3] feat: add design_add_and_search_words_data_structure --- Makefile | 2 +- .../README.md | 47 ++++ .../__init__.py | 0 .../helpers.py | 25 ++ .../playground.py | 30 ++ .../solution.py | 37 +++ .../test_solution.py | 130 +++++++++ leetcode/group_anagrams/README.md | 46 +++ leetcode/group_anagrams/__init__.py | 0 leetcode/group_anagrams/helpers.py | 13 + leetcode/group_anagrams/playground.py | 29 ++ leetcode/group_anagrams/solution.py | 25 ++ leetcode/group_anagrams/test_solution.py | 39 +++ leetcode/maximum_product_subarray/README.md | 37 +++ leetcode/maximum_product_subarray/__init__.py | 0 leetcode/maximum_product_subarray/helpers.py | 8 + .../maximum_product_subarray/playground.py | 29 ++ leetcode/maximum_product_subarray/solution.py | 16 ++ .../maximum_product_subarray/test_solution.py | 36 +++ leetcode/next_permutation/README.md | 51 ++++ leetcode/next_permutation/__init__.py | 0 leetcode/next_permutation/helpers.py | 10 + leetcode/next_permutation/playground.py | 29 ++ leetcode/next_permutation/solution.py | 23 ++ leetcode/next_permutation/test_solution.py | 36 +++ leetcode/valid_sudoku/README.md | 63 +++++ leetcode/valid_sudoku/__init__.py | 0 leetcode/valid_sudoku/helpers.py | 8 + leetcode/valid_sudoku/playground.py | 39 +++ leetcode/valid_sudoku/solution.py | 22 ++ leetcode/valid_sudoku/test_solution.py | 261 ++++++++++++++++++ ...n_add_and_search_words_data_structure.json | 80 ++++++ .../json/problems/group_anagrams.json | 72 +++++ .../problems/maximum_product_subarray.json | 73 +++++ .../json/problems/next_permutation.json | 70 +++++ .../leetcode/json/problems/valid_sudoku.json | 73 +++++ .../cli/resources/leetcode/json/tags.json5 | 2 +- 37 files changed, 1459 insertions(+), 2 deletions(-) create mode 100644 leetcode/design_add_and_search_words_data_structure/README.md create mode 100644 leetcode/design_add_and_search_words_data_structure/__init__.py create mode 100644 leetcode/design_add_and_search_words_data_structure/helpers.py create mode 100644 leetcode/design_add_and_search_words_data_structure/playground.py create mode 100644 leetcode/design_add_and_search_words_data_structure/solution.py create mode 100644 leetcode/design_add_and_search_words_data_structure/test_solution.py create mode 100644 leetcode/group_anagrams/README.md create mode 100644 leetcode/group_anagrams/__init__.py create mode 100644 leetcode/group_anagrams/helpers.py create mode 100644 leetcode/group_anagrams/playground.py create mode 100644 leetcode/group_anagrams/solution.py create mode 100644 leetcode/group_anagrams/test_solution.py create mode 100644 leetcode/maximum_product_subarray/README.md create mode 100644 leetcode/maximum_product_subarray/__init__.py create mode 100644 leetcode/maximum_product_subarray/helpers.py create mode 100644 leetcode/maximum_product_subarray/playground.py create mode 100644 leetcode/maximum_product_subarray/solution.py create mode 100644 leetcode/maximum_product_subarray/test_solution.py create mode 100644 leetcode/next_permutation/README.md create mode 100644 leetcode/next_permutation/__init__.py create mode 100644 leetcode/next_permutation/helpers.py create mode 100644 leetcode/next_permutation/playground.py create mode 100644 leetcode/next_permutation/solution.py create mode 100644 leetcode/next_permutation/test_solution.py create mode 100644 leetcode/valid_sudoku/README.md create mode 100644 leetcode/valid_sudoku/__init__.py create mode 100644 leetcode/valid_sudoku/helpers.py create mode 100644 leetcode/valid_sudoku/playground.py create mode 100644 leetcode/valid_sudoku/solution.py create mode 100644 leetcode/valid_sudoku/test_solution.py create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/design_add_and_search_words_data_structure.json create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/group_anagrams.json create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/maximum_product_subarray.json create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/next_permutation.json create mode 100644 leetcode_py/cli/resources/leetcode/json/problems/valid_sudoku.json diff --git a/Makefile b/Makefile index ab376fd..6fd145a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= gas_station +PROBLEM ?= design_add_and_search_words_data_structure FORCE ?= 0 COMMA := , diff --git a/leetcode/design_add_and_search_words_data_structure/README.md b/leetcode/design_add_and_search_words_data_structure/README.md new file mode 100644 index 0000000..7ea8ac7 --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/README.md @@ -0,0 +1,47 @@ +# Design Add and Search Words Data Structure + +**Difficulty:** Medium +**Topics:** String, Depth-First Search, Design, Trie +**Tags:** grind + +**LeetCode:** [Problem 211](https://leetcode.com/problems/design-add-and-search-words-data-structure/description/) + +## Problem Description + +Design a data structure that supports adding new words and finding if a string matches any previously added string. + +Implement the `WordDictionary` class: + +- `WordDictionary()` Initializes the object. +- `void addWord(word)` Adds `word` to the data structure, it can be matched later. +- `bool search(word)` Returns `true` if there is any string in the data structure that matches `word` or `false` otherwise. `word` may contain dots `'.'` where dots can be matched with any letter. + +## Examples + +### Example 1: + +``` +Input +["WordDictionary","addWord","addWord","addWord","search","search","search","search"] +[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]] +Output +[null,null,null,null,false,true,true,true] + +Explanation +WordDictionary wordDictionary = new WordDictionary(); +wordDictionary.addWord("bad"); +wordDictionary.addWord("dad"); +wordDictionary.addWord("mad"); +wordDictionary.search("pad"); // return False +wordDictionary.search("bad"); // return True +wordDictionary.search(".ad"); // return True +wordDictionary.search("b.."); // return True +``` + +## Constraints + +- `1 <= word.length <= 25` +- `word` in `addWord` consists of lowercase English letters. +- `word` in `search` consist of `'.'` or lowercase English letters. +- There will be at most `2` dots in `word` for `search` queries. +- At most `10^4` calls will be made to `addWord` and `search`. diff --git a/leetcode/design_add_and_search_words_data_structure/__init__.py b/leetcode/design_add_and_search_words_data_structure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/design_add_and_search_words_data_structure/helpers.py b/leetcode/design_add_and_search_words_data_structure/helpers.py new file mode 100644 index 0000000..5fba0eb --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/helpers.py @@ -0,0 +1,25 @@ +from typing import Any + + +def run_word_dictionary(solution_class: type, operations: list[str], inputs: list[list[str]]): + wd: Any = None + results: list[bool | None] = [] + + for op, args in zip(operations, inputs): + if op == "WordDictionary": + wd = solution_class() + results.append(None) + elif op == "addWord": + assert wd is not None + wd.add_word(args[0]) + results.append(None) + elif op == "search": + assert wd is not None + results.append(wd.search(args[0])) + + return results + + +def assert_word_dictionary(result: list[bool | None], expected: list[bool | None]) -> bool: + assert result == expected + return True diff --git a/leetcode/design_add_and_search_words_data_structure/playground.py b/leetcode/design_add_and_search_words_data_structure/playground.py new file mode 100644 index 0000000..b365ea1 --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_word_dictionary, run_word_dictionary +from solution import WordDictionary + +# %% +# Example test case +operations = ["WordDictionary", "addWord", "addWord", "addWord", "search", "search", "search", "search"] +inputs = [[], ["bad"], ["dad"], ["mad"], ["pad"], ["bad"], [".ad"], ["b.."]] +expected = [None, None, None, None, False, True, True, True] + +# %% +result = run_word_dictionary(WordDictionary, operations, inputs) +result + +# %% +assert_word_dictionary(result, expected) diff --git a/leetcode/design_add_and_search_words_data_structure/solution.py b/leetcode/design_add_and_search_words_data_structure/solution.py new file mode 100644 index 0000000..3cc2ba3 --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/solution.py @@ -0,0 +1,37 @@ +from typing import Any + + +class WordDictionary: + + # Time: O(1) + # Space: O(1) + def __init__(self) -> None: + self.root: dict[str, Any] = {} + + # Time: O(m) where m = len(word) + # Space: O(m) for new word + def add_word(self, word: str) -> None: + node = self.root + for char in word: + if char not in node: + node[char] = {} + node = node[char] + node["#"] = True + + # Time: O(n * 26^k) where n = len(word), k = number of dots + # Space: O(n) for recursion stack + def search(self, word: str) -> bool: + def dfs(i: int, node: dict[str, Any]) -> bool: + if i == len(word): + return "#" in node + + char = word[i] + if char == ".": + for key in node: + if key != "#" and dfs(i + 1, node[key]): + return True + return False + else: + return char in node and dfs(i + 1, node[char]) + + return dfs(0, self.root) diff --git a/leetcode/design_add_and_search_words_data_structure/test_solution.py b/leetcode/design_add_and_search_words_data_structure/test_solution.py new file mode 100644 index 0000000..2142c7d --- /dev/null +++ b/leetcode/design_add_and_search_words_data_structure/test_solution.py @@ -0,0 +1,130 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_word_dictionary, run_word_dictionary +from .solution import WordDictionary + + +class TestDesignAddAndSearchWordsDataStructure: + + @logged_test + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + [ + "WordDictionary", + "addWord", + "addWord", + "addWord", + "search", + "search", + "search", + "search", + ], + [[], ["bad"], ["dad"], ["mad"], ["pad"], ["bad"], [".ad"], ["b.."]], + [None, None, None, None, False, True, True, True], + ), + ( + ["WordDictionary", "addWord", "search", "search", "search"], + [[], ["a"], ["a"], ["."], ["aa"]], + [None, None, True, True, False], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["at"], ["and"], ["an"], [".at"], ["an."]], + [None, None, None, False, False, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search"], + [[], ["word"], ["world"], ["word"], ["wor."]], + [None, None, None, True, True], + ), + ( + ["WordDictionary", "addWord", "search", "search"], + [[], ["test"], ["test"], ["t..t"]], + [None, None, True, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["a"], ["b"], ["a"], ["."], ["c"]], + [None, None, None, True, True, False], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["abc"], ["def"], ["..."], ["a.."], ["..f"]], + [None, None, None, True, True, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [ + [], + ["programming"], + ["algorithm"], + ["prog......."], + ["algo....."], + ["........ing"], + ], + [None, None, None, True, True, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search"], + [[], ["x"], ["xy"], ["."], [".."]], + [None, None, None, True, True], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["hello"], ["world"], ["hi"], ["word"], ["......"]], + [None, None, None, False, False, False], + ), + ( + [ + "WordDictionary", + "addWord", + "addWord", + "addWord", + "search", + "search", + "search", + "search", + ], + [[], ["cat"], ["car"], ["card"], ["c.."], ["ca."], ["c..d"], ["....."]], + [None, None, None, None, True, True, True, False], + ), + ( + [ + "WordDictionary", + "addWord", + "addWord", + "addWord", + "search", + "search", + "search", + ], + [ + [], + ["run"], + ["runner"], + ["running"], + ["run"], + ["run..."], + ["run....."], + ], + [None, None, None, None, True, True, False], + ), + ( + ["WordDictionary", "addWord", "addWord", "search", "search", "search"], + [[], ["abc"], ["xyz"], ["..."], [".."], ["...."]], + [None, None, None, True, False, False], + ), + ], + ) + def test_word_dictionary( + self, + operations: list[str], + inputs: list[list[str]], + expected: list[bool | None], + ): + result = run_word_dictionary(WordDictionary, operations, inputs) + assert_word_dictionary(result, expected) diff --git a/leetcode/group_anagrams/README.md b/leetcode/group_anagrams/README.md new file mode 100644 index 0000000..c7f9561 --- /dev/null +++ b/leetcode/group_anagrams/README.md @@ -0,0 +1,46 @@ +# Group Anagrams + +**Difficulty:** Medium +**Topics:** Array, Hash Table, String, Sorting +**Tags:** grind + +**LeetCode:** [Problem 49](https://leetcode.com/problems/group-anagrams/description/) + +## Problem Description + +Given an array of strings `strs`, group the anagrams together. You can return the answer in **any order**. + +An **anagram** is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once. + +## Examples + +### Example 1: + +``` +Input: strs = ["eat","tea","tan","ate","nat","bat"] +Output: [["bat"],["nat","tan"],["ate","eat","tea"]] +Explanation: +- There is no string in strs that can be rearranged to form "bat". +- The strings "nat" and "tan" are anagrams as they can be rearranged to form each other. +- The strings "ate", "eat", and "tea" are anagrams as they can be rearranged to form each other. +``` + +### Example 2: + +``` +Input: strs = [""] +Output: [[""]] +``` + +### Example 3: + +``` +Input: strs = ["a"] +Output: [["a"]] +``` + +## Constraints + +- `1 <= strs.length <= 10^4` +- `0 <= strs[i].length <= 100` +- `strs[i]` consists of lowercase English letters. diff --git a/leetcode/group_anagrams/__init__.py b/leetcode/group_anagrams/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/group_anagrams/helpers.py b/leetcode/group_anagrams/helpers.py new file mode 100644 index 0000000..fee8547 --- /dev/null +++ b/leetcode/group_anagrams/helpers.py @@ -0,0 +1,13 @@ +def run_group_anagrams(solution_class: type, strs: list[str]): + implementation = solution_class() + return implementation.group_anagrams(strs) + + +def assert_group_anagrams(result: list[list[str]], expected: list[list[str]]) -> bool: + # Sort both result and expected for comparison since order doesn't matter + result_sorted = [sorted(group) for group in result] + expected_sorted = [sorted(group) for group in expected] + result_sorted.sort() + expected_sorted.sort() + assert result_sorted == expected_sorted + return True diff --git a/leetcode/group_anagrams/playground.py b/leetcode/group_anagrams/playground.py new file mode 100644 index 0000000..dd57ae4 --- /dev/null +++ b/leetcode/group_anagrams/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_group_anagrams, run_group_anagrams +from solution import Solution + +# %% +# Example test case +strs = ["eat", "tea", "tan", "ate", "nat", "bat"] +expected = [["bat"], ["nat", "tan"], ["ate", "eat", "tea"]] + +# %% +result = run_group_anagrams(Solution, strs) +result + +# %% +assert_group_anagrams(result, expected) diff --git a/leetcode/group_anagrams/solution.py b/leetcode/group_anagrams/solution.py new file mode 100644 index 0000000..9230c16 --- /dev/null +++ b/leetcode/group_anagrams/solution.py @@ -0,0 +1,25 @@ +class Solution: + + # Time: O(n * k) - when k > 26 use counting O(k), when k ≤ 26 use sorting O(k log k) + # Space: O(n * k) + def group_anagrams(self, strs: list[str]) -> list[list[str]]: + groups: dict[str | tuple[int, ...], list[str]] = {} + + for s in strs: + if len(s) >= 26: + # Use counting for short strings (better time) + # Time: O(k) - single pass through string + O(26) for tuple + # Space: O(26) = O(1) per key + count = [0] * 26 + for c in s: + count[ord(c) - ord("a")] += 1 + key: tuple[int, ...] | str = tuple(count) + else: + # Use sorting for long strings (better space) + # Time: O(k log k) - sorting dominates + # Space: O(k) per key + key: tuple[int, ...] | str = "".join(sorted(s)) + + groups.setdefault(key, []).append(s) + + return list(groups.values()) diff --git a/leetcode/group_anagrams/test_solution.py b/leetcode/group_anagrams/test_solution.py new file mode 100644 index 0000000..aa72701 --- /dev/null +++ b/leetcode/group_anagrams/test_solution.py @@ -0,0 +1,39 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_group_anagrams, run_group_anagrams +from .solution import Solution + + +class TestGroupAnagrams: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "strs, expected", + [ + ( + ["eat", "tea", "tan", "ate", "nat", "bat"], + [["bat"], ["nat", "tan"], ["ate", "eat", "tea"]], + ), + ([""], [[""]]), + (["a"], [["a"]]), + (["abc", "bca", "cab", "xyz"], [["abc", "bca", "cab"], ["xyz"]]), + (["ab", "ba"], [["ab", "ba"]]), + (["abc"], [["abc"]]), + (["listen", "silent", "hello"], [["listen", "silent"], ["hello"]]), + (["aab", "aba", "baa"], [["aab", "aba", "baa"]]), + (["race", "care", "acre"], [["race", "care", "acre"]]), + (["", "b"], [[""], ["b"]]), + (["a", "aa", "aaa"], [["a"], ["aa"], ["aaa"]]), + (["abc", "def", "ghi"], [["abc"], ["def"], ["ghi"]]), + (["abcd", "dcba", "lls", "sll"], [["abcd", "dcba"], ["lls", "sll"]]), + (["ac", "c"], [["ac"], ["c"]]), + (["huh", "tit"], [["huh"], ["tit"]]), + ], + ) + def test_group_anagrams(self, strs: list[str], expected: list[list[str]]): + result = run_group_anagrams(Solution, strs) + assert_group_anagrams(result, expected) diff --git a/leetcode/maximum_product_subarray/README.md b/leetcode/maximum_product_subarray/README.md new file mode 100644 index 0000000..6ced65a --- /dev/null +++ b/leetcode/maximum_product_subarray/README.md @@ -0,0 +1,37 @@ +# Maximum Product Subarray + +**Difficulty:** Medium +**Topics:** Array, Dynamic Programming +**Tags:** grind + +**LeetCode:** [Problem 152](https://leetcode.com/problems/maximum-product-subarray/description/) + +## Problem Description + +Given an integer array `nums`, find a subarray that has the largest product, and return the product. + +The test cases are generated so that the answer will fit in a **32-bit** integer. + +## Examples + +### Example 1: + +``` +Input: nums = [2,3,-2,4] +Output: 6 +Explanation: [2,3] has the largest product 6. +``` + +### Example 2: + +``` +Input: nums = [-2,0,-1] +Output: 0 +Explanation: The result cannot be 2, because [-2,-1] is not a subarray. +``` + +## Constraints + +- `1 <= nums.length <= 2 * 10^4` +- `-10 <= nums[i] <= 10` +- The product of any subarray of `nums` is **guaranteed** to fit in a **32-bit** integer. diff --git a/leetcode/maximum_product_subarray/__init__.py b/leetcode/maximum_product_subarray/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/maximum_product_subarray/helpers.py b/leetcode/maximum_product_subarray/helpers.py new file mode 100644 index 0000000..d9330e9 --- /dev/null +++ b/leetcode/maximum_product_subarray/helpers.py @@ -0,0 +1,8 @@ +def run_max_product(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.max_product(nums) + + +def assert_max_product(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/maximum_product_subarray/playground.py b/leetcode/maximum_product_subarray/playground.py new file mode 100644 index 0000000..98c1811 --- /dev/null +++ b/leetcode/maximum_product_subarray/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_max_product, run_max_product +from solution import Solution + +# %% +# Example test case +nums = [2, 3, -2, 4] +expected = 6 + +# %% +result = run_max_product(Solution, nums) +result + +# %% +assert_max_product(result, expected) diff --git a/leetcode/maximum_product_subarray/solution.py b/leetcode/maximum_product_subarray/solution.py new file mode 100644 index 0000000..9a58990 --- /dev/null +++ b/leetcode/maximum_product_subarray/solution.py @@ -0,0 +1,16 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def max_product(self, nums: list[int]) -> int: + max_prod = min_prod = result = nums[0] + + for num in nums[1:]: + if num < 0: + max_prod, min_prod = min_prod, max_prod + + max_prod = max(num, max_prod * num) + min_prod = min(num, min_prod * num) + result = max(result, max_prod) + + return result diff --git a/leetcode/maximum_product_subarray/test_solution.py b/leetcode/maximum_product_subarray/test_solution.py new file mode 100644 index 0000000..d9eedec --- /dev/null +++ b/leetcode/maximum_product_subarray/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_max_product, run_max_product +from .solution import Solution + + +class TestMaximumProductSubarray: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([2, 3, -2, 4], 6), + ([-2, 0, -1], 0), + ([1], 1), + ([0], 0), + ([-1], -1), + ([2, -1, 3], 3), + ([-2, -3], 6), + ([1, 2, 3, 4], 24), + ([-1, -2, -3], 6), + ([0, 2], 2), + ([3, -1, 4], 4), + ([-2, 3, -4], 24), + ([2, 3, -2, 4, -1], 48), + ([1, 0, -1, 2, 3], 6), + ([-3, 0, 1, -2], 1), + ], + ) + def test_max_product(self, nums: list[int], expected: int): + result = run_max_product(Solution, nums) + assert_max_product(result, expected) diff --git a/leetcode/next_permutation/README.md b/leetcode/next_permutation/README.md new file mode 100644 index 0000000..f6829ca --- /dev/null +++ b/leetcode/next_permutation/README.md @@ -0,0 +1,51 @@ +# Next Permutation + +**Difficulty:** Medium +**Topics:** Array, Two Pointers +**Tags:** grind + +**LeetCode:** [Problem 31](https://leetcode.com/problems/next-permutation/description/) + +## Problem Description + +A **permutation** of an array of integers is an arrangement of its members into a sequence or linear order. + +- For example, for `arr = [1,2,3]`, the following are all the permutations of `arr`: `[1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1]`. + +The **next permutation** of an array of integers is the next lexicographically greater permutation of its integer. More formally, if all the permutations of the array are sorted in one container according to their lexicographical order, then the **next permutation** of that array is the permutation that follows it in the sorted container. If such arrangement is not possible, the array must be rearranged as the lowest possible order (i.e., sorted in ascending order). + +- For example, the next permutation of `arr = [1,2,3]` is `[1,3,2]`. +- Similarly, the next permutation of `arr = [2,3,1]` is `[3,1,2]`. +- While the next permutation of `arr = [3,2,1]` is `[1,2,3]` because `[3,2,1]` does not have a lexicographical larger rearrangement. + +Given an array of integers `nums`, find the next permutation of `nums`. + +The replacement must be **in place** and use only constant extra memory. + +## Examples + +### Example 1: + +``` +Input: nums = [1,2,3] +Output: [1,3,2] +``` + +### Example 2: + +``` +Input: nums = [3,2,1] +Output: [1,2,3] +``` + +### Example 3: + +``` +Input: nums = [1,1,5] +Output: [1,5,1] +``` + +## Constraints + +- `1 <= nums.length <= 100` +- `0 <= nums[i] <= 100` diff --git a/leetcode/next_permutation/__init__.py b/leetcode/next_permutation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/next_permutation/helpers.py b/leetcode/next_permutation/helpers.py new file mode 100644 index 0000000..de58c67 --- /dev/null +++ b/leetcode/next_permutation/helpers.py @@ -0,0 +1,10 @@ +def run_next_permutation(solution_class: type, nums: list[int]): + implementation = solution_class() + nums_copy = nums.copy() + implementation.next_permutation(nums_copy) + return nums_copy + + +def assert_next_permutation(result: list[int], expected: list[int]) -> bool: + assert result == expected + return True diff --git a/leetcode/next_permutation/playground.py b/leetcode/next_permutation/playground.py new file mode 100644 index 0000000..a4c8779 --- /dev/null +++ b/leetcode/next_permutation/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_next_permutation, run_next_permutation +from solution import Solution + +# %% +# Example test case +nums = [1, 2, 3] +expected = [1, 3, 2] + +# %% +result = run_next_permutation(Solution, nums) +result + +# %% +assert_next_permutation(result, expected) diff --git a/leetcode/next_permutation/solution.py b/leetcode/next_permutation/solution.py new file mode 100644 index 0000000..92c8fef --- /dev/null +++ b/leetcode/next_permutation/solution.py @@ -0,0 +1,23 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def next_permutation(self, nums: list[int]) -> None: + # Find pivot (rightmost ascending pair) + i = len(nums) - 2 + while i >= 0 and nums[i] >= nums[i + 1]: + i -= 1 + + if i >= 0: + # Find successor (rightmost element > pivot) + j = len(nums) - 1 + while nums[j] <= nums[i]: + j -= 1 + nums[i], nums[j] = nums[j], nums[i] + + # Reverse suffix + left, right = i + 1, len(nums) - 1 + while left < right: + nums[left], nums[right] = nums[right], nums[left] + left += 1 + right -= 1 diff --git a/leetcode/next_permutation/test_solution.py b/leetcode/next_permutation/test_solution.py new file mode 100644 index 0000000..9aca312 --- /dev/null +++ b/leetcode/next_permutation/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_next_permutation, run_next_permutation +from .solution import Solution + + +class TestNextPermutation: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([1, 2, 3], [1, 3, 2]), + ([3, 2, 1], [1, 2, 3]), + ([1, 1, 5], [1, 5, 1]), + ([1], [1]), + ([1, 2], [2, 1]), + ([2, 1], [1, 2]), + ([1, 3, 2], [2, 1, 3]), + ([2, 3, 1], [3, 1, 2]), + ([1, 2, 3, 4], [1, 2, 4, 3]), + ([4, 3, 2, 1], [1, 2, 3, 4]), + ([1, 1, 1], [1, 1, 1]), + ([1, 2, 1], [2, 1, 1]), + ([5, 4, 7, 5, 3, 2], [5, 5, 2, 3, 4, 7]), + ([1, 3, 2, 1], [2, 1, 1, 3]), + ([2, 1, 3], [2, 3, 1]), + ], + ) + def test_next_permutation(self, nums: list[int], expected: list[int]): + result = run_next_permutation(Solution, nums) + assert_next_permutation(result, expected) diff --git a/leetcode/valid_sudoku/README.md b/leetcode/valid_sudoku/README.md new file mode 100644 index 0000000..b7cccfa --- /dev/null +++ b/leetcode/valid_sudoku/README.md @@ -0,0 +1,63 @@ +# Valid Sudoku + +**Difficulty:** Medium +**Topics:** Array, Hash Table, Matrix +**Tags:** grind + +**LeetCode:** [Problem 36](https://leetcode.com/problems/valid-sudoku/description/) + +## Problem Description + +Determine if a `9 x 9` Sudoku board is valid. Only the filled cells need to be validated **according to the following rules**: + +1. Each row must contain the digits `1-9` without repetition. +2. Each column must contain the digits `1-9` without repetition. +3. Each of the nine `3 x 3` sub-boxes of the grid must contain the digits `1-9` without repetition. + +**Note:** + +- A Sudoku board (partially filled) could be valid but is not necessarily solvable. +- Only the filled cells need to be validated according to the mentioned rules. + +## Examples + +### Example 1: + +![Example 1](https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png) + +``` +Input: board = +[["5","3",".",".","7",".",".",".","."] +,["6",".",".","1","9","5",".",".","."] +,[".","9","8",".",".",".",".","6","."] +,["8",".",".",".","6",".",".",".","3"] +,["4",".",".","8",".","3",".",".","1"] +,["7",".",".",".","2",".",".",".","6"] +,[".","6",".",".",".",".","2","8","."] +,[".",".",".","4","1","9",".",".","5"] +,[".",".",".",".","8",".",".","7","9"]] +Output: true +``` + +### Example 2: + +``` +Input: board = +[["8","3",".",".","7",".",".",".","."] +,["6",".",".","1","9","5",".",".","."] +,[".","9","8",".",".",".",".","6","."] +,["8",".",".",".","6",".",".",".","3"] +,["4",".",".","8",".","3",".",".","1"] +,["7",".",".",".","2",".",".",".","6"] +,[".","6",".",".",".",".","2","8","."] +,[".",".",".","4","1","9",".",".","5"] +,[".",".",".",".","8",".",".","7","9"]] +Output: false +Explanation: Same as Example 1, except with the 5 in the top left corner being modified to 8. Since there are two 8's in the top left 3x3 sub-box, it is invalid. +``` + +## Constraints + +- `board.length == 9` +- `board[i].length == 9` +- `board[i][j]` is a digit `1-9` or `'.'`. diff --git a/leetcode/valid_sudoku/__init__.py b/leetcode/valid_sudoku/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/valid_sudoku/helpers.py b/leetcode/valid_sudoku/helpers.py new file mode 100644 index 0000000..0ecf3da --- /dev/null +++ b/leetcode/valid_sudoku/helpers.py @@ -0,0 +1,8 @@ +def run_is_valid_sudoku(solution_class: type, board: list[list[str]]): + implementation = solution_class() + return implementation.is_valid_sudoku(board) + + +def assert_is_valid_sudoku(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/valid_sudoku/playground.py b/leetcode/valid_sudoku/playground.py new file mode 100644 index 0000000..68b4e1e --- /dev/null +++ b/leetcode/valid_sudoku/playground.py @@ -0,0 +1,39 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_is_valid_sudoku, run_is_valid_sudoku +from solution import Solution + +# %% +# Example test case +board = [ + ["5", "3", ".", ".", "7", ".", ".", ".", "."], + ["6", ".", ".", "1", "9", "5", ".", ".", "."], + [".", "9", "8", ".", ".", ".", ".", "6", "."], + ["8", ".", ".", ".", "6", ".", ".", ".", "3"], + ["4", ".", ".", "8", ".", "3", ".", ".", "1"], + ["7", ".", ".", ".", "2", ".", ".", ".", "6"], + [".", "6", ".", ".", ".", ".", "2", "8", "."], + [".", ".", ".", "4", "1", "9", ".", ".", "5"], + [".", ".", ".", ".", ".", ".", ".", "7", "9"], +] +expected = True + +# %% +result = run_is_valid_sudoku(Solution, board) +result + +# %% +assert_is_valid_sudoku(result, expected) diff --git a/leetcode/valid_sudoku/solution.py b/leetcode/valid_sudoku/solution.py new file mode 100644 index 0000000..18233fa --- /dev/null +++ b/leetcode/valid_sudoku/solution.py @@ -0,0 +1,22 @@ +class Solution: + + # Time: O(1) - fixed 9x9 board + # Space: O(1) - fixed size sets + def is_valid_sudoku(self, board: list[list[str]]) -> bool: + rows: list[set[str]] = [set() for _ in range(9)] + cols: list[set[str]] = [set() for _ in range(9)] + boxes: list[list[set[str]]] = [[set() for _ in range(3)] for _ in range(3)] + + for i in range(9): + for j in range(9): + if board[i][j] != ".": + num = board[i][j] + + if num in rows[i] or num in cols[j] or num in boxes[i // 3][j // 3]: + return False + + rows[i].add(num) + cols[j].add(num) + boxes[i // 3][j // 3].add(num) + + return True diff --git a/leetcode/valid_sudoku/test_solution.py b/leetcode/valid_sudoku/test_solution.py new file mode 100644 index 0000000..18054bf --- /dev/null +++ b/leetcode/valid_sudoku/test_solution.py @@ -0,0 +1,261 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_is_valid_sudoku, run_is_valid_sudoku +from .solution import Solution + + +class TestValidSudoku: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "board, expected", + [ + # Valid sudoku example 1 + ( + [ + ["5", "3", ".", ".", "7", ".", ".", ".", "."], + ["6", ".", ".", "1", "9", "5", ".", ".", "."], + [".", "9", "8", ".", ".", ".", ".", "6", "."], + ["8", ".", ".", ".", "6", ".", ".", ".", "3"], + ["4", ".", ".", "8", ".", "3", ".", ".", "1"], + ["7", ".", ".", ".", "2", ".", ".", ".", "6"], + [".", "6", ".", ".", ".", ".", "2", "8", "."], + [".", ".", ".", "4", "1", "9", ".", ".", "5"], + [".", ".", ".", ".", "8", ".", ".", "7", "9"], + ], + True, + ), + # Invalid sudoku example 2 (duplicate 8 in top-left box) + ( + [ + ["8", "3", ".", ".", "7", ".", ".", ".", "."], + ["6", ".", ".", "1", "9", "5", ".", ".", "."], + [".", "9", "8", ".", ".", ".", ".", "6", "."], + ["8", ".", ".", ".", "6", ".", ".", ".", "3"], + ["4", ".", ".", "8", ".", "3", ".", ".", "1"], + ["7", ".", ".", ".", "2", ".", ".", ".", "6"], + [".", "6", ".", ".", ".", ".", "2", "8", "."], + [".", ".", ".", "4", "1", "9", ".", ".", "5"], + [".", ".", ".", ".", "8", ".", ".", "7", "9"], + ], + False, + ), + # Empty board (valid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + # Duplicate in row (invalid) + ( + [ + ["1", "1", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Duplicate in column (invalid) + ( + [ + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Duplicate in 3x3 box (invalid) + ( + [ + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", "1", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Valid single row filled + ( + [ + ["1", "2", "3", "4", "5", "6", "7", "8", "9"], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + # Valid single column filled + ( + [ + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + ["2", ".", ".", ".", ".", ".", ".", ".", "."], + ["3", ".", ".", ".", ".", ".", ".", ".", "."], + ["4", ".", ".", ".", ".", ".", ".", ".", "."], + ["5", ".", ".", ".", ".", ".", ".", ".", "."], + ["6", ".", ".", ".", ".", ".", ".", ".", "."], + ["7", ".", ".", ".", ".", ".", ".", ".", "."], + ["8", ".", ".", ".", ".", ".", ".", ".", "."], + ["9", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + # Valid 3x3 box filled (top-left) + ( + [ + ["1", "2", "3", ".", ".", ".", ".", ".", "."], + ["4", "5", "6", ".", ".", ".", ".", ".", "."], + ["7", "8", "9", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + # Duplicate in middle 3x3 box (invalid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", "5", ".", ".", ".", ".", "."], + [".", ".", ".", ".", "5", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Duplicate in bottom-right 3x3 box (invalid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", "7", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", "7", "."], + ], + False, + ), + # Multiple duplicates in same row (invalid) + ( + [ + ["3", "3", "3", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + False, + ), + # Duplicate in last row (invalid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ["9", ".", ".", ".", ".", ".", ".", ".", "9"], + ], + False, + ), + # Duplicate in last column (invalid) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "4"], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "4"], + ], + False, + ), + # Valid with all digits 1-9 in different positions + ( + [ + ["1", ".", ".", ".", ".", ".", ".", ".", "."], + [".", "2", ".", ".", ".", ".", ".", ".", "."], + [".", ".", "3", ".", ".", ".", ".", ".", "."], + [".", ".", ".", "4", ".", ".", ".", ".", "."], + [".", ".", ".", ".", "5", ".", ".", ".", "."], + [".", ".", ".", ".", ".", "6", ".", ".", "."], + [".", ".", ".", ".", ".", ".", "7", ".", "."], + [".", ".", ".", ".", ".", ".", ".", "8", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "9"], + ], + True, + ), + # Valid board with multiple possible solutions (demonstrates "not necessarily solvable" constraint) + ( + [ + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", "1", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + [".", ".", ".", ".", ".", ".", ".", ".", "."], + ], + True, + ), + ], + ) + def test_is_valid_sudoku(self, board: list[list[str]], expected: bool): + result = run_is_valid_sudoku(Solution, board) + assert_is_valid_sudoku(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/design_add_and_search_words_data_structure.json b/leetcode_py/cli/resources/leetcode/json/problems/design_add_and_search_words_data_structure.json new file mode 100644 index 0000000..3841663 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/design_add_and_search_words_data_structure.json @@ -0,0 +1,80 @@ +{ + "problem_name": "design_add_and_search_words_data_structure", + "solution_class_name": "WordDictionary", + "problem_number": "211", + "problem_title": "Design Add and Search Words Data Structure", + "difficulty": "Medium", + "topics": "String, Depth-First Search, Design, Trie", + "_tags": { "list": ["grind"] }, + + "readme_description": "Design a data structure that supports adding new words and finding if a string matches any previously added string.\n\nImplement the `WordDictionary` class:\n\n- `WordDictionary()` Initializes the object.\n- `void addWord(word)` Adds `word` to the data structure, it can be matched later.\n- `bool search(word)` Returns `true` if there is any string in the data structure that matches `word` or `false` otherwise. `word` may contain dots `'.'` where dots can be matched with any letter.", + + "_readme_examples": { + "list": [ + { + "content": "```\nInput\n[\"WordDictionary\",\"addWord\",\"addWord\",\"addWord\",\"search\",\"search\",\"search\",\"search\"]\n[[],[\"bad\"],[\"dad\"],[\"mad\"],[\"pad\"],[\"bad\"],[\".ad\"],[\"b..\"]]\nOutput\n[null,null,null,null,false,true,true,true]\n\nExplanation\nWordDictionary wordDictionary = new WordDictionary();\nwordDictionary.addWord(\"bad\");\nwordDictionary.addWord(\"dad\");\nwordDictionary.addWord(\"mad\");\nwordDictionary.search(\"pad\"); // return False\nwordDictionary.search(\"bad\"); // return True\nwordDictionary.search(\".ad\"); // return True\nwordDictionary.search(\"b..\"); // return True\n```" + } + ] + }, + + "readme_constraints": "- `1 <= word.length <= 25`\n- `word` in `addWord` consists of lowercase English letters.\n- `word` in `search` consist of `'.'` or lowercase English letters.\n- There will be at most `2` dots in `word` for `search` queries.\n- At most `10^4` calls will be made to `addWord` and `search`.", + + "helpers_imports": "from typing import Any", + "helpers_content": "", + "helpers_run_name": "word_dictionary", + "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[str]])", + "helpers_run_body": " wd: Any = None\n results: list[bool | None] = []\n \n for op, args in zip(operations, inputs):\n if op == 'WordDictionary':\n wd = solution_class()\n results.append(None)\n elif op == 'addWord':\n assert wd is not None\n wd.add_word(args[0])\n results.append(None)\n elif op == 'search':\n assert wd is not None\n results.append(wd.search(args[0]))\n \n return results", + "helpers_assert_name": "word_dictionary", + "helpers_assert_signature": "(result: list[bool | None], expected: list[bool | None]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_word_dictionary, run_word_dictionary\nfrom .solution import WordDictionary", + "test_content": "", + "test_class_name": "DesignAddAndSearchWordsDataStructure", + "test_class_content": "", + + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self) -> None", + "body": " # TODO: Initialize data structure\n pass" + }, + { + "name": "add_word", + "signature": "(self, word: str) -> None", + "body": " # TODO: Implement add_word\n pass" + }, + { + "name": "search", + "signature": "(self, word: str) -> bool", + "body": " # TODO: Implement search\n return False" + } + ] + }, + + "_test_helper_methods": { + "list": [] + }, + + "_test_methods": { + "list": [ + { + "name": "test_word_dictionary", + "signature": "(self, operations: list[str], inputs: list[list[str]], expected: list[bool | None])", + "parametrize": "operations, inputs, expected", + "test_cases": "[(['WordDictionary', 'addWord', 'addWord', 'addWord', 'search', 'search', 'search', 'search'], [[], ['bad'], ['dad'], ['mad'], ['pad'], ['bad'], ['.ad'], ['b..']], [None, None, None, None, False, True, True, True]), (['WordDictionary', 'addWord', 'search', 'search', 'search'], [[], ['a'], ['a'], ['.'], ['aa']], [None, None, True, True, False]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['at'], ['and'], ['an'], ['.at'], ['an.']], [None, None, None, False, False, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search'], [[], ['word'], ['world'], ['word'], ['wor.']], [None, None, None, True, True]), (['WordDictionary', 'addWord', 'search', 'search'], [[], ['test'], ['test'], ['t..t']], [None, None, True, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['a'], ['b'], ['a'], ['.'], ['c']], [None, None, None, True, True, False]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['abc'], ['def'], ['...'], ['a..'], ['..f']], [None, None, None, True, True, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['programming'], ['algorithm'], ['prog.......'], ['algo.....'], ['........ing']], [None, None, None, True, True, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search'], [[], ['x'], ['xy'], ['.'], ['..']], [None, None, None, True, True]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['hello'], ['world'], ['hi'], ['word'], ['......']], [None, None, None, False, False, False]), (['WordDictionary', 'addWord', 'addWord', 'addWord', 'search', 'search', 'search', 'search'], [[], ['cat'], ['car'], ['card'], ['c..'], ['ca.'], ['c..d'], ['.....']], [None, None, None, None, True, True, True, False]), (['WordDictionary', 'addWord', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['run'], ['runner'], ['running'], ['run'], ['run...'], ['run.....']], [None, None, None, None, True, True, False]), (['WordDictionary', 'addWord', 'addWord', 'search', 'search', 'search'], [[], ['abc'], ['xyz'], ['...'], ['..'], ['....']], [None, None, None, True, False, False])]", + "body": " result = run_word_dictionary(WordDictionary, operations, inputs)\n assert_word_dictionary(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_word_dictionary, assert_word_dictionary\nfrom solution import WordDictionary", + "playground_setup": "# Example test case\noperations = ['WordDictionary', 'addWord', 'addWord', 'addWord', 'search', 'search', 'search', 'search']\ninputs = [[], ['bad'], ['dad'], ['mad'], ['pad'], ['bad'], ['.ad'], ['b..']]\nexpected = [None, None, None, None, False, True, True, True]", + "playground_run": "result = run_word_dictionary(WordDictionary, operations, inputs)\nresult", + "playground_assert": "assert_word_dictionary(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/group_anagrams.json b/leetcode_py/cli/resources/leetcode/json/problems/group_anagrams.json new file mode 100644 index 0000000..16926b3 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/group_anagrams.json @@ -0,0 +1,72 @@ +{ + "problem_name": "group_anagrams", + "solution_class_name": "Solution", + "problem_number": "49", + "problem_title": "Group Anagrams", + "difficulty": "Medium", + "topics": "Array, Hash Table, String, Sorting", + "_tags": { "list": ["grind"] }, + + "readme_description": "Given an array of strings `strs`, group the anagrams together. You can return the answer in **any order**.\n\nAn **anagram** is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.", + + "_readme_examples": { + "list": [ + { + "content": "```\nInput: strs = [\"eat\",\"tea\",\"tan\",\"ate\",\"nat\",\"bat\"]\nOutput: [[\"bat\"],[\"nat\",\"tan\"],[\"ate\",\"eat\",\"tea\"]]\nExplanation:\n- There is no string in strs that can be rearranged to form \"bat\".\n- The strings \"nat\" and \"tan\" are anagrams as they can be rearranged to form each other.\n- The strings \"ate\", \"eat\", and \"tea\" are anagrams as they can be rearranged to form each other.\n```" + }, + { "content": "```\nInput: strs = [\"\"]\nOutput: [[\"\"]]\n```" }, + { "content": "```\nInput: strs = [\"a\"]\nOutput: [[\"a\"]]\n```" } + ] + }, + + "readme_constraints": "- `1 <= strs.length <= 10^4`\n- `0 <= strs[i].length <= 100`\n- `strs[i]` consists of lowercase English letters.", + + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "group_anagrams", + "helpers_run_signature": "(solution_class: type, strs: list[str])", + "helpers_run_body": " implementation = solution_class()\n return implementation.group_anagrams(strs)", + "helpers_assert_name": "group_anagrams", + "helpers_assert_signature": "(result: list[list[str]], expected: list[list[str]]) -> bool", + "helpers_assert_body": " # Sort both result and expected for comparison since order doesn't matter\n result_sorted = [sorted(group) for group in result]\n expected_sorted = [sorted(group) for group in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert result_sorted == expected_sorted\n return True", + + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_group_anagrams, run_group_anagrams\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "GroupAnagrams", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + + "_solution_methods": { + "list": [ + { + "name": "group_anagrams", + "signature": "(self, strs: list[str]) -> list[list[str]]", + "body": " # TODO: Implement group_anagrams\n return []" + } + ] + }, + + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + + "_test_methods": { + "list": [ + { + "name": "test_group_anagrams", + "signature": "(self, strs: list[str], expected: list[list[str]])", + "parametrize": "strs, expected", + "test_cases": "[(['eat', 'tea', 'tan', 'ate', 'nat', 'bat'], [['bat'], ['nat', 'tan'], ['ate', 'eat', 'tea']]), ([''], [['']]), (['a'], [['a']]), (['abc', 'bca', 'cab', 'xyz'], [['abc', 'bca', 'cab'], ['xyz']]), (['ab', 'ba'], [['ab', 'ba']]), (['abc'], [['abc']]), (['listen', 'silent', 'hello'], [['listen', 'silent'], ['hello']]), (['aab', 'aba', 'baa'], [['aab', 'aba', 'baa']]), (['race', 'care', 'acre'], [['race', 'care', 'acre']]), (['', 'b'], [[''], ['b']]), (['a', 'aa', 'aaa'], [['a'], ['aa'], ['aaa']]), (['abc', 'def', 'ghi'], [['abc'], ['def'], ['ghi']]), (['abcd', 'dcba', 'lls', 'sll'], [['abcd', 'dcba'], ['lls', 'sll']]), (['ac', 'c'], [['ac'], ['c']]), (['huh', 'tit'], [['huh'], ['tit']])]", + "body": " result = run_group_anagrams(Solution, strs)\n assert_group_anagrams(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_group_anagrams, assert_group_anagrams\nfrom solution import Solution", + "playground_setup": "# Example test case\nstrs = ['eat', 'tea', 'tan', 'ate', 'nat', 'bat']\nexpected = [['bat'], ['nat', 'tan'], ['ate', 'eat', 'tea']]", + "playground_run": "result = run_group_anagrams(Solution, strs)\nresult", + "playground_assert": "assert_group_anagrams(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/maximum_product_subarray.json b/leetcode_py/cli/resources/leetcode/json/problems/maximum_product_subarray.json new file mode 100644 index 0000000..4cd6fc9 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/maximum_product_subarray.json @@ -0,0 +1,73 @@ +{ + "problem_name": "maximum_product_subarray", + "solution_class_name": "Solution", + "problem_number": "152", + "problem_title": "Maximum Product Subarray", + "difficulty": "Medium", + "topics": "Array, Dynamic Programming", + "_tags": { "list": ["grind"] }, + + "readme_description": "Given an integer array `nums`, find a subarray that has the largest product, and return the product.\n\nThe test cases are generated so that the answer will fit in a **32-bit** integer.", + + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [2,3,-2,4]\nOutput: 6\nExplanation: [2,3] has the largest product 6.\n```" + }, + { + "content": "```\nInput: nums = [-2,0,-1]\nOutput: 0\nExplanation: The result cannot be 2, because [-2,-1] is not a subarray.\n```" + } + ] + }, + + "readme_constraints": "- `1 <= nums.length <= 2 * 10^4`\n- `-10 <= nums[i] <= 10`\n- The product of any subarray of `nums` is **guaranteed** to fit in a **32-bit** integer.", + + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "max_product", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.max_product(nums)", + "helpers_assert_name": "max_product", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_max_product, run_max_product\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MaximumProductSubarray", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + + "_solution_methods": { + "list": [ + { + "name": "max_product", + "signature": "(self, nums: list[int]) -> int", + "body": " # TODO: Implement max_product\n return 0" + } + ] + }, + + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + + "_test_methods": { + "list": [ + { + "name": "test_max_product", + "signature": "(self, nums: list[int], expected: int)", + "parametrize": "nums, expected", + "test_cases": "[([2, 3, -2, 4], 6), ([-2, 0, -1], 0), ([1], 1), ([0], 0), ([-1], -1), ([2, -1, 3], 3), ([-2, -3], 6), ([1, 2, 3, 4], 24), ([-1, -2, -3], 6), ([0, 2], 2), ([3, -1, 4], 4), ([-2, 3, -4], 24), ([2, 3, -2, 4, -1], 48), ([1, 0, -1, 2, 3], 6), ([-3, 0, 1, -2], 1)]", + "body": " result = run_max_product(Solution, nums)\n assert_max_product(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_max_product, assert_max_product\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [2, 3, -2, 4]\nexpected = 6", + "playground_run": "result = run_max_product(Solution, nums)\nresult", + "playground_assert": "assert_max_product(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/next_permutation.json b/leetcode_py/cli/resources/leetcode/json/problems/next_permutation.json new file mode 100644 index 0000000..dfeb02d --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/next_permutation.json @@ -0,0 +1,70 @@ +{ + "problem_name": "next_permutation", + "solution_class_name": "Solution", + "problem_number": "31", + "problem_title": "Next Permutation", + "difficulty": "Medium", + "topics": "Array, Two Pointers", + "_tags": { "list": ["grind"] }, + + "readme_description": "A **permutation** of an array of integers is an arrangement of its members into a sequence or linear order.\n\n- For example, for `arr = [1,2,3]`, the following are all the permutations of `arr`: `[1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1]`.\n\nThe **next permutation** of an array of integers is the next lexicographically greater permutation of its integer. More formally, if all the permutations of the array are sorted in one container according to their lexicographical order, then the **next permutation** of that array is the permutation that follows it in the sorted container. If such arrangement is not possible, the array must be rearranged as the lowest possible order (i.e., sorted in ascending order).\n\n- For example, the next permutation of `arr = [1,2,3]` is `[1,3,2]`.\n- Similarly, the next permutation of `arr = [2,3,1]` is `[3,1,2]`.\n- While the next permutation of `arr = [3,2,1]` is `[1,2,3]` because `[3,2,1]` does not have a lexicographical larger rearrangement.\n\nGiven an array of integers `nums`, find the next permutation of `nums`.\n\nThe replacement must be **in place** and use only constant extra memory.", + + "_readme_examples": { + "list": [ + { "content": "```\nInput: nums = [1,2,3]\nOutput: [1,3,2]\n```" }, + { "content": "```\nInput: nums = [3,2,1]\nOutput: [1,2,3]\n```" }, + { "content": "```\nInput: nums = [1,1,5]\nOutput: [1,5,1]\n```" } + ] + }, + + "readme_constraints": "- `1 <= nums.length <= 100`\n- `0 <= nums[i] <= 100`", + + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "next_permutation", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n nums_copy = nums.copy()\n implementation.next_permutation(nums_copy)\n return nums_copy", + "helpers_assert_name": "next_permutation", + "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_next_permutation, run_next_permutation\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "NextPermutation", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + + "_solution_methods": { + "list": [ + { + "name": "next_permutation", + "signature": "(self, nums: list[int]) -> None", + "body": " # TODO: Implement next_permutation\n pass" + } + ] + }, + + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + + "_test_methods": { + "list": [ + { + "name": "test_next_permutation", + "signature": "(self, nums: list[int], expected: list[int])", + "parametrize": "nums, expected", + "test_cases": "[([1, 2, 3], [1, 3, 2]), ([3, 2, 1], [1, 2, 3]), ([1, 1, 5], [1, 5, 1]), ([1], [1]), ([1, 2], [2, 1]), ([2, 1], [1, 2]), ([1, 3, 2], [2, 1, 3]), ([2, 3, 1], [3, 1, 2]), ([1, 2, 3, 4], [1, 2, 4, 3]), ([4, 3, 2, 1], [1, 2, 3, 4]), ([1, 1, 1], [1, 1, 1]), ([1, 2, 1], [2, 1, 1]), ([5, 4, 7, 5, 3, 2], [5, 5, 2, 3, 4, 7]), ([1, 3, 2, 1], [2, 1, 1, 3]), ([2, 1, 3], [2, 3, 1])]", + "body": " result = run_next_permutation(Solution, nums)\n assert_next_permutation(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_next_permutation, assert_next_permutation\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [1, 2, 3]\nexpected = [1, 3, 2]", + "playground_run": "result = run_next_permutation(Solution, nums)\nresult", + "playground_assert": "assert_next_permutation(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/valid_sudoku.json b/leetcode_py/cli/resources/leetcode/json/problems/valid_sudoku.json new file mode 100644 index 0000000..553e94a --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/valid_sudoku.json @@ -0,0 +1,73 @@ +{ + "problem_name": "valid_sudoku", + "solution_class_name": "Solution", + "problem_number": "36", + "problem_title": "Valid Sudoku", + "difficulty": "Medium", + "topics": "Array, Hash Table, Matrix", + "_tags": { "list": ["grind"] }, + + "readme_description": "Determine if a `9 x 9` Sudoku board is valid. Only the filled cells need to be validated **according to the following rules**:\n\n1. Each row must contain the digits `1-9` without repetition.\n2. Each column must contain the digits `1-9` without repetition.\n3. Each of the nine `3 x 3` sub-boxes of the grid must contain the digits `1-9` without repetition.\n\n**Note:**\n\n- A Sudoku board (partially filled) could be valid but is not necessarily solvable.\n- Only the filled cells need to be validated according to the mentioned rules.", + + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Sudoku-by-L2G-20050714.svg/250px-Sudoku-by-L2G-20050714.svg.png)\n\n```\nInput: board = \n[[\"5\",\"3\",\".\",\".\",\"7\",\".\",\".\",\".\",\".\"]\n,[\"6\",\".\",\".\",\"1\",\"9\",\"5\",\".\",\".\",\".\"]\n,[\".\",\"9\",\"8\",\".\",\".\",\".\",\".\",\"6\",\".\"]\n,[\"8\",\".\",\".\",\".\",\"6\",\".\",\".\",\".\",\"3\"]\n,[\"4\",\".\",\".\",\"8\",\".\",\"3\",\".\",\".\",\"1\"]\n,[\"7\",\".\",\".\",\".\",\"2\",\".\",\".\",\".\",\"6\"]\n,[\".\",\"6\",\".\",\".\",\".\",\".\",\"2\",\"8\",\".\"]\n,[\".\",\".\",\".\",\"4\",\"1\",\"9\",\".\",\".\",\"5\"]\n,[\".\",\".\",\".\",\".\",\"8\",\".\",\".\",\"7\",\"9\"]]\nOutput: true\n```" + }, + { + "content": "```\nInput: board = \n[[\"8\",\"3\",\".\",\".\",\"7\",\".\",\".\",\".\",\".\"]\n,[\"6\",\".\",\".\",\"1\",\"9\",\"5\",\".\",\".\",\".\"]\n,[\".\",\"9\",\"8\",\".\",\".\",\".\",\".\",\"6\",\".\"]\n,[\"8\",\".\",\".\",\".\",\"6\",\".\",\".\",\".\",\"3\"]\n,[\"4\",\".\",\".\",\"8\",\".\",\"3\",\".\",\".\",\"1\"]\n,[\"7\",\".\",\".\",\".\",\"2\",\".\",\".\",\".\",\"6\"]\n,[\".\",\"6\",\".\",\".\",\".\",\".\",\"2\",\"8\",\".\"]\n,[\".\",\".\",\".\",\"4\",\"1\",\"9\",\".\",\".\",\"5\"]\n,[\".\",\".\",\".\",\".\",\"8\",\".\",\".\",\"7\",\"9\"]]\nOutput: false\nExplanation: Same as Example 1, except with the 5 in the top left corner being modified to 8. Since there are two 8's in the top left 3x3 sub-box, it is invalid.\n```" + } + ] + }, + + "readme_constraints": "- `board.length == 9`\n- `board[i].length == 9`\n- `board[i][j]` is a digit `1-9` or `'.'`.", + + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "is_valid_sudoku", + "helpers_run_signature": "(solution_class: type, board: list[list[str]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.is_valid_sudoku(board)", + "helpers_assert_name": "is_valid_sudoku", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_is_valid_sudoku, run_is_valid_sudoku\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ValidSudoku", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + + "_solution_methods": { + "list": [ + { + "name": "is_valid_sudoku", + "signature": "(self, board: list[list[str]]) -> bool", + "body": " # TODO: Implement is_valid_sudoku\n return False" + } + ] + }, + + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + + "_test_methods": { + "list": [ + { + "name": "test_is_valid_sudoku", + "signature": "(self, board: list[list[str]], expected: bool)", + "parametrize": "board, expected", + "test_cases": "[([['5','3','.','.','7','.','.','.','.'],['6','.','.','1','9','5','.','.','.'],['.','9','8','.','.','.','.','6','.'],['8','.','.','.','6','.','.','.','3'],['4','.','.','8','.','3','.','.','1'],['7','.','.','.','2','.','.','.','6'],['.','6','.','.','.','.','2','8','.'],['.','.','.','4','1','9','.','.','5'],['.','.','.','.','.','.','.','7','9']], True), ([['8','3','.','.','7','.','.','.','.'],['6','.','.','1','9','5','.','.','.'],['.','9','8','.','.','.','.','6','.'],['8','.','.','.','6','.','.','.','3'],['4','.','.','8','.','3','.','.','1'],['7','.','.','.','2','.','.','.','6'],['.','6','.','.','.','.','2','8','.'],['.','.','.','4','1','9','.','.','5'],['.','.','.','.','.','.','.','7','9']], False), ([['.','.','.','.','5','.','.','1','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['9','3','.','.','2','.','4','.','.'],['.','.','7','.','.','.','3','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','3','4','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','5','.','.','.']]], False), ([['.','.','4','.','.','.','6','3','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], True), ([['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], True), ([['1','2','3','4','5','6','7','8','9'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], True), ([['1','1','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], False), ([['1','.','.','.','.','.','.','.','.'],['.','1','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], False), ([['1','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','1','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']]], False), ([['1','.','.','.','.','.','.','.','2'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['3','.','.','.','.','.','.','.','1']]], True), ([['.','.','.','.','.','.','5','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','5','.','.']]], False), ([['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','1','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','1','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.'],['.','.','.','.','.','.','.','.','.']], False)]", + "body": " result = run_is_valid_sudoku(Solution, board)\n assert_is_valid_sudoku(result, expected)" + } + ] + }, + + "playground_imports": "from helpers import run_is_valid_sudoku, assert_is_valid_sudoku\nfrom solution import Solution", + "playground_setup": "# Example test case\nboard = [['5','3','.','.','7','.','.','.','.'],['6','.','.','1','9','5','.','.','.'],['.','9','8','.','.','.','.','6','.'],['8','.','.','.','6','.','.','.','3'],['4','.','.','8','.','3','.','.','1'],['7','.','.','.','2','.','.','.','6'],['.','6','.','.','.','.','2','8','.'],['.','.','.','4','1','9','.','.','5'],['.','.','.','.','.','.','.','7','9']]\nexpected = True", + "playground_run": "result = run_is_valid_sudoku(Solution, board)\nresult", + "playground_assert": "assert_is_valid_sudoku(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/tags.json5 b/leetcode_py/cli/resources/leetcode/json/tags.json5 index 2b9f498..41725b7 100644 --- a/leetcode_py/cli/resources/leetcode/json/tags.json5 +++ b/leetcode_py/cli/resources/leetcode/json/tags.json5 @@ -78,7 +78,7 @@ "zero_one_matrix", ], - grind: [{ tag: "grind-75" }, "daily_temperatures", "gas_station", "house_robber"], + grind: [{ tag: "grind-75" }, "daily_temperatures", "design_add_and_search_words_data_structure", "gas_station", "group_anagrams", "house_robber", "maximum_product_subarray", "next_permutation", "valid_sudoku"], // Test tag for development and testing test: ["binary_search", "two_sum", "valid_palindrome"], From 5eaf57fefdc1beb2ec5bb0976dcd13b8ae6f4ed4 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 19 Sep 2025 08:03:57 +0700 Subject: [PATCH 2/3] style: format tags.json5 --- leetcode_py/cli/resources/leetcode/json/tags.json5 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/leetcode_py/cli/resources/leetcode/json/tags.json5 b/leetcode_py/cli/resources/leetcode/json/tags.json5 index 41725b7..e9c6c53 100644 --- a/leetcode_py/cli/resources/leetcode/json/tags.json5 +++ b/leetcode_py/cli/resources/leetcode/json/tags.json5 @@ -78,7 +78,17 @@ "zero_one_matrix", ], - grind: [{ tag: "grind-75" }, "daily_temperatures", "design_add_and_search_words_data_structure", "gas_station", "group_anagrams", "house_robber", "maximum_product_subarray", "next_permutation", "valid_sudoku"], + grind: [ + { tag: "grind-75" }, + "daily_temperatures", + "design_add_and_search_words_data_structure", + "gas_station", + "group_anagrams", + "house_robber", + "maximum_product_subarray", + "next_permutation", + "valid_sudoku", + ], // Test tag for development and testing test: ["binary_search", "two_sum", "valid_palindrome"], From 66de09b2f79289cfcbf0fb9f569d3a7eff5a7070 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 19 Sep 2025 08:05:13 +0700 Subject: [PATCH 3/3] chore: update deps --- poetry.lock | 186 ++++++++++++++++++++++++++++------------------------ 1 file changed, 101 insertions(+), 85 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5b08393..624fc46 100644 --- a/poetry.lock +++ b/poetry.lock @@ -114,34 +114,34 @@ chardet = ">=3.0.2" [[package]] name = "black" -version = "25.1.0" +version = "25.9.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, - {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, - {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, - {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, - {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, - {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, - {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, - {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, - {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, - {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, - {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, - {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, - {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, - {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, - {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, - {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, - {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, - {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, - {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, - {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, - {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, - {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, + {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"}, + {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"}, + {file = "black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713"}, + {file = "black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1"}, + {file = "black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa"}, + {file = "black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d"}, + {file = "black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608"}, + {file = "black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f"}, + {file = "black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0"}, + {file = "black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4"}, + {file = "black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e"}, + {file = "black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a"}, + {file = "black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175"}, + {file = "black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f"}, + {file = "black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831"}, + {file = "black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357"}, + {file = "black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47"}, + {file = "black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823"}, + {file = "black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140"}, + {file = "black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933"}, + {file = "black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae"}, + {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"}, ] [package.dependencies] @@ -150,6 +150,7 @@ mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" +pytokens = ">=0.1.10" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} @@ -384,14 +385,14 @@ files = [ [[package]] name = "click" -version = "8.2.1" +version = "8.3.0" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, - {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, + {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, + {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, ] [package.dependencies] @@ -1151,50 +1152,50 @@ files = [ [[package]] name = "mypy" -version = "1.18.1" +version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "mypy-1.18.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2761b6ae22a2b7d8e8607fb9b81ae90bc2e95ec033fd18fa35e807af6c657763"}, - {file = "mypy-1.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b10e3ea7f2eec23b4929a3fabf84505da21034a4f4b9613cda81217e92b74f3"}, - {file = "mypy-1.18.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:261fbfced030228bc0f724d5d92f9ae69f46373bdfd0e04a533852677a11dbea"}, - {file = "mypy-1.18.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4dc6b34a1c6875e6286e27d836a35c0d04e8316beac4482d42cfea7ed2527df8"}, - {file = "mypy-1.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1cabb353194d2942522546501c0ff75c4043bf3b63069cb43274491b44b773c9"}, - {file = "mypy-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:738b171690c8e47c93569635ee8ec633d2cdb06062f510b853b5f233020569a9"}, - {file = "mypy-1.18.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c903857b3e28fc5489e54042684a9509039ea0aedb2a619469438b544ae1961"}, - {file = "mypy-1.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a0c8392c19934c2b6c65566d3a6abdc6b51d5da7f5d04e43f0eb627d6eeee65"}, - {file = "mypy-1.18.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f85eb7efa2ec73ef63fc23b8af89c2fe5bf2a4ad985ed2d3ff28c1bb3c317c92"}, - {file = "mypy-1.18.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82ace21edf7ba8af31c3308a61dc72df30500f4dbb26f99ac36b4b80809d7e94"}, - {file = "mypy-1.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a2dfd53dfe632f1ef5d161150a4b1f2d0786746ae02950eb3ac108964ee2975a"}, - {file = "mypy-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:320f0ad4205eefcb0e1a72428dde0ad10be73da9f92e793c36228e8ebf7298c0"}, - {file = "mypy-1.18.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:502cde8896be8e638588b90fdcb4c5d5b8c1b004dfc63fd5604a973547367bb9"}, - {file = "mypy-1.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7509549b5e41be279afc1228242d0e397f1af2919a8f2877ad542b199dc4083e"}, - {file = "mypy-1.18.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5956ecaabb3a245e3f34100172abca1507be687377fe20e24d6a7557e07080e2"}, - {file = "mypy-1.18.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8750ceb014a96c9890421c83f0db53b0f3b8633e2864c6f9bc0a8e93951ed18d"}, - {file = "mypy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb89ea08ff41adf59476b235293679a6eb53a7b9400f6256272fb6029bec3ce5"}, - {file = "mypy-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:2657654d82fcd2a87e02a33e0d23001789a554059bbf34702d623dafe353eabf"}, - {file = "mypy-1.18.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d70d2b5baf9b9a20bc9c730015615ae3243ef47fb4a58ad7b31c3e0a59b5ef1f"}, - {file = "mypy-1.18.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8367e33506300f07a43012fc546402f283c3f8bcff1dc338636affb710154ce"}, - {file = "mypy-1.18.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:913f668ec50c3337b89df22f973c1c8f0b29ee9e290a8b7fe01cc1ef7446d42e"}, - {file = "mypy-1.18.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a0e70b87eb27b33209fa4792b051c6947976f6ab829daa83819df5f58330c71"}, - {file = "mypy-1.18.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c378d946e8a60be6b6ede48c878d145546fb42aad61df998c056ec151bf6c746"}, - {file = "mypy-1.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:2cd2c1e0f3a7465f22731987fff6fc427e3dcbb4ca5f7db5bbeaff2ff9a31f6d"}, - {file = "mypy-1.18.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ba24603c58e34dd5b096dfad792d87b304fc6470cbb1c22fd64e7ebd17edcc61"}, - {file = "mypy-1.18.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ed36662fb92ae4cb3cacc682ec6656208f323bbc23d4b08d091eecfc0863d4b5"}, - {file = "mypy-1.18.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:040ecc95e026f71a9ad7956fea2724466602b561e6a25c2e5584160d3833aaa8"}, - {file = "mypy-1.18.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:937e3ed86cb731276706e46e03512547e43c391a13f363e08d0fee49a7c38a0d"}, - {file = "mypy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f95cc4f01c0f1701ca3b0355792bccec13ecb2ec1c469e5b85a6ef398398b1d"}, - {file = "mypy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:e4f16c0019d48941220ac60b893615be2f63afedaba6a0801bdcd041b96991ce"}, - {file = "mypy-1.18.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e37763af63a8018308859bc83d9063c501a5820ec5bd4a19f0a2ac0d1c25c061"}, - {file = "mypy-1.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:51531b6e94f34b8bd8b01dee52bbcee80daeac45e69ec5c36e25bce51cbc46e6"}, - {file = "mypy-1.18.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbfdea20e90e9c5476cea80cfd264d8e197c6ef2c58483931db2eefb2f7adc14"}, - {file = "mypy-1.18.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99f272c9b59f5826fffa439575716276d19cbf9654abc84a2ba2d77090a0ba14"}, - {file = "mypy-1.18.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8c05a7f8c00300a52f3a4fcc95a185e99bf944d7e851ff141bae8dcf6dcfeac4"}, - {file = "mypy-1.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:2fbcecbe5cf213ba294aa8c0b8c104400bf7bb64db82fb34fe32a205da4b3531"}, - {file = "mypy-1.18.1-py3-none-any.whl", hash = "sha256:b76a4de66a0ac01da1be14ecc8ae88ddea33b8380284a9e3eae39d57ebcbe26e"}, - {file = "mypy-1.18.1.tar.gz", hash = "sha256:9e988c64ad3ac5987f43f5154f884747faf62141b7f842e87465b45299eea5a9"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, + {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, + {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, + {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, + {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, + {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, + {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, + {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, + {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, + {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, + {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, + {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, + {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, + {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, + {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, ] [package.dependencies] @@ -1580,6 +1581,21 @@ text-unidecode = ">=1.3" [package.extras] unidecode = ["Unidecode (>=1.1.1)"] +[[package]] +name = "pytokens" +version = "0.1.10" +description = "A Fast, spec compliant Python 3.12+ tokenizer that runs on older Pythons." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b"}, + {file = "pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044"}, +] + +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] + [[package]] name = "pywin32" version = "311" @@ -2004,31 +2020,31 @@ files = [ [[package]] name = "ruff" -version = "0.13.0" +version = "0.13.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.13.0-py3-none-linux_armv6l.whl", hash = "sha256:137f3d65d58ee828ae136a12d1dc33d992773d8f7644bc6b82714570f31b2004"}, - {file = "ruff-0.13.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:21ae48151b66e71fd111b7d79f9ad358814ed58c339631450c66a4be33cc28b9"}, - {file = "ruff-0.13.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:64de45f4ca5441209e41742d527944635a05a6e7c05798904f39c85bafa819e3"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2c653ae9b9d46e0ef62fc6fbf5b979bda20a0b1d2b22f8f7eb0cde9f4963b8"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cec632534332062bc9eb5884a267b689085a1afea9801bf94e3ba7498a2d207"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd628101d9f7d122e120ac7c17e0a0f468b19bc925501dbe03c1cb7f5415b24"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afe37db8e1466acb173bb2a39ca92df00570e0fd7c94c72d87b51b21bb63efea"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f96a8d90bb258d7d3358b372905fe7333aaacf6c39e2408b9f8ba181f4b6ef2"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b5e3d883e4f924c5298e3f2ee0f3085819c14f68d1e5b6715597681433f153"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03447f3d18479df3d24917a92d768a89f873a7181a064858ea90a804a7538991"}, - {file = "ruff-0.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fbc6b1934eb1c0033da427c805e27d164bb713f8e273a024a7e86176d7f462cf"}, - {file = "ruff-0.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8ab6a3e03665d39d4a25ee199d207a488724f022db0e1fe4002968abdb8001b"}, - {file = "ruff-0.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2a5c62f8ccc6dd2fe259917482de7275cecc86141ee10432727c4816235bc41"}, - {file = "ruff-0.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b7b85ca27aeeb1ab421bc787009831cffe6048faae08ad80867edab9f2760945"}, - {file = "ruff-0.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:79ea0c44a3032af768cabfd9616e44c24303af49d633b43e3a5096e009ebe823"}, - {file = "ruff-0.13.0-py3-none-win32.whl", hash = "sha256:4e473e8f0e6a04e4113f2e1de12a5039579892329ecc49958424e5568ef4f768"}, - {file = "ruff-0.13.0-py3-none-win_amd64.whl", hash = "sha256:48e5c25c7a3713eea9ce755995767f4dcd1b0b9599b638b12946e892123d1efb"}, - {file = "ruff-0.13.0-py3-none-win_arm64.whl", hash = "sha256:ab80525317b1e1d38614addec8ac954f1b3e662de9d59114ecbf771d00cf613e"}, - {file = "ruff-0.13.0.tar.gz", hash = "sha256:5b4b1ee7eb35afae128ab94459b13b2baaed282b1fb0f472a73c82c996c8ae60"}, + {file = "ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b"}, + {file = "ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334"}, + {file = "ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a"}, + {file = "ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783"}, + {file = "ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a"}, + {file = "ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700"}, + {file = "ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae"}, + {file = "ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317"}, + {file = "ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0"}, + {file = "ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5"}, + {file = "ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a"}, + {file = "ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51"}, ] [[package]]