1use std::io;
2
3use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
4use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
5use serde::{Deserialize, Serialize};
6
7use crate::dictionary::character_definition::{CategoryId, CharacterDefinition};
8use crate::dictionary::connection_cost_matrix::ConnectionCostMatrix;
9use crate::dictionary::prefix_dictionary::PrefixDictionary;
10use crate::dictionary::unknown_dictionary::UnknownDictionary;
11use crate::mode::Mode;
12
13#[derive(
15 Clone,
16 Copy,
17 Debug,
18 Eq,
19 PartialEq,
20 Serialize,
21 Deserialize,
22 Default,
23 Archive,
24 RkyvSerialize,
25 RkyvDeserialize,
26)]
27
28pub enum LexType {
29 #[default]
31 System,
32 User,
34 Unknown,
36}
37
38#[derive(
39 Clone,
40 Copy,
41 Debug,
42 Eq,
43 PartialEq,
44 Serialize,
45 Deserialize,
46 Archive,
47 RkyvDeserialize,
48 RkyvSerialize,
49)]
50
51pub struct WordId {
52 id: u32,
54 is_system: bool,
56 lex_type: LexType,
58}
59
60impl WordId {
61 pub fn new(lex_type: LexType, id: u32) -> Self {
63 WordId {
64 id,
65 is_system: matches!(lex_type, LexType::System),
66 lex_type,
67 }
68 }
69
70 #[inline]
76 pub fn id(&self) -> u32 {
77 self.id
78 }
79
80 #[inline]
82 pub fn is_unknown(&self) -> bool {
83 matches!(self.lex_type, LexType::Unknown)
84 }
85
86 #[inline]
88 pub fn is_system(&self) -> bool {
89 self.is_system
90 }
91
92 #[inline]
94 pub fn lex_type(&self) -> LexType {
95 self.lex_type
96 }
97}
98
99impl Default for WordId {
100 fn default() -> Self {
101 WordId {
102 id: u32::MAX,
103 is_system: true,
104 lex_type: LexType::System,
105 }
106 }
107}
108
109#[derive(
110 Default,
111 Clone,
112 Copy,
113 Debug,
114 Eq,
115 PartialEq,
116 Serialize,
117 Deserialize,
118 Archive,
119 RkyvSerialize,
120 RkyvDeserialize,
121)]
122
123pub struct WordEntry {
124 word_id: WordId,
126 word_cost: i16,
128 left_id: u16,
130 right_id: u16,
132}
133
134impl WordEntry {
135 pub(crate) const SERIALIZED_LEN: usize = 10;
137
138 #[inline]
147 pub fn new(word_id: WordId, word_cost: i16, left_id: u16, right_id: u16) -> Self {
148 WordEntry {
149 word_id,
150 word_cost,
151 left_id,
152 right_id,
153 }
154 }
155
156 #[inline]
158 pub fn word_id(&self) -> WordId {
159 self.word_id
160 }
161
162 #[inline]
164 pub fn word_cost(&self) -> i16 {
165 self.word_cost
166 }
167
168 #[inline]
170 pub fn left_id(&self) -> u32 {
171 self.left_id as u32
172 }
173
174 #[inline]
176 pub fn right_id(&self) -> u32 {
177 self.right_id as u32
178 }
179
180 pub(crate) fn serialize<W: io::Write>(&self, wtr: &mut W) -> io::Result<()> {
182 wtr.write_u32::<LittleEndian>(self.word_id.id)?;
183 wtr.write_i16::<LittleEndian>(self.word_cost)?;
184 wtr.write_u16::<LittleEndian>(self.left_id)?;
185 wtr.write_u16::<LittleEndian>(self.right_id)?;
186 Ok(())
187 }
188
189 pub(crate) fn deserialize(data: &[u8], is_system_entry: bool) -> WordEntry {
191 let word_id = WordId::new(
192 if is_system_entry {
193 LexType::System
194 } else {
195 LexType::User
196 },
197 LittleEndian::read_u32(&data[0..4]),
198 );
199 let word_cost = LittleEndian::read_i16(&data[4..6]);
200 let left_id = LittleEndian::read_u16(&data[6..8]);
201 let right_id = LittleEndian::read_u16(&data[8..10]);
202 WordEntry {
203 word_id,
204 word_cost,
205 left_id,
206 right_id,
207 }
208 }
209}
210
211#[derive(Default, Clone, Debug)]
212pub struct Edge {
213 word_entry: WordEntry,
215
216 path_cost: i32,
218 left_index: u16,
220
221 start_index: u32,
223 stop_index: u32,
225
226 kanji_only: bool,
228}
229
230impl Edge {
231 pub fn num_chars(&self) -> usize {
233 (self.stop_index - self.start_index) as usize / 3
234 }
235
236 #[inline]
238 pub(crate) fn word_entry(&self) -> &WordEntry {
239 &self.word_entry
240 }
241
242 #[inline]
244 pub(crate) fn path_cost(&self) -> i32 {
245 self.path_cost
246 }
247
248 #[inline]
250 pub(crate) fn left_index(&self) -> u16 {
251 self.left_index
252 }
253
254 #[inline]
256 pub(crate) fn start_index(&self) -> u32 {
257 self.start_index
258 }
259
260 #[inline]
262 pub(crate) fn stop_index(&self) -> u32 {
263 self.stop_index
264 }
265
266 #[inline]
268 pub(crate) fn kanji_only(&self) -> bool {
269 self.kanji_only
270 }
271}
272
273#[derive(Clone, Debug)]
277pub struct PathEntry {
278 edge_index: u16,
280 left_pos: u32,
282 left_index: u16,
284 cost: i32,
286}
287
288impl PathEntry {
289 #[inline]
291 pub(crate) fn edge_index(&self) -> u16 {
292 self.edge_index
293 }
294
295 #[inline]
297 pub(crate) fn left_pos(&self) -> u32 {
298 self.left_pos
299 }
300
301 #[inline]
303 pub(crate) fn left_index(&self) -> u16 {
304 self.left_index
305 }
306
307 #[inline]
309 pub(crate) fn cost(&self) -> i32 {
310 self.cost
311 }
312}
313
314#[derive(Clone, Default)]
315pub struct Lattice {
316 capacity: usize,
317 ends_at: Vec<Vec<Edge>>, char_info_buffer: Vec<CharData>,
319 categories_buffer: Vec<CategoryId>,
320 char_category_cache: Vec<Vec<CategoryId>>,
321
322 all_paths: Vec<Vec<PathEntry>>,
324 nbest_capacity: usize,
325 last_text_len: usize,
327
328 matches_head: Vec<usize>,
333 matches_store: Vec<(usize, WordEntry, usize)>,
335}
336
337#[derive(Clone, Copy, Debug, Default)]
338struct CharData {
339 byte_offset: u32,
340 is_kanji: bool,
341 categories_start: u32,
342 categories_len: u16,
343 kanji_run_byte_len: u32,
344}
345
346#[inline]
347pub fn is_kanji(c: char) -> bool {
348 let c = c as u32;
349 (0x4E00..=0x9FAF).contains(&c) || (0x3400..=0x4DBF).contains(&c)
351}
352
353impl Lattice {
354 #[inline]
356 fn create_edge(word_entry: WordEntry, start: usize, stop: usize, kanji_only: bool) -> Edge {
357 Edge {
358 word_entry,
359 left_index: u16::MAX,
360 start_index: start as u32,
361 stop_index: stop as u32,
362 path_cost: i32::MAX,
363 kanji_only,
364 }
365 }
366
367 pub fn clear(&mut self) {
368 for edge_vec in &mut self.ends_at {
369 edge_vec.clear();
370 }
371 for path_vec in &mut self.all_paths {
372 path_vec.clear();
373 }
374 self.char_info_buffer.clear();
375 self.categories_buffer.clear();
376 self.char_category_cache.clear();
384 }
385
386 #[inline]
387 fn is_kanji_all(&self, char_idx: usize, byte_len: usize) -> bool {
388 self.char_info_buffer[char_idx].kanji_run_byte_len >= byte_len as u32
389 }
390
391 #[inline]
392 fn get_cached_category(&self, char_idx: usize, category_ord: usize) -> CategoryId {
393 let char_data = &self.char_info_buffer[char_idx];
394 self.categories_buffer[char_data.categories_start as usize + category_ord]
395 }
396
397 fn set_capacity(&mut self, text_len: usize) {
398 self.clear();
399 self.last_text_len = text_len;
400 if self.capacity <= text_len {
401 self.capacity = text_len;
402 self.ends_at
409 .resize_with(text_len + 1, || Vec::with_capacity(16));
410 }
411 }
412
413 fn set_capacity_nbest(&mut self, text_len: usize) {
414 self.set_capacity(text_len);
415 if self.nbest_capacity <= text_len {
416 self.nbest_capacity = text_len;
417 self.all_paths.resize(text_len + 1, Vec::new());
418 }
419 }
420
421 #[inline(never)]
422 #[allow(clippy::too_many_arguments)]
426 pub fn set_text(
427 &mut self,
428 dict: &PrefixDictionary,
429 user_dict: &Option<&PrefixDictionary>,
430 char_definitions: &CharacterDefinition,
431 unknown_dictionary: &UnknownDictionary,
432 cost_matrix: &ConnectionCostMatrix,
433 text: &str,
434 search_mode: &Mode,
435 ) {
436 let len = text.len();
437 self.set_capacity(len);
438
439 self.char_info_buffer.clear();
441 self.categories_buffer.clear();
442
443 if self.char_category_cache.is_empty() {
444 self.char_category_cache.resize(256, Vec::new());
445 }
446
447 for (byte_offset, c) in text.char_indices() {
448 let categories_start = self.categories_buffer.len() as u32;
449
450 if (c as u32) < 256 {
451 let cached = &mut self.char_category_cache[c as usize];
452 if cached.is_empty() {
453 let cats = char_definitions.lookup_categories(c);
454 for &category in cats {
455 cached.push(category);
456 }
457 }
458 for &category in cached.iter() {
459 self.categories_buffer.push(category);
460 }
461 } else {
462 let categories = char_definitions.lookup_categories(c);
463 for &category in categories {
464 self.categories_buffer.push(category);
465 }
466 }
467
468 let categories_len = (self.categories_buffer.len() as u32 - categories_start) as u16;
469
470 self.char_info_buffer.push(CharData {
471 byte_offset: byte_offset as u32,
472 is_kanji: is_kanji(c),
473 categories_start,
474 categories_len,
475 kanji_run_byte_len: 0,
476 });
477 }
478 self.char_info_buffer.push(CharData {
480 byte_offset: len as u32,
481 is_kanji: false,
482 categories_start: 0,
483 categories_len: 0,
484 kanji_run_byte_len: 0,
485 });
486
487 for i in (0..self.char_info_buffer.len() - 1).rev() {
489 if self.char_info_buffer[i].is_kanji {
490 let next_byte_offset = self.char_info_buffer[i + 1].byte_offset;
491 let char_byte_len = next_byte_offset - self.char_info_buffer[i].byte_offset;
492 self.char_info_buffer[i].kanji_run_byte_len =
493 char_byte_len + self.char_info_buffer[i + 1].kanji_run_byte_len;
494 } else {
495 self.char_info_buffer[i].kanji_run_byte_len = 0;
496 }
497 }
498
499 let start_edge = Edge {
500 path_cost: 0,
501 left_index: u16::MAX,
502 ..Default::default()
503 };
504 self.ends_at[0].push(start_edge);
505
506 let mut unknown_word_end: Option<usize> = None;
508
509 self.matches_head.clear();
516 self.matches_head.resize(len + 1, usize::MAX);
517 self.matches_store.clear();
518
519 for m in dict.da.find_overlapping_iter(text) {
521 let start = m.start();
522 let (offset, count) = dict.decode_val(m.value());
523 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
524
525 if offset_bytes < dict.vals_data.len() {
527 let data_slice = &dict.vals_data[offset_bytes..];
528 for i in 0..count {
529 let entry_offset = WordEntry::SERIALIZED_LEN * (i as usize);
530 if entry_offset + WordEntry::SERIALIZED_LEN <= data_slice.len() {
531 let entry = WordEntry::deserialize(&data_slice[entry_offset..], true);
532 if start < self.matches_head.len() {
533 let next = self.matches_head[start];
534 self.matches_head[start] = self.matches_store.len();
535 self.matches_store.push((m.end(), entry, next));
536 }
537 }
538 }
539 }
540 }
541
542 if let Some(ud) = user_dict {
544 for m in ud.da.find_overlapping_iter(text) {
545 let start = m.start();
546 let (offset, count) = ud.decode_val(m.value());
547 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
548
549 if offset_bytes < ud.vals_data.len() {
550 let data_slice = &ud.vals_data[offset_bytes..];
551 for i in 0..count {
552 let entry_offset = WordEntry::SERIALIZED_LEN * (i as usize);
553 if entry_offset + WordEntry::SERIALIZED_LEN <= data_slice.len() {
554 let entry = WordEntry::deserialize(&data_slice[entry_offset..], false);
555 if start < self.matches_head.len() {
556 let next = self.matches_head[start];
557 self.matches_head[start] = self.matches_store.len();
558 self.matches_store.push((m.end(), entry, next));
559 }
560 }
561 }
562 }
563 }
564 }
565
566 for char_idx in 0..self.char_info_buffer.len() - 1 {
567 let start = self.char_info_buffer[char_idx].byte_offset as usize;
568
569 if self.ends_at[start].is_empty() {
572 continue;
573 }
574
575 let mut found: bool = false;
576
577 if start < self.matches_head.len() {
579 let mut match_idx = self.matches_head[start];
580 while match_idx != usize::MAX {
581 let (end, word_entry, next) = self.matches_store[match_idx];
582
583 let prefix_len = end - start;
584 let kanji_only = self.is_kanji_all(char_idx, prefix_len);
585 let edge = Self::create_edge(
586 word_entry, start, end, kanji_only,
588 );
589 self.add_edge_in_lattice(edge, cost_matrix, search_mode);
590 found = true;
591
592 match_idx = next;
593 }
594 }
595
596 if (search_mode.is_search()
598 || unknown_word_end.map(|index| index <= start).unwrap_or(true))
599 && char_idx < self.char_info_buffer.len() - 1
600 {
601 let num_categories = self.char_info_buffer[char_idx].categories_len as usize;
602 for category_ord in 0..num_categories {
603 let category = self.get_cached_category(char_idx, category_ord);
604 unknown_word_end = self.process_unknown_word(
605 char_definitions,
606 unknown_dictionary,
607 cost_matrix,
608 search_mode,
609 category,
610 category_ord,
611 unknown_word_end,
612 start,
613 char_idx,
614 found,
615 );
616 }
617 }
618 }
619
620 if !self.ends_at[len].is_empty() {
622 let mut eos_edge = Edge {
623 start_index: len as u32,
624 stop_index: len as u32,
625 ..Default::default()
626 };
627 let left_edges = &self.ends_at[len];
629 let mut best_cost = i32::MAX;
630 let mut best_left = None;
631 let right_left_id = 0; for (i, left_edge) in left_edges.iter().enumerate() {
634 let left_right_id = left_edge.word_entry.right_id();
635 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
636 let path_cost = left_edge.path_cost.saturating_add(conn_cost);
637 if path_cost < best_cost {
638 best_cost = path_cost;
639 best_left = Some(i as u16);
640 }
641 }
642 if let Some(left_idx) = best_left {
643 eos_edge.left_index = left_idx;
644 eos_edge.path_cost = best_cost;
645 self.ends_at[len].push(eos_edge);
646 }
647 }
648 }
649
650 #[allow(clippy::too_many_arguments)]
651 fn process_unknown_word(
652 &mut self,
653 char_definitions: &CharacterDefinition,
654 unknown_dictionary: &UnknownDictionary,
655 cost_matrix: &ConnectionCostMatrix,
656 search_mode: &Mode,
657 category: CategoryId,
658 category_ord: usize,
659 unknown_word_index: Option<usize>,
660 start: usize,
661 char_idx: usize,
662 found: bool,
663 ) -> Option<usize> {
664 let mut unknown_word_num_chars: usize = 0;
665 let category_data = char_definitions.lookup_definition(category);
666 if category_data.invoke || !found {
667 unknown_word_num_chars = 1;
668 if category_data.group {
669 for i in 1.. {
670 let next_idx = char_idx + i;
671 if next_idx >= self.char_info_buffer.len() - 1 {
672 break;
673 }
674 let num_categories = self.char_info_buffer[next_idx].categories_len as usize;
675 let mut found_cat = false;
676 if category_ord < num_categories {
677 let cat = self.get_cached_category(next_idx, category_ord);
678 if cat == category {
679 unknown_word_num_chars += 1;
680 found_cat = true;
681 }
682 }
683 if !found_cat {
684 break;
685 }
686 }
687 }
688 }
689 if unknown_word_num_chars > 0 {
690 let byte_end_offset =
691 self.char_info_buffer[char_idx + unknown_word_num_chars].byte_offset;
692 let byte_len = byte_end_offset as usize - start;
693
694 let kanji_only = self.is_kanji_all(char_idx, byte_len);
696
697 for &word_id in unknown_dictionary.lookup_word_ids(category) {
698 let word_entry = unknown_dictionary.word_entry(word_id);
699 let edge = Self::create_edge(word_entry, start, start + byte_len, kanji_only);
700 self.add_edge_in_lattice(edge, cost_matrix, search_mode);
701 }
702 return Some(start + byte_len);
703 }
704 unknown_word_index
705 }
706
707 fn add_edge_in_lattice(
709 &mut self,
710 mut edge: Edge,
711 cost_matrix: &ConnectionCostMatrix,
712 mode: &Mode,
713 ) {
714 let start_index = edge.start_index as usize;
715 let stop_index = edge.stop_index as usize;
716 let right_left_id = edge.word_entry.left_id();
717
718 let left_edges = &self.ends_at[start_index];
719 if left_edges.is_empty() {
720 return;
721 }
722
723 let mut best_cost = i32::MAX;
724 let mut best_left = None;
725
726 match mode {
727 Mode::Normal => {
728 for (i, left_edge) in left_edges.iter().enumerate() {
729 let left_right_id = left_edge.word_entry.right_id();
730 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
731 let total_cost = left_edge.path_cost.saturating_add(conn_cost);
732
733 if total_cost < best_cost {
734 best_cost = total_cost;
735 best_left = Some(i as u16);
736 }
737 }
738 }
739 Mode::Decompose(penalty) => {
740 for (i, left_edge) in left_edges.iter().enumerate() {
741 let left_right_id = left_edge.word_entry.right_id();
742 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
743 let penalty_cost = penalty.penalty(left_edge);
744 let total_cost = left_edge
745 .path_cost
746 .saturating_add(conn_cost)
747 .saturating_add(penalty_cost);
748
749 if total_cost < best_cost {
750 best_cost = total_cost;
751 best_left = Some(i as u16);
752 }
753 }
754 }
755 }
756
757 if let Some(best_left_idx) = best_left {
758 edge.path_cost = best_cost.saturating_add(edge.word_entry.word_cost as i32);
759 edge.left_index = best_left_idx;
760 self.ends_at[stop_index].push(edge);
761 }
762 }
763
764 pub fn tokens_offset(&self) -> Vec<(usize, WordId)> {
765 let mut offsets = Vec::new();
766
767 if self.ends_at.is_empty() {
768 return offsets;
769 }
770
771 let mut last_idx = self.ends_at.len() - 1;
772 while last_idx > 0 && self.ends_at[last_idx].is_empty() {
773 last_idx -= 1;
774 }
775
776 if self.ends_at[last_idx].is_empty() {
777 return offsets;
778 }
779
780 let idx = self.ends_at[last_idx].len() - 1;
781 let mut edge = &self.ends_at[last_idx][idx];
782
783 if edge.left_index == u16::MAX {
784 return offsets;
785 }
786
787 loop {
788 if edge.left_index == u16::MAX {
789 break;
790 }
791
792 offsets.push((edge.start_index as usize, edge.word_entry.word_id));
793
794 let left_idx = edge.left_index as usize;
795 let start_idx = edge.start_index as usize;
796
797 edge = &self.ends_at[start_idx][left_idx];
798 }
799
800 offsets.reverse();
801 offsets.pop(); offsets
804 }
805
806 pub fn text_len(&self) -> usize {
810 self.last_text_len
811 }
812
813 pub fn edges_at(&self, byte_pos: usize) -> &[Edge] {
815 &self.ends_at[byte_pos]
816 }
817
818 pub fn paths_at(&self, byte_pos: usize) -> &[PathEntry] {
820 if byte_pos < self.all_paths.len() {
821 &self.all_paths[byte_pos]
822 } else {
823 &[]
824 }
825 }
826
827 fn add_edge_in_lattice_nbest(
829 &mut self,
830 mut edge: Edge,
831 cost_matrix: &ConnectionCostMatrix,
832 mode: &Mode,
833 ) {
834 let start_index = edge.start_index as usize;
835 let stop_index = edge.stop_index as usize;
836 let right_left_id = edge.word_entry.left_id();
837
838 let left_edges = &self.ends_at[start_index];
839 if left_edges.is_empty() {
840 return;
841 }
842
843 let mut best_cost = i32::MAX;
844 let mut best_left = None;
845
846 let new_edge_index = self.ends_at[stop_index].len() as u16;
848
849 match mode {
850 Mode::Normal => {
851 for (i, left_edge) in left_edges.iter().enumerate() {
852 let left_right_id = left_edge.word_entry.right_id();
853 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
854 let total_cost = left_edge.path_cost.saturating_add(conn_cost);
855
856 self.all_paths[stop_index].push(PathEntry {
858 edge_index: new_edge_index,
859 left_pos: start_index as u32,
860 left_index: i as u16,
861 cost: total_cost,
862 });
863
864 if total_cost < best_cost {
865 best_cost = total_cost;
866 best_left = Some(i as u16);
867 }
868 }
869 }
870 Mode::Decompose(penalty) => {
871 for (i, left_edge) in left_edges.iter().enumerate() {
872 let left_right_id = left_edge.word_entry.right_id();
873 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
874 let penalty_cost = penalty.penalty(left_edge);
875 let total_cost = left_edge
876 .path_cost
877 .saturating_add(conn_cost)
878 .saturating_add(penalty_cost);
879
880 self.all_paths[stop_index].push(PathEntry {
882 edge_index: new_edge_index,
883 left_pos: start_index as u32,
884 left_index: i as u16,
885 cost: total_cost,
886 });
887
888 if total_cost < best_cost {
889 best_cost = total_cost;
890 best_left = Some(i as u16);
891 }
892 }
893 }
894 }
895
896 if let Some(best_left_idx) = best_left {
897 edge.path_cost = best_cost.saturating_add(edge.word_entry.word_cost as i32);
898 edge.left_index = best_left_idx;
899 self.ends_at[stop_index].push(edge);
900 }
901 }
902
903 #[allow(clippy::too_many_arguments)]
904 fn process_unknown_word_nbest(
905 &mut self,
906 char_definitions: &CharacterDefinition,
907 unknown_dictionary: &UnknownDictionary,
908 cost_matrix: &ConnectionCostMatrix,
909 search_mode: &Mode,
910 category: CategoryId,
911 category_ord: usize,
912 unknown_word_index: Option<usize>,
913 start: usize,
914 char_idx: usize,
915 found: bool,
916 ) -> Option<usize> {
917 let mut unknown_word_num_chars: usize = 0;
918 let category_data = char_definitions.lookup_definition(category);
919 if category_data.invoke || !found {
920 unknown_word_num_chars = 1;
921 if category_data.group {
922 for i in 1.. {
923 let next_idx = char_idx + i;
924 if next_idx >= self.char_info_buffer.len() - 1 {
925 break;
926 }
927 let num_categories = self.char_info_buffer[next_idx].categories_len as usize;
928 let mut found_cat = false;
929 if category_ord < num_categories {
930 let cat = self.get_cached_category(next_idx, category_ord);
931 if cat == category {
932 unknown_word_num_chars += 1;
933 found_cat = true;
934 }
935 }
936 if !found_cat {
937 break;
938 }
939 }
940 }
941 }
942 if unknown_word_num_chars > 0 {
943 let byte_end_offset =
944 self.char_info_buffer[char_idx + unknown_word_num_chars].byte_offset;
945 let byte_len = byte_end_offset as usize - start;
946
947 let kanji_only = self.is_kanji_all(char_idx, byte_len);
948
949 for &word_id in unknown_dictionary.lookup_word_ids(category) {
950 let word_entry = unknown_dictionary.word_entry(word_id);
951 let edge = Self::create_edge(word_entry, start, start + byte_len, kanji_only);
952 self.add_edge_in_lattice_nbest(edge, cost_matrix, search_mode);
953 }
954 return Some(start + byte_len);
955 }
956 unknown_word_index
957 }
958
959 #[inline(never)]
962 #[allow(clippy::too_many_arguments)]
963 pub fn set_text_nbest(
964 &mut self,
965 dict: &PrefixDictionary,
966 user_dict: &Option<&PrefixDictionary>,
967 char_definitions: &CharacterDefinition,
968 unknown_dictionary: &UnknownDictionary,
969 cost_matrix: &ConnectionCostMatrix,
970 text: &str,
971 search_mode: &Mode,
972 ) {
973 let len = text.len();
974 self.set_capacity_nbest(len);
975
976 self.char_info_buffer.clear();
978 self.categories_buffer.clear();
979
980 if self.char_category_cache.is_empty() {
981 self.char_category_cache.resize(256, Vec::new());
982 }
983
984 for (byte_offset, c) in text.char_indices() {
985 let categories_start = self.categories_buffer.len() as u32;
986
987 if (c as u32) < 256 {
988 let cached = &mut self.char_category_cache[c as usize];
989 if cached.is_empty() {
990 let cats = char_definitions.lookup_categories(c);
991 for &category in cats {
992 cached.push(category);
993 }
994 }
995 for &category in cached.iter() {
996 self.categories_buffer.push(category);
997 }
998 } else {
999 let categories = char_definitions.lookup_categories(c);
1000 for &category in categories {
1001 self.categories_buffer.push(category);
1002 }
1003 }
1004
1005 let categories_len = (self.categories_buffer.len() as u32 - categories_start) as u16;
1006
1007 self.char_info_buffer.push(CharData {
1008 byte_offset: byte_offset as u32,
1009 is_kanji: is_kanji(c),
1010 categories_start,
1011 categories_len,
1012 kanji_run_byte_len: 0,
1013 });
1014 }
1015 self.char_info_buffer.push(CharData {
1017 byte_offset: len as u32,
1018 is_kanji: false,
1019 categories_start: 0,
1020 categories_len: 0,
1021 kanji_run_byte_len: 0,
1022 });
1023
1024 for i in (0..self.char_info_buffer.len() - 1).rev() {
1026 if self.char_info_buffer[i].is_kanji {
1027 let next_byte_offset = self.char_info_buffer[i + 1].byte_offset;
1028 let char_byte_len = next_byte_offset - self.char_info_buffer[i].byte_offset;
1029 self.char_info_buffer[i].kanji_run_byte_len =
1030 char_byte_len + self.char_info_buffer[i + 1].kanji_run_byte_len;
1031 } else {
1032 self.char_info_buffer[i].kanji_run_byte_len = 0;
1033 }
1034 }
1035
1036 let start_edge = Edge {
1037 path_cost: 0,
1038 left_index: u16::MAX,
1039 ..Default::default()
1040 };
1041 self.ends_at[0].push(start_edge);
1042
1043 let mut unknown_word_end: Option<usize> = None;
1044
1045 self.matches_head.clear();
1050 self.matches_head.resize(len + 1, usize::MAX);
1051 self.matches_store.clear();
1052
1053 for m in dict.da.find_overlapping_iter(text) {
1055 let start = m.start();
1056 let (offset, count) = dict.decode_val(m.value());
1057 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
1058
1059 if offset_bytes < dict.vals_data.len() {
1060 let data_slice = &dict.vals_data[offset_bytes..];
1061 for i in 0..count {
1062 let entry_offset = WordEntry::SERIALIZED_LEN * (i as usize);
1063 if entry_offset + WordEntry::SERIALIZED_LEN <= data_slice.len() {
1064 let entry = WordEntry::deserialize(&data_slice[entry_offset..], true);
1065 if start < self.matches_head.len() {
1066 let next = self.matches_head[start];
1067 self.matches_head[start] = self.matches_store.len();
1068 self.matches_store.push((m.end(), entry, next));
1069 }
1070 }
1071 }
1072 }
1073 }
1074
1075 if let Some(ud) = user_dict {
1077 for m in ud.da.find_overlapping_iter(text) {
1078 let start = m.start();
1079 let (offset, count) = ud.decode_val(m.value());
1080 let offset_bytes = (offset as usize) * WordEntry::SERIALIZED_LEN;
1081
1082 if offset_bytes < ud.vals_data.len() {
1083 let data_slice = &ud.vals_data[offset_bytes..];
1084 for i in 0..count {
1085 let entry_offset = WordEntry::SERIALIZED_LEN * (i as usize);
1086 if entry_offset + WordEntry::SERIALIZED_LEN <= data_slice.len() {
1087 let entry = WordEntry::deserialize(&data_slice[entry_offset..], false);
1088 if start < self.matches_head.len() {
1089 let next = self.matches_head[start];
1090 self.matches_head[start] = self.matches_store.len();
1091 self.matches_store.push((m.end(), entry, next));
1092 }
1093 }
1094 }
1095 }
1096 }
1097 }
1098
1099 for char_idx in 0..self.char_info_buffer.len() - 1 {
1100 let start = self.char_info_buffer[char_idx].byte_offset as usize;
1101
1102 if self.ends_at[start].is_empty() {
1103 continue;
1104 }
1105
1106 let mut found: bool = false;
1107
1108 if start < self.matches_head.len() {
1109 let mut match_idx = self.matches_head[start];
1110 while match_idx != usize::MAX {
1111 let (end, word_entry, next) = self.matches_store[match_idx];
1112
1113 let prefix_len = end - start;
1114 let kanji_only = self.is_kanji_all(char_idx, prefix_len);
1115 let edge = Self::create_edge(word_entry, start, end, kanji_only);
1116 self.add_edge_in_lattice_nbest(edge, cost_matrix, search_mode);
1117 found = true;
1118
1119 match_idx = next;
1120 }
1121 }
1122
1123 if (search_mode.is_search()
1124 || unknown_word_end.map(|index| index <= start).unwrap_or(true))
1125 && char_idx < self.char_info_buffer.len() - 1
1126 {
1127 let num_categories = self.char_info_buffer[char_idx].categories_len as usize;
1128 for category_ord in 0..num_categories {
1129 let category = self.get_cached_category(char_idx, category_ord);
1130 unknown_word_end = self.process_unknown_word_nbest(
1131 char_definitions,
1132 unknown_dictionary,
1133 cost_matrix,
1134 search_mode,
1135 category,
1136 category_ord,
1137 unknown_word_end,
1138 start,
1139 char_idx,
1140 found,
1141 );
1142 }
1143 }
1144 }
1145
1146 if !self.ends_at[len].is_empty() {
1148 let eos_edge_index = self.ends_at[len].len() as u16;
1149 let mut eos_edge = Edge {
1150 start_index: len as u32,
1151 stop_index: len as u32,
1152 ..Default::default()
1153 };
1154 let left_edges = &self.ends_at[len];
1155 let mut best_cost = i32::MAX;
1156 let mut best_left = None;
1157 let right_left_id = 0; for (i, left_edge) in left_edges.iter().enumerate() {
1160 let left_right_id = left_edge.word_entry.right_id();
1161 let conn_cost = cost_matrix.cost(left_right_id, right_left_id);
1162 let path_cost = left_edge.path_cost.saturating_add(conn_cost);
1163
1164 self.all_paths[len].push(PathEntry {
1166 edge_index: eos_edge_index,
1167 left_pos: len as u32,
1168 left_index: i as u16,
1169 cost: path_cost,
1170 });
1171
1172 if path_cost < best_cost {
1173 best_cost = path_cost;
1174 best_left = Some(i as u16);
1175 }
1176 }
1177 if let Some(left_idx) = best_left {
1178 eos_edge.left_index = left_idx;
1179 eos_edge.path_cost = best_cost;
1180 self.ends_at[len].push(eos_edge);
1181 }
1182 }
1183 }
1184
1185 pub fn nbest_tokens_offset(
1193 &self,
1194 n: usize,
1195 unique: bool,
1196 cost_threshold: Option<i64>,
1197 ) -> Vec<(Vec<(usize, WordId)>, i64)> {
1198 use std::collections::HashSet;
1199
1200 use crate::nbest::NBestGenerator;
1201 let mut generator = NBestGenerator::new(self);
1202 let mut results = Vec::with_capacity(n);
1203 let mut best_cost: Option<i64> = None;
1204
1205 if unique {
1206 let mut seen: HashSet<Vec<usize>> = HashSet::new();
1207 while results.len() < n {
1208 match generator.next() {
1209 Some((path, cost)) => {
1210 let bc = *best_cost.get_or_insert(cost);
1212 if let Some(threshold) = cost_threshold
1214 && cost > bc + threshold
1215 {
1216 break;
1217 }
1218 let key: Vec<usize> = path.iter().map(|(start, _)| *start).collect();
1219 if seen.insert(key) {
1220 results.push((path, cost));
1221 }
1222 }
1223 None => break,
1224 }
1225 }
1226 } else {
1227 while results.len() < n {
1228 match generator.next() {
1229 Some((path, cost)) => {
1230 let bc = *best_cost.get_or_insert(cost);
1231 if let Some(threshold) = cost_threshold
1232 && cost > bc + threshold
1233 {
1234 break;
1235 }
1236 results.push((path, cost));
1237 }
1238 None => break,
1239 }
1240 }
1241 }
1242 results
1243 }
1244}
1245
1246#[cfg(test)]
1247mod tests {
1248 use crate::viterbi::{Lattice, LexType, WordEntry, WordId};
1249
1250 #[test]
1251 fn test_word_entry() {
1252 let mut buffer = Vec::new();
1253 let word_entry =
1254 WordEntry::new(WordId::new(LexType::System, 1u32), -17i16, 1411u16, 1412u16);
1255 word_entry.serialize(&mut buffer).unwrap();
1256 assert_eq!(WordEntry::SERIALIZED_LEN, buffer.len());
1257 let word_entry2 = WordEntry::deserialize(&buffer[..], true);
1258 assert_eq!(word_entry, word_entry2);
1259 }
1260
1261 #[test]
1267 fn test_set_capacity_presizes_all_new_slots() {
1268 let mut lattice = Lattice::default();
1269
1270 lattice.set_capacity(5);
1271 assert_eq!(lattice.ends_at.len(), 6);
1272 for (i, slot) in lattice.ends_at.iter().enumerate() {
1273 assert!(
1274 slot.capacity() >= 16,
1275 "slot {} has capacity {} < 16 after initial growth",
1276 i,
1277 slot.capacity()
1278 );
1279 }
1280
1281 lattice.set_capacity(10);
1283 assert_eq!(lattice.ends_at.len(), 11);
1284 for (i, slot) in lattice.ends_at.iter().enumerate() {
1285 assert!(
1286 slot.capacity() >= 16,
1287 "slot {} has capacity {} < 16 after second growth",
1288 i,
1289 slot.capacity()
1290 );
1291 }
1292 }
1293}