Trying this with the recursive solution:
Recursively I am creating all the substrings and checking if it is palindrome or not.
Problem is that I want to get rid of global variable count.
class Solution(object):
def countSubstrings(self, s):
"""
:type s: str
:rtype: int
"""
def palin(s):
if s == s[::-1]:
return True
return False
global count
count = 0
def helper(s, cur, dp):
global count
ret = 0
if cur >= len(s)-1:
return 0
if cur in dp:
return
for i in range(cur+1, len(s)):
if palin(s[cur:i+1]):
count += 1
ret = helper(s, i, dp)
else:
ret = helper(s, i, dp)
dp[cur] = ret
helper(s, 0, {})
return count + len(s)
What I have tried so far:
def helper(s, cur, dp, count):
ret = 0
if cur >= len(s)-1:
return count
if cur in dp:
return dp[cur]
for i in range(cur+1, len(s)):
if palin(s[cur:i+1]):
ret = helper(s, i, dp, count + 1)
else:
ret = helper(s, i, dp, count)
dp[cur] = ret
return dp[cur]
Just pass your count variable in your recursive helper function (and increment as necessary).
You can make a new Counter class to keep track of your count
class Counter:
def __init__(self):
self.count = 0
def __add__(self,num):
self.count+=num
return self
Then modify your code to use that counter
class Solution(object):
def countSubstrings(self, s):
"""
:type s: str
:rtype: int
"""
def palin(s):
if s == s[::-1]:
return True
return False
def helper(s, cur, dp,count): #make a parameter for Counter
ret = 0
if cur >= len(s)-1:
return 0
if cur in dp:
return
for i in range(cur+1, len(s)):
if palin(s[cur:i+1]):
count+=1
ret = helper(s, i, dp,count) #pass in the Counter
else:
ret = helper(s, i, dp,count) #pass in here as well
dp[cur] = ret
a = Counter() #Change here
helper(s, 0, {},a) #Change here
return a.count + len(s) #Change here
The way you designed your counting and recursion, you have no choice but to use a mutable object to keep track of the count. And of course there are better ways to do recursion for this problem.
Here is a version with as much recursion as I could think of (including your palindrome function):
class Solution(object):
def countSubstrings(self, s):
"""
:type s: str
:rtype: int
"""
def palin(s):
"""recursively checks if bookends match on narrower substrings"""
if len(s) <= 1:
return True
else:
# checks if bookends match and inner substring is a palindrome
return (s[0] == s[-1]) & palin(s[1:-1])
def first_char_palin_count(s):
"""counts palindromes of all substrings with first char (pos 0)
e.g. will check: "abba", "abb", "ab", "a", "" in palin()
"""
if len(s) <= 0:
return 0
# if s is palindrome + shorter palindromes with first char
else:
return palin(s) + first_char_palin_count(s[:-1])
def helper(s):
"""counts palindromes in all substrings"""
if len(s) <= 0:
return 0
else:
# first char palindromes + palindromes not including first char
return first_char_palin_count(s) + helper(s[1:])
return helper(s)
Notice:
I need 2 functions in my recursion:
one to handle all substrings that include the first character (calls itself)
another (called helper to match yours) to handle all substrings (with and without first character)
I don't need to pass anything but substrings around (no count variable global or local!), because the recursion implicitly the results of all its subproblems (substrings in this case).
Related
I was trying to solve a problem on leetcode but I keep incurring in an error that I don’t understand
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
char = set()
longest = []
curr = []
for i in range(len(s)):
if s[i] in char:
longest = max(curr, longest, key=len)
curr = curr[curr.index(s[i])+1:].append(s[i])
else:
curr.append(s[i])
char.add(s[i])
return max(curr, longest, key=len)
This is the code. The error refers to the fact that when i call the function max() one between curr or longest has no attribute len(). Aren’t both lists?
I looked up the solve but it uses a slightly different method.
As already mentioned in the comment problem When you set longest = max... it ceases to be a list by #mark-ransom already. I will purpose different way of solving this
# lengthOfLongestSubstring
# can be optimized to O(n) using sliding window technique
class Solution2(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
if len(s) == 0:
return 0
if len(s) == 1:
return 1
max_len = 0
for i in range(len(s)):
for j in range(i+1, len(s)+1):
if len(set(s[i:j])) == len(s[i:j]):
max_len = max(max_len, len(s[i:j]))
else:
break
return max_len
class SolutionWithProblem(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
char = set()
longest = []
curr = []
for i in range(len(s)):
if s[i] in char:
longest = max(curr, longest, key=len) # problem here
curr = curr[curr.index(s[i])+1:].append(s[i])
else:
curr.append(s[i])
char.add(s[i])
return max(curr, longest, key=len)
# call the function
s = Solution2()
print(s.lengthOfLongestSubstring("abcabcbb"))
print(s.lengthOfLongestSubstring("bbbbb"))
print(s.lengthOfLongestSubstring("pwwkew"))
print(s.lengthOfLongestSubstring(" "))
print(s.lengthOfLongestSubstring("dvdf"))
print(s.lengthOfLongestSubstring("anviaj"))
print(s.lengthOfLongestSubstring("abba"))
This might not be the best solution. Update to your solution
class Solution3(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
char = set()
longest = []
curr = []
for i in range(len(s)):
if s[i] in char:
if len(curr) > len(longest):
longest = curr
curr = []
char = set()
char.add(s[i])
curr.append(s[i])
if len(curr) > len(longest):
longest = curr
return len(longest)
Similar question was posted but I do not know how to return one with the last index.
The part where I try to solve the problem is in the function pop() up to b = max(...)
import itertools
import operator
class FreqStack:
def __init__(self):
self.STACK = []
self.output = []
def push(self, val: int) -> None:
self.STACK.append(val)
def pop(self) -> int:
SL = sorted((x, i) for i, x in enumerate(self.STACK))
groups = itertools.groupby(SL, key=operator.itemgetter(0))
def _auxfun(g):
item, iterable = g
count = 0
min_index = len(self.STACK)
for _, where in iterable:
count += 1
min_index = min(min_index, where)
return count, -min_index
# pick the highest-count/earliest item
b = max(groups, key=_auxfun)[0]
self.STACK.reverse()
try:
self.STACK.remove(b)
except:
pass
self.STACK.reverse()
return b
I'm trying to optimize this solution for a function that accepts 2 arguments: fullstring and substring. The function will return True if the substring exists in the fullstring, and False if it does not. There is one special wildcard that could be entered in the substring that denotes 0 or 1 of the previous symbol, and there can be more than one wildcard in the substring.
For example, "a*" means "" or "a"
The solution I have works fine but I'm trying to reduce the number of for loops (3) and optimize for time complexity. Using regex is not permitted. Is there a more pythonic way to do this?
Current Solution:
def complex_search(fullstring, substring):
patterns = []
if "*" in substring:
index = substring.index("*")
patterns.append(substring[:index-1] + substring[index+1:])
patterns.append(substring[:index] + substring[index+1:])
else:
patterns.append(substring)
def check(s1, s2):
for a, b in zip(s1, s2):
if a != b:
return False
return True
for pattern in patterns:
for i in range(len(fullstring) - len(pattern) + 1):
if check(fullstring[i:i+len(pattern)], pattern):
return True
return False
>> print(complex_search("dogandcats", "dogs*andcats"))
>> True
Approach
Create all alternatives for the substring based upon '" in substring (can have zero or more '' in substring)
See Function combs(...) below
Use Aho-Corasick to check if one of the substring patterns is in the string. Aho-Corasick is a very efficient algorithm for checking if one or more substrings appear in a string and formed as the basis of the original Unix command fgrep.
For illustrative purposes a Python version of Aho-Corasik is used below, but a C implementation (with Python wrapper) is available at pyahocorasick for higher performance.
See class Aho-Corasick below.
Code
# Note: This is a modification of code explained in https://carshen.github.io/data-structures/algorithms/2014/04/07/aho-corasick-implementation-in-python.html
from collections import deque
class Aho_Corasick():
def __init__(self, keywords):
self.adj_list = []
# creates a trie of keywords, then sets fail transitions
self.create_empty_trie()
self.add_keywords(keywords)
self.set_fail_transitions()
def create_empty_trie(self):
""" initalize the root of the trie """
self.adj_list.append({'value':'', 'next_states':[],'fail_state':0,'output':[]})
def add_keywords(self, keywords):
""" add all keywords in list of keywords """
for keyword in keywords:
self.add_keyword(keyword)
def find_next_state(self, current_state, value):
for node in self.adj_list[current_state]["next_states"]:
if self.adj_list[node]["value"] == value:
return node
return None
def add_keyword(self, keyword):
""" add a keyword to the trie and mark output at the last node """
current_state = 0
j = 0
keyword = keyword.lower()
child = self.find_next_state(current_state, keyword[j])
while child != None:
current_state = child
j = j + 1
if j < len(keyword):
child = self.find_next_state(current_state, keyword[j])
else:
break
for i in range(j, len(keyword)):
node = {'value':keyword[i],'next_states':[],'fail_state':0,'output':[]}
self.adj_list.append(node)
self.adj_list[current_state]["next_states"].append(len(self.adj_list) - 1)
current_state = len(self.adj_list) - 1
self.adj_list[current_state]["output"].append(keyword)
def set_fail_transitions(self):
q = deque()
child = 0
for node in self.adj_list[0]["next_states"]:
q.append(node)
self.adj_list[node]["fail_state"] = 0
while q:
r = q.popleft()
for child in self.adj_list[r]["next_states"]:
q.append(child)
state = self.adj_list[r]["fail_state"]
while (self.find_next_state(state, self.adj_list[child]["value"]) == None
and state != 0):
state = self.adj_list[state]["fail_state"]
self.adj_list[child]["fail_state"] = self.find_next_state(state, self.adj_list[child]["value"])
if self.adj_list[child]["fail_state"] is None:
self.adj_list[child]["fail_state"] = 0
self.adj_list[child]["output"] = self.adj_list[child]["output"] + self.adj_list[self.adj_list[child]["fail_state"]]["output"]
def get_keywords_found(self, line):
""" returns keywords in trie from line """
line = line.lower()
current_state = 0
keywords_found = []
for i, c in enumerate(line):
while self.find_next_state(current_state, c) is None and current_state != 0:
current_state = self.adj_list[current_state]["fail_state"]
current_state = self.find_next_state(current_state, c)
if current_state is None:
current_state = 0
else:
for j in self.adj_list[current_state]["output"]:
yield {"index":i-len(j) + 1,"word":j}
def pattern_found(self, line):
''' Returns true when the pattern is found '''
return next(self.get_keywords_found(line), None) is not None
def combs(word, n = 0, path = ""):
''' Generate all combinations of words with star
e.g. list(combs("he*lp*")) = ['help', 'helpp', 'heelp', 'heelpp']
'''
if n == len(word):
yield path
elif word[n] == '*':
# Next letter
yield from combs(word, n+1, path) # don't add * to path
else:
if n < len(word) - 1 and word[n+1] == '*':
yield from combs(word, n+1, path) # Not including letter at n
yield from combs(word, n+1, path + word[n]) # including letter at n
Test
patterns = combs("dogs*andcats") # ['dogandcats', 'dogsandcats']
aho = Aho_Corasick(patterns) # Aho-Corasick structure to recognize patterns
print(aho.pattern_found("dogandcats")) # Output: True
print(aho.pattern_found("dogsandcats")) # Output: True
I am working on an assignment, and have made a big start but have no idea how to continue and am looking for some advice (not answers). Using the following classes:
class CounterList:
__n_comparisons__ = 0
def __init__(self, data=None):
if data is None:
self.data = []
else:
self.data = data
self.__n_accesses__ = 0
def __getitem__(self, i):
self.__n_accesses__ += 1
return self.data[i]
def __setitem__(self, i, item):
self.__n_accesses__ += 1
if type(item) != CounterNode:
raise ValueError("Only Counter objects can be placed in a CounterList")
else:
self.data[i] = item
def __delitem__(self, key):
self.__n_accesses__ += 1
del(self.data[key])
def __len__(self):
return len(self.data)
def __repr__(self):
return repr(self.data)
def __contains__(self, item):
raise TypeError("You can't use the 'in' keyword with a CounterList")
def __eq__(self, other):
self.__n_comparisons__ += 1
return self.data == other
def insert(self, index, item):
if type(item) != CounterNode:
raise ValueError("Only Counter objects can be added to a CounterList")
else:
self.data.insert(index, item)
def index(self, a=None):
raise TypeError("You can't do that with a CounterList")
def append(self, item):
if type(item) != CounterNode:
raise ValueError("Only Counter objects can be added to a CounterList")
else:
self.data.append(item)
def get_accesses(self):
return self.__n_accesses__
#classmethod
def get_comparisons(cls):
return cls.__n_comparisons__
#classmethod
def reset_comparisons(cls):
cls.__n_comparisons__ = 0
class MyString:
def __init__(self, i):
self.i = i
def __eq__(self, j):
if type(j) != MyString:
CounterList.__n_comparisons__ += 1
return self.i == j
def __le__(self, j):
if type(j) != MyString:
CounterList.__n_comparisons__ += 1
return self.i <= j
def __ne__(self, j):
if type(j) != MyString:
CounterList.__n_comparisons__ += 1
return self.i != j
def __lt__(self, j):
if type(j) != MyString:
CounterList.__n_comparisons__ += 1
return self.i < j
def __gt__(self, j):
if type(j) != MyString:
CounterList.__n_comparisons__ += 1
return self.i > j
def __ge__(self, j):
if type(j) != MyString:
CounterList.__n_comparisons__ += 1
return self.i >= j
def __repr__(self):
return repr(self.i)
def __getattr__(self, attr):
'''All other behaviours use self.i'''
return self.i.__getattr__(attr)
class CounterNode:
def __init__(self, word, count=1):
self.word = MyString(word)
self.count = count
def __repr__(self):
return str(self.word) + ": " + str(self.count)
I am required to write a sequential searching program, which produces the output in the format:
['hello': 3, 'world': 3]
Where each word in the list is checked against the words in the new list, and if the word is not present then the word is added to the list with a counter of 1, and if the word is in the list the program should simply add 1 to that words count.
The code I have so far is:
from classes_1 import CounterNode, CounterList
def word_counter_seq(words_list):
my_list = CounterList()
for new_word in words_list:
i = 0
q = 1
if not my_list:
new_counter = CounterNode (new_word, 1)
my_list.append(new_counter)
elif new_word == my_list[i].word:
my_list[i].count +=1
elif len(my_list)>1:
if new_word == my_list[i].word:
my_list[i].count +=1
i+=1
elif new_word == my_list[q].word:
my_list[q].count +=1
q+=1
else:
new_counter = CounterNode (new_word, 1)
my_list.append(new_counter)
else:
new_counter = CounterNode (new_word, 1)
my_list.append(new_counter)
return my_list
However with the code as it is now, it only returns the first two elements in the original list correctly, and any consequent items are returned with a counter of 1 and as separate items. For example:
['hello': 3, 'world': 3, 'test': 1, 'test': 1]
instead of:
['hello': 3, 'world': 3, 'test': 2]
Judging by the classes your assignment asks you to implement, it seems the intention is to force you to use arrays (although a dict would probably be better here...). Think about it this way:
for each new_word in word_list:
for each element in your_list:
if new_word equals element:
#do something
else:
#do something else
By doing that you eliminate the unnecessary
if not list:
check and make your code a lot cleaner, so won't confuse yourself. However I noticed that your CounterList seems to measure how many accesses are using. So if what your assignment requires is a solution that minimises accesses then think about this list:
'test', 'book', 'whatever', 'test'
your idea of comparing adjacent elements won't work unless you make it like this:
'book', 'test', 'test', 'whatever'
Sorry this answer is a little vague, I don't want to spoil your homework for you.
I've read the following implementation of the trie in python:
https://stackoverflow.com/a/11016430/2225221
and tried to make the remove fnction for it.
Basically, I had problems even with the start: If you want to remove a word from a trie, it can has sub-"words", or it can be "subword" of another word.
If you remove with "del dict[key]", you are removing these above mentioned two kinds of words also.
Could anyone help me in this, how to remove properly the chosen word (let us presume it's in the trie)
Basically, to remove a word from the trie (as it is implemented in the answer you linked to), you'd just have to remove its _end marker, for example like this:
def remove_word(trie, word):
current_dict = trie
for letter in word:
current_dict = current_dict.get(letter, None)
if current_dict is None:
# the trie doesn't contain this word.
break
else:
del current_dict[_end]
Note however that this doesn't ensure that the trie has its minimal size. After deleting the word, there may be branches in the trie left that are no longer used by any words. This doesn't affect the correctness of the data structure, it just means that the trie may consume more memory than absolutely necessary. You could improve this by iterating backwards from the leaf node and delete branches until you find one that has more than one child.
EDIT: Here's an idea how you could implement a remove function that also culls any unnecessary branches. There's probably a more efficient way to do it, but this might get you started:
def remove_word2(trie, word):
current_dict = trie
path = [current_dict]
for letter in word:
current_dict = current_dict.get(letter, None)
path.append(current_dict)
if current_dict is None:
# the trie doesn't contain this word.
break
else:
if not path[-1].get(_end, None):
# the trie doesn't contain this word (but a prefix of it).
return
deleted_branches = []
for current_dict, letter in zip(reversed(path[:-1]), reversed(word)):
if len(current_dict[letter]) <= 1:
deleted_branches.append((current_dict, letter))
else:
break
if len(deleted_branches) > 0:
del deleted_branches[-1][0][deleted_branches[-1][1]]
del path[-1][_end]
Essentially, it first finds the "path" to the word that is about to be deleted and then iterates through that backwards to find nodes that can be removed. It then removes the root of the path that can be deleted (which also implicitly deletes the _end node).
I think it is better to do it recursively, code as following:
def remove(self, word):
self.delete(self.tries, word, 0)
def delete(self, dicts, word, i):
if i == len(word):
if 'end' in dicts:
del dicts['end']
if len(dicts) == 0:
return True
else:
return False
else:
return False
else:
if word[i] in dicts and self.delete(dicts[word[i]], word, i + 1):
if len(dicts[word[i]]) == 0:
del dicts[word[i]]
return True
else:
return False
else:
return False
def remove_a_word_util(self, word, idx, node):
if len(word) == idx:
node.is_end_of_word = False
return bool(node.children)
ch = word[idx]
if ch not in node.children:
return True
flag = self.remove_a_word_util(word, idx+1, node.children[ch])
if flag:
return True
node.children.pop(ch)
return bool(node.children) or node.is_end_of_word
One method of handling structures like this is through recursion. The great thing about recursion in this case is that it zips to the bottom of the trie, then passes the returned values back up through the branches.
The following function does just that. It goes to the leaf and deletes the _end value, just in case the input word is a prefix of another. It then passes up a boolean (boo) which indicates that the current_dict is still in an outlying branch. Once we hit a point where the current dict has more than one child, we delete the appropriate branch and set boo to False so each remaining recursion will do nothing.
def trie_trim(term, trie=SYNONYMS, prev=0):
# checks that we haven't hit the end of the word
if term:
first, rest = term[0], term[1:]
current_length = len(trie)
next_length, boo = trie_trim(rest, trie=trie[first], prev=current_length)
# this statement avoids trimming excessively if the input is a prefix because
# if the word is a prefix, the first returned value will be greater than 1
if boo and next_length > 1:
boo = False
# this statement checks for the first occurrence of the current dict having more than one child
# or it checks that we've hit the bottom without trimming anything
elif boo and (current_length > 1 or not prev):
del trie[first]
boo = False
return current_length, boo
# when we do hit the end of the word, delete _end
else:
del trie[_end]
return len(trie) + 1, True
A bit of a long one, but I hope this helps answer your question:
class Trie:
WORD_END = "$"
def __init__(self):
self.trie = {}
def insert(self, word):
cur = self.trie
for char in word:
if char not in cur:
cur[char] = {}
cur = cur[char]
cur[Trie.WORD_END] = word
def delete(self, word):
def _delete(word, cur_trie, i=0):
if i == len(word):
if Trie.WORD_END not in cur_trie:
raise ValueError("'%s' is not registered in the trie..." %word)
cur_trie.pop(Trie.WORD_END)
if len(cur_trie) > 0:
return False
return True
if word[i] not in cur_trie:
raise ValueError("'%s' is not registered in the trie..." %word)
cont = _delete(word, cur_trie[word[i]], i+1)
if cont:
cur_trie.pop(word[i])
if Trie.WORD_END in cur_trie:
return False
return True
return False
_delete(word, self.trie)
t = Trie()
t.insert("bar")
t.insert("baraka")
t.insert("barakalar")
t.delete("barak") # raises error as 'barak' is not a valid WORD_END although it is a valid path.
t.delete("bareka") # raises error as 'e' does not exist in the path.
t.delete("baraka") # deletes the WORD_END of 'baraka' without deleting any letter as there is 'barakalar' afterwards.
t.delete("barakalar") # deletes until the previous word (until the first Trie.WORD_END; "$" - by going backwards with recursion) in the same path (until 'baraka').
In case you need the whole DS:
class TrieNode:
def __init__(self):
self.children = {}
self.wordCounter = 0
self.prefixCounter = 0
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word: str) -> None:
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node.prefixCounter += 1
node = node.children[char]
node.wordCounter += 1
def countWordsEqualTo(self, word: str) -> int:
node = self.root
if node.children:
for char in word:
node = node.children[char]
else:
return 0
return node.wordCounter
def countWordsStartingWith(self, prefix: str) -> int:
node = self.root
if node.children:
for char in prefix:
node = node.children[char]
else:
return 0
return node.prefixCounter
def erase(self, word: str) -> None:
node = self.root
for char in word:
if node.children:
node.prefixCounter -= 1
node = node.children[char]
else:
return None
node.wordCounter -= 1
if node.wordCounter == 0:
self.dfsRemove(self.root, word, 0)
def dfsRemove(self, node: TrieNode, word: str, idx: int) -> None:
if len(word) == idx:
node.wordCounter = 0
return
char = word[idx]
if char not in node.children:
return
self.dfsRemove(node.children[char], word, idx+1)
node.children.pop(char)
trie = Trie();
trie.insert("apple"); #// Inserts "apple".
trie.insert("apple"); #// Inserts another "apple".
print(trie.countWordsEqualTo("apple")) #// There are two instances of "apple" so return 2.
print(trie.countWordsStartingWith("app")) #// "app" is a prefix of "apple" so return 2.
trie.erase("apple") #// Erases one "apple".
print(trie.countWordsEqualTo("apple")) #// Now there is only one instance of "apple" so return 1.
print(trie.countWordsStartingWith("app")) #// return 1
trie.erase("apple"); #// Erases "apple". Now the trie is empty.
print(trie.countWordsEqualTo("apple")) #// return 0
print(trie.countWordsStartingWith("app")) #// return 0
I would argue that this implementation is the most succinct and easiest to understand after a bit of staring.
def removeWord(word, node=None):
if not node:
node = self.root
if word == "":
node.isEnd = False
return
newnode = node.children[word[0]]
removeWord(word[1:], newnode)
if not newnode.isEnd and len(newnode.children) == 0:
del node.children[word[0]]
Although it's a little tricky to understand with the default parameter node=None at first, this is the most succinct implementation of a Trie removal that handles marking the word node.isEnd = False while also pruning extraneous nodes.
The method is first called as Trie.removeWord("ToBeDeletedWord").
In subsequent recursion calls, a node tied to the corresponding letter ("T" then "o" then "B" then "e" etc. etc.) is added to the next recursion (e.g "remove 'oBeDeletedWord' with the node at T").
Once we hit the end node that has the full string ToBeDeletedWord , the last recursion calls removeWord("", <node d>)
In this last recursion call, we mark node.isEnd = False. Later, the node is no longer marked isEnd and it has no children so we can call the delete operator.
Once that last recursion call ends, the rest of the recursions (e.g TobeDeletedWor, TobeDeletedWo, TobeDeletedW, etc. etc.) will then observe that it too is not an end node and there are no more children. These nodes will also delete.
You will have to read this a couple of times but this implementation is concise, readable, and correct. The difficulty is that the recursion happens midfunction rather than at the beginning or end.
TL;DR
class TrieNode:
children: dict[str, "TrieNode"]
def __init__(self) -> None:
self.children = {}
self.end = False
def __contains__(self, char: str) -> bool:
return char in self.children
def __getitem__(self, __name: str) -> "TrieNode":
return self.children[__name]
def __setitem__(self, __name: str, __value: "TrieNode") -> None:
self.children[__name] = __value
def __len__(self):
return len(self.children)
def __delitem__(self, __name: str):
del self.children[__name]
class Trie:
def __init__(self, words: list[str]) -> None:
self.root = TrieNode()
for w in words:
self.insert(w)
def insert(self, word: str):
curr = self.root
for c in word:
curr = curr.children.setdefault(c, TrieNode())
curr.end = True
def remove(self, word: str):
def _remove(node: TrieNode, index: int):
if index >= len(word):
node.end = False
if not node.children:
return True
elif word[index] in node:
if _remove(node[word[index]], index + 1):
del node[word[index]]
_remove(self.root, 0)