1use lindera::LinderaResult;
2use lindera::mode::Mode;
3use lindera::token::Token;
4use lindera::worker::SegmentWorker;
5
6use crate::character_filter::{BoxCharacterFilter, OffsetMapping};
7use crate::token_filter::BoxTokenFilter;
8use crate::tokenizer::{Tokenizer, correct_offsets};
9
10pub struct AnalysisWorker {
41 character_filters: Vec<BoxCharacterFilter>,
43 token_filters: Vec<BoxTokenFilter>,
45 segment_worker: SegmentWorker,
47 text_buf: String,
50 offset_mappings: Vec<OffsetMapping>,
53}
54
55impl Tokenizer {
56 pub fn new_worker(&self) -> AnalysisWorker {
66 self.clone().into_worker()
67 }
68
69 pub fn into_worker(self) -> AnalysisWorker {
76 AnalysisWorker {
77 character_filters: self.character_filters,
78 token_filters: self.token_filters,
79 segment_worker: self.segmenter.into_worker(),
80 text_buf: String::new(),
81 offset_mappings: Vec::new(),
82 }
83 }
84}
85
86impl AnalysisWorker {
87 pub fn tokenize<'w>(&'w mut self, text: &'w str) -> LinderaResult<Vec<Token<'w>>> {
104 let Self {
105 character_filters,
106 token_filters,
107 segment_worker,
108 text_buf,
109 offset_mappings,
110 } = self;
111 offset_mappings.clear();
112
113 let (mut tokens, final_text_len) = if character_filters.is_empty() {
114 (segment_worker.segment(text)?, text.len())
115 } else {
116 text_buf.clear();
117 text_buf.push_str(text);
118 for character_filter in character_filters.iter() {
119 let mapping = character_filter.apply(text_buf)?;
120 if !mapping.is_empty() {
121 offset_mappings.push(mapping);
122 }
123 }
124 let final_text_len = text_buf.len();
127 (segment_worker.segment(text_buf.as_str())?, final_text_len)
128 };
129
130 for token_filter in token_filters.iter() {
131 token_filter.apply(&mut tokens)?;
132 }
133
134 correct_offsets(&mut tokens, offset_mappings, final_text_len);
135
136 Ok(tokens)
137 }
138
139 pub fn tokenize_nbest<'w>(
156 &'w mut self,
157 text: &'w str,
158 n: usize,
159 unique: bool,
160 cost_threshold: Option<i64>,
161 ) -> LinderaResult<Vec<(Vec<Token<'w>>, i64)>> {
162 let Self {
163 character_filters,
164 token_filters,
165 segment_worker,
166 text_buf,
167 offset_mappings,
168 } = self;
169 offset_mappings.clear();
170
171 let (mut all_results, final_text_len) = if character_filters.is_empty() {
172 (
173 segment_worker.segment_nbest(text, n, unique, cost_threshold)?,
174 text.len(),
175 )
176 } else {
177 text_buf.clear();
178 text_buf.push_str(text);
179 for character_filter in character_filters.iter() {
180 let mapping = character_filter.apply(text_buf)?;
181 if !mapping.is_empty() {
182 offset_mappings.push(mapping);
183 }
184 }
185 let final_text_len = text_buf.len();
186 (
187 segment_worker.segment_nbest(text_buf.as_str(), n, unique, cost_threshold)?,
188 final_text_len,
189 )
190 };
191
192 for (tokens, _cost) in &mut all_results {
193 for token_filter in token_filters.iter() {
194 token_filter.apply(tokens)?;
195 }
196 correct_offsets(tokens, offset_mappings, final_text_len);
197 }
198
199 Ok(all_results)
200 }
201
202 pub fn set_mode(&mut self, mode: Mode) {
208 self.segment_worker.set_mode(mode);
209 }
210
211 pub fn set_keep_whitespace(&mut self, keep: bool) {
218 self.segment_worker.set_keep_whitespace(keep);
219 }
220
221 pub fn shrink_to(&mut self, text_len_hint: usize) {
228 self.segment_worker.shrink_to(text_len_hint);
229 self.text_buf.shrink_to(text_len_hint);
230 self.offset_mappings.shrink_to_fit();
231 }
232
233 pub fn reset(&mut self) {
240 self.segment_worker.reset();
241 self.text_buf = String::new();
242 self.offset_mappings = Vec::new();
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 #[cfg(feature = "embed-ipadic")]
249 mod with_ipadic {
250 use std::collections::HashMap;
251
252 use lindera::dictionary::load_dictionary;
253 use lindera::mode::Mode;
254 use lindera::segmenter::Segmenter;
255
256 use crate::character_filter::BoxCharacterFilter;
257 use crate::character_filter::mapping::MappingCharacterFilter;
258 use crate::token_filter::BoxTokenFilter;
259 use crate::token_filter::lowercase::LowercaseTokenFilter;
260 use crate::tokenizer::Tokenizer;
261
262 fn ipadic_tokenizer(with_filters: bool) -> Tokenizer {
263 let dictionary = match load_dictionary("embedded://ipadic") {
264 Ok(dictionary) => dictionary,
265 Err(err) => panic!("failed to load embedded IPADIC: {err}"),
266 };
267 let segmenter = Segmenter::new(Mode::Normal, dictionary, None);
268 let mut tokenizer = Tokenizer::new(segmenter);
269 if with_filters {
270 let mut mapping = HashMap::new();
271 mapping.insert("リンデラ".to_string(), "Lindera".to_string());
272 let filter = match MappingCharacterFilter::new(mapping) {
273 Ok(filter) => filter,
274 Err(err) => panic!("failed to build mapping filter: {err}"),
275 };
276 tokenizer
277 .character_filters
278 .push(BoxCharacterFilter::from(filter));
279 tokenizer
280 .token_filters
281 .push(BoxTokenFilter::from(LowercaseTokenFilter::new()));
282 }
283 tokenizer
284 }
285
286 fn assert_same_tokens(
287 tokenizer: &Tokenizer,
288 worker_tokens: &[(String, usize, usize)],
289 text: &str,
290 ) {
291 let expected = match tokenizer.tokenize(text) {
292 Ok(tokens) => tokens,
293 Err(err) => panic!("tokenize failed: {err}"),
294 };
295 let expected: Vec<(String, usize, usize)> = expected
296 .iter()
297 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
298 .collect();
299 assert_eq!(expected, worker_tokens, "mismatch for {text:?}");
300 }
301
302 #[test]
303 fn test_worker_matches_tokenize_without_filters() {
304 let tokenizer = ipadic_tokenizer(false);
305 let mut worker = tokenizer.new_worker();
306
307 let texts = [
308 "すもももももももものうち",
309 "関西国際空港限定トートバッグ",
310 "",
311 ];
312 for _ in 0..3 {
313 for text in texts {
314 let actual: Vec<(String, usize, usize)> = {
315 let tokens = match worker.tokenize(text) {
316 Ok(tokens) => tokens,
317 Err(err) => panic!("worker.tokenize failed: {err}"),
318 };
319 tokens
320 .iter()
321 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
322 .collect()
323 };
324 assert_same_tokens(&tokenizer, &actual, text);
325 }
326 }
327 }
328
329 #[test]
330 fn test_worker_matches_tokenize_with_filters() {
331 let tokenizer = ipadic_tokenizer(true);
332 let mut worker = tokenizer.new_worker();
333
334 let texts = [
338 "リンデラは形態素解析器です。",
339 "TOKYO と リンデラ",
340 "すもももももももものうち",
341 ];
342 for _ in 0..3 {
343 for text in texts {
344 let actual: Vec<(String, usize, usize)> = {
345 let tokens = match worker.tokenize(text) {
346 Ok(tokens) => tokens,
347 Err(err) => panic!("worker.tokenize failed: {err}"),
348 };
349 tokens
350 .iter()
351 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
352 .collect()
353 };
354 assert_same_tokens(&tokenizer, &actual, text);
355 }
356 }
357 }
358
359 #[test]
360 fn test_worker_matches_tokenize_nbest() {
361 let tokenizer = ipadic_tokenizer(true);
362 let mut worker = tokenizer.new_worker();
363 let text = "リンデラとすもももももももものうち";
364
365 let expected = match tokenizer.tokenize_nbest(text, 3, false, None) {
366 Ok(results) => results,
367 Err(err) => panic!("tokenize_nbest failed: {err}"),
368 };
369 for _ in 0..2 {
370 let actual = match worker.tokenize_nbest(text, 3, false, None) {
371 Ok(results) => results,
372 Err(err) => panic!("worker.tokenize_nbest failed: {err}"),
373 };
374 assert_eq!(expected.len(), actual.len());
375 for ((e_tokens, e_cost), (a_tokens, a_cost)) in expected.iter().zip(actual.iter()) {
376 assert_eq!(e_cost, a_cost);
377 let e: Vec<(String, usize, usize)> = e_tokens
378 .iter()
379 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
380 .collect();
381 let a: Vec<(String, usize, usize)> = a_tokens
382 .iter()
383 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
384 .collect();
385 assert_eq!(e, a);
386 }
387 }
388 }
389
390 #[test]
391 fn test_worker_reset_and_shrink_keep_output() {
392 let tokenizer = ipadic_tokenizer(true);
393 let mut worker = tokenizer.new_worker();
394 let text = "リンデラは形態素解析器です。";
395
396 let before: Vec<String> = {
397 let tokens = match worker.tokenize(text) {
398 Ok(tokens) => tokens,
399 Err(err) => panic!("worker.tokenize failed: {err}"),
400 };
401 tokens.iter().map(|t| t.surface.to_string()).collect()
402 };
403
404 worker.reset();
405 let after_reset: Vec<String> = {
406 let tokens = match worker.tokenize(text) {
407 Ok(tokens) => tokens,
408 Err(err) => panic!("worker.tokenize failed: {err}"),
409 };
410 tokens.iter().map(|t| t.surface.to_string()).collect()
411 };
412 assert_eq!(before, after_reset, "output changed after reset()");
413
414 worker.shrink_to(0);
415 let after_shrink: Vec<String> = {
416 let tokens = match worker.tokenize(text) {
417 Ok(tokens) => tokens,
418 Err(err) => panic!("worker.tokenize failed: {err}"),
419 };
420 tokens.iter().map(|t| t.surface.to_string()).collect()
421 };
422 assert_eq!(before, after_shrink, "output changed after shrink_to()");
423 }
424 }
425}