摩斯码

1. use std::collections::HashMap;
2. use std::io;

4. const UNKNOWN_CHARACTER: &str = "........";
5. const _UNKNOWN_MORSE_CHARACTER: &str = "_";

7. pub fn encode(message: &str) -> String {
8. let dictionary = _morse_dictionary();
9. message
10. .chars()
11. .into_iter()
12. .map(|char| char.to_uppercase().to_string())
13. .map(|letter| dictionary.get(letter.as_str()))
14. .map(|option| option.unwrap_or(&UNKNOWN_CHARACTER).to_string())
15. .collect::<Vec<String>>()
16. .join(" ")
17. }

19. // Declaritive macro for creating readable map declarations, for more info see https://doc.rust-lang.org/book/ch19-06-macros.html
20. macro_rules! map {
21. ($($key:expr => $value:expr),* $(,)?) => {
22. std::iter::Iterator::collect(std::array::IntoIter::new([$(($key, $value),)*]))
23. };
24. }

26. fn _morse_dictionary() -> HashMap<&'static str, &'static str> {
27. map! {
28. "A" => ".-",      "B" => "-...",    "C" => "-.-.",
29. "D" => "-..",     "E" => ".",       "F" => "..-.",
30. "G" => "--.",     "H" => "....",    "I" => "..",
31. "J" => ".---",    "K" => "-.-",     "L" => ".-..",
32. "M" => "--",      "N" => "-.",      "O" => "---",
33. "P" => ".--.",    "Q" => "--.-",    "R" => ".-.",
34. "S" => "...",     "T" => "-",       "U" => "..-",
35. "V" => "...-",    "W" => ".--",     "X" => "-..-",
36. "Y" => "-.--",    "Z" => "--..",

38. "1" => ".----",   "2" => "..---",   "3" => "...--",
39. "4" => "....-",   "5" => ".....",   "6" => "-....",
40. "7" => "--...",   "8" => "---..",   "9" => "----.",
41. "0" => "-----",

43. "&" => ".-...",   "@" => ".--.-.",  ":" => "---...",
44. "," => "--..--",  "." => ".-.-.-",  "'" => ".----.",
45. "\"" => ".-..-.", "?" => "..--..",  "/" => "-..-.",
46. "=" => "-...-",   "+" => ".-.-.",   "-" => "-....-",
47. "(" => "-.--.",   ")" => "-.--.-",  " " => "/",
48. "!" => "-.-.--",
49. }
50. }

52. fn _morse_to_alphanumeric_dictionary() -> HashMap<&'static str, &'static str> {
53. map! {
54. ".-"   =>  "A",      "-..." => "B",    "-.-." => "C",
55. "-.."  =>  "D",      "."    => "E",       "..-." => "F",
56. "--."  =>  "G",      "...." => "H",    ".." => "I",
57. ".---" =>  "J",     "-.-" => "K",     ".-.." => "L",
58. "--"   =>  "M",       "-." => "N",      "---" => "O",
59. ".--." =>  "P",     "--.-" => "Q",    ".-." => "R",
60. "..."  =>  "S",      "-" => "T",       "..-" => "U",
61. "...-" =>  "V",     ".--" => "W",     "-..-" => "X",
62. "-.--" =>  "Y",     "--.." => "Z",

64. ".----" => "1",    "..---" => "2",   "...--" => "3",
65. "....-" => "4",    "....." => "5",   "-...." => "6",
66. "--..." => "7",    "---.." => "8",   "----." => "9",
67. "-----" => "0",

69. ".-..." => "&",    ".--.-." => "@",  "---..." => ":",
70. "--..--" => ",",   ".-.-.-" => ".",  ".----." => "'",
71. ".-..-." => "\"",  "..--.." => "?",  "-..-." => "/",
72. "-...-" => "=",   ".-.-." => "+",   "-....-" => "-",
73. "-.--." => "(",   "-.--.-" => ")",  "/" => " ",
74. "-.-.--" => "!",  " " => " ",       "" => ""
75. }
76. }

78. fn _check_part(string: &str) -> bool {
79. for c in string.chars() {
80. match c {
81. '.' | '-' | ' ' => (),
82. _ => return false,
83. }
84. }
85. true
86. }

88. fn _check_all_parts(string: &str) -> bool {
89. string.split('/').all(_check_part)
90. }

92. fn _decode_token(string: &str) -> String {
93. _morse_to_alphanumeric_dictionary()
94. .get(string)
95. .unwrap_or(&_UNKNOWN_MORSE_CHARACTER)
96. .to_string()
97. }

99. fn _decode_part(string: &str) -> String {
100. string
101. .split(' ')
102. .map(_decode_token)
103. .collect::<Vec<String>>()
104. .join("")
105. }

107. /// Convert morse code to ascii.
108. ///
109. /// Given a morse code, return the corresponding message.
110. /// If the code is invalid, the undecipherable part of the code is replaced by `_`.
111. pub fn decode(string: &str) -> Result<String, io::Error> {
112. if !_check_all_parts(string) {
113. return Err(io::Error::new(
114. io::ErrorKind::InvalidData,
115. "Invalid morse code",
116. ));
117. }

119. let mut partitions: Vec<String> = vec![];

121. for part in string.split('/') {
122. partitions.push(_decode_part(part));
123. }

125. Ok(partitions.join(" "))
126. }

128. #[cfg(test)]
129. mod tests {
130. use super::*;

132. #[test]
133. fn encrypt_only_letters() {
134. let message = "Hello Morse";
135. let cipher = encode(message);
136. assert_eq!(
137. cipher,
138. ".... . .-.. .-.. --- / -- --- .-. ... .".to_string()
139. )
140. }

142. #[test]
143. fn encrypt_letters_and_special_characters() {
144. let message = "What's a great day!";
145. let cipher = encode(message);
146. assert_eq!(
147. cipher,
148. ".-- .... .- - .----. ... / .- / --. .-. . .- - / -.. .- -.-- -.-.--".to_string()
149. )
150. }

152. #[test]
153. fn encrypt_message_with_unsupported_character() {
154. let message = "Error?? {}";
155. let cipher = encode(message);
156. assert_eq!(
157. cipher,
158. ". .-. .-. --- .-. ..--.. ..--.. / ........ ........".to_string()
159. )
160. }

162. #[test]
163. fn decrypt_valid_morsecode_with_spaces() {
164. let expected = "Hello Morse! How's it goin, \"eh\"?"
165. .to_string()
166. .to_uppercase();
167. let encypted = encode(&expected);
168. let result = decode(&encypted).unwrap();

170. assert_eq!(expected, result);
171. }

173. #[test]
174. fn decrypt_valid_character_set_invalid_morsecode() {
175. let expected = format!(
176. "{}{}{}{} {}",
177. _UNKNOWN_MORSE_CHARACTER,
178. _UNKNOWN_MORSE_CHARACTER,
179. _UNKNOWN_MORSE_CHARACTER,
180. _UNKNOWN_MORSE_CHARACTER,
181. _UNKNOWN_MORSE_CHARACTER,
182. );

184. let encypted = ".-.-.--.-.-. --------. ..---.-.-. .-.-.--.-.-. / .-.-.--.-.-.".to_string();
185. let result = decode(&encypted).unwrap();

187. assert_eq!(expected, result);
188. }

190. #[test]
191. fn decrypt_invalid_morsecode_with_spaces() {
192. let encypted = "1... . .-.. .-.. --- / -- --- .-. ... .";
193. let result = decode(encypted).map_err(|e| e.kind());
194. let expected = Err(io::ErrorKind::InvalidData);

196. assert_eq!(expected, result);
197. }
198. }