1use std::ops::Deref;
19
20pub trait Soundex {
21 fn soundex(&self) -> String;
32}
33
34impl<T: Deref<Target = str>> Soundex for T {
36 fn soundex(&self) -> String {
37 if self.is_empty() {
38 return Default::default();
39 }
40
41 let mut reslut = String::with_capacity(4);
42 let mut last = None;
43 let mut count = 0;
44
45 for next in self.chars() {
46 let score = number_map(next);
47
48 if last.is_none() {
49 if !next.is_alphanumeric() {
50 continue;
51 }
52
53 last = score;
54 reslut.push(next.to_ascii_uppercase());
55 } else {
56 if !next.is_ascii_alphabetic() || is_drop(next) || score == last {
57 continue;
58 }
59
60 last = score;
61 reslut.push(score.unwrap());
62 }
63
64 count += 1;
65
66 #[cfg(not(feature = "full"))]
67 {
68 if count == 4 {
69 break;
70 }
71 }
72 }
73
74 if count < 4 {
75 reslut.push_str("0".repeat(4 - count).as_str());
76 }
77
78 reslut
79 }
80}
81
82#[inline(always)]
83fn number_map(i: char) -> Option<char> {
84 match i.to_ascii_lowercase() {
85 'b' | 'f' | 'p' | 'v' => Some('1'),
86 'c' | 'g' | 'j' | 'k' | 'q' | 's' | 'x' | 'z' => Some('2'),
87 'd' | 't' => Some('3'),
88 'l' => Some('4'),
89 'm' | 'n' => Some('5'),
90 'r' => Some('6'),
91 _ => Some('0'),
92 }
93}
94
95#[inline(always)]
96fn is_drop(c: char) -> bool {
97 matches!(
98 c.to_ascii_lowercase(),
99 'a' | 'e' | 'i' | 'o' | 'u' | 'y' | 'h' | 'w'
100 )
101}
102
103pub fn equal<LEFT, RIGHT>(left: LEFT, right: RIGHT) -> bool
110where
111 LEFT: Soundex,
112 RIGHT: Soundex,
113{
114 left.soundex() == right.soundex()
115}
116
117#[cfg(test)]
118mod tests {
119 use super::Soundex;
120 use crate::equal;
121
122 #[test]
123 fn test_soundex() {
124 let m = vec![
125 ("", "".to_string()),
126 ("c你rfpv", "C610".to_string()),
127 ("你rfpv", "你610".to_string()),
128 ("x", "X000".to_string()),
129 ("xxxxx", "X000".to_string()),
130 ("difficult", "D1243".to_string()),
131 ("Knuth", "K530".to_string()),
132 ("Kant", "K530".to_string()),
133 ("Jarovski", "J612".to_string()),
134 ("Resnik", "R252".to_string()),
135 ("Reznick", "R252".to_string()),
136 ("Euler", "E460".to_string()),
137 ("Peterson", "P3625".to_string()),
138 ("Jefferson", "J1625".to_string()),
139 ("bb你iiiffpvsgsslkfldsjfasdas", "B24214321232".to_string()),
140 ];
141
142 for (i, v) in m.into_iter() {
143 if cfg!(feature = "full") {
144 assert_eq!(i.soundex(), v, "{}", i);
145 assert_eq!(i.to_string().soundex(), v, "{}", i);
146 assert_eq!(i.to_string().as_mut().soundex(), v, "{}", i);
147 } else {
148 assert_eq!(i.soundex(), String::from_iter(v.chars().take(4)), "{}", i);
149 assert_eq!(
150 i.to_string().soundex(),
151 String::from_iter(v.chars().take(4)),
152 "{}",
153 i
154 );
155 assert_eq!(
156 i.to_string().as_mut().soundex(),
157 String::from_iter(v.chars().take(4)),
158 "{}",
159 i
160 );
161 }
162 }
163 }
164
165 #[test]
166 fn test_equal() {
167 assert!(equal("hello", "hello".to_string()));
168 assert!(equal("hello", "hello"));
169 assert!(!equal("hello world", "hello".to_string()));
170 }
171}