Java 中的簡單摩斯電碼翻譯
1. 概述
莫爾斯電碼使用點和破折號序列對文字字元進行編碼,以表示字母、數字和標點符號。 Samuel Morse 和 Alfred Vail 在 1830 年代初開發了它用於電報用途。
在本教程中,我們將編寫一個將英文翻譯為摩斯電碼的方法。然後,我們將編寫執行相反操作的方法。
2. 編寫摩斯電碼
讓我們了解摩斯電碼及其字母。
2.1.什麼是摩斯電碼?
在莫爾斯電碼中,每個字母都由短信號(點)和長信號(破折號)的獨特組合表示,允許透過一系列開關信號進行通訊。按照通常的用法,我們用“”來表示點.
”並帶有“ –
”破折號。這兩個字符足以寫出整個莫爾斯字母。
然而,我們還需要更多的東西來寫句子。由於莫爾斯電碼確實針對非書面通信,因此流程對於解密莫爾斯電碼至關重要。因此,負責傳輸莫爾斯電文的操作員會在每個字母之間留下短暫的停頓。此外,他會在每個字之間留出更長的停頓。因此,不考慮這些暫停的表示將不允許解碼。
常見的選擇是留一個空格“ ”來表示每個單字之間的停頓。我們還將使用“ /
”來編碼兩個單字之間的空格字元。由於斜線也是一個字符,因此它周圍會出現空格,就像其他字符一樣。
2.2.英語和莫爾斯電碼之間的雙向映射
為了輕鬆地從英語翻譯成莫爾斯電碼,或反向翻譯,我們希望在兩個字母之間有一個雙向映射。因此,我們將使用 Apache Commons Collection 的BidiMap
資料結構。它是一個允許按鍵或按值存取的Map
。這樣,我們將把它用於兩種翻譯方法。但是,如果我們只想以一種方式進行翻譯,我們會直接使用Map
。
首先,讓我們在pom.xml
中包含該函式庫的最新版本:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
我們現在可以創建映射並在靜態區塊中初始化它:
public class MorseTranslator {
private static final BidiMap<String, String> morseAlphabet = new DualHashBidiMap<>();
static {
morseAlphabet.put("A", ".-");
morseAlphabet.put("B", "-...");
morseAlphabet.put("C", "-.-.");
morseAlphabet.put("D", "-..");
morseAlphabet.put("E", ".");
morseAlphabet.put("F", "..-.");
morseAlphabet.put("G", "--.");
morseAlphabet.put("H", "....");
morseAlphabet.put("I", "..");
// etc
morseAlphabet.put(" ", "/");
}
}
請注意,我們添加了空白字元的翻譯。此外,我們限制使用字母、數字和標點符號。如果我們還想使用重音字符,則需要使用其他資料結構或做出選擇,因為各種重音字符可以匹配相同的摩斯電碼。例如,「 à
」和「 å
」都對應於摩斯電碼中的「 .–.-
」。
3. 將英文翻譯成摩斯電碼
首先,讓我們寫一個將英文句子翻譯成摩斯電碼的方法。
3.1.通用演算法
我們的BidiMap
僅包含大寫字母,因為大寫不會改變翻譯。因此,我們將從單字大寫開始。然後,我們將迭代這些字母並一一翻譯它們:
static String englishToMorse(String english) {
String upperCaseEnglish = english.toUpperCase();
String[] morse = new String[upperCaseEnglish.length()];
for (int index = 0; index < upperCaseEnglish.length(); index++) {
String morseCharacter = morseAlphabet.get(String.valueOf(upperCaseEnglish.charAt(index)));
morse[index] = morseCharacter;
}
return String.join(" ", morse);
}
將翻譯儲存到 Morse String
陣列中很方便。此中間數組具有與輸入中的字元數一樣多的值。最後,我們使用String.join()
方法連接所有項目,並使用空格作為分隔符號。
我們現在可以測試我們的方法。由於我們想檢查大小寫並不重要,因此我們將編寫一個參數化測試,其中包含期望相同輸出的各種輸入:
@ParameterizedTest
@ValueSource(strings = {"MORSE CODE!", "morse code!", "mOrSe cOdE!"})
void givenAValidEnglishWordWhateverTheCapitalization_whenEnglishToMorse_thenTranslatedToMorse(String english) {
assertEquals("-- --- .-. ... . / -.-. --- -.. . -.-.-----.", MorseTranslator.englishToMorse(english));
}
此外,我們可以注意到兩個單字之間的空格如預期般翻譯為“ /
”。
3.2.邊緣情況
目前,我們的程式沒有考慮潛在的格式錯誤的輸入。但是,我們希望拒絕包含無效字元的句子。在這種情況下,我們將拋出IllegalArgumentException
:
String morseCharacter = morseAlphabet.get(String.valueOf(upperCaseEnglish.charAt(index)));
if (morseCharacter == null) {
throw new IllegalArgumentException("Character " + upperCaseEnglish.charAt(index) + " can't be translated to morse");
}
morse[index] = morseCharacter;
修改非常簡單,因為如果字元無效,則它不會作為雙向映射的鍵出現。因此, get()
方法傳回null
。我們還可以在我們的方法之上添加null
安全檢查。簡而言之,我們的最終方法如下:
static String englishToMorse(String english) {
if (english == null) {
return null;
}
String upperCaseEnglish = english.toUpperCase();
String[] morse = new String[upperCaseEnglish.length()];
for (int index = 0; index < upperCaseEnglish.length(); index++) {
String morseCharacter = morseAlphabet.get(String.valueOf(upperCaseEnglish.charAt(index)));
if (morseCharacter == null) {
throw new IllegalArgumentException("Character " + upperCaseEnglish.charAt(index) + " can't be translated to morse");
}
morse[index] = morseCharacter;
}
return String.join(" ", morse);
}
最後,我們可以添加一個帶有不可翻譯句子的單元測試:
@Test
void givenAnEnglishWordWithAnIllegalCharacter_whenEnglishToMorse_thenThrows() {
String english = "~This sentence starts with an illegal character";
assertThrows(IllegalArgumentException.class, () -> MorseTranslator.englishToMorse(english));
}
4. 將摩斯電碼翻譯成英語
現在讓我們來寫相反的方法。在深入研究邊緣情況之前,我們將再次關注大局。
4.1.通用演算法
概念是相同的:對於每個摩爾斯字符,我們在BidiMap
中找到英文翻譯。 getKey()
方法允許我們做到這一點。然後,我們需要迭代每個摩爾斯字元:
static String morseToEnglish(String morse) {
String[] morseUnitCharacters = morse.split(" ");
StringBuilder stringBuilder = new StringBuilder();
for (int index = 0; index < morseUnitCharacters.length; index ++) {
String englishCharacter = morseAlphabet.getKey(morseUnitCharacters[index]);
stringBuilder.append(englishCharacter);
}
return stringBuilder.toString();
}
借助String.split()
方法,我們隔離了每個莫爾斯電碼字元。將每個英文翻譯附加到StringBuilder
是連接結果最有效的方法。
現在讓我們驗證我們的方法是否回傳正確的結果:
@Test
void givenAValidMorseWord_whenMorseToEnglish_thenTranslatedToUpperCaseEnglish() {
assertEquals("MORSE CODE!", MorseTranslator.morseToEnglish("-- --- .-. ... . / -.-. --- -.. . -.-.-----."));
}
最後,我們可以回想一下,輸出始終是大寫字母。
4.2.邊緣情況
此外,我們希望拒絕包含無效莫爾斯字元的輸入。就像在englishToMorse()
一樣,在這種情況下我們將拋出IllegalArgumentException
。此外,我們也可以處理null
輸入的特定情況。在這裡,由於split()
方法的內部功能,我們還必須單獨處理空輸入。
回顧一下,讓我們來寫最終的方法:
static String morseToEnglish(String morse) {
if (morse == null) {
return null;
}
if (morse.isEmpty()) {
return "";
}
String[] morseUnitCharacters = morse.split(" ");
StringBuilder stringBuilder = new StringBuilder();
for (int index = 0; index < morseUnitCharacters.length; index ++) {
String englishCharacter = morseAlphabet.getKey(morseUnitCharacters[index]);
if (englishCharacter == null) {
throw new IllegalArgumentException("Character " + morseUnitCharacters[index] + " is not a valid morse character");
}
stringBuilder.append(englishCharacter);
}
return stringBuilder.toString();
}
處理無效字元與前一種情況一樣簡單,因為如果莫爾斯電碼與BidiMap
的任何值都不匹配, getKey()
方法將傳回null
。
最後,我們也可以測試錯誤情況:
@Test
void givenAMorseWordWithAnIllegalCharacter_whenMorseToEnglish_thenThrows() {
assertThrows(IllegalArgumentException.class, () -> MorseTranslator.morseToEnglish(".!!!!!!!"));
}
5. 結論
在本文中,我們了解了摩斯電碼並編寫了一個簡單的摩斯電碼和英語之間的雙向翻譯者。大多數考慮因素都不是特定於莫爾斯電碼的,因此我們可以使我們的程式碼更加通用,以處理可以定義與英語雙向映射的任何語言。
與往常一樣,程式碼可以在 GitHub 上取得。