Trying to solve a number to mnemonics problem using recursion

Question:

Given a stringified phone number of non-zero length, write a function that returns all mnemonics for this phone number in any order.

`

def phoneNumberMnemonics(phoneNumber, Mnemonics=[''], idx=0):
    number_lookup={'0':['0'], '1':['1'], '2':['a','b','c'], '3':['d','e','f'], '4':['g','h','i'], '5':['j','k','l'], '6':['m','n','o'], '7':['p','q','r','s'], '8':['t','u','v'], '9':['w','x','y','z']}

    if idx==len(phoneNumber):
        return Mnemonics
    else:
        new_Mnemonics=[]
        for letter in number_lookup[phoneNumber[idx]]:
            for mnemonic in Mnemonics:
                new_Mnemonics.append(mnemonic+letter)
        phoneNumberMnemonics(phoneNumber, new_Mnemonics, idx+1)
        

`

If I use the input "1905", my function outputs null. Using a print statement right before the return statement, I can see that the list Mnemonics is

['1w0j', '1x0j', '1y0j', '1z0j', '1w0k', '1x0k', '1y0k', '1z0k', '1w0l', '1x0l', '1y0l', '1z0l']

Which is the correct answer. Why is null being returned?

I am not very good at implementing recursion (yet?), your help is appreciated.

Asked By: trying2learn

||

Answers:

The correct list (Mnemonics) was generated for the deepest call of the recursion. However, it was not passed back to previous calls.

To fix this, the Mnemonics not only needs to be returned in the "else" block, but it also needs to be set to equal the output of the recursive function phone Number Mnemonics.

def phoneNumberMnemonics(phoneNumber, Mnemonics=[''], idx=0):
number_lookup={'0':['0'], '1':['1'], '2':['a','b','c'], '3':['d','e','f'], '4':['g','h','i'], '5':['j','k','l'], '6':['m','n','o'], '7':['p','q','r','s'], '8':['t','u','v'], '9':['w','x','y','z']}

print(idx, len(phoneNumber))
if idx==len(phoneNumber):
    pass
else:
    new_Mnemonics=[]
    for letter in number_lookup[phoneNumber[idx]]:
        for mnemonic in Mnemonics:
            new_Mnemonics.append(mnemonic+letter)
    Mnemonics=phoneNumberMnemonics(phoneNumber, new_Mnemonics, idx+1)
return Mnemonics

I still feel that I’m lacking sophistication in my understanding of recursion. Advice, feedback, and clarifications are welcome.

Answered By: trying2learn

There are different recursive expressions of this problem, but the simplest to think about when you are starting out is a "pure functional" one. This means you never mutate recursively determined values. Rather compute fresh new ones: lists, etc. (Python does not give you a choice regarding strings; they’re always immutable.) In this manner you can think about values only, not how they’re stored and what’s changing them, which is extremely error prone.

A pure-functional way to think about this problem is this:

  • If the phone number is the empty string, then the return value is just a list containing the empty string.

  • Else break the number into its first character and the rest. Recursively get all the mnemonics R of the rest. Then find all the letters corresponding to the first and prepend each of these to each member of R to make a new string (This is called a Cartesian cross product, which comes up often in recursion.) Return all of those strings.

In this expression, the pure function has the form

M(n: str) -> list[str]:

It’s accepting a string of digits and returning a list of mnemonics.

Putting this thought into python is fairly simple:

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

def mneumonics(n: str):
  if len(n) == 0:
    return ['']
  rest = mneumonics(n[1:])
  first = LETTERS_BY_DIGIT[n[0]]
  rtn = []  # A fresh list to return.
  for f in first:  # Cartesian cross:
    for r in rest: #   first X rest
      rtn.append(f + r);  # Fresh string
  return rtn

print(mneumonics('1905'))

Note that this code does not mutate the recursive return values rest at all. It makes a new list of new strings.

When you’ve mastered all the Python idioms, you’ll see a slicker way to code the same thing:

def mneumonics(n: str):
  return [''] if len(n) == 0 else [
    c + r for c in LETTERS_BY_DIGIT[n[0]] for r in mneumonics(n[1:])]

Is this the most efficient code to solve this problem? Absolutely not. But this isn’t a very practical thing to do anyway. It’s better to go for a simple, correct solution that’s easy to understand rather than worry about efficiency before you have a solid grasp of this way of thinking.

As others have said, using recursion at all on this problem is not a great choice if this were a production requirement.

Answered By: Gene