forked from norvig/paip-lisp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
krep1.lisp
174 lines (143 loc) · 5.86 KB
/
krep1.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
;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*-
;;; Code from Paradigms of Artificial Intelligence Programming
;;; Copyright (c) 1991 Peter Norvig
;;; krep1.lisp: Knowledge representation code; first version.
(requires "prolog")
;;; ==============================
;; An nlist is implemented as a (count . elements) pair:
(defun make-empty-nlist ()
"Create a new, empty nlist."
(cons 0 nil))
(defun nlist-n (x) "The number of elements in an nlist." (car x))
(defun nlist-list (x) "The elements in an nlist." (cdr x))
(defun nlist-push (item nlist)
"Add a new element to an nlist."
(incf (car nlist))
(push item (cdr nlist))
nlist)
;;; ==============================
(defstruct (dtree (:type vector))
(first nil) (rest nil) (atoms nil) (var (make-empty-nlist)))
;;; ==============================
;; Not all Lisps handle the closure properly, so change the local PREDICATES
;; to a global *predicates* - norvig Jun 11 1996
(defvar *predicates* nil)
(defun get-dtree (predicate)
"Fetch (or make) the dtree for this predicate."
(cond ((get predicate 'dtree))
(t (push predicate *predicates*)
(setf (get predicate 'dtree) (make-dtree)))))
(defun clear-dtrees ()
"Remove all the dtrees for all the predicates."
(dolist (predicate *predicates*)
(setf (get predicate 'dtree) nil))
(setf *predicates* nil))
;;; ==============================
(defun index (key)
"Store key in a dtree node. Key must be (predicate . args);
it is stored in the predicate's dtree."
(dtree-index key key (get-dtree (predicate key))))
(defun dtree-index (key value dtree)
"Index value under all atoms of key in dtree."
(cond
((consp key) ; index on both first and rest
(dtree-index (first key) value
(or (dtree-first dtree)
(setf (dtree-first dtree) (make-dtree))))
(dtree-index (rest key) value
(or (dtree-rest dtree)
(setf (dtree-rest dtree) (make-dtree)))))
((null key)) ; don't index on nil
((variable-p key) ; index a variable
(nlist-push value (dtree-var dtree)))
(t ;; Make sure there is an nlist for this atom, and add to it
(nlist-push value (lookup-atom key dtree)))))
(defun lookup-atom (atom dtree)
"Return (or create) the nlist for this atom in dtree."
(or (lookup atom (dtree-atoms dtree))
(let ((new (make-empty-nlist)))
(push (cons atom new) (dtree-atoms dtree))
new)))
;;; ==============================
(defun test-index ()
(let ((props '((p a b) (p a c) (p a ?x) (p b c)
(p b (f c)) (p a (f . ?x)))))
(clear-dtrees)
(mapc #'index props)
(write (list props (get-dtree 'p))
:circle t :array t :pretty t)
(values)))
;;; ==============================
(defun fetch (query)
"Return a list of buckets potentially matching the query,
which must be a relation of form (predicate . args)."
(dtree-fetch query (get-dtree (predicate query))
nil 0 nil most-positive-fixnum))
;;; ==============================
(defun dtree-fetch (pat dtree var-list-in var-n-in best-list best-n)
"Return two values: a list-of-lists of possible matches to pat,
and the number of elements in the list-of-lists."
(if (or (null dtree) (null pat) (variable-p pat))
(values best-list best-n)
(let* ((var-nlist (dtree-var dtree))
(var-n (+ var-n-in (nlist-n var-nlist)))
(var-list (if (null (nlist-list var-nlist))
var-list-in
(cons (nlist-list var-nlist)
var-list-in))))
(cond
((>= var-n best-n) (values best-list best-n))
((atom pat) (dtree-atom-fetch pat dtree var-list var-n
best-list best-n))
(t (multiple-value-bind (list1 n1)
(dtree-fetch (first pat) (dtree-first dtree)
var-list var-n best-list best-n)
(dtree-fetch (rest pat) (dtree-rest dtree)
var-list var-n list1 n1)))))))
(defun dtree-atom-fetch (atom dtree var-list var-n best-list best-n)
"Return the answers indexed at this atom (along with the vars),
or return the previous best answer, if it is better."
(let ((atom-nlist (lookup atom (dtree-atoms dtree))))
(cond
((or (null atom-nlist) (null (nlist-list atom-nlist)))
(values var-list var-n))
((and atom-nlist (< (incf var-n (nlist-n atom-nlist)) best-n))
(values (cons (nlist-list atom-nlist) var-list) var-n))
(t (values best-list best-n)))))
;;; ==============================
(proclaim '(inline mapc-retrieve))
(defun mapc-retrieve (fn query)
"For every fact that matches the query,
apply the function to the binding list."
(dolist (bucket (fetch query))
(dolist (answer bucket)
(let ((bindings (unify query answer)))
(unless (eq bindings fail)
(funcall fn bindings))))))
;;; ==============================
(defun retrieve (query)
"Find all facts that match query. Return a list of bindings."
(let ((answers nil))
(mapc-retrieve #'(lambda (bindings) (push bindings answers))
query)
answers))
(defun retrieve-matches (query)
"Find all facts that match query.
Return a list of expressions that match the query."
(mapcar #'(lambda (bindings) (subst-bindings bindings query))
(retrieve query)))
;;; ==============================
(defmacro query-bind (variables query &body body)
"Execute the body for each match to the query.
Within the body, bind each variable."
(let* ((bindings (gensym "BINDINGS"))
(vars-and-vals
(mapcar
#'(lambda (var)
(list var `(subst-bindings ,bindings ',var)))
variables)))
`(mapc-retrieve
#'(lambda (,bindings)
(let ,vars-and-vals
,@body))
,query)))