Telephone number in spoken words
8088 Assembly, IBM PC DOS, 164 159 156 155 bytes
Binary:
00000000: d1ee 8a0c 03f1 53fd ac3a d075 0343 e2f7 ......S..:.u.C..
00000010: 85db 741c 5f8a d043 f6c3 0174 0a57 bd64 ..t._..C...t.W.d
00000020: 0155 83eb 0374 0957 bd5d 0155 4b4b 75f7 .U...t.W.].UKKu.
00000030: 8ad0 2c2f 7213 518a f0b0 24b1 31bf 6a01 ..,/r.Q...$.1.j.
00000040: fcf2 aefe ce75 fa59 57e2 bc5a 85d2 740c .....u.YW..Z..t.
00000050: b409 cd21 b220 b402 cd21 ebef c364 6f75 ...!. ...!...dou
00000060: 626c 6524 7472 6970 6c65 246f 6824 6f6e ble$triple$oh$on
00000070: 6524 7477 6f24 7468 7265 6524 666f 7572 e$two$three$four
00000080: 2466 6976 6524 7369 7824 7365 7665 6e24 $five$six$seven$
00000090: 6569 6768 7424 6e69 6e65 24 eight$nine$
Build and test executable using xxd -r
from above, or download PHONE.COM.
Unassembled listing:
D1 EE SHR SI, 1 ; point SI to DOS PSP (80H) for input string
8A 0C MOV CL, BYTE PTR[SI] ; load input string length into CX
03 F1 ADD SI, CX ; move SI to end of input
53 PUSH BX ; push a 0 to signal end of output stack
CHAR_LOOP:
FD STD ; set LODS direction to reverse
AC LODSB ; load next char from [SI] into AL, advance SI
3A D0 CMP DL, AL ; is it same as previous char?
75 03 JNZ NEW_CHAR ; if not, it's a different char
43 INC BX ; otherwise it's a run, so increment run length
E2 F7 LOOP CHAR_LOOP ; move on to next char
NEW_CHAR:
85 DB TEST BX, BX ; is there a run greater than 0?
74 1C JZ GET_WORD ; if not, look up digit name
5F POP DI ; get name for the current digit
8A D0 MOV DL, AL ; save current char in DL
43 INC BX ; adjust run count (BX=1 means run of 2, etc)
F6 C3 01 TEST BL, 1 ; is odd? if so, it's a triple
74 0A JZ IS_DBL ; is even, so is a double
57 PUSH DI ; push number string ("one", etc) to stack
BD 0164 MOV BP, OFFSET T ; load "triple" string
55 PUSH BP ; push to stack
83 EB 03 SUB BX, 3 ; decrement run count by 3
74 09 JZ GET_WORD ; if end of run, move to next input char
IS_DBL:
57 PUSH DI ; push number string to stack
BD 015D MOV BP, OFFSET D ; load "double" string
55 PUSH BP ; push to stack
4B DEC BX ; decrement by 2
4B DEC BX
75 F7 JNZ IS_DBL ; if not end of run, loop double again
GET_WORD:
8A D0 MOV DL, AL ; save current char into DL
2C 2F SUB AL, '0'-1 ; convert ASCII char to 1-based index
72 13 JB NOT_FOUND ; if not a valid char, move to next
51 PUSH CX ; save outer loop counter
8A F0 MOV DH, AL ; DH is the index to find, use as scan loop counter
B0 24 MOV AL, '$' ; word string is $ delimited
B1 31 MOV CL, 031H ; search through length of word data (49 bytes)
BF 016A MOV DI, OFFSET W ; reset word data pointer to beginning
FC CLD ; set DF to scan forward for SCAS
SCAN_LOOP:
F2/ AE REPNZ SCASB ; search until delimiter '$' is found in [DI]
FE CE DEC DH ; delimiter found, decrement counter
75 FA JNZ SCAN_LOOP ; if counter reached 0, index has been found
59 POP CX ; restore outer loop position
57 PUSH DI ; push string on stack
NOT_FOUND:
E2 BC LOOP CHAR_LOOP ; move to next char in input
OUTPUT_STACK:
5A POP DX ; get string from top of stack
85 D2 TEST DX, DX ; it is the last?
74 0C JZ EXIT ; if so, exit
B4 09 MOV AH, 09H ; DOS display string function
CD 21 INT 21H ; write string to console
B2 20 MOV DL, ' ' ; load space delimiter
B4 02 MOV AH, 02H ; DOS display char function
CD 21 INT 21H ; write char to console
EB EF JMP OUTPUT_STACK ; continue looping
EXIT:
C3 RET ; return to DOS
D DB "double$"
T DB "triple"
W DB "$oh$","one$","two$","three$","four$","five$","six$","seven$","eight$","nine$"
TL;DR:
The input string is read right to left to make it easier to find a triple. The output is pushed onto the x86 stack to simplify reversing out the display order and also facilitate rearranging the "double" and "triple" words to precede the digit's name.
If the next digit is different than the last, the name is looked up in the list of words and pushed on to the stack. Since there's no formal concept of an "indexed array of variable-length strings" in machine code, the list of words is scanned i
(the word's index) number of times for the string delimiter ($
) to find the corresponding word. Helpfully, x86 does have a pair of short instructions (REPNZ SCASB
which is similar to memchr()
in C), that simplifies this (thanks CISC!).
If the digit is the same as the previous one, the counter for the length of a "run" is incremented and continues looping leftward on the input. Once the run is over, the digit's name is taken from the stack since it will need to placed after the "double" or "triple" for each grouping. If the length of the run is odd (and run length is > 1
), the digit's name followed by the string "triple" is pushed to the stack and the run length is reduced by 3. Since the run length will now be even, the step is repeated for "double" until the run length is 0.
When the input string has reached the end, the stack is dumped out with each saved string written to the screen in reverse order.
I/O:
A standalone PC DOS executable, input from command line output to console.
Download and test PHONE.COM.
05AB1E, 53 52 51 50 49 bytes
γε€T2äθ¬MÊi¨₃1ǝR]˜“Šç€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š‹¶½¿“#s踻
Try it online!
Explanation:
γ # split input in groups of consecutive equal digits
ε ] # for each group
€T # add a 10 before each digit (66 -> [10, 6, 10, 6])
2äθ # keep only the second half of that list
¬MÊi ] # if the first element is not the maximum
¨ # drop the last element
₃1ǝ # replace the second element with 95
R # reverse the list
˜ # flatten
“...“ # compressed string: "oh one two ... nine double triple"
# # split on spaces
sè # index (wraps around, so 95 yields "triple")
¸» # join with spaces
05AB1E, 61 56 53 52 51 bytes
γvyDg;LàäRv… ‹¶½¿#yg蓊瀵‚•„í†ìˆÈŒšï¿Ÿ¯¥Š“#yè])áðý
-9 bytes thanks to @Grimy.
Try it online or verify all test cases.
Explanation:
γ # Split the (implicit) input into substrings of equal adjacent characters
# i.e. "199999991779" → ["1","9999999","1","77","9"]
v # Loop over each substring `y`:
Dg # Get the length of a copy of the substring
; # Halve it
L # Create a list in the range [1, length/2], where odd lengths are
# automatically truncated/floored
# i.e. "1" (length=1) → 0.5 → [1,0]
# i.e. "9999999" (length=7) → 3.5 → [1,2,3]
à # Pop and push the maximum of this list
y ä # Divide the string into that many parts
# → ["1"]
# → ["999","99","99"]
R # Reverse the list
# → ["99","99","999"]
v # Inner 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 `y` to index into the string-list (with automatic wrap-around,
# so since there are 10 words, it basically indexes with a single digit
# due to an implicit modulo-10)
# i.e. "77" → "seven"
] # Close both loops
) # Wrap all values on the stack into a list
á # Only keep letters, which removes the empty strings from the 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"
.