pythonalgorithmrecursionreturnmnemonics

Trying to solve a number to mnemonics problem using recursion


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.


Solution

  • 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:

    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.