|
4 | 4 | ;; each phone number can be expressed as a list of words.
|
5 | 5 | ;; Run: (main "word-list-file-name" "phone-number-file-name")
|
6 | 6 |
|
7 |
| -(defvar *dict* nil |
| 7 | +(declaim (optimize (speed 3) (debug 0) (safety 0))) |
| 8 | +(declaim (inline nth-digit char->digit update-key update-key-fast)) |
| 9 | + |
| 10 | +(defglobal *dict* nil |
8 | 11 | "A hash table mapping a phone number (integer) to a list of words from the
|
9 | 12 | input dictionary that produce that number.")
|
10 | 13 |
|
| 14 | +(declaim (ftype (function (simple-string (integer 0 50)) (integer 48 57)) nth-digit)) |
| 15 | +(defun nth-digit (digits i) |
| 16 | + "The i-th element of a character string of digits, as an integer 0 to 9." |
| 17 | + (- (char-code (char digits i)) #.(char-code #\0))) |
| 18 | + |
| 19 | +(declaim (ftype (function (base-char) (integer 0 9)) char->digit)) |
| 20 | +(defun char->digit (ch) |
| 21 | + "Convert a character to a digit according to the phone number rules." |
| 22 | + (ecase (char-downcase ch) |
| 23 | + ((#\e) 0) |
| 24 | + ((#\j #\n #\q) 1) |
| 25 | + ((#\r #\w #\x) 2) |
| 26 | + ((#\d #\s #\y) 3) |
| 27 | + ((#\f #\t) 4) |
| 28 | + ((#\a #\m) 5) |
| 29 | + ((#\c #\i #\v) 6) |
| 30 | + ((#\b #\k #\u) 7) |
| 31 | + ((#\l #\o #\p) 8) |
| 32 | + ((#\g #\h #\z) 9))) |
| 33 | + |
| 34 | +(deftype handler-fn () `(function (simple-string list) null)) |
| 35 | + |
| 36 | +(declaim (ftype (function (simple-string) handler-fn) choose-handler)) |
11 | 37 | (defun choose-handler (print-or-count)
|
12 | 38 | "Chooses a solution handler function.
|
13 | 39 | When a numbers file is fully consumed, call the function with nil words to signal EOF."
|
|
17 | 43 | (when words (format t "~a:~{ ~a~}~%" num (reverse words)))))
|
18 | 44 | ((string= print-or-count "count")
|
19 | 45 | (let ((count 0))
|
| 46 | + (declare (type fixnum count)) |
20 | 47 | #'(lambda (num words)
|
| 48 | + (declare (ignore num)) |
21 | 49 | (if words
|
22 | 50 | (incf count)
|
23 | 51 | (progn ;; no words is the EOF signal
|
24 | 52 | (format t "~a~%" count)
|
25 | 53 | (setf count 0)))))) ;; reset count for the next file
|
26 | 54 | (t (error "Unknown option: ~a" print-or-count))))
|
27 |
| - |
28 | 55 |
|
29 |
| -(defun main (&optional print-or-count (dict "tests/words.txt") (nums "tests/numbers.txt") (dict-size 100)) |
30 |
| - "Read the input file ¨DICT and load it into *dict*. Then for each line in |
31 |
| - NUMS, print all the translations of the number into a sequence of words, |
32 |
| - according to the rules of translation." |
33 |
| - (let ((handler (choose-handler print-or-count))) |
34 |
| - (setf *dict* (load-dictionary dict dict-size)) |
35 |
| - (with-open-file (in nums) |
36 |
| - (loop for num = (read-line in nil) while num do |
37 |
| - (find-translations handler num (remove-if-not #'digit-char-p num)))) |
38 |
| - (funcall handler "" nil))) |
| 56 | +(defun update-key-fast (n digit) |
| 57 | + (logand #.(1- (ash 1 (integer-length most-positive-fixnum))) |
| 58 | + (+ (* 10 n) digit))) |
39 | 59 |
|
| 60 | +(defun update-key (n digit) |
| 61 | + (+ (* 10 n) digit)) |
| 62 | + |
| 63 | +(declaim (ftype (function (handler-fn simple-string simple-string &optional |
| 64 | + (integer 0 50) list) null) find-translations)) |
40 | 65 | (defun find-translations (handler num digits &optional (start 0) (words nil))
|
41 | 66 | "Print each possible translation of NUM into a string of words. DIGITS
|
42 | 67 | must be WORD with non-digits removed. On recursive calls, START is the
|
|
57 | 82 | becomes 12 and ER becomes 102."
|
58 | 83 | (if (>= start (length digits))
|
59 | 84 | (funcall handler num words)
|
60 |
| - (let ((found-word nil) |
61 |
| - (n 1)) ; leading zero problem |
62 |
| - (loop for i from start below (length digits) do |
63 |
| - (setf n (+ (* 10 n) (nth-digit digits i))) |
64 |
| - (loop for word in (gethash n *dict*) do |
65 |
| - (setf found-word t) |
66 |
| - (find-translations handler num digits (+ 1 i) (cons word words)))) |
67 |
| - (when (and (not found-word) (not (numberp (first words)))) |
68 |
| - (find-translations handler num digits (+ start 1) |
69 |
| - (cons (nth-digit digits start) words)))))) |
| 85 | + (loop with found-word = nil |
| 86 | + with n = 1 ; leading zero problem |
| 87 | + with len = (length digits) |
| 88 | + with update-n = (if (< (+ start len) 19) #'update-key-fast #'update-key) |
| 89 | + for i from start below len |
| 90 | + do (setf n (funcall update-n n (nth-digit digits i))) |
| 91 | + (loop for word in (gethash n *dict*) |
| 92 | + do (setf found-word t) |
| 93 | + (find-translations handler num digits (1+ i) (cons word words))) |
| 94 | + finally (return (when (and (not found-word) (not (numberp (first words)))) |
| 95 | + (find-translations handler num digits (1+ start) |
| 96 | + (cons (nth-digit digits start) words))))))) |
| 97 | + |
| 98 | +(declaim (ftype (function (simple-string) integer) word->number)) |
| 99 | +(defun word->number (word) |
| 100 | + "Translate a word (string) into a phone number, according to the rules." |
| 101 | + (let ((update-n (if (< (length word) 19) #'update-key-fast #'update-key))) |
| 102 | + (loop with n = 1 ; leading zero problem |
| 103 | + for i from 0 below (length word) |
| 104 | + for ch of-type base-char = (char word i) |
| 105 | + do (when (alpha-char-p ch) |
| 106 | + (setf n (funcall update-n n (char->digit ch)))) |
| 107 | + finally (return n)))) |
70 | 108 |
|
| 109 | +(declaim (ftype (function (simple-string (integer 0))) load-dictionary)) |
71 | 110 | (defun load-dictionary (file size)
|
72 | 111 | "Create a hashtable from the file of words (one per line). Takes a hint
|
73 | 112 | for the initial hashtable size. Each key is the phone number for a word;
|
|
78 | 117 | (push word (gethash (word->number word) table))))
|
79 | 118 | table))
|
80 | 119 |
|
81 |
| -(defun word->number (word) |
82 |
| - "Translate a word (string) into a phone number, according to the rules." |
83 |
| - (let ((n 1)) ; leading zero problem |
84 |
| - (loop for i from 0 below (length word) |
85 |
| - for ch = (char word i) do |
86 |
| - (when (alpha-char-p ch) (setf n (+ (* 10 n) (char->digit ch))))) |
87 |
| - n)) |
88 |
| - |
89 |
| -(defun nth-digit (digits i) |
90 |
| - "The i-th element of a character string of digits, as an integer 0 to 9." |
91 |
| - (- (char-code (char digits i)) #.(char-code #\0))) |
92 |
| - |
93 |
| -(defun char->digit (ch) |
94 |
| - "Convert a character to a digit according to the phone number rules." |
95 |
| - (ecase (char-downcase ch) |
96 |
| - ((#\e) 0) |
97 |
| - ((#\j #\n #\q) 1) |
98 |
| - ((#\r #\w #\x) 2) |
99 |
| - ((#\d #\s #\y) 3) |
100 |
| - ((#\f #\t) 4) |
101 |
| - ((#\a #\m) 5) |
102 |
| - ((#\c #\i #\v) 6) |
103 |
| - ((#\b #\k #\u) 7) |
104 |
| - ((#\l #\o #\p) 8) |
105 |
| - ((#\g #\h #\z) 9))) |
| 120 | +(defun main (&optional print-or-count (dict "tests/words.txt") (nums "tests/numbers.txt") (dict-size 100)) |
| 121 | + "Read the input file ¨DICT and load it into *dict*. Then for each line in |
| 122 | + NUMS, print all the translations of the number into a sequence of words, |
| 123 | + according to the rules of translation." |
| 124 | + (let ((handler (choose-handler print-or-count))) |
| 125 | + (setf *dict* (load-dictionary dict dict-size)) |
| 126 | + (with-open-file (in nums) |
| 127 | + (loop for num = (read-line in nil) while num do |
| 128 | + (find-translations handler num (remove-if-not #'digit-char-p num)))) |
| 129 | + (funcall handler "" nil))) |
106 | 130 |
|
107 |
| -(apply #'main (cdr sb-ext:*posix-argv*)) |
| 131 | +(defun main-with-options () |
| 132 | + (apply #'main (cdr sb-ext:*posix-argv*))) |
0 commit comments