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 set_max_grouping_len(&mut self, max_grouping_len: Option<usize>) {
228 self.segment_worker.set_max_grouping_len(max_grouping_len);
229 }
230
231 pub fn set_unknown_word_ladder(&mut self, unknown_word_ladder: bool) {
238 self.segment_worker
239 .set_unknown_word_ladder(unknown_word_ladder);
240 }
241
242 pub fn shrink_to(&mut self, text_len_hint: usize) {
249 self.segment_worker.shrink_to(text_len_hint);
250 self.text_buf.shrink_to(text_len_hint);
251 self.offset_mappings.shrink_to_fit();
252 }
253
254 pub fn reset(&mut self) {
261 self.segment_worker.reset();
262 self.text_buf = String::new();
263 self.offset_mappings = Vec::new();
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 #[cfg(feature = "embed-ipadic")]
270 mod with_ipadic {
271 use std::collections::HashMap;
272
273 use lindera::dictionary::load_dictionary;
274 use lindera::mode::Mode;
275 use lindera::segmenter::Segmenter;
276
277 use crate::character_filter::BoxCharacterFilter;
278 use crate::character_filter::mapping::MappingCharacterFilter;
279 use crate::token_filter::BoxTokenFilter;
280 use crate::token_filter::lowercase::LowercaseTokenFilter;
281 use crate::tokenizer::Tokenizer;
282
283 fn ipadic_tokenizer(with_filters: bool) -> Tokenizer {
284 let dictionary = match load_dictionary("embedded://ipadic") {
285 Ok(dictionary) => dictionary,
286 Err(err) => panic!("failed to load embedded IPADIC: {err}"),
287 };
288 let segmenter = Segmenter::new(Mode::Normal, dictionary, None);
289 let mut tokenizer = Tokenizer::new(segmenter);
290 if with_filters {
291 let mut mapping = HashMap::new();
292 mapping.insert("リンデラ".to_string(), "Lindera".to_string());
293 let filter = match MappingCharacterFilter::new(mapping) {
294 Ok(filter) => filter,
295 Err(err) => panic!("failed to build mapping filter: {err}"),
296 };
297 tokenizer
298 .character_filters
299 .push(BoxCharacterFilter::from(filter));
300 tokenizer
301 .token_filters
302 .push(BoxTokenFilter::from(LowercaseTokenFilter::new()));
303 }
304 tokenizer
305 }
306
307 fn assert_same_tokens(
308 tokenizer: &Tokenizer,
309 worker_tokens: &[(String, usize, usize)],
310 text: &str,
311 ) {
312 let expected = match tokenizer.tokenize(text) {
313 Ok(tokens) => tokens,
314 Err(err) => panic!("tokenize failed: {err}"),
315 };
316 let expected: Vec<(String, usize, usize)> = expected
317 .iter()
318 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
319 .collect();
320 assert_eq!(expected, worker_tokens, "mismatch for {text:?}");
321 }
322
323 #[test]
324 fn test_worker_matches_tokenize_without_filters() {
325 let tokenizer = ipadic_tokenizer(false);
326 let mut worker = tokenizer.new_worker();
327
328 let texts = [
329 "すもももももももものうち",
330 "関西国際空港限定トートバッグ",
331 "",
332 ];
333 for _ in 0..3 {
334 for text in texts {
335 let actual: Vec<(String, usize, usize)> = {
336 let tokens = match worker.tokenize(text) {
337 Ok(tokens) => tokens,
338 Err(err) => panic!("worker.tokenize failed: {err}"),
339 };
340 tokens
341 .iter()
342 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
343 .collect()
344 };
345 assert_same_tokens(&tokenizer, &actual, text);
346 }
347 }
348 }
349
350 #[test]
351 fn test_worker_matches_tokenize_with_filters() {
352 let tokenizer = ipadic_tokenizer(true);
353 let mut worker = tokenizer.new_worker();
354
355 let texts = [
359 "リンデラは形態素解析器です。",
360 "TOKYO と リンデラ",
361 "すもももももももものうち",
362 ];
363 for _ in 0..3 {
364 for text in texts {
365 let actual: Vec<(String, usize, usize)> = {
366 let tokens = match worker.tokenize(text) {
367 Ok(tokens) => tokens,
368 Err(err) => panic!("worker.tokenize failed: {err}"),
369 };
370 tokens
371 .iter()
372 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
373 .collect()
374 };
375 assert_same_tokens(&tokenizer, &actual, text);
376 }
377 }
378 }
379
380 #[test]
381 fn test_worker_matches_tokenize_nbest() {
382 let tokenizer = ipadic_tokenizer(true);
383 let mut worker = tokenizer.new_worker();
384 let text = "リンデラとすもももももももものうち";
385
386 let expected = match tokenizer.tokenize_nbest(text, 3, false, None) {
387 Ok(results) => results,
388 Err(err) => panic!("tokenize_nbest failed: {err}"),
389 };
390 for _ in 0..2 {
391 let actual = match worker.tokenize_nbest(text, 3, false, None) {
392 Ok(results) => results,
393 Err(err) => panic!("worker.tokenize_nbest failed: {err}"),
394 };
395 assert_eq!(expected.len(), actual.len());
396 for ((e_tokens, e_cost), (a_tokens, a_cost)) in expected.iter().zip(actual.iter()) {
397 assert_eq!(e_cost, a_cost);
398 let e: Vec<(String, usize, usize)> = e_tokens
399 .iter()
400 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
401 .collect();
402 let a: Vec<(String, usize, usize)> = a_tokens
403 .iter()
404 .map(|t| (t.surface.to_string(), t.byte_start, t.byte_end))
405 .collect();
406 assert_eq!(e, a);
407 }
408 }
409 }
410
411 #[test]
412 fn test_worker_reset_and_shrink_keep_output() {
413 let tokenizer = ipadic_tokenizer(true);
414 let mut worker = tokenizer.new_worker();
415 let text = "リンデラは形態素解析器です。";
416
417 let before: Vec<String> = {
418 let tokens = match worker.tokenize(text) {
419 Ok(tokens) => tokens,
420 Err(err) => panic!("worker.tokenize failed: {err}"),
421 };
422 tokens.iter().map(|t| t.surface.to_string()).collect()
423 };
424
425 worker.reset();
426 let after_reset: Vec<String> = {
427 let tokens = match worker.tokenize(text) {
428 Ok(tokens) => tokens,
429 Err(err) => panic!("worker.tokenize failed: {err}"),
430 };
431 tokens.iter().map(|t| t.surface.to_string()).collect()
432 };
433 assert_eq!(before, after_reset, "output changed after reset()");
434
435 worker.shrink_to(0);
436 let after_shrink: Vec<String> = {
437 let tokens = match worker.tokenize(text) {
438 Ok(tokens) => tokens,
439 Err(err) => panic!("worker.tokenize failed: {err}"),
440 };
441 tokens.iter().map(|t| t.surface.to_string()).collect()
442 };
443 assert_eq!(before, after_shrink, "output changed after shrink_to()");
444 }
445 }
446}