Month: February 2022

NPR Sunday Puzzle – Language on 3 Adjacent Phone Keys – Solved in Python

Posted on Updated on

Link to the NPR puzzle.

This week’s challenge: What language in seven letters can be spelled using the letters on three consecutive keys on a telephone? It’s a language you would probably recognize, but not one that many people can speak.

This is an easy and fun puzzle that only requires basic tools to solve. I elected to solve the puzzle using Python. Some of the language features used include:

  • String handling
    • String length
    • strip method
    • casefold method
  • Dictionaries
  • Looping
  • Basic logic and arithmetic operations
  • File reading

Basic Approach

  1. Build a dictionary linking alphabetic letters to keyboard numbers
  2. Open the file containing language names
  3. Loop through each line in the file
  4. Use the strip method to make sure the language name has no trailing blanks or newlines
  5. If the language name has length other than 7, go to the next line
  6. Create two integer variables to reflect the highest and lowest key used by the language, ‘minKey‘ and ‘maxKey
    • Consider how to type the language ‘Klingon’ on your keypad:
    • The highest key is 6, the lowest key is 4. Because K and I are on key 4, while O and N are on key 6
    • We know that Klingon is a valid solution because it uses keys 4, 5, 6, which form a set of 3 adjacent keys, as required by the puzzle
  7. Now, loop through each letter in the language name
  8. Use our dictionary to look up the key for each letter
  9. If the current key is lower than the lowest so far, update minKey
  10. Likewise, update maxKey if the current key is higher than the previous max
  11. If the difference between minKey and maxKey is greater than 2, move to the next line
  12. Finally, after processing every letter in the language, if we haven’t disqualified it, print the language name to the console

Now, let’s look at each of those steps along with the code to implement it. We’ll start with building the dictionary.

Code to Build the Dictionary

import string
# Build a dictionary whose key is the letter and whose value is the keypad key
# For example, keyDic['a'] = 2 because letter 'a' is on key 2
# Another example, keyDic['s'] = 7 because letter 'S' is on key 7
keyDic = {}
key = 0
for lc in string.ascii_lowercase:
    # insert an entry into the dictionary, note that // performs integer division
    keyDic[lc] = 2 + (key // 3)

    # There are letters (PQRS) on key 7 and also 4 on key 9 (WXYZ)
    if lc != 'r' and lc != 'y':
        key += 1

Interpretation: Build an empty dictionary with this line: “keyDic = {}”. The loop iterates the range consisting of the lower case ascii letters, ‘a’ through ‘z’. We are able to do this by importing string (note the first line of code). Note that integer division ignores fractional values, so 3.333 becomes 3.

We add new entries to the dictionary with this statement: “keyDic[lc] = 2 + (key // 3)”

The dictionary looks like this, note the 4 entries sharing value 4 (PQRS) and the 4 entries sharing value 9 (WXYZ):

{'a': 2,
'b': 2,
'c': 2,
'd': 3,
'e': 3,
'f': 3,
'g': 4,
'h': 4,
'i': 4,
'j': 5,
'k': 5,
'l': 5,
'm': 6,
'n': 6,
'o': 6,
'p': 7,
'q': 7,
'r': 7,
's': 7,
't': 8 ,
'u': 8,
'v': 8,
'w': 9,
'x': 9,
'y': 9,
'z': 9 }

Interpretation: look at the first line. The key is ‘a’, its associated value is 2, because a is on the keboard pad for 2.

Code to Open the Language File and Loop Through Each Line

    # This file contains 472 languages from Wikipedia, including constructed languages:
    fPath = "C:\\Users\\Randy\\Documents\\LearnPython\\LanguagList.txt"
    fHandle = open(fPath, "r", encoding='utf-8')

    # Loop handles one language each pass
    for aLine in fHandle:

Interpretation: the file name is in a variable ‘fPath’. We need double slashes (i.e. ‘\\’) to separate the path because, normally, \ is uses as a prefix for characters that interfere with the quotation marks. Note that I have to specify the file encoding, i.e. utf-8, because some language names have special characters, such as the languages ‘Brežice’ or ‘Kēlen’.

Code to Clean the Line of code

    # Loop handles one language each pass
    for aLine in fHandle:
        # Need to remove the newline character
        cleanLine = aLine.strip()

Check the length of the language

    # Puzzle requirements specifies the language has length seven:
    if len(cleanLine) == 7:

Interpretation: we’re in a loop, we execute the following lines only if the length is seven

Declare and Initialize Variables minKey and maxKey

        #To initialize the minKey/maxKey variables, we use the 1st character
        ltr1 = cleanLine[0].casefold()
        if ltr1 in keyDic:
            # these two variables reflect the highest and lowest keys on the keypad
            # as used by the language name
            minKey = keyDic[ltr1]
            maxKey = keyDic[ltr1]

Interpretation: get the first letter in the language, i.e. ‘cleanLine[0]’. Like every other letter in the language name, we check if it exists in the dictionary before accessing the dictionary value. The ‘casefold’ method converts it to lower case. minKey and maxKey share the same value at first; that will change shortly.

Loop Through the Letters in the Language

            # loop through the letters in the language, after converting to
            # lower case, and skipping the first character, which we already looked at
            for c in cleanLine.casefold()[1:]:
                if c in keyDic:
                    # look up the key for the language letter:
                    key = keyDic[c]
                    if key < minKey:
                        minKey = key
                    if key > maxKey:
                        maxKey = key

Interpretation: we already looked at the first letter and we won’t reprocess it. So we slice the word using indexing, i.e. [1:]. (Look at the end of the first line of code in the sample above). This indexing means ‘give me the slice starting at position 1, through the end of the word’. We check if the letter is in the dictionary and update minKey or maxKey as necessary.

If the difference between minKey and maxKey is greater than 2, move to the next line

                    # The puzzle says the language can be spelled by 3 consecutive keys
                    if maxKey - minKey > 2:
                        # Since the diffenece is more than 2, it doesn't qualify
                        # for example, if maxKey = 4 and minKey = 2, then it qualifies
                        # but when maxKey = 5 and minKey = 2, it does not
                        qualifies = False
                        break
                else:
                    # the letter is not on the keypad, probably it is 
                    # a non-standard letter like â, å, -, etc
                    qualifies = False
                    break

Print the Language Name if it Hasn’t Been Disqualified

            if qualifies:
                print(cleanLine)

That’s it! The code loops around after this last statement and processes the next language name. It runs in a couple milliseconds and we get the output shown below:

Terminal Output Showing 2 Language Solutions: Klingon and Kikingo

Get the Code

I uploaded my code (just 67 short lines) to my DropBox account, along with my language file (472 language names from Wikipedia). You can download the code here: get the code here. Note: you will need your own DropBox account (free).