# Create a T9 keyboard

This question asks for a T9 dictionary matching functionality which is a very interesting problem. But T9 has another way of typing and that is typing character-by-character. You would NOT need a dictionary to implement this keyboard.

Here is key-map of a T9 keyboard if you forgot:

+-------+-------+-------+
|   1   |   2   |   3   |
|  .?!  |  ABC  |  DEF  |
+-------+-------+-------+
|   4   |   5   |   6   |
|  GHI  |  JKL  |  MNO  |
+-------+-------+-------+
|   7   |   8   |   9   |
| PQRS  |  TUV  |  WXYZ |
+-------+-------+-------+
|   *   |   0   |   #   |
|   ←   | SPACE |   →   |
+-------+-------+-------+

### How T9 works

To type a character with T9, you need to press number key representing that character n times. n is order of that character written on that key. Numbers are the last character you can type for each key. For example, to type B I press 2 two times, or to type 5 I press 5 four times. To finish typing this character I press #. * is simply backspace. In our version of keyboard there is no capitalization.

### Input and output examples:

8#99999#055#33#999#22#666#2#777#3# → T9 KEYBOARD

Explanation:

• 8 selects T and # moves to next character
• 99999 select last character of 9 key which is 9 and # moves to next charachter
• 0 inserts a space
• 33 selects second character of 3 key which is K and # moves to next character
• And so on...

### Rules

Your function or program should accept a string representing T9 keypresses. Output is the resulting text from those keypresses, as outlined above.

This is basic code golf, so the winner is shortest in bytes, and standard rules/loopholes apply.

• Bonus does not have any effect on score ? Why would I go for it ? – Optimizer Dec 17 '14 at 23:13
• Also, your example T9 KEYBOARD is completely wrong. That one reads T9 JEYBARD – Optimizer Dec 17 '14 at 23:24
• @Mohsen usually, bonuses in code golf will subtract a fixed amount from the score. you'll have to figure out how much is reasonable. for the first bonus probably not more than 10 or 20 bytes. the second bonus, I don't even understand. if I give the sequence of keypresses as a string to the function, how would there be any sort of time between keypresses? I think a more reasonable bonus would be to allow omitting # if consecutive buttons are different anyway. that being said: without that bonus what should happen if # is omitted? – Martin Ender Dec 17 '14 at 23:46
• You need to add a possible byte count benefit for these bonuses. Bonuses are optional but you seem to ask every answer to implement the bonuses as if they are compulsory. Please clear the tone, if they are mandatory, move them to rules, if they are not, do not ask every answer to implement the bonuses. I will wait for a couple of hours for your reply before voting to close as unclear. – Optimizer Dec 18 '14 at 12:57
• No reply even after 18 hours. Voting to close as unclear. – Optimizer Dec 19 '14 at 7:49

# CJam, 109 94 bytes (2nd bonus)

A very naive and long solution

q'#/);{__'*-:A-,_g{){;}*A_}*;'0/{_,g{)~".?~1"a'[,65>292994 5b{/(X):X+\s}%+1:Xm>=\,=}*}%S*1/~}%

This is a full program, although a function will be of the same length.

The input goes into STDIN

Example:

8#99999#055#33#999#***22#666#2#777#3#

Output:

T9 BOARD

Try it online here

• Can you make it work for the first bonus? – Mohsen Dec 18 '14 at 2:07
• @Mohsen Not until there is an actual benefit of going for bonus! Lets say, a 25% code length reduction in final score. – Optimizer Dec 18 '14 at 5:49

# JavaScript ES6, 220-10=210 178 bytes

As a part of Helka's CMC, I've outgolfed my first challenge.

n=>(g=n=>n==(k=n.replace(/.\*/,""))?n:g(k))(n.match(/(\d)\1*|\*/g).map(e=>e<"0"?e:(a=" |.?!|ABC|DEF|GHI|JKL|MNO|PQRS|TUV|WXYZ".split|[+e[0]]+e[0])[~-e.length%a.length]).join)

Sample outputs:

> f=n=>(g=n=>n==(k=n.replace(/.\*/,""))?n:g(k))(n.match(/(\d)\1*|\*/g).map(e=>e<"0"?e:(a=" |.?!|ABC|DEF|GHI|JKL|MNO|PQRS|TUV|WXYZ".split|[+e[0]]+e[0])[~-e.length%a.length]).join)
[Function]
> f("8#99999#055#33#999#***22#666#2#777#3#")
'T9 BOARD'
> f("8#44#33#0#999#*77#88#444#222#55#0#22#777#666#9#66#0#333#666#99#0#5#88#6#7#7777#0#666#888#33#777#0#8#44#33#0#555#2#99#*9999#999#0#3#666#4#111#")
'THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG!'
> f("8#99999#055#33#999#***22#666#2#777#3#")
'T9 BOARD'

## Explanation

(g=n=>n==(k=n.replace(/.\*/,""))?n:g(k))

This implements recursive replacement, replacing all characters followed by * until there are no *s left.

n.match(/(\d)\1*|\*/g)

This matches all runs of consecutive digits, or *s.

a=" |.?!|ABC|DEF|GHI|JKL|MNO|PQRS|TUV|WXYZ".split|[+e[0]]+e[0]

This creates the desired dictionary, obtaining the encoded part from the large string, then appending the desired digit to it.

a[~-e.length%a.length]

This gets the character, modulo a's length.

.join

This prepares the string for processing and removal of *s.

• Can you make it work with the first bonus? – Mohsen Dec 18 '14 at 2:09
• @Mohsen Yes, and that might actual help it. I'll work on it through today and tomorrow. – Conor O'Brien Dec 18 '14 at 2:10
• Please at least do not advertise a score that is incorrect as the answer is not even up to the spec. – Optimizer Dec 18 '14 at 17:13
• @Mohsen It is now working with the first bonus. – Conor O'Brien Dec 18 '14 at 22:49
• t("2#2"); gives B instead of AA. Try to match any # instead of removing them. – Titus Sep 15 '16 at 2:20

# Python, 167 157 151 bytes

(doesn't support '*')

Nothing special. I use regex to convert the input to a list, then i loop the entries. I use the first character and length of each entry to search it in a lookup list:

def f(i):
import re
t9 = [" 0",".?!1","ABC2","DEF3","GHI4","JKL5","MNO6","PQRS7","TUV9","WXYZ9"]
i = re.findall(r'[1-9]+|0+',i)
for j in i:

After some golfing it looks like this:

import re;m=lambda i:"".join([" 0,.?!1,ABC2,DEF3,GHI4,JKL5,MNO6,PQRS7,TUV9,WXYZ9".split(",")[int(j[0])][len(j)-1] for j in re.findall(r'[1-9]+|0+',i)])

No bonuses (yet). I don't know how I would implement the first bonus in regex. The second bonus would add a lot of bytes as the lookup elements are not the same size. Don't really understand the third bonus.

# Perl 5: 106 (104 code + 2 flags)

Modified to handle deletes.

#!perl -lp
s/((\d)\2*)#?|./chr$2*5+length$1/ge;y//d 0-3.?!1 ABC2 DEF3 GHI4 JKL5 MNO6 P-S7TUV8 W-Z9/c;1while s/.?d//

Usage:

perl t9.pl <<<'8#99999#055#33#999#22#666#2#777#3#'
perl t9.pl <<<'899999055339992266627773'

# Perl 5: 88 (86 code + 2 flags)

Old version without star-delete.

#!perl -lp
s/(\d)(\1*)#?/chr$1*5+length$2/ge;y// 0-3.?!1 ABC2 DEF3 GHI4 JKL5 MNO6 P-S7TUV8 W-Z9/c
• @Optimizer tried it, and it indeed doesn't work with *. Is it actually required though? It says: "Note that it can include * for backspace..." – Def Dec 18 '14 at 17:20
• Since its not part of the bonus. Its a mandatory rule. – Optimizer Dec 18 '14 at 17:21
• That being said. the question is very unclear on what's a rule and what's a bonus. I asked OP for clarification several hours ago. If there is no response, I am voting to close this question as unclear. – Optimizer Dec 18 '14 at 17:24
• Sorry, I was misled as current answers in the languages that I can read also do not support *. – nutki Dec 18 '14 at 17:25
• If you're referring to my python answer, you're right. I misinterpreted the question. – Def Dec 18 '14 at 17:30

{split(".?!1-ABC2-DEF3-GHI4-JKL5-MNO6-PQRS7-TUV8-WXYZ9- 0",k,"-");split($0"#",a,"");while(1+(b=a[++i])){if(b==p)++c;else{for(g in k)if(p==substr(k[g],l=length(k[g])))printf(substr(k[g],1+((c-1)%l),1));c=1;p=b}}} This is a full program which read the input from stdin. It would be more efficient to not resplit the keyboard for each line, but it would make the script longer. Also if the "0" key was anything else than 0, the script would be 4 bytes shorter, but that's part of the game :o) # C (245 bytes) #define M "8#44#33#0#999#*77#88#444#222#55#0#22#777#666#9#66#0#333#666#99#0#5#88#6#7#7777#0#666#888#33#777#0#8#44#33#0#555#2#99#*9999#999#0#3#666#4#111#" #include<stdio.h> char K[][4]={" ",".?!","ABC","DEF","GHI","JKL","MNO","PQRS","TUV","WXYZ"},I[]=M;int i,j,k,r;main(){for(;I[i];++i){if(I[i]=='#')I[j++]=K[k][--r],r=k=0;else if(I[i]=='*')j?--j:0;else if(!r++)k=I[i]-'0';}I[j]=0;printf("%s\n",I);} ## Output THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG! ## Explanation The byte count does not include the input string given in the first #define. I use a two-dimensional array as the lookup table for what character to print. The program reads in characters delimited by '#'. For each group, the input number determines the first-dimension array index, and the number of repetitions of the input number determines the second-dimension array index. The '*' moves back the index of the array for the output string so as to overwrite the previous letter. So the input string 44# (1 repetition of '4') is translated to lookup table K[4][1], which is the character H. ## Ungolfed Version #define INPUT "8#44#33#0#999#*77#88#444#222#55#0#22#777#666#9#66#0#333#666#99#0#5#88#6#7#7777#0#666#888#33#777#0#8#44#33#0#555#2#99#*9999#999#0#3#666#4#" #include<stdio.h> static const char keyboard[10][4] = {" ", ".?!", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ"}; int main(void) { char input[] = INPUT; char output[256]; int i, j; int key = 0; int reps = 0; for (i = j = 0; input[i] != '\0'; ++i) { switch (input[i]) { case '#': output[j] = keyboard[key][reps - 1]; ++j; reps = key = 0; break; case '*': if (j > 0) --j; break; default: if (reps == 0) { key = (int)input[i] - '0'; } ++reps; break; } } output[j] = '\0'; printf("%s\n", output); return(0); } # Ruby 254, 248, 229 bytes Golfed: n=->(t){r,m,b=[]," _.?!1_ABC2_DEF3_GHI4_JKL5_MNO6_PQRS7_TUV8_WXYZ9_*_0_#".split("_"),nil;t.scan(/((.)\2*)/){|l,_|(!(l=~/\#/)?(l=~/\*/?(r.pop l.size):(l=="00"?r<<(b ? "0 ":" 0"):(c=m[l[0].to_i];r<<c[l.size%c.size-1]))):b=l)};r*""} Ungolfed: def t9totext(t) bonq = nil numpad = [" ",".?!1","ABC2","DEF3","GHI4","JKL5","MNO6","PQRS7","TUV8","WXYZ9","*","0","#"] r = [] t.scan(/((.)\2*)/) do |l, _| if !(l =~ /\#/) if l =~ /\*/ r.pop(l.size) elsif l == "00" r << (bonq ? "0 " : " 0") else c = numpad[l[0].to_i] r << c[l.size % c.size - 1] end else bonq = l end end r.join end All these specs should succeed: it "outputs the correct word" do expect(n.call('8#99999#055#33#999#22#666#2#777#3#1')).to eq("T9 KEYBOARD.") expect(n.call('4433555#55566609666666677755533*3111')).to eq("HELLO WORLD!") expect(n.call('7##222#222**7#222#4')).to eq('PPCG') expect(n.call('00#0#00')).to eq(' 0 0 ') end The 0 0 answer looks a bit like a hacky solution. Will look into it when I've got the time. # PHP, 183-10=173 bytes All versions take input from command line argument; call with php -r '<code>' <string>. Note: All versions throw a warning if input starts with *. Prepend$o=[]; to the code to remove that flaw.

preg_match_all("%(\d)\1*|\*%",$argv[1],$m);foreach($m[0]as$w)if("*"==$w)array_pop($o);else$o[]="- 0 .?!1 ABC2 DEF3 GHI4 JKL5 MNO6 PQRS7TUV8 WXYZ9"[$w[0]*5+strlen($w)];echo join($o);
• does not need hash tags
• fails if a key is pressed too often

210-10-??=??? bytes

$a=[" 0",".?!1",ABC2,DEF3,GHI4,JKL5,MNO6,PQRS7,TUV8,WXYZ9];preg_match_all("%(\d)\1*|\*%",$argv[1],$m);foreach($m[0]as$w)if("*"==$w)array_pop($o);else$o[]=$a[$w[0]][strlen($w)%strlen($a[$w[0]])-1];echo join($o);
• does not need hash tags
• rotates if a key is pressed too often

181 bytes, no bonus

preg_match_all("%\d+#|\*%",$argv[1],$m);foreach($m[0]as$w)if("*"==$w)array_pop($o);else$o[]=" 0 .?!1 ABC2 DEF3 GHI4 JKL5 MNO6 PQRS7TUV8 WXYZ9"[$w[0]*5+strlen($w)-2];echo join($o);

breakdown

The "no hash tags" versions split the string to (streak of equal numbers) and (asterisk) and forget everything else. The no-bonus version takes (streak of numbers followed by #) and (asterisk).

Then loop through the matches: If a '*' is found, remove the last element of the result array.

The difference between the versions is in the else part:

• no bonus version: offset map string to (key*5), then add (keystrokes=word length-1)-1, add character from that position to result.
• simple no-tag version: almost the same, but: (keystrokes=word length); added a character to the map string to get rid of the other -1.
• rotating version: take item (key) from map array, add character (keystrokes%item length-1) from that item to result.

# JavaScript, 147 bytes

Conor´s answer fixed with the regex from my PHP answer and golfed down.

t=i=>i.match(/(\d)\1*|\*/g).map(w=>(" 0~.?!1~ABC2~DEF3~GHI4~JKL5~MNO6~PQRS7~TUV8~WXYZ9".split~[w[0]]||"*")[w.length-1]).join.replace(/.\*/g,"")

breakdown

t=i=>i
.match(/(\d)\1*|\*/g)   // split input to streaks of equal numbers and single *
.map(w=>                // replace each item with ...
// .. take string depending on the digit
(" 0~.?!1~ABC2~DEF3~GHI4~JKL5~MNO6~PQRS7~TUV8~WXYZ9".split~[w[0]]
||"*")              // .. ("*" for not a digit)
[w.length-1]        // -> the (item length)th character of that string
)
.join                 // join without delimiter
.replace(/.\*/g,"")     // and recursively remove every (letter,asterisk) combination

rotating version, 158 bytes

added s= to remember the string and %s.length to rotate.

t=i=>i.match(/(\d)\1*|\*/g).map(w=>(s=" 0~.?!1~ABC2~DEF3~GHI4~JKL5~MNO6~PQRS7~TUV8~WXYZ9".split~[w[0]]||"*")[w.length%s.length-1]).join.replace(/.\*/g,"")