Create a T9 keyboard

12

1

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.

Mohsen

Posted 2014-12-17T23:04:19.840

Reputation: 677

Bonus does not have any effect on score ? Why would I go for it ? – Optimizer – 2014-12-17T23:13:25.430

2Also, your example T9 KEYBOARD is completely wrong. That one reads T9 JEYBARD – Optimizer – 2014-12-17T23:24:24.857

8#99999#055#33#999#22#666#2#777#3#1 – Optimizer – 2014-12-17T23:25:48.130

@Optimizer for bonuses I don't know what should I do? Thanks for finding the issue. It's fixed. – Mohsen – 2014-12-17T23:28:34.330

1@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 – 2014-12-17T23:46:07.183

1You 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 – 2014-12-18T12:57:16.770

2No reply even after 18 hours. Voting to close as unclear. – Optimizer – 2014-12-19T07:49:15.887

Does 000 give a sole space or 0+space? I.e. should it rotate or print when the max number of presses for a key is reached? – Titus – 2016-09-14T23:20:36.000

The exact behaviour of backspace is undefined: Does it reset the current character or does it delete it and we´re back at the previous position to continue typing? Does 9911*9# give XW or Y? – Titus – 2016-09-14T23:29:58.737

Should 899999055339992266627773 also give T9 KEYBOARD? – Titus – 2016-09-14T23:49:02.473

Answers

5

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

Optimizer

Posted 2014-12-17T23:04:19.840

Reputation: 25 836

Can you make it work for the first bonus? – Mohsen – 2014-12-18T02:07:31.520

3@Mohsen Not until there is an actual benefit of going for bonus! Lets say, a 25% code length reduction in final score. – Optimizer – 2014-12-18T05:49:56.783

2

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.

Conor O'Brien

Posted 2014-12-17T23:04:19.840

Reputation: 36 228

1Can you make it work with the first bonus? – Mohsen – 2014-12-18T02:09:18.733

@Mohsen Yes, and that might actual help it. I'll work on it through today and tomorrow. – Conor O'Brien – 2014-12-18T02:10:51.443

Please at least do not advertise a score that is incorrect as the answer is not even up to the spec. – Optimizer – 2014-12-18T17:13:47.193

@Mohsen It is now working with the first bonus. – Conor O'Brien – 2014-12-18T22:49:50.697

t("2#2"); gives B instead of AA. Try to match any # instead of removing them. – Titus – 2016-09-15T02:20:51.480

1

AWK 211 bytes (with the bonuses)

{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)

LeFauve

Posted 2014-12-17T23:04:19.840

Reputation: 402

1

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)
  answer = []
  for j in i:
    answer = answer + [t9[int(j[0])][len(j)-1]]
  return ''.join(answer)

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.

Def

Posted 2014-12-17T23:04:19.840

Reputation: 602

1

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

nutki

Posted 2014-12-17T23:04:19.840

Reputation: 3 634

@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 – 2014-12-18T17:20:06.267

Since its not part of the bonus. Its a mandatory rule. – Optimizer – 2014-12-18T17:21:20.733

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 – 2014-12-18T17:24:35.903

Sorry, I was misled as current answers in the languages that I can read also do not support *. – nutki – 2014-12-18T17:25:16.927

If you're referring to my python answer, you're right. I misinterpreted the question. – Def – 2014-12-18T17:30:56.420

1

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.

Biketire

Posted 2014-12-17T23:04:19.840

Reputation: 200

1

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);
}

musarithmia

Posted 2014-12-17T23:04:19.840

Reputation: 531

0

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.

Titus

Posted 2014-12-17T23:04:19.840

Reputation: 13 814

0

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,"")

Titus

Posted 2014-12-17T23:04:19.840

Reputation: 13 814