1use std::borrow::Cow;
2use std::env;
3use std::fs::File;
4use std::io::Read;
5use std::path::Path;
6
7use serde_json::{Value, json};
8
9use crate::character_filter::{BoxCharacterFilter, CharacterFilterLoader, OffsetMapping};
10use crate::token_filter::{BoxTokenFilter, TokenFilterLoader};
11use lindera::LinderaResult;
12use lindera::dictionary::Lattice;
13use lindera::error::LinderaErrorKind;
14use lindera::mode::Mode;
15use lindera::segmenter::Segmenter;
16use lindera::token::Token;
17
18pub type TokenizerConfig = Value;
19
20fn yaml_to_config(file_path: &Path) -> LinderaResult<TokenizerConfig> {
21 let mut input_read = File::open(file_path).map_err(|err| {
22 LinderaErrorKind::Io.with_error(err).add_context(format!(
23 "Failed to open tokenizer config file: {}",
24 file_path.display()
25 ))
26 })?;
27
28 let mut buffer = Vec::new();
29 input_read.read_to_end(&mut buffer).map_err(|err| {
30 LinderaErrorKind::Io.with_error(err).add_context(format!(
31 "Failed to read tokenizer config file: {}",
32 file_path.display()
33 ))
34 })?;
35
36 match serde_yaml_ng::from_slice::<serde_yaml_ng::Value>(&buffer) {
37 Ok(value) => {
38 match value {
40 serde_yaml_ng::Value::Mapping(_) => {
41 Ok(serde_json::to_value(value).map_err(|err| {
42 LinderaErrorKind::Deserialize
43 .with_error(err)
44 .add_context(format!(
45 "Failed to convert YAML to JSON for config file: {}",
46 file_path.display()
47 ))
48 })?)
49 }
50 _ => Err(LinderaErrorKind::Deserialize
51 .with_error(anyhow::anyhow!("Invalid YAML"))
52 .add_context(format!(
53 "Config file must contain a YAML mapping: {}",
54 file_path.display()
55 ))),
56 }
57 }
58 Err(err) => Err(LinderaErrorKind::Deserialize
59 .with_error(err)
60 .add_context(format!(
61 "Failed to parse YAML config file: {}",
62 file_path.display()
63 ))),
64 }
65}
66
67fn empty_config() -> Value {
69 json!({
70 "segmenter": {},
71 "character_filters": [],
72 "token_filters": []
73 })
74}
75
76fn ensure_keys(mut config: Value) -> Value {
78 if config.get("segmenter").is_none() {
79 config["segmenter"] = json!({});
80 }
81
82 if config.get("character_filters").is_none() {
83 config["character_filters"] = json!([]);
84 }
85
86 if config.get("token_filters").is_none() {
87 config["token_filters"] = json!([]);
88 }
89
90 config
91}
92
93#[derive(Debug)]
94pub struct TokenizerBuilder {
95 config: TokenizerConfig,
96}
97
98impl TokenizerBuilder {
99 pub fn new() -> LinderaResult<Self> {
100 if let Ok(config_path) = env::var("LINDERA_CONFIG_PATH") {
101 Self::from_file(Path::new(&config_path))
102 } else {
103 Ok(Self {
104 config: empty_config(),
105 })
106 }
107 }
108
109 pub fn from_file(file_path: &Path) -> LinderaResult<Self> {
110 let config = yaml_to_config(file_path)?;
111
112 Ok(TokenizerBuilder {
113 config: ensure_keys(config),
114 })
115 }
116
117 pub fn from_config(config: TokenizerConfig) -> LinderaResult<Self> {
118 Ok(TokenizerBuilder {
119 config: ensure_keys(config),
120 })
121 }
122
123 pub fn set_segmenter_mode(&mut self, mode: &Mode) -> &mut Self {
124 self.config["segmenter"]["mode"] = json!(mode.as_str());
125 self
126 }
127
128 pub fn set_segmenter_dictionary(&mut self, uri: &str) -> &mut Self {
129 self.config["segmenter"]["dictionary"] = json!(uri);
130 self
131 }
132
133 pub fn set_segmenter_user_dictionary(&mut self, uri: &str) -> &mut Self {
134 self.config["segmenter"]["user_dictionary"] = json!(uri);
135 self
136 }
137
138 pub fn set_segmenter_keep_whitespace(&mut self, keep_whitespace: bool) -> &mut Self {
139 self.config["segmenter"]["keep_whitespace"] = json!(keep_whitespace);
140 self
141 }
142
143 pub fn set_segmenter_use_mmap(&mut self, use_mmap: bool) -> &mut Self {
154 self.config["segmenter"]["use_mmap"] = json!(use_mmap);
155 self
156 }
157
158 pub fn append_character_filter(&mut self, kind: &str, args: &Value) -> &mut Self {
159 if let Some(array) = self.config["character_filters"].as_array_mut() {
160 array.push(json!({ "kind": kind, "args": args }));
161 }
162 self
163 }
164
165 pub fn append_token_filter(&mut self, kind: &str, args: &Value) -> &mut Self {
166 if let Some(array) = self.config["token_filters"].as_array_mut() {
167 array.push(json!({ "kind": kind, "args": args }));
168 }
169 self
170 }
171
172 pub fn build(&self) -> LinderaResult<Tokenizer> {
173 Tokenizer::from_config(&self.config).map_err(|err| {
174 LinderaErrorKind::Parse.with_error(anyhow::anyhow!("failed to build tokenizer: {err}"))
175 })
176 }
177}
178
179pub struct Tokenizer {
180 pub segmenter: Segmenter,
185
186 pub character_filters: Vec<BoxCharacterFilter>,
192
193 pub token_filters: Vec<BoxTokenFilter>,
198}
199
200impl Tokenizer {
201 pub fn new(segmenter: Segmenter) -> Self {
217 Self {
218 segmenter,
219 character_filters: Vec::new(),
220 token_filters: Vec::new(),
221 }
222 }
223
224 pub fn from_config(config: &TokenizerConfig) -> LinderaResult<Self> {
225 let segmenter_config = config.get("segmenter").ok_or_else(|| {
226 LinderaErrorKind::Deserialize.with_error(anyhow::anyhow!("missing segmenter config."))
227 })?;
228 let segmenter = Segmenter::from_config(segmenter_config)?;
229
230 let mut tokenizer = Tokenizer::new(segmenter);
232
233 if let Some(character_filter_settings) = config["character_filters"].as_array() {
235 for character_filter_setting in character_filter_settings {
236 let character_filter_name = character_filter_setting["kind"].as_str();
237 if let Some(character_filter_name) = character_filter_name {
238 tokenizer.append_character_filter(CharacterFilterLoader::load_from_value(
240 character_filter_name,
241 &character_filter_setting["args"],
242 )?);
243 }
244 }
245 }
246
247 if let Some(token_filter_settings) = config["token_filters"].as_array() {
249 for token_filter_setting in token_filter_settings {
250 let token_filter_name = token_filter_setting["kind"].as_str();
251 if let Some(token_filter_name) = token_filter_name {
252 tokenizer.append_token_filter(TokenFilterLoader::load_from_value(
254 token_filter_name,
255 &token_filter_setting["args"],
256 )?);
257 }
258 }
259 }
260
261 Ok(tokenizer)
262 }
263
264 pub fn append_character_filter(&mut self, character_filter: BoxCharacterFilter) -> &mut Self {
279 self.character_filters.push(character_filter);
280
281 self
282 }
283
284 pub fn append_token_filter(&mut self, token_filter: BoxTokenFilter) -> &mut Self {
299 self.token_filters.push(token_filter);
300
301 self
302 }
303
304 pub fn tokenize<'a>(&'a self, text: &'a str) -> LinderaResult<Vec<Token<'a>>> {
337 let mut lattice = Lattice::default();
338 self.tokenize_with_lattice(text, &mut lattice)
339 }
340
341 pub fn tokenize_with_lattice<'a>(
375 &'a self,
376 text: &'a str,
377 lattice: &mut Lattice,
378 ) -> LinderaResult<Vec<Token<'a>>> {
379 let mut normalized_text: Cow<'a, str> = Cow::Borrowed(text);
380
381 let mut offset_mappings: Vec<OffsetMapping> =
382 Vec::with_capacity(self.character_filters.len());
383
384 if !self.character_filters.is_empty() {
387 let text_mut = normalized_text.to_mut();
389
390 for character_filter in &self.character_filters {
391 let mapping = character_filter.apply(text_mut)?;
392
393 if !mapping.is_empty() {
394 offset_mappings.push(mapping);
397 }
398 }
399 }
400
401 let final_text_len = normalized_text.len();
403
404 let mut tokens = self
407 .segmenter
408 .segment_with_lattice(normalized_text, lattice)?;
409
410 for token_filter in &self.token_filters {
412 token_filter.apply(&mut tokens)?;
413 }
414
415 correct_offsets(&mut tokens, &offset_mappings, final_text_len);
417
418 Ok(tokens)
419 }
420
421 pub fn tokenize_nbest<'a>(
426 &'a self,
427 text: &'a str,
428 n: usize,
429 unique: bool,
430 cost_threshold: Option<i64>,
431 ) -> LinderaResult<Vec<(Vec<Token<'a>>, i64)>> {
432 let mut lattice = Lattice::default();
433 self.tokenize_nbest_with_lattice(text, &mut lattice, n, unique, cost_threshold)
434 }
435
436 pub fn tokenize_nbest_with_lattice<'a>(
442 &'a self,
443 text: &'a str,
444 lattice: &mut Lattice,
445 n: usize,
446 unique: bool,
447 cost_threshold: Option<i64>,
448 ) -> LinderaResult<Vec<(Vec<Token<'a>>, i64)>> {
449 let mut normalized_text: Cow<'a, str> = Cow::Borrowed(text);
450
451 let mut offset_mappings: Vec<OffsetMapping> =
452 Vec::with_capacity(self.character_filters.len());
453
454 if !self.character_filters.is_empty() {
455 let text_mut = normalized_text.to_mut();
456 for character_filter in &self.character_filters {
457 let mapping = character_filter.apply(text_mut)?;
458 if !mapping.is_empty() {
459 offset_mappings.push(mapping);
460 }
461 }
462 }
463
464 let final_text_len = normalized_text.len();
465
466 let mut all_results = self.segmenter.segment_nbest_with_lattice(
467 normalized_text,
468 lattice,
469 n,
470 unique,
471 cost_threshold,
472 )?;
473
474 for (tokens, _cost) in &mut all_results {
476 for token_filter in &self.token_filters {
477 token_filter.apply(tokens)?;
478 }
479
480 correct_offsets(tokens, &offset_mappings, final_text_len);
481 }
482
483 Ok(all_results)
484 }
485}
486
487pub(crate) fn correct_offsets(
502 tokens: &mut [Token<'_>],
503 offset_mappings: &[OffsetMapping],
504 final_text_len: usize,
505) {
506 if offset_mappings.is_empty() {
507 return;
508 }
509 for token in tokens.iter_mut() {
510 for mapping in offset_mappings.iter().rev() {
512 token.byte_start = mapping.correct_offset(token.byte_start, final_text_len);
513 token.byte_end = mapping.correct_offset(token.byte_end, final_text_len);
514 }
515 }
516}
517
518impl Clone for Tokenizer {
519 fn clone(&self) -> Self {
536 let character_filters: Vec<BoxCharacterFilter> = self
537 .character_filters
538 .iter()
539 .map(|filter| filter.box_clone())
540 .collect();
541
542 let token_filters: Vec<BoxTokenFilter> = self
543 .token_filters
544 .iter()
545 .map(|filter| filter.box_clone())
546 .collect();
547
548 Tokenizer {
549 character_filters,
550 segmenter: self.segmenter.clone(),
551 token_filters,
552 }
553 }
554}
555
556#[cfg(test)]
557mod tests {
558 use super::TokenizerBuilder;
559
560 #[test]
561 fn test_set_segmenter_use_mmap_writes_flat_key_under_segmenter() {
562 let mut builder = TokenizerBuilder::new().unwrap();
563 builder.set_segmenter_use_mmap(true);
564
565 assert_eq!(builder.config["segmenter"]["use_mmap"], true);
566 }
567
568 #[cfg(feature = "embed-ipadic")]
569 #[test]
570 fn test_tokenizer_config_from_slice() {
571 use std::path::PathBuf;
572
573 use crate::tokenizer::yaml_to_config;
574
575 let config_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
576 .join("../resources")
577 .join("config")
578 .join("lindera.yml");
579
580 let result = yaml_to_config(&config_file);
581
582 assert!(result.is_ok());
583 }
584
585 #[test]
586 #[cfg(feature = "embed-ipadic")]
587 fn test_tokenizer_config_clone() {
588 use std::path::PathBuf;
589
590 use crate::tokenizer::yaml_to_config;
591
592 let config_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
593 .join("../resources")
594 .join("config")
595 .join("lindera.yml");
596
597 let tokenizer_config = yaml_to_config(&config_file).unwrap();
598
599 let cloned_tokenizer_config = tokenizer_config.clone();
600
601 assert_eq!(tokenizer_config, cloned_tokenizer_config);
602 }
603
604 #[test]
605 #[cfg(feature = "embed-ipadic")]
606 fn test_tokenize_ipadic() {
607 use std::borrow::Cow;
608 use std::path::PathBuf;
609
610 use crate::tokenizer::TokenizerBuilder;
611
612 let config_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
613 .join("../resources")
614 .join("config")
615 .join("lindera.yml");
616
617 let builder = TokenizerBuilder::from_file(&config_file).unwrap();
618
619 let tokenizer = builder.build().unwrap();
620
621 {
622 let text = "リンデラは形態素解析エンジンです。";
623 let mut tokens = tokenizer.tokenize(text).unwrap();
624 let mut tokens_iter = tokens.iter_mut();
625 {
626 let token = tokens_iter.next().unwrap();
627 assert_eq!(token.surface, Cow::Borrowed("Lindera"));
628 assert_eq!(token.byte_start, 0);
629 assert_eq!(token.byte_end, 15);
630 assert_eq!(token.position, 0);
631 assert_eq!(token.position_length, 1);
632 assert!(token.word_id.is_unknown());
633 assert_eq!(
634 token.details,
635 Some(vec![
636 Cow::Borrowed("名詞"),
637 Cow::Borrowed("固有名詞"),
638 Cow::Borrowed("組織"),
639 Cow::Borrowed("*"),
640 Cow::Borrowed("*"),
641 Cow::Borrowed("*"),
642 Cow::Borrowed("*"),
643 Cow::Borrowed("*"),
644 Cow::Borrowed("*"),
645 ])
646 );
647 }
648 {
649 let token = tokens_iter.next().unwrap();
650 assert_eq!(token.surface, Cow::Borrowed("形態素"));
651 assert_eq!(token.byte_start, 18);
652 assert_eq!(token.byte_end, 27);
653 assert_eq!(token.position, 2);
654 assert_eq!(token.position_length, 1);
655 assert_eq!(
656 token.details,
657 Some(vec![
658 Cow::Borrowed("名詞"),
659 Cow::Borrowed("一般"),
660 Cow::Borrowed("*"),
661 Cow::Borrowed("*"),
662 Cow::Borrowed("*"),
663 Cow::Borrowed("*"),
664 Cow::Borrowed("形態素"),
665 Cow::Borrowed("ケイタイソ"),
666 Cow::Borrowed("ケイタイソ"),
667 ])
668 );
669 }
670 {
671 let token = tokens_iter.next().unwrap();
672 assert_eq!(token.surface, Cow::Borrowed("解析"));
673 assert_eq!(token.byte_start, 27);
674 assert_eq!(token.byte_end, 33);
675 assert_eq!(token.position, 3);
676 assert_eq!(token.position_length, 1);
677 assert_eq!(
678 token.details,
679 Some(vec![
680 Cow::Borrowed("名詞"),
681 Cow::Borrowed("サ変接続"),
682 Cow::Borrowed("*"),
683 Cow::Borrowed("*"),
684 Cow::Borrowed("*"),
685 Cow::Borrowed("*"),
686 Cow::Borrowed("解析"),
687 Cow::Borrowed("カイセキ"),
688 Cow::Borrowed("カイセキ"),
689 ])
690 );
691 }
692 {
693 let token = tokens_iter.next().unwrap();
694 assert_eq!(token.surface, Cow::Borrowed("エンジン"));
695 assert_eq!(token.byte_start, 33);
696 assert_eq!(token.byte_end, 48);
697 assert_eq!(token.position, 4);
698 assert_eq!(token.position_length, 1);
699 assert_eq!(
700 token.details,
701 Some(vec![
702 Cow::Borrowed("名詞"),
703 Cow::Borrowed("一般"),
704 Cow::Borrowed("*"),
705 Cow::Borrowed("*"),
706 Cow::Borrowed("*"),
707 Cow::Borrowed("*"),
708 Cow::Borrowed("エンジン"),
709 Cow::Borrowed("エンジン"),
710 Cow::Borrowed("エンジン"),
711 ])
712 );
713 }
714
715 let mut tokens_iter = tokens.iter();
716 {
717 let token = tokens_iter.next().unwrap();
718 let start = token.byte_start;
719 let end = token.byte_end;
720 assert_eq!(token.surface, Cow::Borrowed("Lindera"));
721 assert_eq!(&text[start..end], "リンデラ");
722 }
723 }
724
725 {
726 let text = "10㌎のガソリン";
727 let mut tokens = tokenizer.tokenize(text).unwrap();
728 let mut tokens_iter = tokens.iter_mut();
729 {
730 let token = tokens_iter.next().unwrap();
733 assert_eq!(token.surface, Cow::Owned::<str>("10ガロン".into()));
734 assert_eq!(token.byte_start, 0);
735 assert_eq!(token.byte_end, 9);
736 assert_eq!(token.position, 0);
737 assert_eq!(token.position_length, 2);
738 }
739 {
740 let token = tokens_iter.next().unwrap();
741 assert_eq!(token.surface, Cow::Borrowed("ガソリン"));
742 assert_eq!(token.byte_start, 12);
743 assert_eq!(token.byte_end, 27);
744 assert_eq!(token.position, 3);
745 assert_eq!(token.position_length, 1);
746 assert_eq!(
747 token.details,
748 Some(vec![
749 Cow::Borrowed("名詞"),
750 Cow::Borrowed("一般"),
751 Cow::Borrowed("*"),
752 Cow::Borrowed("*"),
753 Cow::Borrowed("*"),
754 Cow::Borrowed("*"),
755 Cow::Borrowed("ガソリン"),
756 Cow::Borrowed("ガソリン"),
757 Cow::Borrowed("ガソリン"),
758 ])
759 );
760 }
761
762 let mut tokens_iter = tokens.iter();
763 {
764 let token = tokens_iter.next().unwrap();
766 let start = token.byte_start;
767 let end = token.byte_end;
768 assert_eq!(token.surface, Cow::Owned::<str>("10ガロン".into()));
769 assert_eq!(&text[start..end], "10㌎");
770 }
771 {
772 let token = tokens_iter.next().unwrap();
773 let start = token.byte_start;
774 let end = token.byte_end;
775 assert_eq!(token.surface, Cow::Borrowed("ガソリン"));
776 assert_eq!(&text[start..end], "ガソリン");
777 }
778 }
779
780 {
781 let text = "お釣りは百三十四円です。";
782 let mut tokens = tokenizer.tokenize(text).unwrap();
783 let mut tokens_iter = tokens.iter_mut();
784 {
785 let token = tokens_iter.next().unwrap();
786 assert_eq!(token.surface, Cow::Borrowed("お釣り"));
787 assert_eq!(token.byte_start, 0);
788 assert_eq!(token.byte_end, 9);
789 assert_eq!(token.position, 0);
790 assert_eq!(token.position_length, 1);
791 assert_eq!(
792 token.details,
793 Some(vec![
794 Cow::Borrowed("名詞"),
795 Cow::Borrowed("一般"),
796 Cow::Borrowed("*"),
797 Cow::Borrowed("*"),
798 Cow::Borrowed("*"),
799 Cow::Borrowed("*"),
800 Cow::Borrowed("お釣り"),
801 Cow::Borrowed("オツリ"),
802 Cow::Borrowed("オツリ"),
803 ])
804 );
805 }
806 {
807 let token = tokens_iter.next().unwrap();
808 assert_eq!(token.surface, Cow::Borrowed("134円"));
809 assert_eq!(token.byte_start, 12);
810 assert_eq!(token.byte_end, 27);
811 assert_eq!(token.position, 2);
812 assert_eq!(token.position_length, 5);
813 assert_eq!(
814 token.details,
815 Some(vec![
816 Cow::Borrowed("名詞"),
817 Cow::Borrowed("数"),
818 Cow::Borrowed("*"),
819 Cow::Borrowed("*"),
820 Cow::Borrowed("*"),
821 Cow::Borrowed("*"),
822 Cow::Borrowed("*"),
823 Cow::Borrowed("*"),
824 Cow::Borrowed("*"),
825 ])
826 );
827 }
828 }
829
830 {
831 let text = "ここは騒々しい";
832 let mut tokens = tokenizer.tokenize(text).unwrap();
833 let mut tokens_iter = tokens.iter_mut();
834 {
835 let token = tokens_iter.next().unwrap();
836 assert_eq!(token.surface, Cow::Borrowed("ここ"));
837 assert_eq!(token.byte_start, 0);
838 assert_eq!(token.byte_end, 6);
839 assert_eq!(token.position, 0);
840 assert_eq!(token.position_length, 1);
841 assert_eq!(
842 token.details,
843 Some(vec![
844 Cow::Borrowed("名詞"),
845 Cow::Borrowed("代名詞"),
846 Cow::Borrowed("一般"),
847 Cow::Borrowed("*"),
848 Cow::Borrowed("*"),
849 Cow::Borrowed("*"),
850 Cow::Borrowed("ここ"),
851 Cow::Borrowed("ココ"),
852 Cow::Borrowed("ココ"),
853 ])
854 );
855 }
856 {
857 let token = tokens_iter.next().unwrap();
858 assert_eq!(token.surface, Cow::Borrowed("騒騒しい"));
859 assert_eq!(token.byte_start, 9);
860 assert_eq!(token.byte_end, 21);
861 assert_eq!(token.position, 2);
862 assert_eq!(token.position_length, 1);
863 assert_eq!(
864 token.details,
865 Some(vec![
866 Cow::Borrowed("形容詞"),
867 Cow::Borrowed("自立"),
868 Cow::Borrowed("*"),
869 Cow::Borrowed("*"),
870 Cow::Borrowed("形容詞・イ段"),
871 Cow::Borrowed("基本形"),
872 Cow::Borrowed("騒騒しい"),
873 Cow::Borrowed("ソウゾウシイ"),
874 Cow::Borrowed("ソーゾーシイ"),
875 ])
876 );
877 }
878 }
879 }
880
881 #[test]
882 #[cfg(not(windows))]
883 #[should_panic(expected = "No such file or directory")]
884 fn test_create_tokenizer_builder_from_non_existent_file() {
885 use std::path::PathBuf;
886
887 use crate::tokenizer::TokenizerBuilder;
888
889 let config_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
890 .join("../resources")
891 .join("config")
892 .join("non_existent_file.yml");
893
894 TokenizerBuilder::from_file(&config_file).unwrap();
895 }
896
897 #[test]
898 #[cfg(windows)]
899 #[should_panic(expected = "The system cannot find the file specified.")]
900 fn test_create_tokenizer_builder_from_non_existent_file() {
901 use std::path::PathBuf;
902
903 use crate::tokenizer::TokenizerBuilder;
904
905 let config_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
906 .join("../resources")
907 .join("config")
908 .join("non_existent_file.yml");
909
910 TokenizerBuilder::from_file(&config_file).unwrap();
911 }
912
913 #[test]
914 #[should_panic(expected = "Invalid YAML")]
915 fn test_create_tokenizer_builder_from_invalid_file() {
916 use std::path::PathBuf;
917
918 use crate::tokenizer::TokenizerBuilder;
919
920 let config_file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
921 .join("../resources")
922 .join("config")
923 .join("invalid.yml");
924
925 TokenizerBuilder::from_file(&config_file).unwrap();
926 }
927
928 #[test]
929 #[cfg(feature = "embed-ipadic")]
930 fn test_tokenize_nbest_1best_matches_tokenize() {
931 use crate::tokenizer::TokenizerBuilder;
932
933 let mut builder = TokenizerBuilder::new().unwrap();
934 builder.set_segmenter_dictionary("embedded://ipadic");
935
936 let tokenizer = builder.build().unwrap();
937
938 let text = "すもももももももものうち";
939 let normal_tokens = tokenizer.tokenize(text).unwrap();
940 let nbest_results = tokenizer.tokenize_nbest(text, 1, false, None).unwrap();
941
942 assert_eq!(nbest_results.len(), 1);
943 let (nbest_tokens, _cost) = &nbest_results[0];
944 assert_eq!(normal_tokens.len(), nbest_tokens.len());
945 for (normal, nbest) in normal_tokens.iter().zip(nbest_tokens.iter()) {
946 assert_eq!(normal.surface.as_ref(), nbest.surface.as_ref());
947 }
948 }
949
950 #[test]
951 #[cfg(feature = "embed-ipadic")]
952 fn test_tokenize_nbest_multiple_results() {
953 use crate::tokenizer::TokenizerBuilder;
954
955 let mut builder = TokenizerBuilder::new().unwrap();
956 builder.set_segmenter_dictionary("embedded://ipadic");
957
958 let tokenizer = builder.build().unwrap();
959
960 let text = "すもももももももものうち";
961 let results = tokenizer.tokenize_nbest(text, 5, false, None).unwrap();
962
963 assert!(results.len() >= 2);
965
966 for (tokens, _cost) in &results {
968 assert!(!tokens.is_empty());
969 }
970 }
971}