Easily Typeable Words

This competition is over.

The winner is CJam with 22 characters, beating TwiNight's answer by one character. Congratulations Dennis!

An honorable mention goes to Falko, who went totally crazy with the free imports.

.

A while ago I wanted to know how I can out-type modern smartphones with my Nokia 3310, and while some answers were really good, I still can't keep up! Maybe I should take a different approach and simply not write any words that are awkward to type.

We'll call a piece of text easily typeable if no two consecutive letters are on the same button on the telephone keyboard, given the standard layout:

Your task is to write a program/function that accepts a string s from stdin/as a parameter and returns a truthy value if s is easily typeable and a falsy value otherwise. The input will only consist of lowercase letters and spaces and is guaranteed to be non-empty!

Scoring

This is codegolf, so lowest character-count wins.

Import statements will not be counted towards your final score, so if you've ever wanted to use std::set_symmetric_difference, liftM4, or itertools.combinations in your code, now is the time!

-3 if your source code is easily typeable, assuming everything that's not a letter is on button 0. After all, I might want to message your code to some friends!

Testcases

Here are a few testcases to check if your code is working as intended:

"x" -> True
"aardvark" -> False
"ardvark" -> True
"flonk" -> False

"im codegolfing all day long" -> False
"i indulge in minimizing bytecount" -> True

"havent heard from you in a long time" -> False
"your silence was of undue permanence" -> True

"how are  you" -> False
"how are you" -> True


Happy golfing!

• Are two consecutive spaces bad? – Martin Ender Sep 10 '14 at 15:31
• @MartinBüttner yes! Should probably add a testcase for that. – Flonk Sep 10 '14 at 15:31
• I have a nokia dumbphone, if I press space twice, I get a number 0. – overactor Sep 10 '14 at 19:21
• Related question: devise a phone keyboard layout which maximises some score based on how easily typeable the most frequently occurring words are. – justinpc Sep 11 '14 at 8:30
• @jpcooper like the two mentioned here? I've used 8pen and really like it, except that my phone glass heats up using it (from the contact, not from CPU activity) and the coefficient of friction makes it hard to use for long inputs. Using the s-pen on the Note 3 is so much easier :) – Eben Sep 11 '14 at 19:38

CJam, 343127 22 characters

1l{'h-_9/-D+3/X\:X^*}/


Try it online.

Example run

$cjam <(echo "1l{'h-_9/-D+3/X\:X^*}/") <<< 'aardvark'; echo 0$ cjam <(echo "1l{'h-_9/-D+3/X\:X^*}/") <<< 'ardvark'; echo
66000


How it works

1l                         " Push a R := 1 and read a line L from STDIN.                  ";
" Initialize X := 1. (implicit)                                ";
{                  }/    " For each character C of L, do the following:                 ";
'h-                    "     C -= 'h'                                                 ";
_9/-D+3/            "     Y := (C - C / 9 + 13) / 3                                ";
X\  ^*      "     R *= X ^ Y                                               ";
:X        "     X := Y                                                   ";
" Print R. (implicit)                                          ";


Background

The core of the code consists in applying a map F to each character C of the input string so that the images of symbols on the same key match. I found the a suitable map by observing the following:

The map T : C ↦ (C - 'h') + 13 transforms the string S := " abcdefghijklmnopqrstuvxyz" as follows:

[-59   6  7  8   9 10 11  12 13 14  15 16 17  18 19 20  21 22 23 24  25 26 27  28 29 30 31]


For the keys 0 to 6, it would suffice to divide T(C) by 3, but we have to apply some sort of correction to the characters in s, t, v, y and z.

The map D : C ↦ (C - 'h') / 9 transforms the string S into the following array:

[ -8   0  0  0   0  0  0   0  0  0   0  0  0   0  0  0   0  1  1  1   1  1  1   1  1  1  2]


This corrects the quotients of s, t, v, y and z, without affecting the others.

Finally, the map F : C ↦ (T(C) - D(C)) / 3 transforms the string S as follows:

[-17   2  2  2   3  3  3   4  4  4   5  5  5   6  6  6   7  7  7  7   8  8  8   9  9  9  9]


All that rests is to compare the consecutive characters somehow. For that purpose, we XOR F(C) with the image of the previous character – for the first, we XOR F(C) with 1 (default value of the variable X), which has no preimage – and multiply all the results.

The product will be falsy if and only if one of the factors is zero, i.e., if and only if two consecutive characters have the same image by F.

• I think the byte (Not character) count for this one is 54 – user16402 Sep 10 '14 at 19:23
• @Optimizer I think the code-golf tag wiki says bytes – user16402 Sep 10 '14 at 19:26
• This answer no longer contains non-ASCII characters. – Dennis Sep 11 '14 at 5:57
• @professorfish The tag wiki is just the default. If the challenge specifies characters, it's characters. – Martin Ender Sep 11 '14 at 7:47

Python 2 - 80, 68, 64, 61, 58, 50, 48, 45, 44 42

Even though it's getting a little ridiculous now, I'll keep on making use of free library imports, even the __builtin__ library:

from numpy import diff as D
from pprint import pprint as P
from __builtin__ import all as A
from __builtin__ import raw_input as I
from __builtin__ import bytearray as B


So only the following short line counts towards the code length:

P(A(D([(o-o/112-o/59)/3for o in B(I())])))


Credits to Markuz for the ideas regarding input()! These free-import challenges always introduce you to some lesser known libraries. ;)

Alternative using only the operator library (98, 83 79):

from operator import ne as n
K=[(ord(c)-1-(c>'p')-(c>'w'))/3for c in input()]
print all(map(n,K[1:],K[:-1]))


I'll stop here. But you could further golf this version using sys, pprint and other libraries...

Alternative without libraries (105):

s=input()
n=lambda c:(ord(c)-1-(c>'p')-(c>'w'))/3
print all([n(s[i])!=n(s[i+1])for i in range(len(s)-1)])

• And once again we independently post the same solution in Ruby and Python. Looks like this time you're winning though. ;) ... Can't you also save 4 bytes, by assigning ord(c) to a variable (say o) and then subtracting c/112 and c/119 instead of the booleans? – Martin Ender Sep 10 '14 at 16:10
• @MartinBüttner: Yeah, once in a while Python can beat Ruby. Unfortunately I can't assign variables within lambda expressions that easily. With [(o-1-o/112-o/119)/3for o in map(ord,s)] I end up with 80 bytes again. – Falko Sep 10 '14 at 16:19
• Ah I see. Sick new improvement though! :D – Martin Ender Sep 10 '14 at 16:26
• Very impressive. And you could still save 3 bytes with from sys import argv as s, using s[1] instead of input() – Markuz Sep 10 '14 at 16:54
• importing input from __builtin__ as well is actually even better :D saving yet another Byte. – Markuz Sep 10 '14 at 18:24

Ruby Regex (most popular flavours), 106 83 bytes

Because regex

^(?!.*(  |[abc]{2}|[def]{2}|[ghi]{2}|[jkl]{2}|[mno]{2}|[p-s]{2}|[tuv]{2}|[w-z]{2}))


I've just cut the middleman (Ruby) and made this a pure-regex solution. Works in a lot of flavours and only finds a match if the string does not contain two consecutive characters on the same button.

• Can't you put that {2} outside the alternation, saving 22 bytes? – Niet the Dark Absol Sep 11 '14 at 8:28
• @NiettheDarkAbsol unfortunately not, because then it can pick different alternatives for the two repetitions. – Martin Ender Sep 11 '14 at 8:29
• Aah, of course. I knew there was a reason XD – Niet the Dark Absol Sep 11 '14 at 8:29
• Kudos for a readable, understandable, golfed solution! – GreenAsJade Sep 14 '14 at 22:33

Bash+coreutils, 49

tr a-z $[36#8g7e9m4ddqd6]7778888|grep -Pq '(.)\1'  Returns an exit code of 1 for TRUE and 0 for FALSE: $ for s in "x" "aardvark" "ardvark" "flonk" "im codegolfing all day long" "i indulge in minimizing bytecount" "havent heard from you in a long time" "your silence was of undue permanence" "how are  you" "how are you"; do echo "./3310.sh <<< \"$s\" returns$(./3310.sh <<< "$s"; echo$?)"; done
./3310.sh <<< "x" returns 1
./3310.sh <<< "aardvark" returns 0
./3310.sh <<< "ardvark" returns 1
./3310.sh <<< "flonk" returns 0
./3310.sh <<< "im codegolfing all day long" returns 0
./3310.sh <<< "i indulge in minimizing bytecount" returns 1
./3310.sh <<< "havent heard from you in a long time" returns 0
./3310.sh <<< "your silence was of undue permanence" returns 1
./3310.sh <<< "how are  you" returns 0
./3310.sh <<< "how are you" returns 1
$ • Very nice! This would be 46 characters in Perl: perl -pE'y/a-z/aaadddgggjjjmmmpppptttwwww/;$_=!/(.)\1/' <(echo "x") It prints 1 for true and nothing for false. – chilemagic Sep 10 '14 at 18:24
• @chilemagic Go ahead and post the perl answer :). Don't forget to add one to the score for using the -p command-line parameter (as per code-golf conventions). – Digital Trauma Sep 11 '14 at 0:19
• I thought I could find a way to shorten aaadddgggjjjmmmpppptttwwww but I've given up. – Ben Jackson Sep 11 '14 at 20:57
• @BenJackson I figured out a way. We can actually use a string of any distinct characters - 11122233344455566667778888 will do. By base 36 encoding the first 19 digits of this number, we can save 1 char! – Digital Trauma Sep 11 '14 at 21:17

APL(Dyalog), 24 23

~∨/2=/⌊¯13⌈.21-.31×⎕AV⍳⍞


∧/2≠/⌊¯13⌈.21-.31×⎕AV⍳⍞


Explanation

⍞: Takes string input from screen
⎕AV: This is the atomic vector which is bascially a string of all characters APL recognizes, which of course include all lowercase letters (index 18~43) and space (index 5)
⍳: IndexOf function. For many functions in APL that takes one or two scalar arguments, you can feed it an array in place of a scalar - APL will do the looping for you. So ⍳ returns a numeric array of indices. .21-.31×: Times 0.31 and then subtract from 0.21. This is a little trick that maps letter on the same key (especially PQRS) to the same number (when rounded down to integers), except Z, which get mapped to its own group
¯13⌈: max with -13. This brings Z back to the group with WXY
⌊: Round down to integers
2≠/: Pairwise-≠. Returns a boolean array for each consecutive pair.
∧/: AND together all entries of the resulting array.

• I was going to post something like this, but you beat me to it. Damn Z key! You can still shave 1 char off by saying ∧/2≠/ (all consecutive pairs are typed on different keys) instead of ~∨/2=/ (no consecutive pair is typed on the same key.) APL FTW!!! – Tobia Sep 10 '14 at 21:53
• Yes tkx. I was thinking "I should be able to shave off 1 char here why can't I do it OMGGGG!!!" But I got to go to class so I just post what I have. And, yes, DAMN Z KEY. Unfortunately I am on my phone so I can't edit it until later – TwiNight Sep 10 '14 at 23:12
• And I made a conscious thought about De Morgan's Laws and still can't figure it out... How dumb – TwiNight Sep 10 '14 at 23:16
• Good luck texting this to your friends. ;) – Thane Brimhall Sep 10 '14 at 23:34
• This looks very interesting. Is there any way to try this code without purchasing a Dyalog APL interpreter? The online interpreter I usually use doesn't seem to understand the dialect... – Dennis Sep 11 '14 at 6:08

Perl - 44

This is basically a Perl adaptation of @DigitalTrauma's answer posted with his permission. Shaved off 2 characters thanks to @KyleStrand.

y/b-y/aadddgggjjjmmmpppptttzzz/;$_=!/(.)\1/  43 characters + 1 for -p flag. y/// is the same as tr///. It prints 1 for true and nothing for false. I can post a detailed explanation if requested. Example run: perl -pE'y/b-y/aadddgggjjjmmmpppptttzzz/;$_=!/(.)\1/' <(echo "x")


Perl - 81

$s=join"]{2}|[",qw(abc def ghi jkl mno p-s tuv w-z);say/^(?!.*( |[$s]{2}))/?1:0


+1 for -n flag. It works by using join to create the regex (same one as Martin's), which shaves of a few bytes.

Example run:

perl -nE'$s=join"]{2}|[",qw(abc def ghi jkl mno p-s tuv w-z);say/^(?!.*( |[$s]{2}))/?1:0' <(echo "your silence was of undue permanence")

• Couldn't you shave off two characters from the Perl solution by letting a and z remain untransliterated? y/b-y/aadddgggjjjmmmpppptttzzz/;$_=!/(.)\1/ Also, this won't handle spaces, will it? – Kyle Strand Sep 12 '14 at 7:53 • ...oh, right, two spaces in a row is already two identical characters in a row. My bad. – Kyle Strand Sep 12 '14 at 7:59 • @KyleStrand nice call on letting a and z remain the same. Updated answer! – chilemagic Sep 12 '14 at 17:12 JavaScript - 159 156 bytes function g(s){p=n=-1;for(i=0;i!=s.length;i++){p=n;n=s.charCodeAt(i);n-=97;if(n>17)n--;if(n>23)n--;if(p==-1)continue;if(~~(p/3)==~~(n/3))return 0;}return 1;}  Returns 1 for truthy and 0 for falsy. If only I could get rid of the keywords. • At least you can get rid of some whitespaces and if's :) 141 : function g(s){p=n=-1;for(i=0;i<s.length;i++){p=n;n=s.charCodeAt(i)-97;n>17&&n--;n>23&&n--;if(~p)continue;if(~(p/3)==~(n/3))return 0}return 1} – Optimizer Sep 10 '14 at 17:07 • You use lots of interesting things in your answer that I haven't seen before. I usually write in C++ but I thought I'd give JS a shot as it's quicker to test online. – Lozzaaa Sep 10 '14 at 17:16 • I only found this place today and thought I'd give it a shot. My next try will be superior :D – Lozzaaa Sep 10 '14 at 17:22 • You can make your code a character shorter by replacing the != in the for loop by a <. – ProgramFOX Sep 10 '14 at 17:48 • Yes that was in Optimizer's optimisations :) what's the etiquette on using people's suggestions in my answer? Now that there is another JavaScript entry that I can beat by accepting those modifications. – Lozzaaa Sep 14 '14 at 20:45 c, 74 bytes main(c,d,r){for(;~(c=getchar());r*=d!=c/3,d=c/3)c-=--c/'p'*(c-'k')/7;c=r;}  Returns a non-zero exit status for TRUE and 0 for FALSE: $ for s in "x" "aardvark" "ardvark" "flonk" "im codegolfing all day long" "i indulge in minimizing bytecount" "havent heard from you in a long time" "your silence was of undue permanence" "how are  you" "how are you"; do echo "./3310 <<< \"$s\" returns$(./3310 <<< "$s"; echo$?)"; done
./3310 <<< "x" returns 40
./3310 <<< "aardvark" returns 0
./3310 <<< "ardvark" returns 216
./3310 <<< "flonk" returns 0
./3310 <<< "im codegolfing all day long" returns 0
./3310 <<< "i indulge in minimizing bytecount" returns 72
./3310 <<< "havent heard from you in a long time" returns 0
./3310 <<< "your silence was of undue permanence" returns 232
./3310 <<< "how are  you" returns 0
./3310 <<< "how are you" returns 8
$ • You can save 3 bytes by changing your while to for(;c=~getchar();d=c/3), and another byte by changing your first if to a ?: operator. – Allbeert Sep 10 '14 at 18:35 • @Allbeert - Thanks. The parenthesis around c=getchar() are required though because ~ has higher precedence than =. Still, I'll take the other two bytes :) – Digital Trauma Sep 10 '14 at 19:25 • For the last bit, does something like exit(d!=c/3); instead of if(d==c/3)exit(0); work? – user16402 Sep 10 '14 at 19:25 • @professorfish That would make the exit at that point unconditional, which I don't want – Digital Trauma Sep 10 '14 at 19:26 • You can save one character with r*=d^c/3 – Alchymist Sep 12 '14 at 9:43 Ruby 1.8, 898381 78 bytes p$*[0].chars.map{|c|c=c[0];(c-c/?p-c/?w-1)/3}.each_cons(2).map{|a,b|a!=b}.all?


Here is another submission. To my shame, it beats the regex. :(

This takes the string via command-line argument and prints a boolean.

As for the algorithm, I'm shifting down the letters after p by one and after z by two, and then I check that there are no collisions after integer division by 3.

PS: This is the first time, that using Ruby 1.8 shortened the code (due to the shorter way to get character codes).

Cobra - 80

def f(s)
for c in s
t,f=x,t<>x
print f


JavaScript (ES6) 66 74

F=s=>[...s].every(c=>[...' adgjmptw'].map(x=>s+=c<x,w=s,s=0)|s!=w)


The inner loop find the group for each character. Conceptually is a 'reduce' but 'map' is shorter. The outer loop compare the group of consecutive chars and exits with false if they are equal.

Test In Firefox/Firebug console

;["x","aardvark","ardvark","flonk","im codegolfing all day long",
"i indulge in minimizing bytecount","havent heard from you in a long time",
"your silence was of undue permanence","how are  you","how are you"]
.forEach(x=>console.log(x + ' -> ' + F(x)))


Output

x -> true
aardvark -> false
ardvark -> true
flonk -> false
im codegolfing all day long -> false
i indulge in minimizing bytecount -> true
havent heard from you in a long time -> false
your silence was of undue permanence -> true
how are  you -> false
how are you -> true

• You can do .some instead of every. Because even if it fails one time, the answer is falsy. – Optimizer Sep 11 '14 at 8:14
• @Optimizer some and every are interchangeble, fiddling with the conditions. But here simply put some instead of every will not work, try it. – edc65 Sep 11 '14 at 8:23
• Hmm, you are right. I need to understand your logic first. – Optimizer Sep 11 '14 at 8:26
• Please don't mind if I start using this [...s].every trick in my golfs :) – Optimizer Sep 11 '14 at 14:32

Perl, 83 bytes

$_=<>;chop;map{$_=ord;$_=($_-$_/112-$_/119-1)/3;die 0 if$l==$_;$l=$_}split//;die 1


Making heavy abuse of $_ in Perl. • As you can observe, is allowed to pass command-line parameters to the interpreter, just you have to count the extra parameters. (The bare minimum needed to access the code, -e in Perl, is free.) 71 characters alternative with command-line parameters: perl -nlaF -e 'map{$_=ord;$_=($_-$_/112-$_/119-1)/3;die 0 if$l==$_;$l=$_}@F;die 1'. – manatwork Sep 10 '14 at 16:41
• @manatwork you don't need the -l, but it looks good! – chilemagic Sep 10 '14 at 16:45
• @chilemagic, I just tried to reproduce the original code's equivalent, so I added -l as replacement for chop. But of course, you are right. – manatwork Sep 10 '14 at 17:04
• Thanks @manatwork, I didn't even think to making use of the command line options for Perl. – mcreenan Sep 10 '14 at 18:26

Two tasks are tricky in Python; detecting chains, and assigning the groups. Both can be assisted using numpy, but it is not in the standard library.

Python 2 (only standard library) - 59 chars function

from itertools import imap as M
from __builtin__ import bytearray as A, all as E
from operator import ne as D, not_ as N
from re import S, sub as X, search as F

# 68
#def f(s):
# g=[(n-n/115-n/61)/3for n in A(s)]
# return E(M(D,g,g[1:]))

# 67 with regex via regex
#f=lambda s:N(F(X('(\S)(.)',r'|[\1-\2]{2}','  acdfgijlmopstvwz'),s))

# 59 slightly optimized ordinal classifier and regex sequence detector
f=lambda s:N(F(r'(.)\1',A((n-n/23-n/30)/3for n in A(s)),S))

# 69 using itertools.groupby
#from itertools import groupby as G
#from __builtin__ import sum as S, len as L
#f=lambda s:N(S(L(A(g))-1for _,g in G((n-n/115-n/61)/3for n in A(s))))


Python 2 (only standard library) - 53 chars stdin to exit value

Here I abuse the fact that issubclass(bool,int), so changing all() to any() gets me a valid exit value, shaving off the not() from the return value. The removal of function overhead made the regex versions fall behind in size.

from itertools import groupby as G, imap as M
from __builtin__ import bytearray as A, any as E
from __builtin__ import raw_input as I
from sys import exit as Q
from operator import eq as S

g=[(n-n/23-n/30)/3for n in A(I())]
Q(E(M(S,g,g[1:])))


J - 42 char

Function taking string on the right.

*/@(2~:/\(I.4 3 4 1,~5#3){~(u:97+i.26)&i.)


First we map the alphabet (u:97+i.26) into the numbers 0 through 25, all other characters (including spaces) going to 26 (i.). Then we map ({~) the first three elements map to the first key, the next three to the next key, and so on through the keys of the phone pad, making sure to map the space/other punctuation to a separate key at the end. (4 3 4 1,~5#3 is equal to 3 3 3 3 3 4 3 4 1 and I. turns that into a 27-item array where the first three are key 1, etc.) Then we check for pairwise inequality (2~:/\) and AND all the results together (*/).

   */@(2~:/\(I.4 3 4 1,~5#3){~(u:97+i.26)&i.) 'i indulge in minimizing bytecount'
1
f =: */@(2~:/\(I.4 3 4 1,~5#3){~(u:97+i.26)&i.)
f 'im codegolfing all day long'
0
f '*/@(2~:/\(I.4 3 4 1,~5#3){~(u:97+i.26)&i.)'  NB. no -3 bonus :(
0


Racket, 119

(define(f t)(for*/and([s(map ~a'(abc def ghi jkl mno pqrs tuv wxyz))][i s][j s])(not(regexp-match(format"~a~a"i j)t))))


Ungolfed (combinatoric regexing):

(define(f t)
(for*/and([s (map ~a '(abc def ghi jkl mno pqrs tuv wxyz))]
[i s]
[j s])
(not (regexp-match (format "~a~a" i j) t))))


JavaScript - 152

Not a winner but I gave it a shot. Beats @Lozzaaa by 4 bytes as of posting time :)

function m(a){c="abc-def-ghi-jkl-mno-pqrstuv-wxyz";j=a.split("");for(z in j)if(j[z]=Math.floor(c.indexOf(j[z])/4),0!=z&&j[z-1]==j[z])return 0;return 1};


Passes all the tests.
Takes advantage of JS's lack of typing to make a multi type array, and it takes advantage of indexOf returning -1 for space support.

Usage:

m("string here")


Assumes lowercase alphabetic characters and spaces only. Returns 1 for true, 0 for false.

Maybe if I knew ES6 I could try the second challenge...

• "if only ... " - Did you see my answer ? :P – Optimizer Sep 10 '14 at 21:18
• Yes I did. I don't know ES6 (yet), sadly. However, this was interesting to make. – DankMemes Sep 10 '14 at 21:19
• Yeah, your solution using an interesting approach. – Optimizer Sep 10 '14 at 21:21

ES6, JavaScript 89 70 characters

I know its not a winner because when coming to handy operations like getting ASCII value of character, JS puts a lot of bloat (.charCodeAt()).

N=s=>[...s].every(c=>l-(l=(c.charCodeAt()-(c>"r")-(c>"y")-1)/3|0),l=1)


Run it in Web Console of latest Firefox.

Usage:

N("testing if this works")


The function returns either true or false.

EDIT: Golfed a lot using the [...x].every trick learned from @edc65 (Thanks!)

I will try to golf it more :)

GML (Game Maker Language), 149

s=argument0p=n=-1for(i=0;i<string_length(s);i++){p=n;n=string_char_at(s,i)-97;x=n>17&&n--;x=n>23&&n--‌​;if(!p)x=1if(!(p/3)=!(n/3))x=0}show_message(x)


Python 3 - 152 chars

Not the shortest I could go, but it'll do for now

k=['abc','def','ghi','jkl','mno','pqrs','tuv','wxyz',' ']
x=input()
l=2>1
for i in range(len(x)-1):
for j in k:
if x[i+1] in j and x[i] in j:l=1>2
print(l)