Daily Challenge #23 - Morse Code Decoder

dev.to staff on July 24, 2019

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 ... [Read Full]
markdown guide
 

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')
".... . -.-- .--- ..- -.. ."
 

Super simple and really easy to understand, nice!

 

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

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

 

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

 

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
 

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)
 

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
 

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

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
 

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

 

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...

 

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

 
 

I think it would be a lot of fun if it was reading morse code streaming from a service.

code of conduct - report abuse