-
Notifications
You must be signed in to change notification settings - Fork 1
/
array-hash-tables-plists.lisp
264 lines (228 loc) · 11.6 KB
/
array-hash-tables-plists.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
;;; Chapter 13 - Arrays, Hash Tables, And Property Lists
;;; Exercises
;;; Ex 13.1
;;; Write a function called SUBPROP that deletes an element from a set stored under a property name.
;;; For example, if the symbol ALPHA has the list (A B C D E) as the value of its FOOPROP property, doing (SUBPROP 'ALPHA 'D 'FOOPROP) should leave (A B C E) as the value of ALPHA's FOOPROP property.
(setf (get 'alpha 'fooprop) '(a b c d e))
(defun subprop (name element prop)
(setf (get name prop)
(remove element
(get name prop))))
;;; Ex 13.2
;;; Write a function called FORGET-MEETING that forgets that two particular persons have ever met each other.
;;; Use SUBPROP in your solution.
(defun addprop (sym elem prop)
(pushnew elem (get sym prop)))
(defun record-meeting (x y)
(addprop x y 'has-met)
(addprop y x 'has-met)
t)
(symbol-plist 'john)
(defun forget-meeting (x y)
(remprop x 'has-met)
(remprop y 'has-met)
t)
;;; Alternative solution
(defun forget-meeting (person1 person2)
(subprop person1 person2 'has-met)
(subprop person2 person1 'has-met)
'forgotten)
;;; Ex 13.3
;;; Using SYMBOL-PLIST, write your own version of the GET function.
(defun my-get (symbol property)
(do ((p (symbol-plist symbol) (cddr p)))
((null p) nil)
(if (equal property (first p))
(return (second p)))))
;;; Ex 13.4
;;; Write a predicate HASPROP that returns T or NIL to indicate whether a symbol has a particular property, independent of the value of that property.
;;; Note: If symbol A has a property FOO with value NIL, (HASPROP 'A 'FOO) should still return T.
(defun hasprop (symbol property)
(do ((p (symbol-plist symbol) (cddr p)))
((null p) nil)
(if (equal property (first p))
(return t))))
;;; Ex 13.8
;;; Follow the steps below to create a histogram-drawing program. Your functions should not assume that the histogram will have exactly eleven bins.
;;; In other words, don't use eleven as a constant in you program; use (LENGTH *HIST-ARRAY*) instead.
;;; That way your program will be able to generate histograms of any size.
;;; a.
;;; Write expressions to set up a global variable *HIST-ARRAY* that holds the array of counts, and a global variable *TOTAL-POINTS* that holds the number of points recorded so far.
(setf *hist-array* nil)
(setf *total-points* 0)
;;; b.
;;; Write a function NEW-HISTOGRAM to initialize these variables appropriately.
;;; It should take one input: the number of bins the histogram is to have.
(defun new-histogram (n)
(setf *total-points* 0)
(setf *hist-array*
(make-array n
:initial-element 0))
t)
;;; c.
;;; Write the function RECORD-VALUE that takes a number as input.
;;; If the number is between zero and ten, it should increment the appropriate element of the array, and also update *TOTAL-POINTS*.
;;; If the input is out of range, RECORD-VALUE should issue an appropriate error message.
(defun record-value (n)
(cond ((or (< n 0) (> n 10))
(format t "~&Number ~A is out of range." n))
(t (setf (aref *hist-array* n)
(+ (aref *hist-array* n) 1))
(dotimes (i 11 *total-points*)
(setf *total-points*
(+ *total-points*
(aref *hist-array* i)))))))
;;; Better alternative
(defun record-value (v)
(incf *total-points*)
(if (and (>= v 0)
(< v (length *hist-array*)))
(incf (aref *hist-array* v))
(error "Value ~S out of bounds." v)))
;;; d.
;;; Write a function PRINT-HIST-LINE that takes a value from zero to ten as input, looks up that value in the array, and prints the corresponding line of the histogram.
;;; To get the numbers to line up in columns properly, you will need to use the format directives ~2S to display the value and ~3S to display the count.
;;; You can use a DOTIMES to print the asterisks.
(defun print-hist-line (n)
(let ((cnt (aref *hist-array* n)))
(format t "~&~2D [~3D] " n cnt)
(dotimes (i cnt)
(format t "*"))))
;;; e.
;;; Write the function PRINT-HISTOGRAM.
(defun print-histogram (iterations)
(new-histogram 11)
(dotimes (i iterations)
(record-value (random 11)))
(dotimes (i 11)
(print-hist-line i))
(format t "~& ~3D total" *total-points*))
;;; Ex 13.9
;;; Set up the global variable CRYPTO-TEXT as shown. Then build the cryptogram-solv§ing tool by following these instruction:
;;; a.
;;; Each letter in the alphabet has a corresponding letter to which it deciphers, for example, P deciphers to A.
;;; As we solve the cryptogram we will store this information in two hash tables called *ENCIPHER-TABLE* and *DECIPHER-TABLE*.
;;; We will use *DECIPHER-TABLE* to print out the deciphered cryptogram.
;;; We need *ENCIPHER-TABLE* to check for two letters being deciphered to the same thing, for example, if P is deciphered to A and then we tried to decipher K to A, a look at *ENCIPHER-TABLE* would reveal that A had already been assigned to P.
;;; Similarly, if P is deciphered to A and then we tried deciphering P to E, a look at *DECIPHER-TABLE* would tell us that P had already been deciphered to A.
;;; Write expressions to initialize these global variables.
(setf *decipher-table* (make-hash-table))
(setf *encipher-table* (make-hash-table))
(setf crypto-text
'("zj ze kljjls jf slapzi ezvlij pib kl jufwxuj p hffv jupi jf"
"enlpo pib slafml pvv bfwkj"))
;;; b.
;;; Write a function MAKE-SUBSTITUTION that takes two character objects as input and stores the appropriate entries in *DECIPHER-TABLE* and *ENCIPHER-TABLE* so that the first letter deciphers to the second and the second letter enciphers to the first.
;;; This function does not need to check if either letter already has an entry in these hash tables.
(defun make-substitution (a b)
(setf (gethash a *decipher-table*) b)
(setf (gethash b *encipher-table*) a))
;;; c.
;;; Write a function UNDO-SUBSTITUTION that takes one letter as input.
;;; It should set the *DECIPHER-TABLE* entry of that letter, and the *ENCIPHER-TABLE* entry of the letter it deciphered to, to NIL.
(defun undo-substitution (a b)
(setf (gethash a *decipher-table*) nil)
(setf (gethash b *encipher-table*) nil))
;;; d.
;;; Look up the documentation for the CLRHASH function, and write a function CLEAR that clears the two hash tables used in this problem.
(defun clear ()
(clrhash *decipher-table*)
(clrhash *encipher-table*)
'clear-ok)
;;; e.
;;; Write a function DECIPHER-STRING that takes a single encoded string as input and return a new, partially decoded string. It should begin by making a new string the same length as the input, containing all spaces.
;;; Here is how to do that, assuming the variable LEN holds the length: (make-string len :initial-element #\Space).
;;; Next the function should iterate through the elements of the input string, which are character objects. For each character that deciphers to something non-NIL, that value should be inserted into the corresponding position in the new string.
;;; Finally, the function should return the new string.
;;; When testing this function, make sure its inputs are all lowercase.
(defun decipher-string (str)
(do* ((len (length str))
(new-str (make-string len
:initial-element #\Space))
(i 0 (1+ i)))
((equal i len) new-str)
(let* ((char (aref str i))
(new-char
(gethash char *decipher-table*)))
(when new-char
(setf (aref new-str i) new-char)))))
;;; f.
;;; Write a function SHOW-LINE that displays one line of cryptogram text, with the deciphered text displayed beneath it.
(defun show-line (line)
(format t "~&~A" line)
(format t "~&~A"
(decipher-string line)))
;;; g.
;;; Write a function SHOW-TEXT that takes a cryptogram (list of strings) as input and displays the lines as in the examples at the beginning of this exercise.
(defun show-text (cryptogram)
(format t "~&-------------------------------------")
(dolist (element cryptogram)
(format t "~&~A" element)
(format t "~&~A" (decipher-string element)))
(format t "~&-------------------------------------"))
;;; h.
;;; Type in the definition of GET-FIRST-CHAR, which returns the first character in the lowercase printed of an object.
(defun get-first-char (x)
(char-downcase
(char (format nil "~A" x) 0)))
;;; i.
;;; Write a function READ-LETTER that reads an object from the keyboard. If the object is the symbol END or UNDO, it should be returned as the value of READ-LETTER.
;;; Otherwise READ-LETTER should use GET-FIRST-CHAR on the object to extract the first character of its printed representation; it should return that character as its result.
(defun read-letter ()
(do ((answer nil))
(nil)
(setf answer (read))
(if (or (equal answer 'end)
(equal answer 'undo))
(return answer)
(return (get-first-char answer)))))
;;; j.
;;; Write a function SUB-LETTER that takes a character object as input. If that character has been deciphered already, SUB-LETTER should print an error message that tells to what the letter has been deciphered.
;;; Otherwise SUB-LETTER should ask "What does (letter) decipher to?" and read a letter.
;;; If the result is a character and it has not yet been enciphered, SUB-LETTER should call MAKE-SUBSTITION to record the substitution.
;;; Otherwise an appropriate error message should be printed.
(defun sub-letter (c)
(let ((deciphered (gethash c *decipher-table*)))
(cond ((not (null deciphered)) (format t
"~&´~A´ has already been deciphered as ´~A´"
c deciphered))
(t (format t "~&What does ´~A´ decipher to? " c)
(setf answer (read))
(setf answer (get-first-char answer))
(setf deciphered (gethash answer *decipher-table*))
(if deciphered
(format t "~&´~A´ has already been deciphered as ´~A´"
answer deciphered)
(make-substitution c answer))))))
;;; k.
;;; Write a function UNDO-LETTER that asks "Undo which letter?" and reads in a character.
;;; If that character has been deciphered UNDO-LETTER should call UNDO-SUBSTITUTION on the letter.
;;; Otherwise an appropriate error message should be printed.
(defun undo-letter ()
(format t "~&Undo which letter? ")
(let* ((l (read))
(l (get-first-char l))
(d (gethash l *decipher-table*)))
(if d
(undo-substitution l d)
(format t "~&´~A´ cannot be undone." l))))
;;; Write the main function SOLVE that takes a cryptogram as input. SOLVE should perform the following loop.
;;; First it should display the cryptogram.
;;; Then it should ask "Substitute which letter?" and call READ-LETTER.
;;; If the result is a character, SOLVE should call SUB-LETTER; if the result is the symbol UNDO, it should call UNDO-LETTER;
;;; If the result is the symbol END, it should return T;
;;; otherwise it should issue an error message.
;;; Then it should go back to the beginning of the loop, unless the value returned by READ-LETTER was END.
(defun solve (cryptogram)
(clear)
(show-text cryptogram)
(do ((answer nil))
(nil)
(format t "~&Substitute which letter? ")
(setf answer (read-letter))
(cond ((equal answer 'undo)
(undo-letter)
(show-text cryptogram))
((equal answer 'end) (return t))
(t (sub-letter answer)
(show-text cryptogram)))))