Telephone number in spoken words; &050okoDed 592th.tta) th:e ʻ
Goal
Write a program or function that translates a numerical telephone number into text that makes it easy to say. When digits are repeated, they should be read as "double n" or "triple n".
Requirements
Input
A string of digits.
- Assume all characters are digits from 0 to 9.
- Assume the string contains at least one character.
Output
Words, separated by spaces, of how these digits can be read out loud.
Translate digits to words:
0 "oh"
1 "one"
2 "two"
3 "three"
4 "four"
5 "five"
6 "six"
7 "seven"
8 "eight"
9 "nine"When the same digit is repeated twice in a row, write "double number".
- When the same digit is repeated thrice in a row, write "triple number".
- When the same digit is repeated four or more times, write "double number" for the first two digits and evaluate the rest of the string.
- There is exactly one space character between each word. A single leading or trailing space is acceptable.
- Output is not case sensitive.
Scoring
Source code with the least bytes.
Test Cases
input output
-------------------
0123 oh one two three
4554554 four double five four double five four
000 triple oh
66667888 double six double six seven triple eight
19999999179 one double nine double nine triple nine one seven nine
-
9\\$\\begingroup\\$ Anyone interested in "speech golf" should note that "double six" takes longer to say than "six six". Of all the numerical possibilities here, only "triple seven" saves syllables. \\$\\endgroup\\$ – Purple P 9 hours ago
-
1\\$\\begingroup\\$ @Purple P: And as I'm sure you know, 'double-u double-u double-u'>'world wide web'.. \\$\\endgroup\\$ – Chas Brown 9 hours ago
-
2\\$\\begingroup\\$ I vote to change that letter to "dub". \\$\\endgroup\\$ – Hand-E-Food 9 hours ago
-
\\$\\begingroup\\$ @Adám, no. Added to output requirements. Thanks! \\$\\endgroup\\$ – Hand-E-Food 8 hours ago
-
\\$\\begingroup\\$ You should have used non-English/standard digit names for all digits to avoid build-ins… \\$\\endgroup\\$ – Adám 7 hours ago
12 Answers
05AB1E, 61 56 bytes
γεDg;Å2¨3ª£}˜v… ‹¶½¿#yg蓊瀵‚•„í†ìˆÈŒšï¿Ÿ¯¥Š“#yнè})õKðý
-5 bytes thanks to @Grimy.
Try it online or verify all test cases.
Explanation:
γ # Split the (implicit) input into substrings of equal adjacent characters
ε # Map each substring to:
Dg # Get the length of a copy of the substring
; # Halve this length
Å2 # Create a list with that many 2s (truncates .5 decimals to an integer)
¨3ª # Remove the last 2, and append a 3 instead
£ # Split the substring into parts according to this list
# i.e. "9999999" (length=7) → [2,2,3] → ["99","99","999"]
}˜ # After the map: flatten the list
v # Loop over each item `y`:
… ‹¶½¿ # Push dictionary word: " double triple"
# # Split it on spaces: ["","","double","triple"]
yg # Get the length of the current item `y`
è # And use it to (0-based) index into the list
“Šç€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š“
# Push dictionary string "oh two three four five six seven eight nine"
# # Split it on spaces: ["oh","two","three",...,"nine"]
yн # Use the first digit of substring `y`
è # To index into this list
}) # After the loop: wrap all values on the stack into a list
õK # Remove empty strings from this list
ðý # And join the list on spaces
# (after which the result is output implicitly)
See this 05AB1E tip of mine (section How to use the dictionary?) to understand why … ‹¶½¿ is " double triple" and “Šç€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š“ is "oh two three four five six seven eight nine".
-
1\\$\\begingroup\\$ @Grimy Ah, of course.. I added the
if(length>=4)before adding the rest, but of course it isn't necessary for integers of size1,2,3, because the;Å2¨3ª£will leave the strings intact (merely wrapped in a list we flatten after the map anyway). Thanks for noticing! And looking forward seeing your answer withÅγ. I indeed had the feeling the first part could be done a lot shorter somehow. \\$\\endgroup\\$ – Kevin Cruijssen 2 hours ago -
\\$\\begingroup\\$ Here's my current best.
Åγdidn't turn out as great as I thought; a better score might be possible by golfing your approach instead. \\$\\endgroup\\$ – Grimy 1 hour ago -
\\$\\begingroup\\$ @Grimy Not as short as I thought, but yours is still shorter than mine, so obvious +1. I like the single string with added
10/12indices for thedouble/triple. Out of curiosity though, why the work-around¸»when a simpleðýsuffices? ;) \\$\\endgroup\\$ – Kevin Cruijssen 1 hour ago -
\\$\\begingroup\\$ It's actually 11 for triple, not 12. Why
ðýwhen¸»does the job?ðýis a rigid two-byter, while¸»offers golfing opportunities if you can wrangle the rest of the code such that¸is not necessary. \\$\\endgroup\\$ – Grimy 1 hour ago -
\\$\\begingroup\\$ @Grimy Ah, the 12 was a typo.. And I personally tend to use the most straight-forward approach if byte-counts are the same anyway (unless an equal-bytes but less straight-forward approach is a huge improvement for performance). I personally think using "push space, join by it" is easier to understand for someone reading through the docs than "wrap list in list, join each inner-most list by spaces, and then those strings by newlines (but since there is only a single inner string, no newlines are added)". ;) But feel free to use whatever you'd want; everyone has their own coding style of course. \\$\\endgroup\\$ – Kevin Cruijssen 1 hour ago
QuadR, 137 bytesSBCS
Title case with a leading space.
∊¯2↑¨@(∊∘⎕A)⍵
(.)\\1*
{⍺←(,¨⎕D)⎕R('OhOneTwoThreeFourFiveSixSevenEightNine'(∊⊂⊣)⎕A)⋄' '∊w←⍺,⊃⍵:⍬⋄1=≢⍵:⍺⍵⋄3=≢⍵:'Triple',w⋄'Double',w,∇2↓⍵}⍵M
Try it online!
∊ ϵnlist (flatten)
¯2↑¨ take the last two characters (padding on left with a space) of each characters
@ at positions where
(∊∘⎕A) characters are members of the uppercase Alphabet
⍵ in the result of the below PCRE Replace operation…
(.) any character
\\1 followed by itself
* zero or more times, is replaced by the result of the following…
{…}⍵M "dfn"; ⍵ is the Match of the above pattern
('OhOneTwoThreeFourFiveSixSevenEightNine'(…)⎕A) apply the following anonymous tacit function with the long string and the uppercase Alphabet as left arguments:
∊ membership (of letters in the long string in the uppercase alphabet)
⊂ partitions (with a new partition beginning whenever is-a-member
⊣ the left argument (i.e. the long string)
(…)⎕R PCRE Replace the following patterns with those words:
⎕D the digits 0 through 9
,¨ treat each as a separate pattern
⍺← assign this replacement function to ⍺ (for alphabetise)
⋄ then,
⊃⍵ the first character of the match
, as a string
⍺ apply ⍺ to it
w← assign this to w (for word)
' '∊…: if space is a member thereof (i.e. if the match was empty):
⍬ return nothing (becomes the empty string)
⋄ else,
1=≢⍵: if one equals the tally of characters in the match (i.e. its length):
⍺⍵ alphabetise that digit
⋄ else,
3=≢⍵: if three equals the tally of characters in the match (i.e. its length):
'Triple',w prepend "Triple" to the word
⋄ else,
2↓⍵ drop to digits from the match
∇ recurse on that
w, prepend the word
'Double', prepend "Double"
05AB1E, 53 52 51 bytes
γεεT‚Nè}D¤›ài¨₃1ǝ]혓Šç€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š‹¶½¿“#s踻
Try it online!
Explanation:
γ # split input in groups of consecutive equal digits
ε ] # for each group
ε } # for each digit in the group
T‚Nè # if the 0-based index is odd, replace the digit with 10
D¤›ài ] # if the last element is not the maximum
¨ # drop the last element
₃1ǝ # replace the second element with 95
í # reverse each group
˜ # flatten
“...“ # compressed string: "oh one two ... nine double triple"
# # split on spaces
sè # index (wraps around, so 95 yields "triple")
¸» # join with spaces
Wolfram Language (Mathematica), 115 bytes
{Switch[L=Tr[1^{##}],1," ",3," triple ",_," double "],If[#<1,"oh",IntegerName@#],If[L>3,#0@##3,""]}&@@@Split@#<>""&
Try it online!
Takes a list of digits as input. Output includes a leading space.
JavaScript (ES6), 161 160 152 144 bytes
The output includes a single leading space.
s=>[n=>' '+'oh/one/two/three/four/five/six/seven/eight/nine'.split`/`[n],'$1 double$2','triple$2'].map(r=>s=s.replace(/(\\S*)( \\S+)\\2|\\d/g,r))&&s
Try it online!
How?
The conversion is processed in three steps:
- replace each digit with the corresponding English word, preceded with a space
- replace each pattern
"X X"with"double X" - replace each pattern
"double X X"with"triple X"
To save bytes, we use the same regular expression for all steps:
/(\\S*)( \\S+)\\2|\\d/g (\\S*) -> 1st capturing group: any word, or nothing at all
( \\S+) -> 2nd capturing group: a space, followed by a word
\\2 -> a copy of the 2nd capturing group
|\\d -> or try to capture a digit instead (for step 1)
For step 1, we use a callback function that picks the correct word from a lookup table:
"799999"→" seven nine nine nine nine nine"
For step 2, we replace with "$1 double$2":
" (seven)( nine)( nine)"→" seven double nine""( nine)( nine) nine"→" double nine nine"
For step 3, we replace with "triple$2":
" (double)( nine)( nine)"→" triple nine"
Formatted source code
Scala, 221 bytes
Got it. Somehow the recursive version I was trying to build was strongly more verbose-y than this one (still recursive though, but only in one case). Function f takes as an input string the phone number and outputs its phonetics with a trailing whitespace.
var u="oh one two three four five six seven eight nine" split " "
"(.)\\\\1*".r.replaceAllIn(s,x=>{var o=x.matched
var k=u((o(0)+"").toInt)+" "
o.length match{case 3=>"triple "+k
case 1=>k
case _=>"double "+k+f(o drop 2)}})
Try it online!
Edit : it does not link to the version using ..." split " " but to the version with ...".split(" ") which is one byte longer.
Scala, 223 bytes
And here comes the leading whitespace version, two bytes longer for some reason (even with massive refactoring).
var u="oh one two three four five six seven eight nine" split " "
"(.)\\\\1*".r.replaceAllIn(s,x=>{var o=x.matched
var k=u((o(0)+"").toInt)
" , double , triple ".split(",")(if(o.length>3){k+=f(o drop 2);1}else o.length-1)+k})
Try it online!
Python 2, 171 169 168 bytes
s=input()
while s:c=s[0];n=(s[1:2]==c)+(s[:3]==c*3!=s[1:4]);print' eellpbiurotd'[-n:0:-2]+'oh one two three four five six seven eight nine'.split()[int(c)],;s=s[1+n:]
Try it online!
-1 byte, thanks to Jitse
-
\\$\\begingroup\\$ Beat me again! Save 1 byte like so \\$\\endgroup\\$ – Jitse 4 hours ago
-
\\$\\begingroup\\$ @Jitse, that doesn't work for
1312;) \\$\\endgroup\\$ – TFeld 4 hours ago -
\\$\\begingroup\\$ Ah, you're right! \\$\\endgroup\\$ – Jitse 4 hours ago
-
\\$\\begingroup\\$ How about this one then:
['','double ','triple '][n]to' eellpbiurotd'[-n:0:-2]for 168 bytes: Try it online! \\$\\endgroup\\$ – Jitse 4 hours ago -
\\$\\begingroup\\$ Alternatively, also 168 bytes \\$\\endgroup\\$ – Jitse 3 hours ago
Lua 5.1, 166 bytes
for I,N in('111 triple 11 double 1 '):gmatch'(%d+)(%D+)'do for i,n in('0oh1one2two3three4four5five6six7seven8eight9nine'):gmatch'(.)(%l+)'do s=s:gsub(i*I,N..n)end end
Ensure s is a predefined string value populated only with digits; that will be the variable to be modified. The result will include a leading space [\\u20] character.
-
\\$\\begingroup\\$ Welcome to the site! As Lua can take input via standard methods, it's against the rules to require
sto already have the input. Aside from that, you've got a good first post! I would recommend you include a link to an online testing site e.g. tio.run/#lua so that others can test your solution \\$\\endgroup\\$ – caird coinheringaahing 4 hours ago
PHP, 174 169 166 bytes
for(;$s=strspn($argn,$d=$argn[$i],$i++);$s==3?($i+=2)&&print'triple ':$s<2?:++$i&&$a[]=print'double ',print[oh,one,two,three,four,five,six,seven,eight,nine][$d].' ');
Try it online!
For each digit at index of $i starting from 0:
- If span of the same digit starting from location of
$iis equal to 3, prints'triple 'and adds 2 to$iso next iteration will have 2 digits jumped over. - If span of the same digit starting from location of
$iis equal to or more than 2 but is not 3, prints'double 'and adds 1 to$iso next iteration will have 1 digit jumped over. - Prints word for the digit and a space.
$i++.
Red, 242 bytes
func[s][b:[copy t skip t]parse s[any[change[b t ahead not t](rejoin["triple "t])|
change b(rejoin["double "t])| skip]]foreach c s[prin either i:
find"0123456789"c[rejoin[pick[:oh:one:two:three:four:five:six:seven:eight:nine]index? i" "]][c]]]
Try it online!
Retina 0.8.2, 105 bytes
+`(.)\\1
=$1
.
$&
= =
triple
=
double
9
nine
8
eight
7
seven
6
six
5
five
4
four
3
three
2
two
1
one
0
oh
Try it online! Outputs a leading space. Explanation: I originally tried a regex that automatically matches 2 or 3 digits but @Arnauld's approach of turned out to be golfier. Explanation:
+`(.)\\1
=$1
Match pairs of identical digits and replace the first with a =. Then repeat, so that for an odd number the second last digit is also replaced with a =.
.
$&
Space the digits (and =s) out.
= =
triple
Handle the case of three identical digits.
=
double
9
nine
8
eight
7
seven
6
six
5
five
4
four
3
three
2
two
1
one
0
oh
Replace all remaining characters with words.
T-SQL 2017, 234 bytes
Added some linebreaks to make it readable
WHILE''<left(@,1)SELECT @=stuff(@,1,iif(p<4,p,2),'')+
iif(p=1,' ',iif(p=3,' triple ',' double '))
+trim(substring('oh one two threefour five six seveneightnine',left(@,1)*5,5))
FROM(SELECT~-patindex('%[^'+left(@,1)+']%',@)p)z
PRINT @
Try it online