Telephones in the United States (and perhaps some other places) have letters associated with some of the numbers.

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

No letters are associated with the number 0 or 1.

Your challenge: Given any phone number in seven-digit or ten-digit format, return English words or phrases into which it could be translated using the code above. Spaces do not matter.

So, for example 873-5377 can be translated to “useless” or “trek-err” and possibly a few others depending on your words list.

For the purposes of this contest,

  1. All digits in the phone number must be used and must be translated into letters. This means that phone numbers containing 0s or 1s can not be translated.

  2. If your operating system or programming environment has a built-in words list (/usr/share/dict/words or the equivalent), go ahead and use it. Otherwise, you can assume that a word list has been downloaded to a convenient directory. However, (a) no-preprocessing of the dictionary to limit it to seven and ten letter words or anything like that, and (b) you have to open the dictionary and load the words yourself; you can’t just pass a variable that contains a list of words.

  3. If your dictionary includes proper nouns, it’s OK to have them in your output.

  4. You can not assume that your input is an integer. The number 213-867-5309 might be input as (213) 867-5409 or 2138675309. Strip all non-numerical characters and make sure what’s left is a seven or ten-digit integer.

  5. Your answer can be a function or a complete program, but no snippets.


  1. You may implement a version of the code that replaces rule 1 with a more forgiving test — you may leave up to 2 numbers as numbers while changing all the other numbers to words. If you choose this path, the code must complete a comprehensive search and not do something trivial like leaving the first two numbers as numbers while doing word substitution on the rest. Obviously in this variation, phone numbers with up to two 0s and 1s may be translated.

Using the same test number as above, rule 6 gives us the answers "us-elf-77", "us-ekes-7", and others in addition to the answers above.


Show us what you get for 868-454-6523 and 323-454-6503.


Shortest code wins, but if you’ve implemented rule 6 you can cut your length in half. I also plan to call out interesting solutions or solutions from verbose languages that might have won if their command names were shorter.

  • 3
    \$\begingroup\$ Related. \$\endgroup\$
    – Arnauld
    Apr 1 '19 at 14:35
  • 3
    \$\begingroup\$ I think the scoring section needs to be deleted on this one... not only does it give a bonus, the criterion is not objective therefore this should not be scored by length. \$\endgroup\$ Apr 1 '19 at 18:16
  • 2
    \$\begingroup\$ What do you mean by "you have to open the dictionary and load the words yourself; you can’t just pass a variable that contains a list of words."? Is a function argument that contains the words enough or do we need to read from an I/O stream? \$\endgroup\$ Apr 1 '19 at 18:21
  • 3
    \$\begingroup\$ @MichaelStern Simply because anyone reading from the linux dictionary may have a shorter byte-count than someone trying to encode words or something. Nobody is writing code that can be objectively compared for this challenge because we may be using different word sources. Therefore the inputs and outputs for each piece of code are not the same, which is basically saying it's not explicitly objective. 6 exacerbates this. \$\endgroup\$ Apr 1 '19 at 18:32
  • 4
    \$\begingroup\$ I like the idea of this, but it feels like there's too many different things going into it here, and having test cases without given outputs doesn't help. It'd be one thing if this was take a phone number in the most convenient format and check a list of phrases from input to see if they fit, but this is clean up and validate a phone number, load a dictionary from the file system, check the number against every possible phrase from it, and output everything that fits, with a -50% bonus for allowing 1 or 2 numbers to be left out of the phrase. Again, I like the idea, but it needs trimming. \$\endgroup\$ Apr 1 '19 at 18:53

05AB1E, 207 bytes


Try it online!

(1) The first line of the code, will replace each number in the input with the corresponding set of possible letters.

þ                                              # Only digits of input.
 "! ! ABC DEF GHI JKL MNO PQRS TUV WXYZ"       # Push keypad letters.
                                        S#     # Split into list and on spaces.
                                          sS   # Split input into digits.
                                            èU # Push indexes of digits and store in X.

(2) The second line of the code, will push every possible dictionary word that is stored in 05AB1E. It accomplishes this by pushing all pairings of the dictionary encrypted characters and converting them to their dictionary stored equivalents. There are 100 unique dictionary characters, and 100*100 unique pairs of them. This means we are getting access to a total of 10,000 words from the 05AB1E dictionary.

(3) The third line of the code, will filter the dictionary by the length of the number of digits in the input.

(4) The fourth line of the code, will filter again the words from the dictionary which match the filter of possible letters outlined by line #1.

(5) The final line of the code, will simply return an empty array provided that the input was not 7 or 10 digits long after parsing.

Using no external libraries/files, this code can detect any word that is in the 05AB1E dictionary. More specifically, if the input is limited to 7 characters the following list:


For 10 characters, you'd be able to detect:


It works for other lengths too, but I thought I'd specifically mention the ones within the spec that it will indeed work for. For example 227 results in:

["ABS", "BAR", "BBS", "CAP", "CAR", "CAS", "CBS"]

If the winner of this challenge is being chosen by shortest code, I can make this 140 bytes shorter if needed.

  • \$\begingroup\$ Cool, that's better :D \$\endgroup\$ Apr 1 '19 at 18:17
  • 1
    \$\begingroup\$ @JonathanAllan "Yugoslavia" is a better example word anyway :). \$\endgroup\$ Apr 1 '19 at 18:19
  • \$\begingroup\$ Oh, 227 should not result in any words - again point (4) - "Strip all non-numerical characters and make sure what’s left is a seven or ten-digit integer." - not sure what this means for leading zeros. \$\endgroup\$ Apr 1 '19 at 18:21
  • 1
    \$\begingroup\$ @JonathanAllan fixed as well. \$\endgroup\$ Apr 1 '19 at 18:30
  • 1
    \$\begingroup\$ Using A…!!ÿ•eŒi•5в£€S as keypad letters and lower-case saves 27 bytes. \$\endgroup\$
    – Emigna
    Apr 1 '19 at 19:23

C# (Visual C# Interactive Compiler), 217 bytes

n=>n.Where(c=>c>47&c<58).Select(c=>", , abc def ghi jkl mno pqrs tuv wxyz".Split()[c-48]).Aggregate(new[]{""},(a,b)=>a.SelectMany(c=>b.Select(d=>c+d)).ToArray()).Where(x=>File.ReadAllText("a.txt").Contains($@"

Expects the dictionary in a file named a.txt.

Try it online!

  • \$\begingroup\$ Why not just store it in a file called "a"? \$\endgroup\$ Apr 1 '19 at 16:59
  • \$\begingroup\$ @ExpiredData Not enough storage xD \$\endgroup\$
    – Gymhgy
    Apr 1 '19 at 17:02
  • \$\begingroup\$ No I mean... 213 bytes \$\endgroup\$ Apr 1 '19 at 17:09
  • \$\begingroup\$ Does this deal with (4) from the question (throwing away non digit characters and only returning any results if there are exactly 10 or 7 digits)? \$\endgroup\$ Apr 1 '19 at 18:07
  • \$\begingroup\$ @JonathanAllan See the where statement in the beginning? \$\endgroup\$
    – Gymhgy
    Apr 1 '19 at 18:25

VDM-SL, 313 395 385 289 278 bytes

f(i)==let m={'2'|->"abc",'3'|->"def",'4'|->"ghi",'5'|->"jkl",'6'|->"mno",'7'|->"pqrs",'8'|->"tuv",'9'|->"wxyz"},d=IO`freadval[set of ?],q=[i(p)|p in set inds i&i(p) in set dom m]in let r in set d("f").#2 be st forall x in set inds q&len(q)=len(r)and r(x)in set elems m(q(x))in r

Turns out IO is easier than I thought!

Still expensive because of building the map (I'm sure there'll be a good map comprehension for it I've just not thought of it yet) and could save further bytes if the string was guaranteed to only contain values in the domain of the map


let m=...,                           /* m is a map of char to seq of char */
d=IO`freadval[set of ?]            /* d is the file read function that will read a set of something */ 
q=[i(p)|p in set inds i&i(p) in set dom m] /* q is a sequence comprehension defined by 
                                             take the values in i where i is in the domain 
                                             of our map m */
let r in set d("f").#2                    /* let r be a value in my dictionary of words (read from the file f) */ 
be st                                     /* such that */ 
forall x in set inds q&len(q)=len(r)      /* the length of q and r are the same */
and r(x)in set elems m(q(x))              /* for every char in q the char in 
                                                  r at that position is in the range of the 
                                                  map for that char in q */
in r         /* return r*/
  • \$\begingroup\$ This language is new to me; what answers do you get for 323 454-6503 and 868-454-6523? \$\endgroup\$ Apr 1 '19 at 16:09
  • \$\begingroup\$ I'll post an explanation soon. Surely answers are dependent on the dictionary? \$\endgroup\$ Apr 1 '19 at 16:11
  • \$\begingroup\$ Yes, any comprehensive answer depends on the dictionary used but I have no way to test your code except by asking for representative output. There are some answers that are going to appear for any reasonable dictionary, and if the code produces words that couldn’t emerge from these numbers then we know there’s a problem. \$\endgroup\$ Apr 1 '19 at 16:15
  • \$\begingroup\$ @MichaelStern It will take a long time to terminate for those inputs with a none toy dictionary, since it scans every value in the dictionary to test whether it is a valid fit for all characters in the input. It'd be better if you gave test cases as valid/invalid so I can construct a dictionary of the valid and invalid and show it gives correct values for each \$\endgroup\$ Apr 1 '19 at 16:31
  • \$\begingroup\$ Does this deal with (4) from the question (throwing away non digit characters and only returning any results if there are exactly 10 or 7 digits)? \$\endgroup\$ Apr 1 '19 at 18:06

Not the answer you're looking for? Browse other questions tagged or ask your own question.