Simple Morse Code Translation in Java – Java 中的简单莫尔斯电码翻译

最后修改: 2024年 1月 22日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Morse code encodes text characters using sequences of dots and dashes to represent letters, numbers, and punctuation. Samuel Morse and Alfred Vail developed it in the early 1830s for telegraphy use.

摩尔斯电码使用点和破折号的序列对文本字符进行编码,以表示字母、数字和标点符号。塞缪尔-摩尔斯和阿尔弗雷德-维尔于 19 世纪 30 年代初开发了摩尔斯电码,用于电报。

In this tutorial, we’ll write a method that translates from English to Morse code. Then, we’ll write the method which does the opposite.

在本教程中,我们将编写一个将英语翻译成莫尔斯电码的方法。然后,我们将编写一个相反的方法。

2. Writing Morse Code

2.编写莫尔斯电码

Let’s understand Morse code and its alphabet.

让我们来了解摩尔斯电码及其字母表。

2.1. What Is the Morse Code?

2.1.什么是莫尔斯电码?

In Morse code, each letter is represented by a unique combination of short signals (dots) and long signals (dashes), allowing for communication through a series of on-off signals. According to the common usage, we’ll represent dots with “.” and dashes with ““. Those two characters are enough to write the whole Morse alphabet.

在莫尔斯电码中,每个字母都由短信号(点)和长信号(破折号)的独特组合来表示,从而可以通过一系列开关信号进行通信。根据通常的用法,我们用”.“表示点,用”“表示破折号。这两个字符足以写出整个摩尔斯字母表。

However, we’ll need something more to write sentences. As Morse indeed targeted non-written communication, the flow is essential to decrypt a Morse message. For this reason, the operator responsible for transmitting a Morse message would leave a short pause between each letter. Additionally, he would leave a longer pause between each word. As a result, a representation that wouldn’t take those pauses into account wouldn’t allow for decoding.

然而,我们还需要更多的东西来书写句子。由于摩尔斯确实针对的是非书面通信,所以要解密摩尔斯电文,语流是必不可少的。因此,负责传输莫尔斯电文的操作员会在每个字母之间留下短暂的停顿。此外,他还会在每个单词之间留出较长的停顿。因此,如果不考虑这些停顿,就无法进行解码。

A common choice is to leave a blank space ” ” to represent the pause between each word. We’ll also use a “/” to codify the space character between two words. As the slash is also a character, blank spaces will surround it like the others.

一种常见的选择是在每个单词之间留一个空格””来表示停顿。我们还将使用”/“来表示两个单词之间的空格。由于斜线也是一个字符,因此它周围也会像其他字符一样留出空格。

2.2. Bidirectional Mapping Between English and Morse

2.2.英语和莫尔斯语之间的双向映射

To translate easily from English to Morse and reversely, we’d like to have a bidirectional mapping between both alphabets. Thus, we’ll use Apache Commons Collection’s BidiMap data structure. It’s a Map that allows access by key or by value. This way, we’ll use it for both translating methods. However, if we’re interested in translating only one way, we’d directly use a Map.

为了方便地从英语翻译成摩尔斯语,以及进行反向翻译,我们希望在两种字母之间建立双向映射。因此,我们将使用 Apache Commons Collection 的 BidiMap 数据结构。这是一个 Map 结构,允许通过键或值进行访问。这样,我们将在两种翻译方法中使用它。但是,如果我们只对一种翻译方式感兴趣,我们可以直接使用 Map

First, let’s include the latest version of the library in our pom.xml:

首先,让我们在 pom.xml 中包含 库的最新版本

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

We can now create our mapping and initialize it in a static block:

现在,我们可以创建映射,并在 static 块中对其进行初始化:

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(" ", "/");
    }
    
}

Let’s note that we added the translation for the blank character. Furthermore, we restrict ourselves to letters, numbers, and punctuation characters. If we want to use also accented characters, we’d need to use another data structure or make choices because various accented characters can match the same Morse code. For instance, “à” and “å” both correspond to “.–.-” in Morse.

请注意,我们添加了空白字符的翻译。此外,我们仅限于字母、数字和标点符号。如果我们还想使用重音字符,就需要使用另一种数据结构或进行选择,因为各种重音字符可以匹配相同的莫尔斯电码。例如,”à“和”å“都对应莫尔斯码中的”.-.-“。

3. Translating English Into Morse

3.将英语翻译成莫尔斯语

First, let’s write a method to translate an English sentence into Morse code.

首先,让我们编写一个将英语句子翻译成莫尔斯电码的方法。

3.1. General Algorithm

3.1.一般算法

Our BidiMap contains only capital letters because capitalization doesn’t change the translation. Thus, we’ll start with capitalizing the word. Then, we’ll iterate over the letters and translate them one by one:

我们的 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);
}

It’s convenient to store the translations into an array of Morse Strings. This intermediate array has as many values as the number of characters in the input. In the end, we use the String.join() method to concatenate all entries, using a blank space as a delimiter.

将翻译存储到摩尔斯字符串数组中非常方便。这个中间数组的值与输入字符数相同。最后,我们使用 String.join() 方法连接所有条目,并使用空格作为分隔符。

We can now test our method. Since we’d like to check that capitalization doesn’t matter, we’ll write a parameterized test with various inputs expecting the same output:

现在我们可以测试我们的方法了。由于我们想检查大写是否无关紧要,因此我们将编写一个 参数化测试,使用不同的输入,期望相同的输出:

@ParameterizedTest
@ValueSource(strings = {"MORSE CODE!", "morse code!", "mOrSe cOdE!"})
void givenAValidEnglishWordWhateverTheCapitalization_whenEnglishToMorse_thenTranslatedToMorse(String english) {
    assertEquals("-- --- .-. ... . / -.-. --- -.. . -.-.-----.", MorseTranslator.englishToMorse(english));
}

In addition, we can note that the space between the two words translates to “ / ” as expected.

此外,我们还可以注意到,两个单词之间的空格被翻译成” / “。

3.2. Edge Cases

3.2.边缘案例

For the moment, our program doesn’t take into account potentially malformed inputs. However, we’d like to refuse sentences that contain invalid characters. In such cases, we’ll throw an IllegalArgumentException:

目前,我们的程序并不考虑潜在的畸形输入。但是,我们希望拒绝包含无效字符的句子。在这种情况下,我们将抛出 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;

The modification is pretty straightforward because if a character is invalid, it isn’t present as a key of the bidirectional map. Hence, the get() method returns null. We can also add a null safety check on top of our method. In a nutshell, our final method reads:

修改非常简单,因为如果一个字符无效,它就不会作为双向映射的键出现。因此,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);
}

Lastly, we can add a unit test with a non-translatable sentence:

最后,我们可以添加一个不可翻译句子的单元测试:

@Test
void givenAnEnglishWordWithAnIllegalCharacter_whenEnglishToMorse_thenThrows() {
    String english = "~This sentence starts with an illegal character";
    assertThrows(IllegalArgumentException.class, () -> MorseTranslator.englishToMorse(english));
}

4. Translating Morse Into English

4.将莫尔斯语翻译成英语

Let’s now write the reverse method. Once again, we’ll focus on the big picture before diving into edge cases.

现在我们来编写反向方法。在深入研究边缘案例之前,我们将再次关注全局。

4.1. General Algorithm

4.1.一般算法

The concept is the same: for each Morse character, we find the English translation in the BidiMap. The getKey() method allows us to do that. Then, we need to iterate over every Morse character:

概念是一样的:对于每个摩尔斯字符,我们都要在 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();
}

We isolated every Morse character thanks to the String.split() method. Appending every English translation to a StringBuilder is the most efficient way to concatenate the result.

由于使用了String.split()方法,我们分离了每个莫尔斯字符。将每个英文翻译附加到 StringBuilder 是连接结果的最有效方法。

Let’s now verify that our method returns the correct result:

现在让我们验证一下我们的方法是否返回了正确的结果:

@Test
void givenAValidMorseWord_whenMorseToEnglish_thenTranslatedToUpperCaseEnglish() {
    assertEquals("MORSE CODE!", MorseTranslator.morseToEnglish("-- --- .-. ... . / -.-. --- -.. . -.-.-----."));
}

Finally, we can recall that the output will always be in capital letters.

最后,我们可以回顾一下,输出结果将始终使用大写字母。

4.2. Edge Cases

4.2.边缘案例

Additionally, we want to refuse inputs containing invalid Morse characters. Like in englishToMorse(), we’ll throw an IllegalArgumentException in this case. Moreover, we can also handle the specific case of a null input. Here, we also have to deal with an empty input separately because of the internal functioning of the split() method.

此外,我们希望拒绝包含无效莫尔斯字符的输入。就像在 englishToMorse() 中一样,在这种情况下我们将抛出 IllegalArgumentException 异常。此外,我们还可以处理 null 输入的特殊情况。在这里,由于 split() 方法的内部功能,我们还必须单独处理空输入。

To recap, let’s write our final method:

总结一下,让我们来编写最后一个方法:

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();
}

Dealing with invalid characters was as straightforward as in the previous case because if a Morse code doesn’t match any of the BidiMap‘s values, the getKey() method returns null.

处理无效字符与前一种情况一样简单,因为如果莫尔斯码与 BidiMap 的任何值不匹配,getKey() 方法就会返回 null

Lastly, we can also test the error case:

最后,我们还可以测试错误案例:

@Test
void givenAMorseWordWithAnIllegalCharacter_whenMorseToEnglish_thenThrows() {
    assertThrows(IllegalArgumentException.class, () -> MorseTranslator.morseToEnglish(".!!!!!!!"));
}

5. Conclusion

5.结论

In this article, we learned about the Morse code and wrote a simple two-way translator between Morse and English. Most considerations aren’t specific to Morse, so we could probably make our code more generic to deal with any language that can define a bidirectional mapping with English.

在本文中,我们了解了摩尔斯电码,并编写了一个简单的摩尔斯和英语双向翻译器。大多数考虑因素并不是莫尔斯语所特有的,因此我们也许可以让我们的代码更通用,以处理任何可以定义英语双向映射的语言。

As always, the code is available over on GitHub.

与往常一样,代码可在 GitHub 上获取。