Count rotary dial pulses in a phone number (including letters)

In your grandparents' day, dialing a phone number was done with a rotary dial like this: To dial each digit, put your finger in the corresponding hole, pull it over to the finger stop, and release it. A mechanism will cause the dial to spin back to its resting position, and the phone will disconnect and reconnect a circuit a specified number of times, making audible clicks.

Dialing the digit N requires N such “pulses”, except for N=0 which is ten pulses.

Rotary phones have the property that large digits (8, 9, 0) take longer to dial than small digits (1, 2, 3). This was an important consideration in drawing up early area code maps, and why New York City with its heavy population (and phone line) density got 212 (only 5 pulses), while while 907 (26 pulses) went to sparsely-inhabited Alaska. Of course, this all became irrelevant when touch-tone dialing became popular.

The challenge

Write, in as few bytes as possible, a program or function that takes as input a string (or sequence of characters) containing a telephone number, and outputs its number of rotary dial pulses. These are to be counted as follows:

Digits

• Digits 1-9 count as that number of pulses.
• Digit 0 counts as 10 pulses.

Letters

Note that digits 2-9 on the dial have letters of the Latin alphabet associated with them. These were originally intended for named exchanges, but were latter re-appropriated for phonewords, and for text message input systems.

You must support having letters in your phone numbers, using the E.161 assignment of letters to digits:

• A, B, C = 2
• D, E, F = 3
• G, H, I = 4
• J, K, L = 5
• M, N, O = 6
• P, Q, R, S = 7
• T, U, V = 8
• W, X, Y, Z = 9

You may assume that the input has already been case-folded, to either upper or lower case.

Other characters

You must allow arbitrary use of the characters ()+-./ and space as formatting separators. You may chose to allow any non-alphanumeric character for this purpose, if it's easier to implement.

These characters do not contribute to the pulse count.

Example code

A non-golfed lookup table and function in Python:

PULSES = {
'1': 1,
'2': 2, 'A': 2, 'B': 2, 'C': 2,
'3': 3, 'D': 3, 'E': 3, 'F': 3,
'4': 4, 'G': 4, 'H': 4, 'I': 4,
'5': 5, 'J': 5, 'K': 5, 'L': 5,
'6': 6, 'M': 6, 'N': 6, 'O': 6,
'7': 7, 'P': 7, 'Q': 7, 'R': 7, 'S': 7,
'8': 8, 'T': 8, 'U': 8, 'V': 8,
'9': 9, 'W': 9, 'X': 9, 'Y': 9, 'Z': 9,
'0': 10
}

def pulse_count(phone_num):
return sum(PULSES.get(digit, 0) for digit in phone_num)

Example input and output

• 911 → 11
• 867-5309 → 48
• 713 555 0123 → 42
• +1 (212) PE6-5000 → 57
• 1-800-FLOWERS → 69
• PUZZLES → 48
• I assume that the Arbitrary ASCII punctuation and spaces are restricted to those normally used for phone numbers (+- ()*#.) just like letters are restricted to uppercase. Correct me if I'm wrong.
May 23 '19 at 5:18
• @Adám: I've restricted the required punctuation marks to just a few common separators. It deliberately does not include * and #, which have special meanings on touch-tone phones and are not dialable on rotaries. May 23 '19 at 5:38
• Can we use all-lowercase input instead of all-uppercase? Can we take an array of chars instead of a string? May 23 '19 at 19:12
• I'm a time traveller! I'm a time traveller! I'm a time traveller! Since I definitely used phones like this when I was a kid, clearly I AM MY OWN GRANDFATHER!!!!!!! Which is actually pretty icky when I think about it. Bleah!!! May 24 '19 at 2:59
• I'm the grandfather. I used phones like this in the 1950s. And when I moved to a town in a rural location, I discovered that the phone company did not offer touch-tone service. This was in 1985! No kidding! My grandmother had a phone in the parlor that had a hook and a crank. You took the earpiece off the hook, and turned the crank to obtain a switchboard operator. She had to replace it when direct distance dialing was set up. May 24 '19 at 13:02

05AB1E, 191817 15 bytes

AÁ0ªā6+žq÷9š‡þO

Try it online!

This is the first answer to use π. Why use π, you might ask? Well, the letters are associated with 22233344455566677778889999, in order. Notice how most digits repeat 3 times, but 7 repeats 4 times. You could say that each digit repeats (3+1/7) times, on average. I wonder if there’s any number that’s approximately 3+1/7 and takes fewer bytes than 22/7…

This only gives 4 7s, not 4 9s, so we still need to handle Z as a special case.

A               # alphabet (abcdefghijklmnopqrstuvwxyz)
Á              # rotate right (zabcdefghijklmnopqrstuvwxy)
0ª            # append 0 (zabcdefghijklmnopqrstuvwxy0)

ā6+             # range [7..33]
žq÷          # divide by π (22233344455566677778889991010)
9š        # prepend 9 (922233344455566677778889991010)

‡               # transliterate the implicit input with the two lists above
# this replaces z → 9, a → 2, … y → 9, 0 → 10
þ              # remove all non-digits
O             # sum
• Why lowercase instead of uppercase? May 24 '19 at 1:20
• @dan04 because 05AB1E has a built-in to push "abcdefghijklmnopqrstuvwxyz", but not for "ABCDEFGHIJKLMNOPQRSTUVWXYZ". I could convert the alphabet to uppercase rather than convert the input to lowercase, but that’s the same bytecount. May 24 '19 at 1:37
• I've edited the question to make your first two commands unnecessary. May 24 '19 at 1:44
• @Jonah I started with the idea of dividing a range by a constant to get the desired sequence, then while looking for the best way to express "slightly more than 3" in 05AB1E I remembered pi was a built-in. May 24 '19 at 11:18
• +1 for use of pi May 25 '19 at 2:54

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

n=>n.Sum(x=>x>64?(x-59-x/83-x/90)/3:x>47?1-~x%~9:0)

Saved 1 byte thanks to @recursive

Saved 10 bytes thanks to @ExpiredData's observation that only () +-/. will be in the input

Try it online!

n =>                     // Function taking input as string
n.Sum(x =>             // Map each value 'x' through the following
x>64 ?               //   If 'x' is an uppercase letter
(x-59-x/83-x/90)/3 //     Take each char's ASCII value subtracted by 59, and subtract
//     one if the char is 'S' and one if the char is 'Z'
: x>47 ?             //   Else if the char is a digit
1-~x%~9            //   Take 1 - (-x - 1) % -10 (Maps 0 to 10, and 1-9 to themselves
: 0                  //   Else, 0
)                      // And sum it all up, then return it
• -10 is ~9, which should work in context. May 23 '19 at 4:14
• @recursive That's smart, thanks May 23 '19 at 4:17
• x<91 check is redundant since the input will only consist of ()+-./ the space key and numbers which are all < 64 hence we can determine if the character is uppercase simply by checking x>64 (so -5 bytes) May 23 '19 at 10:37
• Same goes for the x<58 check, since nothing in the range 58-64 will be in the input May 23 '19 at 10:38
• 51 bytes May 23 '19 at 10:39

APL (Dyalog Unicode), 27 bytesSBCS

Anonymous tacit prefix function.

Try it online!

()∘⍳ find the ɩndex* of each character in the following string:
* elements that are not found, get the index 1+the maximum index, i.e. 11
⎕D the digits: "0123456789"

1⌽ cyclically rotate one step left; "1234567890"

11| division remainder when divided by 11*
* this gives 0 for all non-digits
+ add that to the following:

'@ADGJMPTW'∘⍸ the ɩnterval ɩndex* for each character
* So [−∞,"@") gives 0, ["@","A") gives 1, ["A","D") gives 2, etc.
+/ sum that

Python 2, 74 bytes

lambda s:sum([(n-59-n/83-n/90)/3,1-~n%~9][n<58]for n in map(ord,s)if n>47)

Try it online!

Does some arithmetic on the ASCII value for each character. The first option checks for letters and the second options checks for numbers. The clarification that all punctuation characters allowed in the input are ones with ASCII values less than 48 let me simplify the logic, but a new method altogether might now be better.

Python 2, 84 bytes

lambda s:sum(1+'1xxxx2ABCx3DEFx4GHIx5JKLx6MNOx7PQRS8TUVx9WXYZ0'.find(c)/5for c in s)

Try it online!

Uses a hardcoded lookup string, with each block of 5 characters corresponding to the characters giving each value starting with 1. Blank spaces are filled with x, which can't be in the input which is capitalized. Fortuitously, characters not appearing in the string produce -1 for the .find which gives a summand of zero.

JavaScript (Node.js), ... 76 69 bytes

s=>s.replace(/\w/g,q=>w+=1/q?+q||10:parseInt(q,35)*.32-1|0||9,w=0)&&w

Try it online!

-7 thanks @Arnauld!

Explanation

q |     1/q     |  +q  | parseInt(q,35)*.32 | parseInt(q,35)*.32-1|0 | Output
---+-------------+------+--------------------+------------------------+--------
0 | Infinity(T) | 0(F) |         N/A        |           N/A          |   10
1 |  1.0000(T)  | 1(T) |         N/A        |           N/A          |    1
2 |  0.5000(T)  | 2(T) |         N/A        |           N/A          |    2
3 |  0.3333(T)  | 3(T) |         N/A        |           N/A          |    3
4 |  0.2500(T)  | 4(T) |         N/A        |           N/A          |    4
5 |  0.2000(T)  | 5(T) |         N/A        |           N/A          |    5
6 |  0.1666(T)  | 6(T) |         N/A        |           N/A          |    6
7 |  0.1428(T)  | 7(T) |         N/A        |           N/A          |    7
8 |  0.1250(T)  | 8(T) |         N/A        |           N/A          |    8
9 |  0.1111(T)  | 9(T) |         N/A        |           N/A          |    9
A |    NaN(F)   |  N/A |        3.20        |          2(T)          |    2
B |    NaN(F)   |  N/A |        3.52        |          2(T)          |    2
C |    NaN(F)   |  N/A |        3.84        |          2(T)          |    2
D |    NaN(F)   |  N/A |        4.16        |          3(T)          |    3
E |    NaN(F)   |  N/A |        4.48        |          3(T)          |    3
F |    NaN(F)   |  N/A |        4.80        |          3(T)          |    3
G |    NaN(F)   |  N/A |        5.12        |          4(T)          |    4
H |    NaN(F)   |  N/A |        5.44        |          4(T)          |    4
I |    NaN(F)   |  N/A |        5.76        |          4(T)          |    4
J |    NaN(F)   |  N/A |        6.08        |          5(T)          |    5
K |    NaN(F)   |  N/A |        6.40        |          5(T)          |    5
L |    NaN(F)   |  N/A |        6.72        |          5(T)          |    5
M |    NaN(F)   |  N/A |        7.04        |          6(T)          |    6
N |    NaN(F)   |  N/A |        7.36        |          6(T)          |    6
O |    NaN(F)   |  N/A |        7.68        |          6(T)          |    6
P |    NaN(F)   |  N/A |        8.00        |          7(T)          |    7
Q |    NaN(F)   |  N/A |        8.32        |          7(T)          |    7
R |    NaN(F)   |  N/A |        8.64        |          7(T)          |    7
S |    NaN(F)   |  N/A |        8.96        |          7(T)          |    7
T |    NaN(F)   |  N/A |        9.28        |          8(T)          |    8
U |    NaN(F)   |  N/A |        9.60        |          8(T)          |    8
V |    NaN(F)   |  N/A |        9.92        |          8(T)          |    8
W |    NaN(F)   |  N/A |       10.24        |          9(T)          |    9
X |    NaN(F)   |  N/A |       10.56        |          9(T)          |    9
Y |    NaN(F)   |  N/A |       10.88        |          9(T)          |    9
Z |    NaN(F)   |  N/A |         NaN        |          0(F)          |    9

All of [space]().+-/ are not captured by /\w/g, so they won't affect the total.

Perl 5-p, 52 51 bytes

@Grimy gets credit for -1

y/A-Z/22233344455566677778889/;map$\+=$_||10,/./g}{

Try it online!

• /\d/g should be /./g for -1 (yes it still handles punctuation correctly). May 26 '19 at 11:33

J, 39 bytes

1#.'?@CFILOSVZ'&I.+11|1+'1234567890'i.]

Try it online!

A port of Adám's APL solution

• Nice use of interval index May 24 '19 at 4:27
• @Jonah Thanks to Adam :) May 24 '19 at 7:04

Retina 0.8.2, 34 bytes

TWTPMJGDARd
}TL2L
0
55
\d
$* 1 Try it online! Link includes test cases. Explanation: TWTPMJGDARd Convert the letters WTPMJGDA to the digits 9..0. }TL2L Shuffle all the remaining letters down by 1 and repeat until all of the letters have been converted to digits. 0 55 Replace 0 with 55 as they take the same number of pulses to dial. \d$*
1

Take the digital sum.

K4, 44 bytes

Solution:

+/(1+(1+&(5#3),4 3 4),!10)(.Q.A,1_.Q.n,"0")?

Examples:

q)k)+/(1+(1+&(5#3),4 3 4),!10)(.Q.A,1_.Q.n,"0")?"911"
11
q)k)+/(1+(1+&(5#3),4 3 4),!10)(.Q.A,1_.Q.n,"0")?"867-5309"
48
q)k)+/(1+(1+&(5#3),4 3 4),!10)(.Q.A,1_.Q.n,"0")?"+1 (212) PE6-5000"
57
q)k)+/(1+(1+&(5#3),4 3 4),!10)(.Q.A,1_.Q.n,"0")?"1-800-FLOWERS"
69

Explanation:

Naive approach, likely pretty golfable. Lookup index of character, lookup score, sum.

+/(1+(1+&(5#3),4 3 4),!10)(.Q.A,1_.Q.n,"0")? / the solution
? / lookup
(               )  / do this together
"0"   / string "0"
,      / join with
.Q.n       / string "0123456789"
1_           / drop first
,             / join with
.Q.A              / "A..Z"
(                      )                   / do this together
!10                    / range 0..9
,                       / join with
(              )                        / do this together
4 3 4                         / list (4;3;4)
,                              / join with
(5#3)                               / list (3;3;3;3;3)
&                                    / where, creates list 0 0 0 1 1 1 2 2 etc
+/                                           / sum up

{sum +<<m:g/\d/X||10}o{S:g[<:L>]=$/.ord*.313-28+|0+9} Try it online! Multiplies the ASCII code with 0.313 instead of 1/3 and uses bitwise OR which rounds to zero to get the correct bias. C (gcc), 948986 80 bytes Thanks to ceilingcat, nwellnhof and Rogem for the suggestions. c;f(char*s){c=*s-48;s=*s?(c<10U?c?c:10:c-17<26U?(c-11-c/35-c/42)/3:0)+f(s+1):0;} Try it online! • 80 bytes by using recursion instead of a loop. – user77406 May 24 '19 at 19:16 • Suggest c<43U instead of c-17<26U Jul 25 '19 at 1:45 Bash, 256 bytes You can replace the (( … )) constructs with let for identical byte count. There may be a good algorithm to reduce the case statements but not found it so far. With a bit of rework you could make it a function also (but not in same or less bytes unless you can discount the function fname { … } top and tail). read p;while ((${#p}>0));do case ${p:0:1} in ([1-9]) ((d+=${p:0:1}));; () ((d+=10));; ([ABC) ((d+=2));; ([P-S]) ((d+=7));; ([W-Z]) ((d+=9));;([DEF]) ((d+=3));; ([GHI]) ((d+=4));; ([JKL]) ((d+=5));; ([MNO]) ((d+=6));; (?) d=$d; esac;p=${p#?};done;echo $d Try it online! A better solution using the map character technique makes use of the tr tool: [Bash with tr], 173 bytes read p;p=$(echo $p|tr A-Z 22233344455566677778889999);while ((${#p}>0));do case ${p:0:1} in ([1-9]) ((d+=${p:0:1}));; () ((d+=10));; (?) d=$d; esac;p=${p#?}; done;echo $d Try it online! • One algorithm I missed of course is to do some string replacement/translation on A-Z. This would be good. I will amend the above to suit. – PJF May 23 '19 at 13:12 • while((${#p})) works, saving three bytes. c=${p:0:1};case c in ([0-9]) ((d+=c?c:10));; saves another 16. With tr -dc 0-9 added to the tr pipeline you don't need a case statement at all and the addition can be folded into the while condition with &&. May 24 '19 at 8:41 • Thanks OMG. I don't often use ternary assignment so I missed that. Interesting use of delete complement as well (but that assumes like my solution ignore any other character). I have managed to get it down to 133 bytes as in: read p;p=$(echo $p|tr A-Z 22233344455566677778889999|tr -dc [0-9]);while ((${#p}));do c=${p:0:1}&&((d+=c?c:10));p=${p#?};done;echo $d – PJF May 24 '19 at 13:16 • 118: p=$(head -1|tr A-Z 22233344455566677778889|tr -dc 0-9);while((${#p}));do((d+=(c=${p:0:1})?c:10));p=${p#?};done;echo$d .. the last three 9's are not needed because tr will reuse the last replacement character if the second argument is too short. May 24 '19 at 14:42
• The first example can be stripped down from 256 to 236 by removing a few unnecessary spaces. read p;while((${#p}>0));do case${p:0:1} in ([1-9])((d+=${p:0:1}));;()((d+=10));;([ABC)((d+=2));;([P-S])((d+=7));;([W-Z])((d+=9));;([DEF])((d+=3));;([GHI])((d+=4));;([JKL])((d+=5));;([MNO])((d+=6));;(?)d=$d;esac;p=${p#?};done;echo$d Jun 15 '19 at 16:03

Jelly, 33 24 bytes

7r32:ØP«9Ṿ€ØAżFyfØDV€o⁵S

Try it online!

A monadic link taking a string as its argument and returning the number of pulses. Rewritten inspired by @Grimy’s 05AB1E answer so be sure to upvote them!

PowerShell,109102 87 bytes

$(switch -r($args|% t*y){\d{$_}[A-Y]{("{0}"-f(.313*$_-18))}[Z]{9}0{10}})-join'+'|iex

Try it online!

EDIT: Used @mazzy's idea for a regex switch with some string formatting to cast char -> int -> string and grab only the first 'digit'

Original:

[char[]]"$args"|%{$a+=(48,(('22233344455566677778889999')[$_-65],(58,$_)[$_-ne48])[$_-lt64])[$_-gt47]-=48};$a

I was hoping to get <100 bytes, so I'll keep looking at it to see if there is anything else I can do. There's probably a way to remove the number string

Sorry if this is confusing as I nested arrays with boolean indexing statements but -

Explanation:

[char[]]"$args"|%{ reads the input casted as a string and then explodes it to a char array and begins a for-each loop with checking ()[$_-gt47] to see if any ()+-./ was entered (all have ascii character values <48)
Note: Powershell accepts $true and$false as 1 and 0 respectively for array indices

Then we get either 48 for the symbols, or:
('22233344455566677778889999'[$_-65],(58,$_)[$_-ne48])[$_-lt64]

The [$_-lt64] checks for a number or a letter (all assumed capital here). If it's a letter, '22233344455566677778889999'[$_-65] changes it to 0-25 to index into the array and output the pulse value (as a char). If the character is a number, we instead look at: (58,$_)[$_-ne48] checking for 0 and outputting 58 or just the numeric char itself.

Around everything $a+= ... -=48 initializes a numeric variable$a at 0 and then adds the output. The output is the ascii char value of a number, so subtract 48.

Note: if the input was a symbol, we get $a+=48-48, effectively ignoring it. If it was 0, we get$a+=58-48 to obtain our +10

Lastly, ;$a just outputs our final value post for-each loop • you could save a few bytes Try it online! Jun 12 '19 at 19:27 • Ah, yeah I had some extra parentheses and the = in there, left over from my previous methods of solving this, thanks for the catch! Though, I haven't seen the t*y before, could you explain why that works to explode the string into a character array? Jun 12 '19 at 19:36 • Jun 12 '19 at 19:38 • to get '< 100 bytes': Try it online! :) Jun 12 '19 at 19:44 • good idea with -f and . Jun 13 '19 at 15:30 PowerShell, 9585 79 bytes inspired by nwellnhof's answer. inspired by  from Sinusoid's answer.$(switch -r($args|% t*y){\d{$_}0{10}[A-Y]{"$(.313*$_-18)"}Z{9}})-join'+'|iex

Try it online!

Unrolled version:

$( switch -r($args|% toCharArray){
\d    {$_} 0 {10} [A-Y] {"$(.313*$_-18)"} Z {9} } )-join '+'|Invoke-Expression key .313*$_-18 "\$(...)"
--- ---------- -----------
A      2.345 2
B      2.658 2
C      2.971 2
D      3.284 3
E      3.597 3
F      3.910 3
G      4.223 4
H      4.536 4
I      4.849 4
J      5.162 5
K      5.475 5
L      5.788 5
M      6.101 6
N      6.414 6
O      6.727 6
P      7.040 7
Q      7.353 7
R      7.666 7
S      7.979 7
T      8.292 8
U      8.605 8
V      8.918 8
W      9.231 9
X      9.544 9
Y      9.857 9
• Team effort! :D Jun 13 '19 at 15:46

Stax, 21 bytes

éT6T&\²|└t5φ╛│█♪√┘↑▓^

Run and debug it

Kotlin, 113 bytes

{s:String->var t=0
for(c in s){val v=c-'0'
t+=when(v){0->10
in 1..9->v
in 17..42->(v-11-v/35-v/42)/3
else->0}}
t}

Try it online!