Challenge: MORSE_CODE.PY Converting words to morse code and vice versa using dictionaries and that's it.

morse_code = {'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': '-----', ' ': '   '}


def convert_to_morse_code(word):
    string = ''

    for letternum in word:
        if letternum.isalnum():
            string += f'{morse_code[letternum.upper()]} '
        else:
            string += '  '

    return string


def convert_to_word(word):
    string = ''

    if '   ' in word:
        word = word.replace('   ', '&lt;/&lt;')
    if ' ' in word:
        word = word.replace(' ', '&lt;')
    for code in word:
        if code == '/':
            word = word.replace(code, '   ')

    word = word.split('&lt;')

    for code in word:
        for key in morse_code.keys():
            if morse_code[key] == code:
                string += key

    return string


print(convert_to_morse_code('HI MICHAEL'))
print(convert_to_word('.... ..   -- .. -.-. .... .- . .-..'))
The question could benefit from some context. You don't need to answer every one of the following, but it would be helpful for reviewers.


Why have you written a morse code converter?
Is this going to be used by anyone else or is it a personal project? Is it a homework question? From this we can tell if the review should focus on finding security holes, or just annoying bugs.
What will the input look like? Can we assume nobody is going to call convert_to_word with a letters that aren't dot, dash, or space?




def convert_to_morse_code(word):
    ...

def convert_to_word(word):
    ...


This looks a little funny, as they both take a "word", yet neither does. One takes a string which may have complete sentences, and the other takes a string containing morse code. I'd suggest adding a docstring for these functions which gives a brief explanation about the expected input.



if letternum.isalnum():
    string += f'{morse_code[letternum.upper()]} '
else:
    string += '  '


What happens if somebody comes along and helpfully adds some international morse code symbols such as "?" or "+" to the dictionary? This code will ignore those. I think the dictionary should be the source of truth, not isalnum.

if letternum.upper() in morse_code:
    string += morse_code[letternum.upper()] + ' '
else:
    string += '  '


The above possibility shows that letternum is no longer an appropriate name. Lets update it the name to c or char, short for character, which is what it is. The other change I would propose here is to use the dictionary method .get as it has a handy parameter 'default value'. If we set the default value as '  ' (for when the character is not a symbol we know how to write in morse code) this takes the place of the else branch.

for char in word:
    string += morse_code.get(char, ' ') + ' '


I think it would be worth noting in the docstring for this function that unrecognised characters are skipped, and this is the line responsible.



for code in word:
    if code == '/':
        word = word.replace(code, '   ')


In general it is a bad idea to modify the thing you are looping over. It can lead to hard to spot bugs. The other thing to note is that the first time '/' is encountered, all occurrences of it are replaced. This doesn't need to be in a loop.



    for key in morse_code.keys():
        if morse_code[key] == code:
            string += key


This loop does a little more work than is necessary. It will get slower and slower each time a new symbol is added to the dictionary. The performance loss will honestly not be noticeable, but if you think the solution sounds nicer you can try implementing it.

The solution is to build a new map which is the inverse of morse_code, where all the keys become values and all the values become keys.

morse_code = {...}
code_to_char = {code: char for char, code in morse_code.items()}
...

    if code in code_to_char:
        string += code_to_char[code]




There are some small problems to which you will need to decide on the answer.


convert_to_word(convert_to_morse_code("Hello, world!")) outputs 'HELLO WORLD '. Is that ok? Should the output match the input more closely? Why is there trailing space?
convert_to_morse_code("HI LOW") outputs '.... ..   .-.. --- .-- '. Some variations exist, such as using a slash to indicate a space ('.... .. / .-.. --- .--') or seven dots (.... .. ....... .-.. --- .--). Could your code have that as an optional feature?
convert_to_word works on some weird input, and not on others. But it does not ever tell the user when something is wrong. I'd prefer if convert_to_word('--- -, .') failed in some way rather than returning 'OE'.