Convert to and from Morse Code in C#

7 minutes

Table of Contents

Introduction

Converting text to Morse code and back again 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 converter from scratch in C#, exploring both the encoding and decoding logic, and showing how even a lightweight exercise like this can deepen your understanding of the C# language. Above all, it is fun fun fun!


Requirements

As mentioned in Parsing Integers Manually article, having strong requirements defind up front can save you time and effort later, and avoid costly refactoring. Therefore we need to come up with the requirements for converting C# strings to and from Morse code, whilst accounting for edge cases and quirks.

Morse Code dictionary

Before we start, we need to know how to represent text as Morse Code, 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.

String symbolMorse Code 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----.

Converting String to Morse Code requirements

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

  • Only characters from A-Z, a-z, or 0-9 are valid as input
  • Space between words shall consist of a single space character
  • Extra whitespace before or after words is not valid
  • Empty input is not valid
  • Null input is not valid

Converting Morse Code to String requirements

Below we have listed the requirements for converting a 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 single a 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 converting a C# String to and from Morse Code, we can define our test cases below.

Valid C# String to Morse Code test cases

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

Test caseTest inputExpected output
Single letterA.-
Multiple letters with spacesA B C D.- / -... / -.-. / -..
Common phraseHELLO WORLD.... . .-.. .-.. --- / .-- --- .-. .-.. -..
Extended sentenceTHERE IS NO SPOON- .... . .-. . / .. ... / -. --- / ... .--. --- --- -.

Invalid C# String to Morse Code test cases

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

Test caseTest inputExpected output
Null inputnullInput cannot be null.
Empty string""Input cannot be empty.
Invalid number character.1Invalid character(s) found in Morse code sequence.
Invalid alphabetic character... aInvalid character(s) found in Morse code sequence.
Invalid special character..;.Invalid character(s) found in Morse code sequence.
Whitespace on both sides .... Input cannot start or end with whitespace.
Whitespace in prefix ....Input cannot start or end with whitespace.
Whitespace in suffix.... Input cannot start or end with whitespace.
Whitespace in between letters.  .Invalid character(s) found in Morse code sequence.
Extra whitespace in between words....  /  ....Extra whitespace between Morse code words is not allowed.
Whitespace only    Input cannot consist only of whitespace.

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 caseMorse code inputExpected 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 a invalid.

Test caseMorse code inputExpected error
Null inputnullInput 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... aInvalid character(s) found in Morse code sequence.
Invalid number character.1Invalid character(s) found in Morse code sequence.
Invalid special character..;.Invalid character(s) found in Morse code sequence.

Converting the test cases to C#

String to Morse Code C# test cases

public sealed class StringToMorseCodeConverterTests
{
    [Theory]
    [MemberData(nameof(StringToMorseCodeTestCases.ValidMessages), MemberType = typeof(StringToMorseCodeTestCases))]
    public void For_StringToMorseCodeConverter_When_TryConvert_With_Valid_MorseCode_Then_Return_Expected_Message(string message, string expectedMessage)
    {
        StringToMorseCodeConverterResult.Success expectedResult = new(expectedMessage);
        StringToMorseCodeConverterResult actualResult = StringToMorseCodeConverter.TryConvert(message);
        Assert.Equal(expectedResult, actualResult);
    }

    [Theory]
    [MemberData(nameof(StringToMorseCodeTestCases.InvalidMessages), MemberType = typeof(StringToMorseCodeTestCases))]
    public void For_StringToMorseCodeConverter_When_TryConvert_With_Invalid_MorseCode_Then_Return_Error_Message(string? message, string expectedErrorMessage)
    {
        StringToMorseCodeConverterResult.Failure expectedResult = new(expectedErrorMessage);
        StringToMorseCodeConverterResult actualResult = StringToMorseCodeConverter.TryConvert(message);
        Assert.Equal(expectedResult, actualResult);
    }
    
    private static class StringToMorseCodeTestCases
    {
        public static readonly TheoryData<string, string> ValidMessages = new()
        {
            { "A", ".-" },
            { "A B C D", ".- / -... / -.-. / -.." },
            { "HELLO WORLD", ".... . .-.. .-.. --- / .-- --- .-. .-.. -.." },
            { "THERE IS NO SPOON", "- .... . .-. . / .. ... / -. --- / ... .--. --- --- -." }
        };

        public static readonly TheoryData<string?, string> InvalidMessages = new()
        {
            { null, IsNull },
            { InvalidMessage.ExtraWhitespaceInBetweenWords, ExtraWhitespaceBetweenWords },
            { InvalidMessage.Empty, IsEmpty },
            { InvalidMessage.InvalidCharacters, InvalidCharacters },
            { InvalidMessage.WhitespaceOnly, IsWhitespace },
            { InvalidMessage.WhitespacePrefix, StartOrEndingSpaces },
            { InvalidMessage.WhitespaceSuffix, StartOrEndingSpaces },
            { InvalidMessage.WhitespaceBothSides, StartOrEndingSpaces }
        };
    }
}

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 C# String to Morse Code converter

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

Defining the map between C# characters and Morse Code strings

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

private static readonly Dictionary<char, string> _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 char, and the value is of the type string.

Define character and word constants

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

private const char _morseCodeLetterSeparator = ' ';
private const string _morseCodeWordSeparator = " / ";
private const string _wordSeparator = " ";
  • _morseCodeLetterSeparator is used to denote the character separating Morse Code letters
  • _morseCodeWordSeparator is used to denote the characters separating Morse Code words
  • _wordSeparator is used to denote the character separating the words in the input

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.


Summary


Full implementation code

Below you can find the full implementations of both the C# String to Morse Code converter and Morse Code to C# parser.

C# String to Morse Code Converter

internal static class StringToMorseCodeConverter
{
    private static readonly Dictionary<char, string> _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 _morseCodeLetterSeparator = ' ';
    private const string _morseCodeWordSeparator = " / ";
    private const string _wordSeparator = " ";

    public static StringToMorseCodeConverterResult TryConvert(string? message)
    {
        if (message is null)
        {
            return new StringToMorseCodeConverterResult.Failure(IsNull);
        }
        
        if (message == string.Empty)
        {
            return new StringToMorseCodeConverterResult.Failure(IsEmpty);
        }
        
        if (string.IsNullOrWhiteSpace(message))
        {
            return new StringToMorseCodeConverterResult.Failure(IsWhitespace);
        }

        if (message.Trim().Length != message.Length)
        {
            return new StringToMorseCodeConverterResult.Failure(StartOrEndingSpaces);
        }
        
        string[] words = message.Split(_wordSeparator);
        if (words.Any(string.IsNullOrWhiteSpace))
        {
            return new StringToMorseCodeConverterResult.Failure(ExtraWhitespaceBetweenWords);
        }
        
        StringBuilder result = new();

        foreach (string word in words)
        {
            IList<string> morseCodeWord = new List<string>();
            
            foreach (char letter in word)
            {
                char normalisedLetter = char.ToUpperInvariant(letter);
                if (!_alphabet.TryGetValue(normalisedLetter, out var morseCodeCharacter))
                {
                    return new StringToMorseCodeConverterResult.Failure(InvalidCharacters);
                }

                morseCodeWord.Add(morseCodeCharacter);
            }

            result.Append(string.Join(_morseCodeLetterSeparator, morseCodeWord));

            if (word != words.Last())
            {
                result.Append(_morseCodeWordSeparator);
            }
        }
        
        return new StringToMorseCodeConverterResult.Success(result.ToString().TrimEnd());
    }
}

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 Strings

View the source code for this article on GitHub