DEV Community

dev.to staff
dev.to staff

Posted on

Daily Challenge #23 - Morse Code Decoder

Using the power of programming, we've translated Pig Latin and told humans the time. Now for this challenge, you have to write a simple Morse code decoder. Today's challenge comes from user jolaf on CodeWars

Your task is to implement a function that would take the Morse code as input and return a decoded human-readable string.

For example:
decodeMorse('.... . -.-- .--- ..- -.. .')
should return "HEY JUDE"


C++/Go/JavaScript/PHP/Python/Ruby/TypeScript:MORSE_CODE['.--']
C#:MorseCode.Get(".--") (returns string)
Elixir:morse_codes variable
Elm:MorseCodes.get : Dict String String
Haskell:morseCodes ! ".--" (Codes are in a Map String String)
Java:MorseCode.get(".--")
Kotlin:MorseCode[".--"] ?: "" or MorseCode.getOrDefault(".--", "")
Rust:self.morse_code
Scala: morseCodes(".--")

NOTE: For coding purposes, you should use ASCII characters . and -, not Unicode characters.

I always thought it would be really cool to make a full-size Morse code decoder, taking audio as input and reading aloud the output. Perhaps I could start building it as a side project - it would give me an excuse to buy another Raspberry Pi.

Good luck, happy coding!


Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge for a future post? Email yo+challenge@dev.to with your suggestions!

Top comments (15)

Collapse
 
ynndvn profile image
La blatte • Edited

JS does not have any builtin morse library, therefore we need to build it in order to use a simple oneliner:

m = {
  '.-': '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
};
decodeMorse=(s)=>s.split(' ').map(e=>m[e]||'?').join('')

And the result is:

decodeMorse('.... . -.-- .--- ..- -.. .')
"HEYJUDE"

// Unknown values are handled
decodeMorse('...... . -.-- .--- ..- -.. .')
"?EYJUDE"

// And it doesn't affect "falsey" values
decodeMorse('...... ----- -.-- .--- ..- -.. .')
"?0YJUDE"

And here goes the encoder, using the same m variable as above

encodeMorse=(s)=>Object.entries(m).map(e=>s=s.replace(RegExp(e[1],'gi'),' '+e[0]))&&s.slice(1)

The encoder produces the following result:

encodeMorse('HEYJUDE')
".... . -.-- .--- ..- -.. ."

encodeMorse('heyjude')
".... . -.-- .--- ..- -.. ."
Collapse
 
coreyja profile image
Corey Alexander

Rust Solution!

Decided to make functions that can go in both directions! So we can both encode and decode worse code!

It's gonna just panic on invalid input at the moment, but it should probably return a Result type but I was feeling lazy in this department

#[macro_use]
extern crate lazy_static;

use std::collections::HashMap;

lazy_static! {
    static ref MORSE_TO_CHAR: HashMap<&'static str, char> = {
        let mut m = HashMap::new();
        m.insert(".-", 'A');
        m.insert("-...", 'B');
        m.insert("-.-.", 'C');
        m.insert("-..", 'D');
        m.insert(".", 'E');
        m.insert("..-.", 'F');
        m.insert("--.", 'G');
        m.insert("....", 'H');
        m.insert("..", 'I');
        m.insert(".---", 'J');
        m.insert("-.-", 'K');
        m.insert(".-..", 'L');
        m.insert("--", 'M');
        m.insert("-.", 'N');
        m.insert("---", 'O');
        m.insert(".--.", 'P');
        m.insert("--.-", 'Q');
        m.insert(".-.", 'R');
        m.insert("...", 'S');
        m.insert("-", 'T');
        m.insert("..-", 'U');
        m.insert("...-", 'V');
        m.insert(".--", 'W');
        m.insert("-..-", 'X');
        m.insert("-.--", 'Y');
        m.insert("--..", 'Z');
        m.insert("-----", '0');
        m.insert(".----", '1');
        m.insert("..---", '2');
        m.insert("...--", '3');
        m.insert("....-", '4');
        m.insert(".....", '5');
        m.insert("-....", '6');
        m.insert("--...", '7');
        m.insert("---..", '8');
        m.insert("----.", '9');
        m
    };
}

lazy_static! {
    static ref CHAR_TO_MORSE: HashMap<char, &'static str> = {
        let mut m = HashMap::new();

        for (morse, c) in MORSE_TO_CHAR.iter() {
            m.insert(*c, *morse);
        }

        m
    };
}

pub fn encode_morse(morse_code: &str) -> String {
    morse_code
        .chars()
        .map(|c| {
            CHAR_TO_MORSE
                .get(&c)
                .expect("Invalid character for morse code")
                .to_string()
        })
        .collect::<Vec<String>>()
        .join(" ")
}

pub fn decode_morse(morse_code: &str) -> String {
    morse_code
        .split_whitespace()
        .map(|word| {
            MORSE_TO_CHAR
                .get(word)
                .expect("Invalid morse code")
                .to_string()
        })
        .collect::<Vec<String>>()
        .join("")
}

#[cfg(test)]
mod tests {
    use crate::*;

    #[test]
    fn it_works_to_decode_example() {
        assert_eq!(
            decode_morse(".... . -.-- .--- ..- -.. ."),
            "HEYJUDE".to_string()
        );
    }

    #[test]
    fn it_works_to_encode_example() {
        assert_eq!(
            encode_morse("HEYJUDE"),
            ".... . -.-- .--- ..- -.. .".to_string()
        );
    }

    #[test]
    fn it_is_reversible() {
        let input = "SOMETESTSTRING";

        assert_eq!(decode_morse(&encode_morse(input)), input.to_string());
    }
}
Collapse
 
coreyja profile image
Corey Alexander

Meta: @thepracticaldev Might want to remove the
Morse code tables are already preloaded for many languages as a dictionary, feel free to use them:
bit since I think that only applies to the CodeWars solution runner, so isn't as relevant here!

Cause for instance Rust definitely does NOT just have a morse code map available at self.morse_code

Collapse
 
michaeltharrington profile image
Michael Tharrington

Great point! Going to make the fix here shortly. Thanks for the heads up!

Collapse
 
choroba profile image
E. Choroba • Edited

Solved several years ago in a golf challenge. Perl (newlines added for readability):

@m{tc,cwt,ctct,cw,t,wct,et,ww,w,tec,ctc,tcw,e,ct,ec,tet,etc,
tct,wt,c,wc,wtc,te,cwc,cte,ew,eec,tee,wec,wte,wwc,wwt,cww,
ewt,ecw,eet}=(A..Z,0..9);s/- ?/c/g,s/\. ?/t/g,s/tt/w/g,
s/cc/e/g,s/(\S+) ? ?/$m{$1}/g,y/ //s,print for@ARGV
Collapse
 
ganderzz profile image
Dylan Paulus

Similar solution. Nim.

import strutils, sequtils, sugar, tables

const morseCodeMap = {
  ".-": "A",
  "-.": "N",
  "-...": "B",
  "---": "O",
  "-.-.": "C",
  ".--.": "P",
  "-..": "D",
  "--.-": "Q",
  ".": "E",
  ".-.": "R",
  "..-.": "F",
  "...": "S",
  "--.": "G",
  "-": "T",
  "....": "H",
  "..-": "U",
  "..": "I",
  "...-": "V",
  ".---": "J",
  ".--": "W",
  "-.-": "K",
  "-..-": "X",
  ".-..": "L",
  "-.--": "Y",
  "--": "M",
  "--..": "Z",
  ".----": "1",
  "-....": "6",
  "..---": "2",
  "--...": "7",
  "...--": "3",
  "---..": "8",
  "....-": "4",
  "----.": "9",
  ".....": "5",
  "-----": "0",
}.toTable()

proc morseCodes(input: string): string =
  return input.splitWhitespace().map((x) => morseCodeMap.getOrDefault(x,
      "")).join("")

when isMainModule:
  const code = ".--. . . .--."

  echo morseCodes(code)
Collapse
 
praneetnadkar profile image
Praneet Nadkar

Its a one liner in C# once you have the dictionary set up:

var output = input.Select(c => { c = dictionary[c]; return c; }).ToList();

Complete code:

            var dictionary = new Dictionary<string, string>
            {
                { ".-", "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"}
            };

            var input = Console.ReadLine().Split(' ').ToList();

            var output = input.Select(c => { c = dictionary[c]; return c; }).ToList();

            Console.WriteLine(string.Join("", output));
            Console.ReadKey();
Collapse
 
brightone profile image
Oleksii Filonenko

Obligatory Elixir:

defmodule Morse do
  @morse_to_char %{
    "-----" => "0",
    ".----" => "1",
    "..---" => "2",
    "...--" => "3",
    "....-" => "4",
    "....." => "5",
    "-...." => "6",
    "--..." => "7",
    "---.." => "8",
    "----." => "9",
    ".-" => "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"
  }

  @spec decode(String.t()) :: String.t()
  def decode(morse) do
    morse
    |> String.split()
    |> Enum.map(&Map.get(@morse_to_char, &1, "?"))
    |> Enum.join()
  end
end
Collapse
 
alvaromontoro profile image
Alvaro Montoro

Am I the only one having trouble with the specified preloaded dictionaries?

Collapse
 
coreyja profile image
Corey Alexander

I'm pretty sure this an aggressive copy pasta from the CodeWars site.

Code wars has a solution runner, and I think these are defined in their specific runner. Cause the Rust example is definitely not something that is just globally available in Rust lol

Here is the Codewars link if you want to try it out. I didn't give it a shot so no idea how well it works: codewars.com/kata/decode-the-morse...

Collapse
 
alvaromontoro profile image
Alvaro Montoro

That's half an hour of my life I won't get back.
I should have thought about that sooner :-/

Collapse
 
mwlang profile image
Michael Lang

Ruby Language

The problem definition was initially tripping because it appeared to somehow render "HEY JUDE" without some sort of demarcation that indicated a space between words since HTML rendering removes extra spacing. However, opening and viewing the HTML source revealed there was indeed multiple spaces between the words. In this implementation, two or more spaces represents a "pause" which is the morse standard for recognizing word breaks.

Code with Specs

MORSE_CODE = Hash[*%w/
  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 -----
/].invert.freeze

def decode_morse str
  words = str.to_s.split(/\s{2,}/)
  words.map{|w| w.split(" ").map{|mc| MORSE_CODE[mc]}.join}.join " "
end

require "spec"

describe "#decode_morse" do
  it { expect(decode_morse nil).to eq "" }
  it { expect(decode_morse "").to eq "" }
  it { expect(decode_morse ".").to eq "E" }
  it { expect(decode_morse ". ").to eq "E" }
  it { expect(decode_morse "... --- ...").to eq "SOS" }
  it { expect(decode_morse "- . ... - .. -. --.  .----  ..---  ...--").to eq "TESTING 1 2 3" }
  it { expect(decode_morse ".... . -.-- .--- ..- -.. .").to eq "HEYJUDE" }
  it { expect(decode_morse ".... . -.--   .--- ..- -.. .").to eq "HEY JUDE" }
  it { expect(decode_morse "-- --- .-. ... .    -.-. --- -.. .").to eq "MORSE CODE" }
end

output

>> rspec morse_code.rb
.........

Finished in 0.00551 seconds (files took 0.15391 seconds to load)
9 examples, 0 failures
Collapse
 
matrossuch profile image
Mat-R-Such • Edited

Python solution:

m = {
  '.-': '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
}
def morse_code(a):
    a=a.split(' ')
    return ''.join(m[i]for i in a)

print(morse_code('.... . -.-- .--- ..- -.. .'))