forked from norvig/paip-lisp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
student.lisp
194 lines (166 loc) · 6.77 KB
/
student.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
;;;; -*- Mode: Lisp; Syntax: Common-Lisp -*-
;;;; Code from Paradigms of AI Programming
;;;; Copyright (c) 1991 Peter Norvig
;;;; student.lisp: Chapter 7's STUDENT program to solve algebra word problems.
(requires "patmatch")
(defstruct (rule (:type list)) pattern response)
(defstruct (exp (:type list)
(:constructor mkexp (lhs op rhs)))
op lhs rhs)
(defun exp-p (x) (consp x))
(defun exp-args (x) (rest x))
(pat-match-abbrev '?x* '(?* ?x))
(pat-match-abbrev '?y* '(?* ?y))
(defparameter *student-rules* (mapcar #'expand-pat-match-abbrev
'(((?x* |.|) ?x)
((?x* |.| ?y*) (?x ?y))
((if ?x* |,| then ?y*) (?x ?y))
((if ?x* then ?y*) (?x ?y))
((if ?x* |,| ?y*) (?x ?y))
((?x* |,| and ?y*) (?x ?y))
((find ?x* and ?y*) ((= to-find-1 ?x) (= to-find-2 ?y)))
((find ?x*) (= to-find ?x))
((?x* equals ?y*) (= ?x ?y))
((?x* same as ?y*) (= ?x ?y))
((?x* = ?y*) (= ?x ?y))
((?x* is equal to ?y*) (= ?x ?y))
((?x* is ?y*) (= ?x ?y))
((?x* - ?y*) (- ?x ?y))
((?x* minus ?y*) (- ?x ?y))
((difference between ?x* and ?y*) (- ?y ?x))
((difference ?x* and ?y*) (- ?y ?x))
((?x* + ?y*) (+ ?x ?y))
((?x* plus ?y*) (+ ?x ?y))
((sum ?x* and ?y*) (+ ?x ?y))
((product ?x* and ?y*) (* ?x ?y))
((?x* * ?y*) (* ?x ?y))
((?x* times ?y*) (* ?x ?y))
((?x* / ?y*) (/ ?x ?y))
((?x* per ?y*) (/ ?x ?y))
((?x* divided by ?y*) (/ ?x ?y))
((half ?x*) (/ ?x 2))
((one half ?x*) (/ ?x 2))
((twice ?x*) (* 2 ?x))
((square ?x*) (* ?x ?x))
((?x* % less than ?y*) (* ?y (/ (- 100 ?x) 100)))
((?x* % more than ?y*) (* ?y (/ (+ 100 ?x) 100)))
((?x* % ?y*) (* (/ ?x 100) ?y)))))
(defun student (words)
"Solve certain Algebra Word Problems."
(solve-equations
(create-list-of-equations
(translate-to-expression (remove-if #'noise-word-p words)))))
(defun translate-to-expression (words)
"Translate an English phrase into an equation or expression."
(or (rule-based-translator
words *student-rules*
:rule-if #'rule-pattern :rule-then #'rule-response
:action #'(lambda (bindings response)
(sublis (mapcar #'translate-pair bindings)
response)))
(make-variable words)))
(defun translate-pair (pair)
"Translate the value part of the pair into an equation or expression."
(cons (binding-var pair)
(translate-to-expression (binding-val pair))))
(defun create-list-of-equations (exp)
"Separate out equations embedded in nested parens."
(cond ((null exp) nil)
((atom (first exp)) (list exp))
(t (append (create-list-of-equations (first exp))
(create-list-of-equations (rest exp))))))
(defun noise-word-p (word)
"Is this a low-content word which can be safely ignored?"
(member word '(a an the this number of $)))
(defun make-variable (words)
"Create a variable name based on the given list of words"
(first words))
(defun solve-equations (equations)
"Print the equations and their solution"
(print-equations "The equations to be solved are:" equations)
(print-equations "The solution is:" (solve equations nil)))
(defun solve (equations known)
"Solve a system of equations by constraint propagation."
;; Try to solve for one equation, and substitute its value into
;; the others. If that doesn't work, return what is known.
(or (some #'(lambda (equation)
(let ((x (one-unknown equation)))
(when x
(let ((answer (solve-arithmetic
(isolate equation x))))
(solve (subst (exp-rhs answer) (exp-lhs answer)
(remove equation equations))
(cons answer known))))))
equations)
known))
(defun isolate (e x)
"Isolate the lone x in e on the left hand side of e."
;; This assumes there is exactly one x in e,
;; and that e is an equation.
(cond ((eq (exp-lhs e) x)
;; Case I: X = A -> X = n
e)
((in-exp x (exp-rhs e))
;; Case II: A = f(X) -> f(X) = A
(isolate (mkexp (exp-rhs e) '= (exp-lhs e)) x))
((in-exp x (exp-lhs (exp-lhs e)))
;; Case III: f(X)*A = B -> f(X) = B/A
(isolate (mkexp (exp-lhs (exp-lhs e)) '=
(mkexp (exp-rhs e)
(inverse-op (exp-op (exp-lhs e)))
(exp-rhs (exp-lhs e)))) x))
((commutative-p (exp-op (exp-lhs e)))
;; Case IV: A*f(X) = B -> f(X) = B/A
(isolate (mkexp (exp-rhs (exp-lhs e)) '=
(mkexp (exp-rhs e)
(inverse-op (exp-op (exp-lhs e)))
(exp-lhs (exp-lhs e)))) x))
(t ;; Case V: A/f(X) = B -> f(X) = A/B
(isolate (mkexp (exp-rhs (exp-lhs e)) '=
(mkexp (exp-lhs (exp-lhs e))
(exp-op (exp-lhs e))
(exp-rhs e))) x))))
(defun print-equations (header equations)
"Print a list of equations."
(format t "~%~a~{~% ~{ ~a~}~}~%" header
(mapcar #'prefix->infix equations)))
(defconstant operators-and-inverses
'((+ -) (- +) (* /) (/ *) (= =)))
(defun inverse-op (op)
(second (assoc op operators-and-inverses)))
(defun unknown-p (exp)
(symbolp exp))
(defun in-exp (x exp)
"True if x appears anywhere in exp"
(or (eq x exp)
(and (listp exp)
(or (in-exp x (exp-lhs exp)) (in-exp x (exp-rhs exp))))))
(defun no-unknown (exp)
"Returns true if there are no unknowns in exp."
(cond ((unknown-p exp) nil)
((atom exp) t)
((no-unknown (exp-lhs exp)) (no-unknown (exp-rhs exp)))
(t nil)))
(defun one-unknown (exp)
"Returns the single unknown in exp, if there is exactly one."
(cond ((unknown-p exp) exp)
((atom exp) nil)
((no-unknown (exp-lhs exp)) (one-unknown (exp-rhs exp)))
((no-unknown (exp-rhs exp)) (one-unknown (exp-lhs exp)))
(t nil)))
(defun commutative-p (op)
"Is operator commutative?"
(member op '(+ * =)))
(defun solve-arithmetic (equation)
"Do the arithmetic for the right hand side."
;; This assumes that the right hand side is in the right form.
(mkexp (exp-lhs equation) '= (eval (exp-rhs equation))))
(defun binary-exp-p (x)
(and (exp-p x) (= (length (exp-args x)) 2)))
(defun prefix->infix (exp)
"Translate prefix to infix expressions."
(if (atom exp) exp
(mapcar #'prefix->infix
(if (binary-exp-p exp)
(list (exp-lhs exp) (exp-op exp) (exp-rhs exp))
exp))))