How do I check whether a string only consists of characters that are keys inside a map?

I’m creating a piece of code that translates a string into Morse code, but I need to check whether the string can be translated into Morse code at all. I’ve got a map that has all the translations, so I figured the most optimal way would be to check whether all the characters in the string appear in this map. Would it somehow be possible to turn this into a regex so I can simply use String.match?/2?

@morse_dict %{
  "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" => "----.",
  "." => ".-.-.-",
  "," => "--..--",
  "?" => "..--..",
  "'" => ".----.",
  "!" => "-.-.--",
  "/" => "-..-.",
  "(" => "-.--.",
  ")" => "-.--.-",
  "&" => ".-...",
  ":" => "---...",
  ";" => "-.-.-.",
  "=" => "-...-",
  "+" => ".-.-.",
  "-" => "-....-",
  "_" => "..--.-",
  "\"" => ".-..-.",
  "$" => "...-..-",
  "@" => ".--.-.",
  " " => "/"
}

Thanks :slight_smile:

You can do it in 3 lines. Split, map, then join

defmodule MorseCode do
  @morse_dict %{
    "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 translate(string) when is_binary(string) do
    characters = String.split(string, "", trim: true)
    maybe_morse = for c <- characters, do: @morse_dict[c]
    if Enum.all?(maybe_morse), do: IO.puts(Enum.join(maybe_morse)), else: IO.puts("Could not translate #{string} to morse code")
  end
end

MorseCode.translate("THIS IS VALID MORSE CODE")
MorseCode.translate("THIS IS <INVALID> MORSE CODE")

Outputs:

-........./...../...-.-.-....-../-----.-...../-.-.----...
Could not translate THIS IS <INVALID> MORSE CODE to morse code
1 Like

I’m not sure if there’s a way to create the regex automatically, but here’s a handwritten one. This is faster than all the versions I could think of (including JonnyCurran’s above) that actually translate the message:

def valid?(str), do: str =~ ~r/^[\w.,?'!\/()&:;=+_\\$@ -]+$/

Here’s a version that does it by generating function clauses from the map at compile-time, and uses binary pattern matching, which is faster than the regex:

for char <- Map.keys(@morse_dict) do
  defp valid_char?(unquote(char)), do: true
end

defp valid_char?(_), do: false

def valid?(<<char::binary-1>>), do: valid_char?(char)
def valid?(<<char::binary-1, rest::binary>>), do: valid_char?(char) and valid?(rest)
2 Likes