about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/61a-pages/Solutions/week15
blob: 4d6712362e9eacc2aef8bf64094043f9c2e819dc (plain) (blame)
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
CS 61A			Week 15 Solutions

LAB
===

4.55

(supervisor ?x (Bitdiddle Ben))

(job ?x (accounting . ?y))

(address ?x (Slumerville . ?y))

The dots are needed because (accounting ?y), for example, would match
only entries in which there was a single element after the word "accounting."
That is, (accounting ?y) would match (accounting scrivener) but not
(accounting chief accountant).


4.62
The base case here involves a 1-element list, not the empty list.

(rule (last-pair (?x) (?x)))

(rule (last-pair (?y . ?z) ?x)
      (last-pair ?z ?x))


HOMEWORK
========

4.56

(and (supervisor ?x (Bitdiddle Ben))
     (address ?x ?y))

(and (salary ?x ?s1)
     (salary (Bitdiddle Ben) ?s2)
     (lisp-value < ?s1 ?s2))

(and (supervisor ?who ?boss)
     (not (job ?boss (computer . ?y)))
     (job ?boss ?z))

The key point here is that we use the same variable name twice if we want
it to match the same item both times.


4.57

(rule (same ?x ?x))		;; Don't use (lisp-value eq? ....)

(rule (replace ?p1 ?p2)
      (and (or (and (job ?p1 ?x) (job ?p2 ?x))
	       (and (job ?p1 ?x) (job ?p2 ?y) (can-do-job ?x ?y)))
      	   (not (same ?p1 ?p2))))

(replace ?x (Fect Cy D))

(and (replace ?x ?y)
     (salary ?x ?s1)
     (salary ?y ?s2)
     (lisp-value < ?s1 ?s2))


4.58
Note the definition of a sub-rule to make things more manageable.

(rule (sup-in-div ?p ?x)
      (and (supervisor ?p ?boss)
	   (job ?boss (?x . ?z))))

(rule (big-shot ?person ?division)
      (and (job ?person (?division . ?x))
	   (not (sup-in-div ?person ?division))))


4.65
This problem requires understanding the basic idea of how the
query system works (read Section 4.4.3).
To respond to a query, the query system generates
a stream of frames which are then used to "instantiate" the query.
In this case, the stream will include frames containing all bindings of
?middle-manager, ?person and ?x satisfying the body of the rule,
and also with ?who bound to ?person.
Since Warbucks supervises Bitdiddle and Scrooge, each of who manages
other people, there will be more than one of these frames.
Hence Warbucks appears more than once in the output.


Extra for Experts
=================

Here's the REVERSE from lecture:

    (assert! (rule (reverse (?a . ?x) ?y)
		   (and (reverse ?x ?z)
			(append ?z (?a) ?y) )))

    (assert! (reverse () ()))

Why won't this run backwards?  It's important to understand this, in order to
solve the problem.  Unfortunately there are a lot of details, so here's a
preview of the punch line:  It'll turn out that the query system tries to use
the recursive rule over and over, in effect constructing longer and longer
lists whose elements aren't known, and never realizing that they can't
possibly be the reverse of the given list.

Let's try to work out what happens if we give the simplest possible
backwards query:

    (reverse ?b (3))

The answer we want is (reverse (3) (3)).  QEVAL starts with the stream of
frames containing one empty frame:

    {[]}

it matches the query against everything in the database.  Only two are
relevant -- the ones about REVERSE.  Starting with the base case assertion

    (reverse () ())

we see that this doesn't match the query, because (3) in the third element of
the query is not () and neither of them is a variable.  That leaves the
recursive rule.  We unify the query against the conclusion of the rule,
after renaming the variables in the rule:

    (reverse ?b (3))
    (reverse (?1a . ?1x) ?1y)

This succeeds, and the empty frame is extended with new bindings:

    [?b = (?1a . ?1x), ?1y = (3)]

Now we use this frame as the starting point for a new query, the rule's body:

    (and (reverse ?1x ?1z) (append ?1z (?1a) ?1y))

Now it gets a little complicated.  QEVAL of an AND query starts by
evaluating the first part in the current frame.  We match

    (reverse ?1x ?1z)

against all rules and assertions.  Again, let's start with the base case,
so we are matching

    (reverse ?1x ?1z)
    (reverse () ())

This extends the frame with new bindings for ?1X and ?1Z:

    [?b = (?1a . ?1x), ?1y = (3), ?1x = (), ?1z = ()]

With these bindings we have to evaluate the second part of the AND:

    (append ?1z (?1a) ?1y)

Substituting values from the frame, this is equivalent to

    (append () (?1a) (3))

which will work fine (leaving out the details about APPEND), giving a
final extended frame of

    [?b = (?1a . ?1x), ?1y = (3), ?1x = (), ?1z = (), ?1a = 3]

So ?b = (?1a . ?1x) = (3 . ()) = (3).

This is a fine solution, and if the query system looks at assertions
before rules, it may even be printed before the evaluator gets into an
infinite loop.  The problem is with the recursive REVERSE rule.

Remember that we are trying to evaluate the query

    (and (reverse ?1x ?1z) (append ?1z (?1a) ?1y))

and that the first step is to evaluate

    (reverse ?1x ?1z)

in the frame

    [?b = (?1a . ?1x), ?1y = (3)]

We've matched the query against the base case for REVERSE, and now we are
trying the recursive rule.  Here are the query and the conclusion (with
variables again renamed) of the rule:

    (reverse ?1x ?1z)
    (reverse (?2a . ?2x) ?2y)

This succeeds; the resulting frame is

    [?b = (?1a . ?1x), ?1y = (3), ?1x = (?2a . ?2x), ?1z = ?2y]

In this frame we must evaluate the body of the rule, namely

    (and (reverse ?2x ?2z) (append ?2z (?2a) ?2y))

Match the REVERSE part against the conclusion of the REVERSE rule
with variables renamed:

    (reverse ?2x ?2z)
    (reverse (?3a . ?3x) ?3y)

This extends the frame some more:

    [?b = (?1a . ?1x), ?1y = (3), ?1x = (?2a . ?2x), ?1z = ?2y,
     ?2x = (?3a . ?3x), ?2z = ?3y]

We human beings can see that this is all nonsense.  Combining some of the
bindings we see that

    ?b = (?1a . (?2a . (?3a . ?3x)))

which is a list of at least three elements.  So if we ever got to the
APPEND part of the rule, it wouldn't match -- the result of reversing (3)
can't be more than one element long!  But QEVAL will never get around to
the second half of the AND query, because it keeps finding longer and
longer lists to try to reverse.

Why isn't this a problem when running the REVERSE rules forward?  Let's
take the query

    (reverse (35) ?d)

This doesn't match the base case, so we try the recursive case renamed:

    (reverse (35) ?d)
    (reverse (?4a . ?4x) ?4y)

We can see a difference right away:  It's the known list, (35), that we
divide into its car and its cdr, giving determined values for some of
the variables in the new frame:

    [?4a = 35, ?4x = (), ?d = ?4y]

We must now evaluate the body of the rule:

    (and (reverse ?4x ?4z) (append ?4z (?4a) ?4y))

I'll skip the part about matching the new REVERSE query against the base
case, which again gives a correct result.  Instead let's see what happens
when we try to use the recursive rule again:

    (reverse ?4x ?4z)
    (reverse (?5a . ?5x) ?5y)

This unification fails!  We want ?4x = (?5a . ?5x), but the frame tells us
that ?4x is empty.

This is why forward reverse doesn't get into an infinite loop: QEVAL notices
that the recursive rule can't apply when we get past the number of elements
in the original list.

----------

That's the end of the analysis of what's wrong.  The recursive rule is
supposed to say "the reverse of my known length-N list (?a . ?x) can be
computed if we first take the reverse of a list of length N-1, namely ?x."
But when run backwards it instead says "the reverse of my known list ?y
consists of a (first) element ?1a followed by a list consisting of an
element ?2a followed by a list consisting of an element ?3a followed ..."

We don't have this problem running the rules forwards because the rule
takes our known list and divides it into car and cdr, so we find out as
soon as we run out of list elements.  The algorithm doesn't require us
to divide the second list, ?y, into pieces, and the cdr of ?y isn't useful
in doing the recursion -- we need all of ?y.  So we'll add an extra
variable whose only purpose is to count down the length of ?y:

(assert! (rule (reverse ?x ?y)
	       (reverse-help ?x ?y ?y)))

(assert! (rule (reverse-help (?a . ?x) ?y (?ignore . ?counter))
	       (and (reverse-help ?x ?z ?counter)
		    (append ?z (?a) ?y))))

(assert! (rule (reverse-help () () ())))

On each recursive invocation of the REVERSE-HELP rule, ?COUNTER gets
smaller.  When it's empty, no more recursions are possible, because an
empty list can't match (?ignore . ?counter).

For forwards queries, the whole counter mechanism is unhelpful, but it
doesn't hurt.  It's the (?a . ?x) that prevents infinite recursion for
forwards queries; the ?counter situation is just like the ?x situation
we saw before for backwards queries -- in effect we get

    ?1counter = (?2ignore . (?3ignore . (?4ignore . ?4counter)))

after three invocations of the rule.  That could keep going on forever,
but the values of ?1x, ?2x, etc., are *known* and therefore eventually
one of them is empty and won't match the recursive rule.

----------

This solution, like the partial solution in the lecture notes, is based on
the recursive-process Scheme procedure

    (define (reverse seq)
      (if (null? seq)
	  '()
	  (append (reverse (cdr seq)) (list (car seq)))))

What if we start instead with the iterative-process version:

    (define (reverse seq)
      (define (iter seq result)
	(if (null? seq)
	    result
	    (iter (cdr seq) (cons (car seq) result)))))

We still have to add an extra counter variable to make this work as a
both-ways logic program, in addition to the Scheme program's extra
result variable:

    (assert! (rule (reverse ?x ?y)
		   (reverse-iter ?x () ?y ?y)))

    (assert! (rule (reverse-iter (?a . ?x) ?result ?y (?b . ?counter))
		   (reverse-iter ?x (?a . ?result) ?y ?counter)))

    (assert! (rule (reverse-iter () ?y ?y ())))