6363def _bfs_paths_graph (
6464 graph : defaultdict , start : int , goal : int
6565) -> Generator [list [int ], None , None ]:
66+ # visited set prevents re-exploring nodes already reached via a shorter
67+ # path, converting worst-case BFS from exponential to O(V + E).
68+ visited : set [int ] = {start }
6669 queue = [(start , [start ])]
6770 while queue :
6871 (vertex , path ) = queue .pop (0 )
6972 for pos in graph [vertex ]:
7073 if pos == goal :
7174 yield path + [pos ]
72- else :
75+ elif pos not in visited :
76+ visited .add (pos )
7377 queue .append ((pos , path + [pos ]))
7478
7579
@@ -89,7 +93,7 @@ def _onecut(text: str, custom_dict: Trie) -> Generator[str, None, None]:
8993 end_pos = 0
9094 while pos_list [0 ] < len_text :
9195 begin_pos = heappop (pos_list )
92- for word in custom_dict .prefixes (text [ begin_pos :] ):
96+ for word in custom_dict .prefixes (text , begin_pos ):
9397 end_pos_candidate = begin_pos + len (word )
9498 if end_pos_candidate in valid_poss :
9599 graph [begin_pos ].append (end_pos_candidate )
@@ -107,20 +111,20 @@ def _onecut(text: str, custom_dict: Trie) -> Generator[str, None, None]:
107111 _bfs_paths_graph (graph , end_pos , pos_list [0 ])
108112 )
109113 graph_size = 0
114+ graph .clear ()
110115 for pos in end_pos_candidates [1 :]:
111116 yield text [end_pos :pos ]
112117 end_pos = pos
113118 elif len_pos_list == 0 : # no candidate, deal with non-dictionary word
114- m = _PAT_NONTHAI .match (text [ begin_pos :] )
119+ m = _PAT_NONTHAI .match (text , begin_pos )
115120 if m : # non-Thai token, skip to the end
116- end_pos = begin_pos + m .end ()
121+ end_pos = m .end ()
117122 else : # Thai token, find minimum skip
118123 for pos in range (begin_pos + 1 , len_text ):
119124 if pos in valid_poss :
120- prefix = text [pos :]
121125 words = [
122126 word
123- for word in custom_dict .prefixes (prefix )
127+ for word in custom_dict .prefixes (text , pos )
124128 if (
125129 (pos + len (word ) in valid_poss )
126130 and not _PAT_THAI_TWOCHARS .match (word )
@@ -131,14 +135,14 @@ def _onecut(text: str, custom_dict: Trie) -> Generator[str, None, None]:
131135 break
132136
133137 # is a non-Thai token
134- if _PAT_NONTHAI .match (prefix ):
138+ if _PAT_NONTHAI .match (text , pos ):
135139 end_pos = pos
136140 break
137141 else :
138142 end_pos = len_text
139143
140- graph [ begin_pos ]. append ( end_pos )
141- graph_size = graph_size + 1
144+ graph_size = 0
145+ graph . clear ()
142146 yield text [begin_pos :end_pos ]
143147 heappush (pos_list , end_pos )
144148
@@ -155,13 +159,17 @@ def segment(
155159
156160 A custom dictionary can be supplied.
157161
162+ For very long texts (hundreds of kilobytes or more), consider using
163+ ``safe_mode=True`` to enable chunk-based processing and reduce memory use.
164+
158165 :param text: text to be tokenized
159166 :type text: str
160167 :param custom_dict: tokenization dictionary,\
161168 defaults to word_dict_trie()
162169 :type custom_dict: Trie, optional
163- :param safe_mode: reduce chance for long processing time for long text\
164- with many ambiguous breaking points, defaults to False
170+ :param safe_mode: use chunk-based processing to reduce memory use and
171+ processing time for long text with many ambiguous breaking points,
172+ defaults to False
165173 :type safe_mode: bool, optional
166174 :return: list of tokens
167175 :rtype: list[str]
0 commit comments