Skip to content

Commit 7415b55

Browse files
author
renatoathaydes
committed
Update fastest CL implementation
1 parent b105919 commit 7415b55

File tree

2 files changed

+78
-48
lines changed

2 files changed

+78
-48
lines changed

src/lisp/build.lisp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
(load "main.lisp")
2+
3+
(sb-ext:save-lisp-and-die "lisp-phone-encoder"
4+
:toplevel 'main-with-options
5+
:executable t)

src/lisp/main.lisp

Lines changed: 73 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,36 @@
44
;; each phone number can be expressed as a list of words.
55
;; Run: (main "word-list-file-name" "phone-number-file-name")
66

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
811
"A hash table mapping a phone number (integer) to a list of words from the
912
input dictionary that produce that number.")
1013

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))
1137
(defun choose-handler (print-or-count)
1238
"Chooses a solution handler function.
1339
When a numbers file is fully consumed, call the function with nil words to signal EOF."
@@ -17,26 +43,25 @@
1743
(when words (format t "~a:~{ ~a~}~%" num (reverse words)))))
1844
((string= print-or-count "count")
1945
(let ((count 0))
46+
(declare (type fixnum count))
2047
#'(lambda (num words)
48+
(declare (ignore num))
2149
(if words
2250
(incf count)
2351
(progn ;; no words is the EOF signal
2452
(format t "~a~%" count)
2553
(setf count 0)))))) ;; reset count for the next file
2654
(t (error "Unknown option: ~a" print-or-count))))
27-
2855

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)))
3959

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))
4065
(defun find-translations (handler num digits &optional (start 0) (words nil))
4166
"Print each possible translation of NUM into a string of words. DIGITS
4267
must be WORD with non-digits removed. On recursive calls, START is the
@@ -57,17 +82,31 @@
5782
becomes 12 and ER becomes 102."
5883
(if (>= start (length digits))
5984
(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))))
70108

109+
(declaim (ftype (function (simple-string (integer 0))) load-dictionary))
71110
(defun load-dictionary (file size)
72111
"Create a hashtable from the file of words (one per line). Takes a hint
73112
for the initial hashtable size. Each key is the phone number for a word;
@@ -78,30 +117,16 @@
78117
(push word (gethash (word->number word) table))))
79118
table))
80119

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)))
106130

107-
(apply #'main (cdr sb-ext:*posix-argv*))
131+
(defun main-with-options ()
132+
(apply #'main (cdr sb-ext:*posix-argv*)))

0 commit comments

Comments
 (0)