1use crate::lattice::{Lattice, Node, NodeId, NodeType, INVALID_NODE_ID};
37use std::cmp::Ordering;
38use std::collections::BinaryHeap;
39use std::rc::Rc;
40
41#[cfg(feature = "simd")]
43pub mod simd;
44
45#[cfg(feature = "simd")]
46pub use simd::{simd_forward_pass_position, simd_update_node_cost};
47
48#[inline(always)]
52const fn saturating_add_chain(a: i32, b: i32, c: i32, d: i32) -> i32 {
53 a.saturating_add(b).saturating_add(c).saturating_add(d)
54}
55
56pub trait ConnectionCost {
61 fn cost(&self, right_id: u16, left_id: u16) -> i32;
72}
73
74#[derive(Debug, Clone, Default)]
78pub struct ZeroConnectionCost;
79
80impl ConnectionCost for ZeroConnectionCost {
81 #[inline(always)]
82 fn cost(&self, _right_id: u16, _left_id: u16) -> i32 {
83 0
84 }
85}
86
87#[derive(Debug, Clone)]
89pub struct FixedConnectionCost {
90 pub default_cost: i32,
92}
93
94impl FixedConnectionCost {
95 #[must_use]
97 pub const fn new(cost: i32) -> Self {
98 Self { default_cost: cost }
99 }
100}
101
102impl ConnectionCost for FixedConnectionCost {
103 #[inline(always)]
104 fn cost(&self, _right_id: u16, _left_id: u16) -> i32 {
105 self.default_cost
106 }
107}
108
109impl<T: mecab_ko_dict::Matrix> ConnectionCost for T {
113 #[inline(always)]
114 fn cost(&self, right_id: u16, left_id: u16) -> i32 {
115 self.get(right_id, left_id)
116 }
117}
118
119#[derive(Debug, Clone, Default)]
136pub struct SpacePenalty {
137 penalties: Vec<(u16, i32)>,
140}
141
142impl SpacePenalty {
143 #[must_use]
145 pub fn new() -> Self {
146 Self::default()
147 }
148
149 #[must_use]
154 pub fn korean_default() -> Self {
155 let mut penalties: Vec<(u16, i32)> = (1700u16..1760)
162 .chain(1780..1810)
163 .map(|id| (id, 6000))
164 .collect();
165
166 penalties.sort_unstable_by_key(|&(id, _)| id);
168 Self { penalties }
169 }
170
171 #[must_use]
188 pub fn from_dicrc(config: &str) -> Self {
189 let mut penalties = Vec::new();
190
191 for part in config.split(';') {
192 let parts: Vec<&str> = part.trim().split(',').collect();
193 if parts.len() == 2 {
194 if let (Ok(id), Ok(penalty)) = (
195 parts[0].trim().parse::<u16>(),
196 parts[1].trim().parse::<i32>(),
197 ) {
198 penalties.push((id, penalty));
199 }
200 }
201 }
202
203 penalties.sort_unstable_by_key(|&(id, _)| id);
205 Self { penalties }
206 }
207
208 pub fn add(&mut self, left_id: u16, penalty: i32) {
210 let pos = self.penalties.partition_point(|&(id, _)| id < left_id);
212 self.penalties.insert(pos, (left_id, penalty));
213 }
214
215 #[must_use]
221 #[inline]
222 pub fn get(&self, left_id: u16) -> i32 {
223 self.penalties
225 .binary_search_by_key(&left_id, |&(id, _)| id)
226 .map_or(0, |idx| self.penalties[idx].1)
227 }
228
229 #[must_use]
231 #[inline]
232 pub fn is_empty(&self) -> bool {
233 self.penalties.is_empty()
234 }
235
236 #[must_use]
238 #[inline]
239 pub fn len(&self) -> usize {
240 self.penalties.len()
241 }
242}
243
244#[derive(Debug, Clone)]
248pub struct ViterbiSearcher {
249 pub space_penalty: SpacePenalty,
251}
252
253impl Default for ViterbiSearcher {
254 fn default() -> Self {
255 Self::new()
256 }
257}
258
259impl ViterbiSearcher {
260 #[must_use]
262 pub fn new() -> Self {
263 Self {
264 space_penalty: SpacePenalty::default(),
265 }
266 }
267
268 #[must_use]
270 pub fn with_space_penalty(mut self, penalty: SpacePenalty) -> Self {
271 self.space_penalty = penalty;
272 self
273 }
274
275 pub fn search<C: ConnectionCost>(&self, lattice: &mut Lattice, conn_cost: &C) -> Vec<NodeId> {
301 self.forward_pass(lattice, conn_cost);
303
304 Self::backward_pass(lattice)
306 }
307
308 fn forward_pass<C: ConnectionCost>(&self, lattice: &mut Lattice, conn_cost: &C) {
312 let char_len = lattice.char_len();
313
314 let mut starting_ids: Vec<NodeId> = Vec::new();
318 let mut ending_nodes: Vec<(NodeId, i32, u16)> = Vec::new();
319
320 for pos in 0..=char_len {
322 starting_ids.clear();
324 starting_ids.extend(lattice.nodes_starting_at(pos).map(|n| n.id));
325
326 ending_nodes.clear();
329 ending_nodes.extend(
330 lattice
331 .nodes_ending_at(pos)
332 .map(|n| (n.id, n.total_cost, n.right_id)),
333 );
334
335 for &node_id in &starting_ids {
336 self.update_node_cost_with_endings(lattice, conn_cost, node_id, &ending_nodes);
337 }
338 }
339 }
340
341 #[inline]
346 fn update_node_cost_with_endings<C: ConnectionCost>(
347 &self,
348 lattice: &mut Lattice,
349 conn_cost: &C,
350 node_id: NodeId,
351 ending_nodes: &[(NodeId, i32, u16)],
352 ) {
353 #[cfg(feature = "simd")]
355 if ending_nodes.len() >= 8 {
356 let (best_cost, best_prev) = simd::simd_update_node_cost(
357 lattice,
358 conn_cost,
359 node_id,
360 ending_nodes,
361 &self.space_penalty,
362 );
363 if let Some(node) = lattice.node_mut(node_id) {
364 node.total_cost = best_cost;
365 node.prev_node_id = best_prev;
366 }
367 return;
368 }
369
370 let (left_id, word_cost, has_space) = {
372 let Some(node) = lattice.node(node_id) else {
373 return;
374 };
375 (node.left_id, node.word_cost, node.has_space_before)
376 };
377
378 let space_penalty = if has_space {
380 self.space_penalty.get(left_id)
381 } else {
382 0
383 };
384
385 let mut best_cost = i32::MAX;
386 let mut best_prev = INVALID_NODE_ID;
387
388 for &(prev_id, prev_cost, prev_right_id) in ending_nodes {
389 if prev_cost == i32::MAX {
391 continue;
392 }
393
394 let connection = conn_cost.cost(prev_right_id, left_id);
396
397 let total = saturating_add_chain(prev_cost, connection, word_cost, space_penalty);
399
400 if total < best_cost {
401 best_cost = total;
402 best_prev = prev_id;
403 }
404 }
405
406 if let Some(node) = lattice.node_mut(node_id) {
408 node.total_cost = best_cost;
409 node.prev_node_id = best_prev;
410 }
411 }
412
413 #[cfg(test)]
415 #[allow(dead_code)]
416 fn update_node_cost<C: ConnectionCost>(
417 &self,
418 lattice: &mut Lattice,
419 conn_cost: &C,
420 node_id: NodeId,
421 pos: usize,
422 ) {
423 let (left_id, word_cost, has_space) = {
425 let Some(node) = lattice.node(node_id) else {
426 return;
427 };
428 (node.left_id, node.word_cost, node.has_space_before)
429 };
430
431 let ending_nodes: Vec<(NodeId, i32, u16)> = lattice
433 .nodes_ending_at(pos)
434 .map(|n| (n.id, n.total_cost, n.right_id))
435 .collect();
436
437 let mut best_cost = i32::MAX;
438 let mut best_prev = INVALID_NODE_ID;
439
440 for (prev_id, prev_cost, prev_right_id) in ending_nodes {
441 if prev_cost == i32::MAX {
442 continue;
443 }
444
445 let connection = conn_cost.cost(prev_right_id, left_id);
446
447 let space_penalty = if has_space {
448 self.space_penalty.get(left_id)
449 } else {
450 0
451 };
452
453 let total = prev_cost
454 .saturating_add(connection)
455 .saturating_add(word_cost)
456 .saturating_add(space_penalty);
457
458 if total < best_cost {
459 best_cost = total;
460 best_prev = prev_id;
461 }
462 }
463
464 if let Some(node) = lattice.node_mut(node_id) {
466 node.total_cost = best_cost;
467 node.prev_node_id = best_prev;
468 }
469 }
470
471 fn backward_pass(lattice: &Lattice) -> Vec<NodeId> {
475 let mut path = Vec::new();
476 let mut current_id = lattice.eos().id;
477
478 while current_id != INVALID_NODE_ID {
479 if let Some(node) = lattice.node(current_id) {
480 if node.node_type != NodeType::Bos && node.node_type != NodeType::Eos {
482 path.push(current_id);
483 }
484 current_id = node.prev_node_id;
485 } else {
486 break;
487 }
488 }
489
490 path.reverse();
491 path
492 }
493
494 #[must_use]
496 pub fn get_best_cost(&self, lattice: &Lattice) -> i32 {
497 lattice.eos().total_cost
498 }
499
500 #[must_use]
504 pub fn has_valid_path(&self, lattice: &Lattice) -> bool {
505 lattice.eos().total_cost != i32::MAX && lattice.eos().prev_node_id != INVALID_NODE_ID
506 }
507}
508
509#[derive(Debug, Clone)]
518struct PathNode {
519 node_id: NodeId,
521 prev: Option<Rc<Self>>,
523}
524
525impl PathNode {
526 #[allow(clippy::missing_const_for_fn)]
530 fn new(node_id: NodeId, prev: Option<Rc<Self>>) -> Self {
531 Self { node_id, prev }
532 }
533
534 fn to_vec(&self) -> Vec<NodeId> {
536 let mut path = Vec::new();
537 let mut current = Some(self);
538
539 while let Some(node) = current {
540 path.push(node.node_id);
541 current = node.prev.as_ref().map(std::convert::AsRef::as_ref);
542 }
543
544 path.reverse();
545 path
546 }
547}
548
549#[derive(Debug, Clone)]
551struct NbestCandidate {
552 node_id: NodeId,
554 cost: i32,
556 path: Option<Rc<PathNode>>,
558}
559
560impl Eq for NbestCandidate {}
561
562impl PartialEq for NbestCandidate {
563 fn eq(&self, other: &Self) -> bool {
564 self.cost == other.cost
565 }
566}
567
568impl Ord for NbestCandidate {
569 fn cmp(&self, other: &Self) -> Ordering {
570 other.cost.cmp(&self.cost)
572 }
573}
574
575impl PartialOrd for NbestCandidate {
576 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
577 Some(self.cmp(other))
578 }
579}
580
581#[derive(Debug, Clone)]
585pub struct NbestSearcher {
586 viterbi: ViterbiSearcher,
588 max_results: usize,
590}
591
592impl NbestSearcher {
593 #[must_use]
595 pub fn new(n: usize) -> Self {
596 Self {
597 viterbi: ViterbiSearcher::new(),
598 max_results: n,
599 }
600 }
601
602 #[must_use]
604 pub fn with_space_penalty(mut self, penalty: SpacePenalty) -> Self {
605 self.viterbi.space_penalty = penalty;
606 self
607 }
608
609 pub fn search<C: ConnectionCost>(
620 &self,
621 lattice: &mut Lattice,
622 conn_cost: &C,
623 ) -> Vec<(Vec<NodeId>, i32)> {
624 self.viterbi.forward_pass(lattice, conn_cost);
626
627 if !self.viterbi.has_valid_path(lattice) {
629 return Vec::new();
630 }
631
632 if self.max_results == 1 {
634 let path = ViterbiSearcher::backward_pass(lattice);
635 let cost = self.viterbi.get_best_cost(lattice);
636 return vec![(path, cost)];
637 }
638
639 self.search_nbest(lattice, conn_cost)
641 }
642
643 fn search_nbest<C: ConnectionCost>(
650 &self,
651 lattice: &Lattice,
652 _conn_cost: &C,
653 ) -> Vec<(Vec<NodeId>, i32)> {
654 let mut results: Vec<(Vec<NodeId>, i32)> = Vec::new();
655 let mut heap: BinaryHeap<NbestCandidate> = BinaryHeap::new();
656
657 let eos = lattice.eos();
659 if eos.total_cost == i32::MAX {
660 return results;
661 }
662
663 heap.push(NbestCandidate {
664 node_id: eos.id,
665 cost: eos.total_cost,
666 path: None,
667 });
668
669 while let Some(candidate) = heap.pop() {
670 if results.len() >= self.max_results {
671 break;
672 }
673
674 let Some(node) = lattice.node(candidate.node_id) else {
675 continue;
676 };
677
678 let current_path = if node.node_type != NodeType::Bos && node.node_type != NodeType::Eos
680 {
681 Some(Rc::new(PathNode::new(candidate.node_id, candidate.path)))
683 } else {
684 candidate.path
685 };
686
687 if node.node_type == NodeType::Bos {
689 let path_vec = current_path.map_or_else(Vec::new, |path_node| path_node.to_vec());
691 results.push((path_vec, candidate.cost));
692 continue;
693 }
694
695 if node.prev_node_id != INVALID_NODE_ID {
697 heap.push(NbestCandidate {
698 node_id: node.prev_node_id,
699 cost: candidate.cost,
700 path: current_path,
701 });
702 }
703 }
704
705 results
706 }
707}
708
709pub struct ViterbiResult<'a> {
711 lattice: &'a Lattice,
713 path: Vec<NodeId>,
715 total_cost: i32,
717}
718
719impl<'a> ViterbiResult<'a> {
720 #[must_use]
722 pub const fn new(lattice: &'a Lattice, path: Vec<NodeId>, total_cost: i32) -> Self {
723 Self {
724 lattice,
725 path,
726 total_cost,
727 }
728 }
729
730 pub fn nodes(&self) -> impl Iterator<Item = &'a Node> + '_ {
732 self.path.iter().filter_map(|&id| self.lattice.node(id))
733 }
734
735 #[must_use]
737 pub const fn cost(&self) -> i32 {
738 self.total_cost
739 }
740
741 #[must_use]
743 pub fn len(&self) -> usize {
744 self.path.len()
745 }
746
747 #[must_use]
749 pub fn is_empty(&self) -> bool {
750 self.path.is_empty()
751 }
752
753 #[must_use]
755 pub fn surfaces(&self) -> Vec<&str> {
756 self.nodes().map(|n| n.surface.as_ref()).collect()
757 }
758}
759
760#[cfg(test)]
761#[allow(clippy::unwrap_used)]
762mod tests {
763 use super::*;
764 use crate::lattice::NodeBuilder;
765
766 struct TestConnectionCost {
768 costs: std::collections::HashMap<(u16, u16), i32>,
769 default: i32,
770 }
771
772 impl TestConnectionCost {
773 fn new(default: i32) -> Self {
774 Self {
775 costs: std::collections::HashMap::new(),
776 default,
777 }
778 }
779
780 fn set(&mut self, right_id: u16, left_id: u16, cost: i32) {
781 self.costs.insert((right_id, left_id), cost);
782 }
783 }
784
785 impl ConnectionCost for TestConnectionCost {
786 fn cost(&self, right_id: u16, left_id: u16) -> i32 {
787 self.costs
788 .get(&(right_id, left_id))
789 .copied()
790 .unwrap_or(self.default)
791 }
792 }
793
794 #[test]
795 fn test_space_penalty_default() {
796 let penalty = SpacePenalty::default();
797 assert!(penalty.is_empty());
798 assert_eq!(penalty.get(100), 0);
799 }
800
801 #[test]
802 fn test_space_penalty_from_dicrc() {
803 let penalty = SpacePenalty::from_dicrc("100,5000;200,3000;300,1000");
804
805 assert_eq!(penalty.len(), 3);
806 assert_eq!(penalty.get(100), 5000);
807 assert_eq!(penalty.get(200), 3000);
808 assert_eq!(penalty.get(300), 1000);
809 assert_eq!(penalty.get(999), 0); }
811
812 #[test]
813 fn test_space_penalty_korean_default() {
814 let penalty = SpacePenalty::korean_default();
815 assert!(!penalty.is_empty());
816
817 assert!(penalty.get(1785) > 0);
819 }
820
821 #[test]
822 fn test_viterbi_simple_path() {
823 let mut lattice = Lattice::new("AB");
826
827 lattice.add_node(
829 NodeBuilder::new("A", 0, 1)
830 .left_id(1)
831 .right_id(1)
832 .word_cost(100),
833 );
834
835 lattice.add_node(
837 NodeBuilder::new("B", 1, 2)
838 .left_id(2)
839 .right_id(2)
840 .word_cost(200),
841 );
842
843 let conn_cost = ZeroConnectionCost;
844 let searcher = ViterbiSearcher::new();
845
846 let path = searcher.search(&mut lattice, &conn_cost);
847
848 assert_eq!(path.len(), 2);
849
850 let first = lattice.node(path[0]).unwrap();
852 assert_eq!(first.surface.as_ref(), "A");
853
854 let second = lattice.node(path[1]).unwrap();
856 assert_eq!(second.surface.as_ref(), "B");
857
858 let total_cost = searcher.get_best_cost(&lattice);
860 assert_eq!(total_cost, 300); }
862
863 #[test]
864 fn test_viterbi_choose_best_path() {
865 let mut lattice = Lattice::new("AB");
869
870 lattice.add_node(
872 NodeBuilder::new("AB", 0, 2)
873 .left_id(1)
874 .right_id(1)
875 .word_cost(500),
876 );
877
878 lattice.add_node(
880 NodeBuilder::new("A", 0, 1)
881 .left_id(2)
882 .right_id(2)
883 .word_cost(100),
884 );
885
886 lattice.add_node(
888 NodeBuilder::new("B", 1, 2)
889 .left_id(3)
890 .right_id(3)
891 .word_cost(200),
892 );
893
894 let conn_cost = ZeroConnectionCost;
895 let searcher = ViterbiSearcher::new();
896
897 let path = searcher.search(&mut lattice, &conn_cost);
898
899 assert_eq!(path.len(), 2);
901 assert_eq!(lattice.node(path[0]).unwrap().surface.as_ref(), "A");
902 assert_eq!(lattice.node(path[1]).unwrap().surface.as_ref(), "B");
903 }
904
905 #[test]
906 fn test_viterbi_with_connection_cost() {
907 let mut lattice = Lattice::new("AB");
911
912 lattice.add_node(
914 NodeBuilder::new("AB", 0, 2)
915 .left_id(1)
916 .right_id(1)
917 .word_cost(300),
918 );
919
920 lattice.add_node(
922 NodeBuilder::new("A", 0, 1)
923 .left_id(2)
924 .right_id(2)
925 .word_cost(100),
926 );
927
928 lattice.add_node(
930 NodeBuilder::new("B", 1, 2)
931 .left_id(3)
932 .right_id(3)
933 .word_cost(100),
934 );
935
936 let mut conn_cost = TestConnectionCost::new(0);
937 conn_cost.set(2, 3, 500);
939
940 let searcher = ViterbiSearcher::new();
941 let path = searcher.search(&mut lattice, &conn_cost);
942
943 assert_eq!(path.len(), 1);
945 assert_eq!(lattice.node(path[0]).unwrap().surface.as_ref(), "AB");
946 }
947
948 #[test]
949 fn test_viterbi_with_space_penalty() {
950 let mut lattice = Lattice::new("A B");
954 lattice.add_node(
958 NodeBuilder::new("AB", 0, 2)
959 .left_id(1)
960 .right_id(1)
961 .word_cost(500),
962 );
963
964 lattice.add_node(
966 NodeBuilder::new("A", 0, 1)
967 .left_id(2)
968 .right_id(2)
969 .word_cost(100),
970 );
971
972 lattice.add_node(
974 NodeBuilder::new("B", 1, 2)
975 .left_id(100) .right_id(3)
977 .word_cost(100)
978 .has_space_before(true),
979 );
980
981 let mut penalty = SpacePenalty::new();
983 penalty.add(100, 1000);
984
985 let conn_cost = ZeroConnectionCost;
986 let searcher = ViterbiSearcher::new().with_space_penalty(penalty);
987
988 let path = searcher.search(&mut lattice, &conn_cost);
989
990 assert_eq!(path.len(), 1);
992 assert_eq!(lattice.node(path[0]).unwrap().surface.as_ref(), "AB");
993 }
994
995 #[test]
996 fn test_viterbi_korean_example() {
997 let mut lattice = Lattice::new("아버지가");
999
1000 lattice.add_node(
1002 NodeBuilder::new("아버지", 0, 3)
1003 .left_id(1)
1004 .right_id(1)
1005 .word_cost(1000),
1006 );
1007 lattice.add_node(
1008 NodeBuilder::new("가", 3, 4)
1009 .left_id(100) .right_id(100)
1011 .word_cost(500),
1012 );
1013
1014 lattice.add_node(
1016 NodeBuilder::new("아버", 0, 2)
1017 .left_id(2)
1018 .right_id(2)
1019 .word_cost(3000),
1020 );
1021 lattice.add_node(
1022 NodeBuilder::new("지가", 2, 4)
1023 .left_id(3)
1024 .right_id(3)
1025 .word_cost(3000),
1026 );
1027
1028 let conn_cost = ZeroConnectionCost;
1029 let searcher = ViterbiSearcher::new();
1030
1031 let path = searcher.search(&mut lattice, &conn_cost);
1032
1033 assert_eq!(path.len(), 2);
1035 assert_eq!(lattice.node(path[0]).unwrap().surface.as_ref(), "아버지");
1036 assert_eq!(lattice.node(path[1]).unwrap().surface.as_ref(), "가");
1037 }
1038
1039 #[test]
1040 fn test_viterbi_empty_lattice() {
1041 let mut lattice = Lattice::new("");
1042
1043 let conn_cost = ZeroConnectionCost;
1044 let searcher = ViterbiSearcher::new();
1045
1046 let path = searcher.search(&mut lattice, &conn_cost);
1047
1048 assert!(path.is_empty());
1050 }
1051
1052 #[test]
1053 fn test_viterbi_no_path() {
1054 let mut lattice = Lattice::new("ABC");
1056
1057 lattice.add_node(
1059 NodeBuilder::new("A", 0, 1)
1060 .left_id(1)
1061 .right_id(1)
1062 .word_cost(100),
1063 );
1064
1065 let conn_cost = ZeroConnectionCost;
1066 let searcher = ViterbiSearcher::new();
1067
1068 let path = searcher.search(&mut lattice, &conn_cost);
1069
1070 assert!(!searcher.has_valid_path(&lattice));
1072 assert!(path.is_empty());
1073 }
1074
1075 #[test]
1076 fn test_nbest_single() {
1077 let mut lattice = Lattice::new("AB");
1078
1079 lattice.add_node(
1080 NodeBuilder::new("A", 0, 1)
1081 .left_id(1)
1082 .right_id(1)
1083 .word_cost(100),
1084 );
1085 lattice.add_node(
1086 NodeBuilder::new("B", 1, 2)
1087 .left_id(2)
1088 .right_id(2)
1089 .word_cost(200),
1090 );
1091
1092 let conn_cost = ZeroConnectionCost;
1093 let searcher = NbestSearcher::new(1);
1094
1095 let results = searcher.search(&mut lattice, &conn_cost);
1096
1097 assert_eq!(results.len(), 1);
1098 assert_eq!(results[0].1, 300); }
1100
1101 #[test]
1102 fn test_viterbi_result_helper() {
1103 let mut lattice = Lattice::new("AB");
1104
1105 let _id1 = lattice.add_node(
1106 NodeBuilder::new("A", 0, 1)
1107 .left_id(1)
1108 .right_id(1)
1109 .word_cost(100),
1110 );
1111 let _id2 = lattice.add_node(
1112 NodeBuilder::new("B", 1, 2)
1113 .left_id(2)
1114 .right_id(2)
1115 .word_cost(200),
1116 );
1117
1118 let conn_cost = ZeroConnectionCost;
1119 let searcher = ViterbiSearcher::new();
1120 let path = searcher.search(&mut lattice, &conn_cost);
1121 let cost = searcher.get_best_cost(&lattice);
1122
1123 let result = ViterbiResult::new(&lattice, path, cost);
1124
1125 assert_eq!(result.len(), 2);
1126 assert_eq!(result.cost(), 300);
1127 assert_eq!(result.surfaces(), vec!["A", "B"]);
1128 }
1129
1130 #[test]
1131 fn test_viterbi_with_dense_matrix() {
1132 use mecab_ko_dict::DenseMatrix;
1133
1134 let mut matrix = DenseMatrix::new(3, 3, 0);
1138
1139 matrix.set(0, 1, 100);
1142 matrix.set(1, 2, 50);
1144 matrix.set(2, 0, 30);
1146
1147 matrix.set(0, 2, 5000);
1149 matrix.set(1, 0, 200);
1151
1152 let mut lattice = Lattice::new("책을");
1153
1154 lattice.add_node(
1156 NodeBuilder::new("책", 0, 1)
1157 .left_id(1) .right_id(1) .word_cost(500),
1160 );
1161
1162 lattice.add_node(
1164 NodeBuilder::new("을", 1, 2)
1165 .left_id(2) .right_id(2) .word_cost(100),
1168 );
1169
1170 let searcher = ViterbiSearcher::new();
1171 let path = searcher.search(&mut lattice, &matrix);
1172
1173 assert!(!path.is_empty());
1175
1176 let result = ViterbiResult::new(&lattice, path, searcher.get_best_cost(&lattice));
1177 assert_eq!(result.surfaces(), vec!["책", "을"]);
1178
1179 assert_eq!(result.cost(), 780);
1182 }
1183}