views:

576

answers:

6

Guys,

I have an interview in 2 days and I am having a very hard time finding a solutions for this question: What I want to do is .. for any phone number .. the program should print out all the possible strings it represents. For eg.) A 2 in the number can be replaced by 'a' or 'b' or 'c', 3 by 'd' 'e' 'f' etc. In this way how many possible permutations can be formed from a given phone number. I don't want anyone to write code for it ... a good algorithm or psuedocode would be great.

Thank you

+1  A: 

This is a counting problem, so it usually helps to find a solution for a smaller problem, then think about how it expands to your general case.

If you had a 1 digit phone number, how many possibilities would there be? What if you had 2 digits? How did you move from one to the other, and could you come up with a way to solve it for n digits?

Chris
+1  A: 

This is the popular correspondence table:

d = { '2': "ABC",
'3': "DEF",
'4': "GHI",
'5': "JKL",
'6': "MNO",
'7': "PQRS",
'8': "TUV",
'9': "WXYZ",
}

Given this, or any other d, (executable) pseudocode to transform a string of digits into all possible strings of letters:

def digstolets(digs):
  if len(digs) == 0:
    yield ''
    return
  first, rest = digs[0], digs[1:]
  if first not in d:
    for x in digstolets(rest): yield first + x
    return
  else:
    for x in d[first]:
      for y in digstolets(rest): yield x + y

tweakable depending on what you want to do for characters in the input string that aren't between 2 and 9 included (this version just echoes them out!-).

For example,

print list(digstolets('1234'))

in this version emits

['1ADG', '1ADH', '1ADI', '1AEG', '1AEH', '1AEI', '1AFG', '1AFH', '1AFI', 
 '1BDG', '1BDH', '1BDI', '1BEG', '1BEH', '1BEI', '1BFG', '1BFH', '1BFI',
 '1CDG', '1CDH', '1CDI', '1CEG', '1CEH', '1CEI', '1CFG', '1CFH', '1CFI']

Edit: the OP asks for more explanation, here's an attempt. Function digstolets (digits to letters) takes a string of digits digs and yields a sequence of strings of characters which can be letters or "non-digits". 0 and 1 count as non-digits here because they don't expand into letters, just like spaces and punctuations don't -- only digits 2 to 9 included expand to letters (three possibilities each in most cases, four in two cases, since 7 can expand to any of PQRS and 9 can expand to any of WXYZ).

First, the base case: if nothing is left (string digs is empty), the only possible result is the empty string, and that's all, this recursive call is done, finished, kaput.

If digs is non-empty it can be split into a "head", the first character, and a "tail", all the rest (0 or more characters after the first one).

The "head" either stays as it is in the output, if a non-digit; or expands to any of three or four possibilities, if a digit. In either case, the one, three, or four possible expansions of the head must be concatenated with every possible expansion of the tail -- whence, the recursive call, to get all possible expansions of the tail (so we loop over all said possible expansion of the tail, and yield each of the one, three, or four possible expansions of the head concatenated with each possible expansion of the tail). And then, once again, th-th-that's all, folks.

I don't know how to put this in terms that are any more elementary -- if the OP is still lost after THIS, I can only recommend a serious, total review of everything concerning recursion. Removing the recursion in favor of an explicitly maintained stack cannot simplify this conceptual exposition -- depending on the language involved (it would be nice to hear about what languages the OP is totally comfortable with!), recursion elimination can be an important optimization, but it's never a conceptual simplification...!-)

Alex Martelli
Hi Alex,Could you please explain me the yield function and what does it do here? I am having a hard time figuring it out.Thanks
Matt
@Matt, `yield` in Python, `yield return` in C#, is the keyword to express "produce this result then continue" (`return` in Python, `yield break` in C#, means "there are no more values to produce").
Alex Martelli
Alex, I am still a little lost.. could you please explain me the logic (algorithm) behind your approach. Thanks!
Matt
@Matt, sure, let me edit the answer to explain exactly the same thing in English then.
Alex Martelli
Thanks a lot for the explanation Alex. I understand the recursion part as well.. but for some reason that 'yield' thing was making me uncomfortable. I was trying to code the same thing in C# but was just wondering how will I call this function and where should I write the print statement.. Could you help me with that too ?? :)
Matt
@Matt, c#'s not my forte, but doesn't `yield return` where I wrote Python's simpler `yield` (and `yield break` where I wrote `return` in Python!) suffice as a translation?
Alex Martelli
A: 

Use recursion and a good data structure to hold the possible characters. Since we are talking numbers, an array of array would work.

char[][] toChar = {{'0'}, {'1'}, {'2', 'A', 'B', 'C'}, ..., {'9', 'W', 'X'. 'Y'} };

Notice that the ith array in this array of arrays holds the characters corresponding to the ith button on the telephone. I.e., tochar[2][0] is '2', tochar[2][1] is 'A', etc.

The recursive function will take index as a parameter. It will have a for loop that iterates through the replacement chars, replacing the char at that index with one from the array. If the length equals the length of the input string, then it outputs the string.

In Java or C#, you would want to use a string buffer to hold the changing string.

function recur(index)
    if (index == input.length) output stringbuffer
    else
        for (i = 0; i < tochar[input[index]].length; i++)
             stringbuffer[index] = tochar[input[index]][i]
             recur(index + 1)
UncleO
this does not seem to be right, can you please re-check it?
Matt
It looks right. The top call is to recur(0). The arrays are 0-based. You'll need to use appropriate syntax to get the ith digit from the input and convert it to an integer, and to set the ith character in the string buffer, instead of array notation like I used. What problems are you finding?
UncleO
A: 

If asked this in an interview, I'd start by breaking the problem down. What are the problems you have to solve?

First, you need to map a number to a set of letters. Some numbers will map to different numbers of letters. So start by figuring out how to store that data. Basically you want a map of a number to a collection of letters.

Once you're there, make it easier, how would you generate all the "words" for a 1-digit number? Basically how to iterate through the collection that's mapped to a given number. And how many possibilities are there?

OK, now the next step is, you've got two numbers and want to generate all the words. How would you do this if you were just gonna do it manually? You'd start with the first letter for the first number, and the first letter for the second number. Then go to the next letter for the second number, keeping the first letter for the first, etc. Think about it as numbers (basically indices into the collections for two numbers which each map to 3 letters):

00,01,02,10,11,12,20,21,22

So how would you generate that sequence of numbers in code?

Once you can do that, translating it to code should be trivial.

Good luck!

Moishe
+1  A: 

Here's what I came up with:

import java.util.*;

public class PhoneMmemonics {

    /**
     * Mapping between a digit and the characters it represents
     */
    private static Map<Character,List<Character>> numberToCharacters = new HashMap<Character,List<Character>>();

    static {
     numberToCharacters.put('0',new ArrayList<Character>(Arrays.asList('0')));
     numberToCharacters.put('1',new ArrayList<Character>(Arrays.asList('1')));
     numberToCharacters.put('2',new ArrayList<Character>(Arrays.asList('A','B','C')));
     numberToCharacters.put('3',new ArrayList<Character>(Arrays.asList('D','E','F')));
     numberToCharacters.put('4',new ArrayList<Character>(Arrays.asList('G','H','I')));
     numberToCharacters.put('5',new ArrayList<Character>(Arrays.asList('J','K','L')));
     numberToCharacters.put('6',new ArrayList<Character>(Arrays.asList('M','N','O')));
     numberToCharacters.put('7',new ArrayList<Character>(Arrays.asList('P','Q','R')));
     numberToCharacters.put('8',new ArrayList<Character>(Arrays.asList('T','U','V')));
     numberToCharacters.put('9',new ArrayList<Character>(Arrays.asList('X','X','Y','Z')));
    }

    /**
     * Generates a list of all the mmemonics that can exists for the number
     * @param phoneNumber
     * @return
     */
    public static List<String> getMmemonics(int phoneNumber) {

     // prepare results
     StringBuilder stringBuffer = new StringBuilder();
     List<String> results = new ArrayList<String>();

     // generate all the mmenonics
     generateMmemonics(Integer.toString(phoneNumber), stringBuffer, results);

     // return results
     return results;
    }

    /**
     * Recursive helper method to generate all mmemonics
     * 
     * @param partialPhoneNumber Numbers in the phone number that haven't converted to characters yet
     * @param partialMmemonic The partial word that we have come up with so far
     * @param results total list of all results of complete mmemonics
     */
    private static void generateMmemonics(String partialPhoneNumber, StringBuilder partialMmemonic, List<String> results) {

     // are we there yet?
     if (partialPhoneNumber.length() == 0) {

      // base case: so add the mmemonic is complete
      results.add(partialMmemonic.toString());
      return;
     }

     // prepare variables for recursion
     int currentPartialLength = partialMmemonic.length();
     char firstNumber = partialPhoneNumber.charAt(0);
     String remainingNumbers = partialPhoneNumber.substring(1);

     // for each character that the single number represents
     for(Character singleCharacter : numberToCharacters.get(firstNumber)) {

      // append single character to our partial mmemonic so far
      // and recurse down with the remaining characters
      partialMmemonic.setLength(currentPartialLength);
      generateMmemonics(remainingNumbers, partialMmemonic.append(singleCharacter), results);
     }
    }
}
enderminh
A: 

A question that comes to my mind is the question of what should 0 and 1 become in such a system? Otherwise, what you have is something where you could basically just recursively go through the letters for each value in the 2-9 range for the simple brute force way to churn out all the values.

Assuming normal phone number length within North America and ignoring special area codes initially there is also the question of how many digits represent 4 values instead of 3 as 7 and 9 tend to get those often unused letters Q and Z, because the count could range from 3^10 = 59,049 to 4^10 = 1,048,576. The latter is 1024 squared, I just noticed.

JB King