Thanks! That was a fun problem for a newbie. It was simple enough
to come up with a basic solution pretty rapidly, with several
opportunities to refine and experiment.
-Mark
Ruby Q. #121: Morse Code
The example Morse code from the quiz is: …—…-…-
MORSE_SYMS: A dictionary mapping letters to their morse code.
MORSE_SYMS = { “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”, “–…” }
morse_words: Given a string of Morse code (s), return an array
of strings of letters which have the same morse code.
The technique is recursive: attempt to match the start of the
Morse code string to the Morse code for each letter. If it
matches, then return an array of words starting with that letter,
and followed by all possible strings made up from the remaining
Morse code.
def morse_words(s)
words = []
MORSE_SYMS.each_pair do |letter, morse|
#
# If the remaining string exactly matches a letter, then
# add that letter (by itself) as a result. This is how the
# recursion “bottoms out.”
#
if s == morse
words << letter
next
end
#
# Does the Morse code for the current letter match the start
# of the Morse code string?
#
l = morse.length
if s[0,l] == morse
#
# Generate the possibilities starting with the current letter.
#
morse_words(s[l..-1]).each do |w|
words << letter+w
end
end
end
return words
end
to_morse: Convert a word into its Morse code equivalent by splitting
the word into individual letters, mapping each letter, and rejoining
them with the given separator.
def to_morse(word, sep="|")
word.scan(/./).map { |c| MORSE_SYMS[c] }.join(sep)
end
if FILE == $0
The command line arguments are paths to files containing
dictionary words separated by whitespace. For example,
pass /usr/share/dict/words.
dictionary = []
ARGV.each do |path|
# puts “Reading #{path}…”
dictionary.concat(open(path).read.split)
end
The following words were translations from the example Morse
code string, but not in my dictionaries, so add them so we
can test the “word in dictionary” case.
dictionary << “sofia”
dictionary << “eugenia”
Read Morse code strings from standard input, and print possible
words to standard output.
$stdin.each do |morse|
words = morse_words(morse.chomp)
words.sort! # Sorted output is easier to scan
#
# Find the length of the longest Morse code string
# so that we can line up columns nicely.
#
morse_max = words.map { |w| to_morse(w).length }.max
#
# Separate the words into two lists: those words which are
# in the dictionary, and those that aren't.
#
dict_words = words & dictionary
non_dict_words = words - dict_words
#
# Print the words. I chose to print the dictionary words
# last, so they'd be easier to spot.
#
puts "Non-dictionary words:"
non_dict_words.each do |word|
print to_morse(word).ljust(morse_max), " => ", word, "\n"
end
puts "Dictionary words:"
dict_words.each do |word|
print to_morse(word).ljust(morse_max), " => ", word, "\n"
end
end
end