# [SUMMARY] Phone Typing (#21)

R

#### Ruby Quiz

The topic of this week's quiz was very interesting to me. I regret that I
couldn't steal enough time to try a solution of my own. However, I've enjoyed
playing with the solutions quite a bit. Thanks Hans!

So what's all the fuss about? Let's run a simple test, using Dave Burt's
solution (because it puts each step on a new line). The following is me typing
a simple sentence with the multitap method. (Note: I did modify one line of
Dave's code used here, to make the digits come last as they do on my phone and
in Brian's solution.) Forgive the length, but in a way it's telling:

m_
mw_
mx_ # second keypress
my_ # third keypress
my _
my w_
my wg_
my wh_ # second keypress
my wi_ # third keypress
my wid_
my wie_ # second keypress
my wif_ # third keypress
my wifd_
my wife_ # second keypress
my wife _
my wife d_
my wife da_
my wife dam_
my wife dan_ # second keypress
my wife dana_
my wife danap_
my wife danaq_ # second keypress
my wife danar_ # third keypress
my wife danas_ # fourth keypress
my wife danas _
my wife danas a_
my wife danas b_ # second keypress
my wife danas bg_
my wife danas bh_ # second keypress
my wife danas bi_ # third keypress
my wife danas bip_
my wife danas biq_ # second keypress
my wife danas bir_ # third keypress
my wife danas birt_
my wife danas birtg_
my wife danas birth_ # second keypress
my wife danas birthd_
my wife danas birthda_
my wife danas birthdaw_
my wife danas birthdax_ # second keypress
my wife danas birthday_ # third keypress
my wife danas birthday _
my wife danas birthday g_
my wife danas birthday h_ # second keypress
my wife danas birthday i_ # third keypress
my wife danas birthday ip_
my wife danas birthday iq_ # second keypress
my wife danas birthday ir_ # third keypress
my wife danas birthday is_ # fourth keypress
my wife danas birthday is _
my wife danas birthday is a_
my wife danas birthday is ap_
my wife danas birthday is app_
my wife danas birthday is apq_ # second keypress
my wife danas birthday is apr_ # third keypress
my wife danas birthday is aprg_
my wife danas birthday is aprh_ # second keypress
my wife danas birthday is apri_ # third keypress
my wife danas birthday is aprij_
my wife danas birthday is aprik_ # second keypress
my wife danas birthday is april_ # third keypress
my wife danas birthday is april _
my wife danas birthday is april d_
my wife danas birthday is april e_ # second keypress
my wife danas birthday is april eg_
my wife danas birthday is april eh_ # second keypress
my wife danas birthday is april ei_ # third keypress
my wife danas birthday is april eig_
my wife danas birthday is april eigg_
my wife danas birthday is april eigh_ # second keypress
my wife danas birthday is april eight_
my wife danas birthday is april eightg_
my wife danas birthday is april eighth_ # second keypress

Here's the same exercise, with LetterWise:

o_
_ # had to push *
m_
my_
my _
my w_
my wh_
my w_ # had to push *
my wi_
my wid_
my wi_ # had to push *
my wif_
my wife_
my wife _
my wife f_
my wife _ # had to push *
my wife d_
my wife da_
my wife dan_
my wife danc_
my wife dan_ # had to push *
my wife dana_
my wife danas_
my wife danas _
my wife danas a_
my wife danas _ # had to push *
my wife danas b_
my wife danas bi_
my wife danas bir_
my wife danas birt_
my wife danas birth_
my wife danas birthe_
my wife danas birth_ # had to push *
my wife danas birthd_
my wife danas birthda_
my wife danas birthday_
my wife danas birthday _
my wife danas birthday h_
my wife danas birthday _ # had to push *
my wife danas birthday i_
my wife danas birthday is_
my wife danas birthday is _
my wife danas birthday is a_
my wife danas birthday is as_
my wife danas birthday is a_ # had to push *
my wife danas birthday is ar_
my wife danas birthday is a_ # had to push *, a second time
my wife danas birthday is ap_
my wife danas birthday is app_
my wife danas birthday is ap_ # had to push *
my wife danas birthday is apr_
my wife danas birthday is apri_
my wife danas birthday is april_
my wife danas birthday is april _
my wife danas birthday is april f_
my wife danas birthday is april _ # had to push *
my wife danas birthday is april d_
my wife danas birthday is april _ # had to push *, a second time
my wife danas birthday is april e_
my wife danas birthday is april ei_
my wife danas birthday is april eig_
my wife danas birthday is april eigh_
my wife danas birthday is april eight_
my wife danas birthday is april eighti_
my wife danas birthday is april eight_ # had to push *
my wife danas birthday is april eighth_

(I'm sure Dana will be looking forward to all your gifts this year.)

That's a pretty radical difference. Let me line up those stats:

Multitap LetterWise
keypresses: 74 52
second press: 20 (27%) 12 (23%)
third press: 14 (19%) 2 (4%)
fourth press: 2 (3%) 0

However, and this is purely subjective so feel free to completely ignore it, I'm
almost positive I entered the sentence faster with Multitap. Isn't that ironic?
The reason is that I can glance at the keypad and know exactly what I need to
do. With LetterWise, I had to wait and see what I got, before I could respond.
The document linked to by the quiz seemed to imply this was a phase I would get
past, eventually.

So, between the two methods explored in the solution, LetterWise is definitely a
by the numbers improvement. Subjectively though, it may take some getting use
to. Try it for yourself.

Oh, should I be talking about Ruby here? Right, let's get on that.

I want to take a look at Dave Burt's code that was used in the examples above,
but not all of it. I'll leave out his TK interface and a few classes mainly
useful in testing.

First, Dave combined the code Hans and I offered with the quiz to build a better
command-line interface. Here's the class for that:

def initialize
super
loop do
when 3, 4, 26, 27 # Break on Ctrl+C, Ctrl+D, Ctrl+Z, Escape
break
when 43 # convert '+' to '*' for ease of typing on numpad
c = 42
when 45, 46, 47 # convert '-', '.', '/' to '#' for ease of typing
c = 35
end
notify_observers(c.chr)
end
end
end
def set_text(text, cursor)
super(text, cursor)
puts to_s
end
def to_s
@text.dup.insert(@cursor, '_')
end

begin
require "Win32API"
c = Win32API.new("crtdll", "_getch", [], "L").Call
end
system "stty raw -echo"
STDIN.getc
ensure
system "stty -raw echo"
end
end
end

Nothing real surprising there. Dave just extended Hans's framework with my
character reading methods. It's a nicer option than what I offered because you
can use the event system. Note the character case block adds some niceties for

Next, here's Dave's Multipress/Multitap implementation:

class TapHandler
end
end

class MultipressTapHandler < TapHandler
attr_accessor :timeout
@timeout = timeout
end
def process_event(ev)
if (@last_event &&
ev.digit == @last_event.digit &&
Time.now - @last_event.when < timeout)
char_index = (char_set.index(@p.text[-1].chr) + 1) % char_set.size
@p.set_text(@p.text[0..-2] + char_set[char_index], @p.cursor)
else
### modified by JEG2 for discussion example ###
### actual code by Dave Burt ###
# @p.set_text(@p.text+ev.digit, @p.cursor+1)
end
@last_event = ev
end
end

All the exciting work in there happens in process_event(). The if checks to see
if it has memorized a previous event. If it has and this new event is the same
digit and the timout for that press hasn't expired yet, the next character in
the InputMap is selected. In all other cases, the first character of the
InputMap is selected. That's all there is to Multitap.

class LetterWiseTapHandler < TapHandler
# method taken from:
# MacKenzie, I. S., Kober, H., Smith, D., Jones, T., Skepner, E. (2001).
# LetterWise: Prefix-based disambiguation for mobile text input.
# Proceedings of the ACM Symposium on User Interface Software and
# Technology - UIST 2001, pp. 111-120. New York: ACM.
# http://www.yorku.ca/mack/uist01.html

# A lot of the logic in this method is captured in the InputMap,
# which maps prefixes of up to 3 letters and a key (0-9) onto
# an array of letters inmost-likely-first order.

#require 'yaml'
# 1.8MB, ~ 6 seconds to load
# 2.3MB, ~ 3 seconds to load

@cycle = ['*']
end
def process_event(ev)
prefix = @p.text[/\w{0,3}\$/]
if ev.digit == '*' # change last letter
@cycle.push @cycle.shift # rotate
@p.set_text(@p.text[0..-2], @p.cursor - 1)
elsif InputMap[prefix]
@cycle = InputMap[prefix][ev.digit].dup
else
@cycle = InputMap[nil][ev.digit].dup
end
@cycle ||= %w[. ! - & @ \$ * +]
@p.set_text(@p.text + @cycle[0], @p.cursor + 1)
end
end

As the comments suggest, there's not a lot of logic in here. Surprisingly,
there isn't that much in the evaled predict3.rb file either. It's just one big
boring hash, which you can see for yourself, if you follow the links to Dave's
solution.

Again, process_event() is where the action is. Really, it just looks up the
last three letters entered in the InputMap to find the order of the letters (the
elsif part) or cycles to the next most likely letter (the if part). The system
seems to support some punctuation characters, but I couldn't figure out how to
get at them when using it.

Here's the code that kicks off the process:

if \$0 == __FILE__

#p = CLIPhonePad.new # Command-line interface
p = CharPhonePad.new # Raw command-line interface
#p = TkPhonePad.new # TK interface

# just dump digits pressed
#tap_handler = NoOpTapHandler.new(p)
# tap the shape of a letter on the keypad!
#tap_handler = IconicTapHandler.new(p)
# standard multi-press, 1.5s timeout
#tap_handler = MultipressTapHandler.new(p)
# letter-wise prediction; * to change letter
tap_handler = LetterWiseTapHandler.new(p)

p.register do |ev|
tap_handler.process_event(ev)
end

end

Dave, repeat after me, "optparse is my friend..." Be kind to the guy who plays
with your code, because it might be me. Here's that and more without the
comment dance:

if \$0 == __FILE__
require "optparse"

# defaults
tap = :letterwise

# parse options
opts = OptionParser.new do |opts|
opts.banner = "Usage: #\$0 [OPTIONS]"

opts.separator ""
opts.separator "Specific Options:"

opts.on( "-i INTERFACE",
[:raw, :tk, :cli],
"Interface you would like to use:",
" raw, tk or cli." ) do |v|
end
opts.on( "-m MODE",
[:letterwise, :multipress, :iconic, :noop],
"The input algorithm to use:",
" letterwise, multipress, iconic or noop." ) do |v|
tap = v
end
opts.on( "-h", "-?", "--help",
"Show this text." ) do
puts opts
exit
end
end
opts.parse!(ARGV)

# handle results
when :raw then p = CharPhonePad.new
when :tk then p = TkPhonePad.new
when :cli then p = CLIPhonePad.new
end
case tap
when :letterwise then tap_handler = LetterWiseTapHandler.new(p)
when :multipress then tap_handler = MultipressTapHandler.new(p)
when :iconic then tap_handler = IconicTapHandler.new(p)
when :noop then tap_handler = NoOpTapHandler.new(p)
end

# run program
p.register { |ev| tap_handler.process_event(ev) }
end

Brian makes them even prettier, so scan his solution for an even better example.

My thanks to both Brian and Dave for playing with this one while I didn't have
time to. Also, thank you Hans for such a clever topic.

Tomorrows quiz takes us all the way to Rome...

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

### Members online

No members online now.