Number Words
July 25, 2014
The heart of the solution is the recursive word
function that builds a list of words from numbers; the critical case is the last two lines, where the accumulating output is split into two pieces when both the first digit and the first two digits are in the alphabet:
(define (word cs ds)
(if (null? ds) (list cs)
(if (null? (cdr ds)) (list (cons (letter (car ds)) cs))
(if (zero? (car ds)) (word cs (cdr ds))
(let ((n (+ (* (car ds) 10) (cadr ds))))
(if (< 26 n)
(word (cons (letter (car ds)) cs) (cdr ds))
(append (word (cons (letter (car ds)) cs) (cdr ds))
(word (cons (letter n) cs) (cddr ds)))))))))
Then the words
function handles initialization and output structuring:
(define (words n)
(map list->string (map reverse (word (list) (digits n)))))
We used the digits
function from the Standard Prelude, and an auxiliary function letter
that maps numbers to letters. You can run the program at http://programmingpraxis.codepad.org/DOBTZzzd.
In Python. words form a number and a number from a word.
In perl this returns a list of the words!
How would a number like 30 be represented? Should that just be a null list?
My function ignores the zero and returns “C” but there is no proper answer since it is always possible to add a string of zeros in the middle of the input. Feel free to make your own rule about the proper treatement of zero.
Recursive version in Python. I started the mapping at 0 -> ‘A’, so that numbers with zeros in them could be converted.
My solution above has a bug that causes duplicates to be generated when the number ends in a ‘2’.
Here’s a revised solution:
Nice problem. I ended up with a sort of dynamic programming solution, C++11:
The following is a C solution. Without the willingness to write a FIFO data set, I had to rely on recursion to get the problem completed. It took about 20 minutes and runs pretty fast. With the queue library, I could implement an iterative approach that would likely use less memory for larger numbers. Without further ado:
C:\Users\Dragonborn\Documents\CodeBlocks\numberwords\main.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 typedef unsigned long long uint;
6
7 void printNumberWords(uint number);
8 void printNumberWords_aux(char* number_str, char* path);
9 char getLetterFromPrefix(char* str, unsigned int n);
10 char* appendCString(const char* str, const char c);
11
12 unsigned int countDigits(uint number);
13 char* uint2string(uint number);
14
15 int main()
16 {
17 printNumberWords(121223213123);
18 return 0;
19 }
20
21 void printNumberWords(uint number)
22 {
23 char* number_str = uint2string(number);
24 const unsigned int LEN = strlen(number_str);
25
26 char option1 = (LEN >= 1) ? getLetterFromPrefix(number_str, 1) : ('');
27 char option2 = (LEN >= 2) ? getLetterFromPrefix(number_str, 2) : ('');
28
29 if(option1 != '')
30 {
31 if(LEN == 1)
32 printf("%c\n", option1);
33 else
34 {
35 char* path = appendCString("", option1);
36 printNumberWords_aux(number_str + 1, path);
37 free(path);
38 }
39 }
40 if(option2 != '')
41 {
42 if(LEN == 2)
43 printf("%c\n", option2);
44 else
45 {
46 char* path = appendCString("", option2);
47 printNumberWords_aux(number_str + 2, path);
48 free(path);
49 }
50 }
51 free(number_str);
52 }
53
54 void printNumberWords_aux(char* number_str, char* path)
55 {
56 const unsigned int LEN = strlen(number_str);
57
58 char option1 = (LEN >= 1) ? getLetterFromPrefix(number_str, 1) : ('');
59 char option2 = (LEN >= 2) ? getLetterFromPrefix(number_str, 2) : ('');
60
61 if(option1 != '')
62 {
63 if(LEN == 1)
64 printf("%s%c\n", path, option1);
65 else
66 {
67 char* new_path = appendCString(path, option1);
68 printNumberWords_aux(number_str + 1, new_path);
69 free(new_path);
70 }
71 }
72 if(option2 != '')
73 {
74 if(LEN == 2)
75 printf("%s%c\n", path, option2);
76 else
77 {
78 char* new_path = appendCString(path, option2);
79 printNumberWords_aux(number_str + 2, new_path);
80 free(new_path);
81 }
82 }
83 }
84
85 char getLetterFromPrefix(char* str, unsigned int n)
86 {
87 static const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
88 static const unsigned int LETTERS_LEN = 26;
89
90 char* nstr = malloc(sizeof(char) * (n+1));
91 nstr[n] = '';
92 memcpy(nstr, str, n * sizeof(char));
93
94 int number = atoi(nstr);
95 free(nstr);
96
97 if(number <= LETTERS_LEN && number > 0)
98 return LETTERS[number - 1];
99 return '';
100 }
101
102 char* appendCString(const char* str, const char c)
103 {
104 const unsigned int LEN = strlen(str);
105 char* rValue = malloc(sizeof(char) * (LEN + 2));
106 memcpy(rValue, str, LEN);
107 rValue[LEN + 0] = c;
108 rValue[LEN + 1] = '';
109 return rValue;
110 }
111
112
113 char* uint2string(uint number)
114 {
115 const unsigned int NUMBER_LEN = countDigits(number);
116 unsigned int pos = NUMBER_LEN - 1;
117 char* rValue = malloc(sizeof(char) * NUMBER_LEN + 1);
118 rValue[NUMBER_LEN] = '';
119 while(number > 0)
120 {
121 rValue[pos] = (number % 10) + '0';
122 number /= 10;
123 pos --;
124 }
125 return rValue;
126 }
127
128 unsigned int countDigits(uint number)
129 {
130 unsigned int rValue = 0;
131 while(number > 0)
132 {
133 rValue++;
134 number /= 10;
135 }
136 return rValue;
137 }
That is a tricky interview question considering the ambiguity with zero.
It is interesting to see how the different solutions react to
hostile inputs containing zeros. There might be some surprises.
My Haskell solution maps 0 to @.
Output:
Good point about zeroes – I’m inclined to just say there aren’t any matches for eg. “30”.
Here’s a nice and simple recursive solution in C (my other solution avoids some duplicated effort in the recursive calls, but the space requirements get quite large). This generates the solutions in lexicographic order too.
There are 259584 solutions for the string “1234567891011121314151617181920212223242526” incidentally (including “ABCDEFGHIJKLMNOPQRSTUVWXYZ” of course).
Whee Rackety goodness!
Full writeup: Number words
I’ve been meaning to do this for a while – here’s an improved version of my “dynamic programming” solution; this one uses a tree structure to store the labellings rather than an explicit list (which gets rather large). Now, we can calculate that eg. ‘12345678910111213141516171819202122232425261234567891011121314151617181920212223242526’ has 67383853056 labellings, which would take a long time to generate explicitly. It also fixes a bug which mishandled the case where no labelling was possible.