Parse Morse Code to a String in C#

10 minutes

Table of Contents

Introduction

Parsing Morse code to text is one of those simple exercises that sharpens your thinking around dictionaries, string manipulation, and control flow. It also gives you a taste of how encoding systems work behind the scenes.

In this article, we will walk through how to create a Morse Code parser from scratch in C#, exploring the decoding logic, and showing how even a lightweight exercise like this can deepen your understanding of the C# language.

On the other hand, if you wish to see how you can convert a C# string to Morse Code, we have another Convert a String to Morse Code in C# article for you.


Requirements

By reading through Illumonos articles regularly you will know that we set great store by defining requirements up front, thereby avoiding costly refactoring later.

Morse Code dictionary

Before we start, we need to know how to map Morse Code to a character or number, and also what characters are supported by the Morse Code alphabet. Below is a dictionary of symbols that make up each valid alphabetical letter and number in Morse Code.

Morse Code symbol String symbol
.-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
-----0
.----1
..---2
...--3
....-4
.....5
-....6
--...7
---..8
----.9

Parsing Morse Code to String requirements

Below we have listed the requirements for converting Morse Code to a C# string.

  • Only Morse Code letters and numbers in the Morse Code dictionary above are valid input
  • Space between Morse Code letters shall be a single space
  • Space between Morse code words shall be a single forward slash character surrounded by a single space on each side ( / )
  • Empty input is not valid
  • Null input is not valid

Testing

Now that we have the requirements for valid input for parsing Morse Code to a C# string, we can define our test cases below.

Valid Morse Code to C# String test cases

Below are the test cases which will be used to determine whether a Morse Code input is a valid.

Test case Morse code input Expected output
Single letter A .- A
Multiple letters with slashes .- / -... / -.-. / -.. A B C D
Common phrase .... . .-.. .-.. --- / .-- --- .-. .-.. -.. HELLO WORLD
Full sentence - .... . .-. . / .. ... / -. --- / ... .--. --- --- -. THERE IS NO SPOON

Invalid Morse Code to C# String test cases

Below are the test cases which will be used to determine whether a Morse Code input is invalid.

Test case Morse code input Expected error
Null input null Input cannot be null.
Empty string "" Input cannot be empty.
Whitespace only      Input cannot consist only of whitespace.
Whitespace in prefix  .... Input cannot start or end with whitespace.
Whitespace in suffix ....  Input cannot start or end with whitespace.
Whitespace on both sides  ....  Input cannot start or end with whitespace.
Extra whitespace in between words ....  /  .... Invalid spacing between Morse code words or letters.
Extra whitespace in between letters .  . Invalid spacing between Morse code words or letters.
Invalid alphabetic character ... a Invalid character(s) found in Morse code sequence.
Invalid number character .1 Invalid character(s) found in Morse code sequence.
Invalid special character ..;. Invalid character(s) found in Morse code sequence.

Converting the test cases to C#

Below are the test cases we have produced, converted to C# xUnit.net tests.

Morse Code to String C# test cases

public sealed class MorseCodeToStringParserTests
{
    [Theory]
    [MemberData(nameof(MorseCodeToStringParserTestCases.ValidMorseCodeData), MemberType = typeof(MorseCodeToStringParserTestCases))]
    public void For_MorseCodeToStringParser_When_TryParse_With_Valid_MorseCode_Then_Return_Expected_Message(string morseCode, string expectedMessage)
    {
        MorseCodeToStringParseResult.Success expectedResult = new(expectedMessage);
        MorseCodeToStringParseResult actualResult = MorseCodeToStringParser.TryParse(morseCode);
        Assert.Equal(expectedResult, actualResult);
    }

    [Theory]
    [MemberData(nameof(MorseCodeToStringParserTestCases.InvalidMorseCodeData), MemberType = typeof(MorseCodeToStringParserTestCases))]
    public void For_MorseCodeToStringParser_When_TryParse_With_Invalid_MorseCode_Then_Return_Error_Message(string? morseCode, string expectedErrorMessage)
    {
        MorseCodeToStringParseResult.Failure expectedResult = new(expectedErrorMessage);
        MorseCodeToStringParseResult actualResult = MorseCodeToStringParser.TryParse(morseCode);
        Assert.Equal(expectedResult, actualResult);
    }

    private static class MorseCodeToStringParserTestCases
    {
        public static readonly TheoryData<string, string> ValidMorseCodeData = new()
        {
            { ".-", "A" },
            { ".- / -... / -.-. / -..", "A B C D" },
            { ".... . .-.. .-.. --- / .-- --- .-. .-.. -..", "HELLO WORLD" },
            { "- .... . .-. . / .. ... / -. --- / ... .--. --- --- -.", "THERE IS NO SPOON" }
        };

        public static readonly TheoryData<string?, string> InvalidMorseCodeData = new()
        {
            { null, IsNull },
            { InvalidMorseCode.Empty, IsEmpty },
            { InvalidMorseCode.WhitespaceOnly, IsWhitespace },
            { InvalidMorseCode.WhitespacePrefix, StartOrEndingSpaces },
            { InvalidMorseCode.WhitespaceSuffix, StartOrEndingSpaces },
            { InvalidMorseCode.WhitespaceBothSides, StartOrEndingSpaces },
            { InvalidMorseCode.WhitespaceInBetweenWords, InvalidSpacingBetweenWordsOrLetters },
            { InvalidMorseCode.WhitespaceInBetweenLetters, InvalidSpacingBetweenWordsOrLetters },
            { InvalidMorseCode.InvalidAlphaCharacter, InvalidMorseCodeCharacter },
            { InvalidMorseCode.InvalidNumberCharacter, InvalidMorseCodeCharacter },
            { InvalidMorseCode.InvalidSpecialCharacter, InvalidMorseCodeCharacter }
        };
    }
}

Implementing the Morse Code to C# String parser

Below we will break down the implementation of the Morse Code to C# String parser. You can see the full source code of the implementation later on as well.

Defining the map between C# characters and Morse Code strings

First we define the map between a Morse Code string and an alphabetical character or number. We can do this via C# Dictionary collection.

private static readonly Dictionary<string, char> _alphabet = new()
{
    [".-"]    = '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',
    ["-----"] = '0',
    [".----"] = '1',
    ["..---"] = '2',
    ["...--"] = '3',
    ["....-"] = '4',
    ["....."] = '5',
    ["-...."] = '6',
    ["--..."] = '7',
    ["---.."] = '8',
    ["----."] = '9'
};

The key is of the type string, and the value is of the type char.

Define character and word constants

Next we will define some constants we can use later on in our parser function.

private const char _letterSeparator = ' ';
private const string _wordSeparator = " / ";
  • _letterSeparator is used to denote the character separating Morse Code letters
  • _wordSeparator is used to denote the characters seperating Morse Code words

Defining the parse function and validating input

Now we will define our parser function and validate to initially weed out bad input.

public static MorseCodeToStringParseResult TryParse(string? morseCode)
{
    if (morseCode is null)
    {
        return new MorseCodeToStringParseResult.Failure(IsNull);
    }

    if (morseCode == string.Empty)
    {
        return new MorseCodeToStringParseResult.Failure(IsEmpty);
    }

    if (string.IsNullOrWhiteSpace(morseCode))
    {
        return new MorseCodeToStringParseResult.Failure(IsWhitespace);
    }

    if (morseCode.Trim().Length != morseCode.Length)
    {
        return new MorseCodeToStringParseResult.Failure(StartOrEndingSpaces);
    }

As our function can fail to convert, we define our TryParse method with a string parameter for the input Morse Code message, and a return type of MorseCodeToStringParseResult. This return type can either be a success or failure type. Success will contain the converted message, and a failure will contain an error message.

In the initial body of the method we are checking for common bad input, such as if it was null, empty, or whitespace. The final boolean expression morseCode.Trim().Length != morseCode.Length enforces our requirement that any Morse Code input must not have any whitespace preceding or following the message, by trimming the message and then checking its initial length against it's trimmed length. If there is a disparity, it will indicate there was whitespace preceding or following the Morse Code message.

Building the message from the Morse Code input

Now that we have completed the initial bad input checks, we will build our message from our input Morse Code.

string[] words = morseCode.Split(_wordSeparator);
StringBuilder result = new();

foreach (string word in words)
{
    string[] letters = word.Split(_letterSeparator);
    if (letters.Any(string.IsNullOrWhiteSpace))
    {
        return new MorseCodeToStringParseResult.Failure(InvalidSpacingBetweenWordsOrLetters);
    }

    foreach (string letter in letters)
    {
        if (_alphabet.TryGetValue(letter, out char character))
        {
            result.Append(character);
        }
        else
        {
            return new MorseCodeToStringParseResult.Failure(InvalidMorseCodeCharacter);
        }
    }

    result.Append(_letterSeparator);
}

To begin we split up the Morse Code message into words, by using the string Split method, along with the Morse Code _wordSeparator variable we declared earlier. That leaves us with an array of potential morse code words.

As mentioned in Convert a String to Morse Code in C#, a StringBuilder is generally faster and uses less memory, therefore we declare one named result.

Checking for invalid spaces between Morse Code letters

We then move on to checking for invalid spaces between Morse Code letters in our Morse Code words.

foreach (string word in words)
{
    string[] letters = word.Split(_letterSeparator);
    if (letters.Any(string.IsNullOrWhiteSpace))
    {
        return new MorseCodeToStringParseResult.Failure(InvalidSpacingBetweenWordsOrLetters);
    }

Next we begin to loop through each Morse Code word in our input message, and for each iteration of the loop we subsequently split each letter within the Morse Code word string into its own array named letters, which is made up of individual Morse Code letters, using the _letterSeparator variable we defined earlier.

After that we perform another check that there is no extra spaces between words, but checking that any of the letter members is not null or whitespace, using the string.IsNullOrWhiteSpace method and LINQ Any method. If there is extra whitespace between our Morse Code letters, we return an error and exit the TryParse method.

This works, because imagine if you split a Morse Code message with an extra space between two words. When you split via the _letterSeparator which is itself a space character, you will end up with one of the elements of your array as an empty string.

Parsing each Morse Code letter

Next we move on to parsing each Morse Code letter in our Morse Code word.

foreach (string letter in letters)
{
    if (_alphabet.TryGetValue(letter, out char character))
    {
        result.Append(character);
    }
    else
    {
        return new MorseCodeToStringParseResult.Failure(InvalidMorseCodeCharacter);
    }
}

result.Append(_letterSeparator);

We loop through each letter in the input Morse Code word, and then check if the string exists in the alphabet we defined earlier. If there is no match for the Morse Code string within our alphabet, we return an error and exit the method.

If the Morse Code string is in the alphabet via a true result from the dictionary TryGetValue method, then we append the corresponding string character from the alphabet dictionary (stored in the character variable) to the result StringBuilder variable.

After looping through each letter in the Morse Code word in the loop, the final step is to append a letter separator (space character) to the end of the parsed message, so that each parsed Morse Code word is separated by a space. We could have defined a word separator variable, but as it is same character as the letter separator, we reused the variable.

Putting the result together

At this point we have exited both the word and letter loops, and all is left to do is to return the parsed Morse Code string.

return new MorseCodeToStringParseResult.Success(result.ToString().TrimEnd());

For string builders it is easy to get a resulting string as we can just call its ToString method. We are doing this, along with returning a new success result.

You may have noticed we have added a TrimEnd method call to the end of the StringBuilder ToString method. We do this because of the result.Append(_letterSeparator) we described earlier. If you think about it, after the last Morse Code word is parsed into a string, there would be an extra space added to the end. To prevent that extra space, we use TrimEnd, which removes all whitespace after a string.


Summary

We hope you have enjoyed implementing a Morse code parser in C#. This fun exercise can be used to reinforce practical knowledge of encoding, validation, data structures, and clean coding.

Here are some key learning outcomes are summarised below:

  • Control flow: Managed conversion logic with clear branching and error handling
  • Dictionaries: Practiced Dictionary for efficient lookups
  • Encoding basics: Learned string to character mapping via Morse code
  • Performance: Used StringBuilder for efficient string construction
  • String handling: Used Split, Trim, and TrimEnd effectively
  • Validation: Applied thorough checks for input correctness

Before you go, please check out our sibling article on Converting C# strings to Morse Code.

Thank you for visiting!


Full implementation code

Below you can find the implementation for the Morse Code to C# parser.

Morse Code to C# String parser

internal static class MorseCodeToStringParser
{
    private static readonly Dictionary<string, char> _alphabet = new()
    {
        [".-"]    = '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',
        ["-----"] = '0',
        [".----"] = '1',
        ["..---"] = '2',
        ["...--"] = '3',
        ["....-"] = '4',
        ["....."] = '5',
        ["-...."] = '6',
        ["--..."] = '7',
        ["---.."] = '8',
        ["----."] = '9'
    };

    private const char _letterSeparator = ' ';
    private const string _wordSeparator = " / ";

    public static MorseCodeToStringParseResult TryParse(string? morseCode)
    {
        if (morseCode is null)
        {
            return new MorseCodeToStringParseResult.Failure(IsNull);
        }

        if (morseCode == string.Empty)
        {
            return new MorseCodeToStringParseResult.Failure(IsEmpty);
        }

        if (string.IsNullOrWhiteSpace(morseCode))
        {
            return new MorseCodeToStringParseResult.Failure(IsWhitespace);
        }

        if (morseCode.Trim().Length != morseCode.Length)
        {
            return new MorseCodeToStringParseResult.Failure(StartOrEndingSpaces);
        }

        string[] words = morseCode.Split(_wordSeparator);
        StringBuilder result = new();

        foreach (string word in words)
        {
            string[] letters = word.Split(_letterSeparator);
            if (letters.Any(string.IsNullOrWhiteSpace))
            {
                return new MorseCodeToStringParseResult.Failure(InvalidSpacingBetweenWordsOrLetters);
            }

            foreach (string letter in letters)
            {
                if (_alphabet.TryGetValue(letter, out char character))
                {
                    result.Append(character);
                }
                else
                {
                    return new MorseCodeToStringParseResult.Failure(InvalidMorseCodeCharacter);
                }
            }

            result.Append(_letterSeparator);
        }

        return new MorseCodeToStringParseResult.Success(result.ToString().TrimEnd());
    }
}

Part of the Fun Exercises series
Filed under Extras, and tagged under Characters

View the source code for this article on GitHub