#![allow(unused)]
fn main() {
use std::collections::HashMap;
use std::io;
const UNKNOWN_CHARACTER: &str = "........";
const _UNKNOWN_MORSE_CHARACTER: &str = "_";
pub fn encode(message: &str) -> String {
let dictionary = _morse_dictionary();
message
.chars()
.into_iter()
.map(|char| char.to_uppercase().to_string())
.map(|letter| dictionary.get(letter.as_str()))
.map(|option| option.unwrap_or(&UNKNOWN_CHARACTER).to_string())
.collect::<Vec<String>>()
.join(" ")
}
// Declaritive macro for creating readable map declarations, for more info see https://doc.rust-lang.org/book/ch19-06-macros.html
macro_rules! map {
($($key:expr => $value:expr),* $(,)?) => {
std::iter::Iterator::collect(std::array::IntoIter::new([$(($key, $value),)*]))
};
}
fn _morse_dictionary() -> HashMap<&'static str, &'static str> {
map! {
"A" => ".-", "B" => "-...", "C" => "-.-.",
"D" => "-..", "E" => ".", "F" => "..-.",
"G" => "--.", "H" => "....", "I" => "..",
"J" => ".---", "K" => "-.-", "L" => ".-..",
"M" => "--", "N" => "-.", "O" => "---",
"P" => ".--.", "Q" => "--.-", "R" => ".-.",
"S" => "...", "T" => "-", "U" => "..-",
"V" => "...-", "W" => ".--", "X" => "-..-",
"Y" => "-.--", "Z" => "--..",
"1" => ".----", "2" => "..---", "3" => "...--",
"4" => "....-", "5" => ".....", "6" => "-....",
"7" => "--...", "8" => "---..", "9" => "----.",
"0" => "-----",
"&" => ".-...", "@" => ".--.-.", ":" => "---...",
"," => "--..--", "." => ".-.-.-", "'" => ".----.",
"\"" => ".-..-.", "?" => "..--..", "/" => "-..-.",
"=" => "-...-", "+" => ".-.-.", "-" => "-....-",
"(" => "-.--.", ")" => "-.--.-", " " => "/",
"!" => "-.-.--",
}
}
fn _morse_to_alphanumeric_dictionary() -> HashMap<&'static str, &'static str> {
map! {
".-" => "A", "-..." => "B", "-.-." => "C",
"-.." => "D", "." => "E", "..-." => "F",
"--." => "G", "...." => "H", ".." => "I",
".---" => "J", "-.-" => "K", ".-.." => "L",
"--" => "M", "-." => "N", "---" => "O",
".--." => "P", "--.-" => "Q", ".-." => "R",
"..." => "S", "-" => "T", "..-" => "U",
"...-" => "V", ".--" => "W", "-..-" => "X",
"-.--" => "Y", "--.." => "Z",
".----" => "1", "..---" => "2", "...--" => "3",
"....-" => "4", "....." => "5", "-...." => "6",
"--..." => "7", "---.." => "8", "----." => "9",
"-----" => "0",
".-..." => "&", ".--.-." => "@", "---..." => ":",
"--..--" => ",", ".-.-.-" => ".", ".----." => "'",
".-..-." => "\"", "..--.." => "?", "-..-." => "/",
"-...-" => "=", ".-.-." => "+", "-....-" => "-",
"-.--." => "(", "-.--.-" => ")", "/" => " ",
"-.-.--" => "!", " " => " ", "" => ""
}
}
fn _check_part(string: &str) -> bool {
for c in string.chars() {
match c {
'.' | '-' | ' ' => (),
_ => return false,
}
}
true
}
fn _check_all_parts(string: &str) -> bool {
string.split('/').all(_check_part)
}
fn _decode_token(string: &str) -> String {
_morse_to_alphanumeric_dictionary()
.get(string)
.unwrap_or(&_UNKNOWN_MORSE_CHARACTER)
.to_string()
}
fn _decode_part(string: &str) -> String {
string
.split(' ')
.map(_decode_token)
.collect::<Vec<String>>()
.join("")
}
/// Convert morse code to ascii.
///
/// Given a morse code, return the corresponding message.
/// If the code is invalid, the undecipherable part of the code is replaced by `_`.
pub fn decode(string: &str) -> Result<String, io::Error> {
if !_check_all_parts(string) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid morse code",
));
}
let mut partitions: Vec<String> = vec![];
for part in string.split('/') {
partitions.push(_decode_part(part));
}
Ok(partitions.join(" "))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encrypt_only_letters() {
let message = "Hello Morse";
let cipher = encode(message);
assert_eq!(
cipher,
".... . .-.. .-.. --- / -- --- .-. ... .".to_string()
)
}
#[test]
fn encrypt_letters_and_special_characters() {
let message = "What's a great day!";
let cipher = encode(message);
assert_eq!(
cipher,
".-- .... .- - .----. ... / .- / --. .-. . .- - / -.. .- -.-- -.-.--".to_string()
)
}
#[test]
fn encrypt_message_with_unsupported_character() {
let message = "Error?? {}";
let cipher = encode(message);
assert_eq!(
cipher,
". .-. .-. --- .-. ..--.. ..--.. / ........ ........".to_string()
)
}
#[test]
fn decrypt_valid_morsecode_with_spaces() {
let expected = "Hello Morse! How's it goin, \"eh\"?"
.to_string()
.to_uppercase();
let encypted = encode(&expected);
let result = decode(&encypted).unwrap();
assert_eq!(expected, result);
}
#[test]
fn decrypt_valid_character_set_invalid_morsecode() {
let expected = format!(
"{}{}{}{} {}",
_UNKNOWN_MORSE_CHARACTER,
_UNKNOWN_MORSE_CHARACTER,
_UNKNOWN_MORSE_CHARACTER,
_UNKNOWN_MORSE_CHARACTER,
_UNKNOWN_MORSE_CHARACTER,
);
let encypted = ".-.-.--.-.-. --------. ..---.-.-. .-.-.--.-.-. / .-.-.--.-.-.".to_string();
let result = decode(&encypted).unwrap();
assert_eq!(expected, result);
}
#[test]
fn decrypt_invalid_morsecode_with_spaces() {
let encypted = "1... . .-.. .-.. --- / -- --- .-. ... .";
let result = decode(encypted).map_err(|e| e.kind());
let expected = Err(io::ErrorKind::InvalidData);
assert_eq!(expected, result);
}
}
}