| 1 |
(in-package :bknr.indices) |
|---|
| 2 |
|
|---|
| 3 |
;;; category tree structure |
|---|
| 4 |
|
|---|
| 5 |
(defun make-node (name children) |
|---|
| 6 |
(cons name children)) |
|---|
| 7 |
|
|---|
| 8 |
(defun node-name (node) |
|---|
| 9 |
(car node)) |
|---|
| 10 |
|
|---|
| 11 |
(defun (setf node-name) (new-value node) |
|---|
| 12 |
(setf (car node) new-value)) |
|---|
| 13 |
|
|---|
| 14 |
(defun node-children (node) |
|---|
| 15 |
(cdr node)) |
|---|
| 16 |
|
|---|
| 17 |
(defun node-children-empty-p (node) |
|---|
| 18 |
(null (node-children node))) |
|---|
| 19 |
|
|---|
| 20 |
(defun (setf node-children) (new-value node) |
|---|
| 21 |
(setf (cdr node) new-value)) |
|---|
| 22 |
|
|---|
| 23 |
(defstruct category-tree |
|---|
| 24 |
(test #'eql) |
|---|
| 25 |
(root-node (make-node :root nil))) |
|---|
| 26 |
|
|---|
| 27 |
(defun node-to-categories (node &optional parent-category) |
|---|
| 28 |
(let ((category (append parent-category (list (node-name node))))) |
|---|
| 29 |
(cons category (mapcan #'(lambda (child) (node-to-categories child category)) |
|---|
| 30 |
(node-children node))))) |
|---|
| 31 |
|
|---|
| 32 |
(defun nodes-to-categories (nodes &optional parent-category) |
|---|
| 33 |
(mapcan #'(lambda (node) (node-to-categories node parent-category)) nodes)) |
|---|
| 34 |
|
|---|
| 35 |
(defun tree-categories (tree &optional category) |
|---|
| 36 |
(nodes-to-categories (node-children (category-tree-root-node tree)) category)) |
|---|
| 37 |
|
|---|
| 38 |
(defun tree-find-node (tree category) |
|---|
| 39 |
(unless (listp category) |
|---|
| 40 |
(setf category (list category))) |
|---|
| 41 |
(do* ((curnode (category-tree-root-node tree) |
|---|
| 42 |
(find catname (node-children curnode) |
|---|
| 43 |
:key #'node-name |
|---|
| 44 |
:test (category-tree-test tree))) |
|---|
| 45 |
(curcat category (cdr curcat)) |
|---|
| 46 |
(catname (car curcat) (car curcat))) |
|---|
| 47 |
((or (null curnode) |
|---|
| 48 |
(null curcat)) |
|---|
| 49 |
curnode))) |
|---|
| 50 |
|
|---|
| 51 |
(defun category-to-node (category) |
|---|
| 52 |
(if (null category) |
|---|
| 53 |
nil |
|---|
| 54 |
(let ((child (category-to-node (cdr category)))) |
|---|
| 55 |
(make-node (first category) |
|---|
| 56 |
(when child (list child)))))) |
|---|
| 57 |
|
|---|
| 58 |
(defun tree-add-category (tree category) |
|---|
| 59 |
(unless (listp category) |
|---|
| 60 |
(setf category (list category))) |
|---|
| 61 |
(do* ((curnode (category-tree-root-node tree)) |
|---|
| 62 |
(curcat category (cdr curcat)) |
|---|
| 63 |
(catname (car curcat) (car curcat))) |
|---|
| 64 |
((or (null curnode) |
|---|
| 65 |
(null curcat)) |
|---|
| 66 |
tree) |
|---|
| 67 |
(let ((node (find catname (node-children curnode) |
|---|
| 68 |
:key #'node-name |
|---|
| 69 |
:test (category-tree-test tree)))) |
|---|
| 70 |
(if node |
|---|
| 71 |
(setf curnode node) |
|---|
| 72 |
(progn |
|---|
| 73 |
(push (category-to-node curcat) |
|---|
| 74 |
(node-children curnode)) |
|---|
| 75 |
(return-from tree-add-category tree)))))) |
|---|
| 76 |
|
|---|
| 77 |
(defun tree-remove-category (tree category) |
|---|
| 78 |
(unless (listp category) |
|---|
| 79 |
(setf category (list category))) |
|---|
| 80 |
(when category |
|---|
| 81 |
(let* ((parent-category (parent-category category)) |
|---|
| 82 |
(parent-node (tree-find-node tree parent-category))) |
|---|
| 83 |
(when parent-node |
|---|
| 84 |
(setf (node-children parent-node) |
|---|
| 85 |
(remove (category-name category) |
|---|
| 86 |
(node-children parent-node) |
|---|
| 87 |
:key #'car |
|---|
| 88 |
:test (category-tree-test tree))) |
|---|
| 89 |
(when (node-children-empty-p parent-node) |
|---|
| 90 |
(tree-remove-category tree parent-category))))) |
|---|
| 91 |
tree) |
|---|
| 92 |
|
|---|
| 93 |
(defun parent-categories (category) |
|---|
| 94 |
(let (res) |
|---|
| 95 |
(dotimes (i (1- (length category))) |
|---|
| 96 |
(push (butlast category (1+ i)) res)) |
|---|
| 97 |
res)) |
|---|
| 98 |
|
|---|
| 99 |
(defun parent-category (category) |
|---|
| 100 |
(butlast category 1)) |
|---|
| 101 |
|
|---|
| 102 |
(defun category-name (category) |
|---|
| 103 |
(car (last category))) |
|---|
| 104 |
|
|---|
| 105 |
(defun tree-find-children (tree category) |
|---|
| 106 |
(nodes-to-categories (node-children (tree-find-node tree category)) category)) |
|---|
| 107 |
|
|---|
| 108 |
(defun tree-find-siblings (tree category) |
|---|
| 109 |
(let ((len (length category))) |
|---|
| 110 |
(if (<= len 1) |
|---|
| 111 |
tree |
|---|
| 112 |
(let ((sib-cat (subseq category 0 (1- (length category))))) |
|---|
| 113 |
(nodes-to-categories (tree-find-children tree sib-cat) sib-cat))))) |
|---|
| 114 |
|
|---|
| 115 |
;;; category index |
|---|
| 116 |
|
|---|
| 117 |
(defclass category-index (hash-index) |
|---|
| 118 |
((tree :initform (make-category-tree) |
|---|
| 119 |
:initarg :tree |
|---|
| 120 |
:accessor category-index-tree)) |
|---|
| 121 |
(:default-initargs :test #'equal)) |
|---|
| 122 |
|
|---|
| 123 |
(defmethod initialize-instance :after ((index category-index) &key (tree-test #'eql)) |
|---|
| 124 |
(with-slots (tree) index |
|---|
| 125 |
(setf tree (make-category-tree :test tree-test)))) |
|---|
| 126 |
|
|---|
| 127 |
(defmethod index-get ((index category-index) category) |
|---|
| 128 |
(let* ((tree (category-index-tree index)) |
|---|
| 129 |
(hash (slot-index-hash-table index)) |
|---|
| 130 |
(categories (cons category |
|---|
| 131 |
(tree-find-children tree category)))) |
|---|
| 132 |
(mapcan #'(lambda (category) |
|---|
| 133 |
(copy-list (gethash category hash))) categories))) |
|---|
| 134 |
|
|---|
| 135 |
(defmethod index-add ((index category-index) object) |
|---|
| 136 |
(unless (slot-boundp object (slot-index-slot-name index)) |
|---|
| 137 |
(return-from index-add)) |
|---|
| 138 |
(let ((key (slot-value object (slot-index-slot-name index))) |
|---|
| 139 |
(hash-table (slot-index-hash-table index)) |
|---|
| 140 |
(tree (category-index-tree index))) |
|---|
| 141 |
(when (and (not (slot-index-index-nil index)) |
|---|
| 142 |
(null key)) |
|---|
| 143 |
(return-from index-add)) |
|---|
| 144 |
(if (nth-value 1 (gethash key hash-table)) |
|---|
| 145 |
(push object (gethash key hash-table)) |
|---|
| 146 |
(progn |
|---|
| 147 |
(tree-add-category tree key) |
|---|
| 148 |
(setf (gethash key hash-table) (list object)))))) |
|---|
| 149 |
|
|---|
| 150 |
(defmethod index-remove ((index category-index) object) |
|---|
| 151 |
(let ((key (slot-value object (slot-index-slot-name index))) |
|---|
| 152 |
(hash-table (slot-index-hash-table index)) |
|---|
| 153 |
(tree (category-index-tree index))) |
|---|
| 154 |
(let ((new-value (delete-first object (gethash key hash-table)))) |
|---|
| 155 |
(if (null new-value) |
|---|
| 156 |
(progn |
|---|
| 157 |
(tree-remove-category tree key) |
|---|
| 158 |
(remhash key hash-table)) |
|---|
| 159 |
(setf (gethash key hash-table) new-value))))) |
|---|
| 160 |
|
|---|
| 161 |
(defmethod index-keys ((index category-index)) |
|---|
| 162 |
(tree-categories (category-index-tree index))) |
|---|
| 163 |
|
|---|
| 164 |
(defmethod index-reinitialize :around ((new-index category-index) |
|---|
| 165 |
(old-index category-index)) |
|---|
| 166 |
(let* ((new-index (call-next-method)) |
|---|
| 167 |
(tree (category-index-tree new-index)) |
|---|
| 168 |
(new-hash (slot-index-hash-table new-index))) |
|---|
| 169 |
(loop for key being the hash-key of new-hash |
|---|
| 170 |
do (tree-add-category tree key)) |
|---|
| 171 |
new-index)) |
|---|
| 172 |
|
|---|
| 173 |
#| |
|---|
| 174 |
|
|---|
| 175 |
(defclass image () |
|---|
| 176 |
((category :index-type category-index |
|---|
| 177 |
:index-reader images-with-category |
|---|
| 178 |
:index-keys all-image-categories |
|---|
| 179 |
:index-var *image-category-index* |
|---|
| 180 |
:initarg :category |
|---|
| 181 |
:reader image-category)) |
|---|
| 182 |
(:metaclass indexed-class)) |
|---|
| 183 |
|
|---|
| 184 |
(make-instance 'image :category '(:photo :stills :nature)) |
|---|
| 185 |
(make-instance 'image :category '(:photo :naked :woman)) |
|---|
| 186 |
(make-instance 'image :category '(:painting :abstract :cubist)) |
|---|
| 187 |
|
|---|
| 188 |
(defclass track () |
|---|
| 189 |
((category :index-type category-index |
|---|
| 190 |
:index-initargs (:tree-test #'equal) |
|---|
| 191 |
:index-reader tracks-with-category |
|---|
| 192 |
:index-keys all-track-categories |
|---|
| 193 |
:index-var *track-category-index* |
|---|
| 194 |
:initarg :category |
|---|
| 195 |
:reader track-category)) |
|---|
| 196 |
(:metaclass indexed-class)) |
|---|
| 197 |
|
|---|
| 198 |
(make-instance 'track :category '("Rock" "New-Age" "Noise")) |
|---|
| 199 |
(make-instance 'track :category '("Rock" "New-Age" "Techno")) |
|---|
| 200 |
|
|---|
| 201 |
|
|---|
| 202 |
|# |
|---|