(in-package :kira)
(defgeneric ternary-node-priority (generic-node)
(:method ((ternary-node-as-list list)) 0))
(define-persistent-class ternary-node ()
((label :initarg :label :accessor ternary-node-label)
(data :initform nil :initarg :data :accessor ternary-node-data)
(left :initform nil :initarg :left :accessor ternary-node-left)
(middle :initform nil :initarg :middle :accessor ternary-node-middle)
(right :initform nil :initarg :right :accessor ternary-node-right)
(priority :initform 0 :initarg :priority :accessor ternary-node-priority)))
(defconstant +max-priority+ (1- (ash 1 55)))
(defconstant +ternary-burst-threshold+ 64)
(defun ternary-search (key position node)
(let ((current-label (char key position)))
(cond ((listp node)
(loop for (entry-key . entry-key-association) in node
if (string-equal entry-key key :start2 position)
return entry-key-association))
((char-equal (ternary-node-label node) current-label)
(if (= (the array-index (1+ position)) (length key))
(ternary-node-data node)
(let ((middle (ternary-node-middle node)))
(ternary-search key (1+ position) middle))))
((char-greaterp current-label (ternary-node-label node))
(ternary-search key position (ternary-node-right node)))
((ternary-search key position (ternary-node-left node))))))
(defun get-term (key ternary-search-tree)
(ternary-search (string key) 0 ternary-search-tree))
(define-compiler-macro get-term (key ternary-search-tree)
`(ternary-search (string ,key) 0 ,ternary-search-tree))
(defun ternary-rotate-with-left (node)
(let ((left (ternary-node-left node)))
(with-accessors ((place ternary-node-right)) left
(setf (ternary-node-left node) place place node)
left)))
(defun ternary-rotate-with-right (node)
(let ((right (ternary-node-right node)))
(with-accessors ((place ternary-node-left)) right
(setf (ternary-node-right node) place place node)
right)))
(define-setf-expander get-term (key place &environment environment)
(multiple-value-bind (temps vals news writer reader) (get-setf-expansion place environment)
(unless (endp (cdr news)) (error "Multiple values aren't supported in ternary search trees."))
(with-gensyms (key-string node new-data)
(values `(,key-string ,@temps ,node)
`((string ,key) ,@vals ,reader)
`(,new-data)
`(prog1 ,new-data
(%ternary-insert ,key-string ,new-data 0 ,node)
(when (or (listp ,node) (not (eq ,node ,reader)))
(let ((,(car news) ,node)) ,writer)))
`(get-term ,key-string ,reader)))))
(defun nlabeled (split-key alist &aux (new-ternary-node-label (char split-key 0)))
"Destructively strip the leading character from each key in ALIST matching SPLIT-KEY."
(loop for entry in alist if (char-equal (char (car entry) 0) new-ternary-node-label)
collect entry into middle and do (setf (car entry) (subseq (car entry) 1))
finally (return (values new-ternary-node-label middle))))
(defun alist-insert (key data position alist)
(assert (< position (length key)) (key position) "Key ~S exhausted." key)
(loop for entry in alist if (string-equal (car entry) key :start2 position)
do (return-from alist-insert (progn (setf (cdr entry) data) alist))
finally (let ((alist-delta (list (cons (subseq key position) data))))
(setq alist (merge 'list alist alist-delta #'string-lessp :key #'car))
(let ((list-under-threshold-p (< (length alist) +ternary-burst-threshold+)))
(when list-under-threshold-p (return-from alist-insert alist))
(let* ((midpoint (floor (length alist) 2))
(split-key (car (nth midpoint alist))))
(multiple-value-bind (label middle) (nlabeled split-key alist)
(let* ((priority (the fixnum (1+ (the fixnum (random +max-priority+)))))
(alist (sort (set-difference alist middle) #'string-lessp :key #'car))
(right-position (position label alist :test #'string-lessp :key #'car))
(data (whereas ((empty (assoc "" middle :test #'string=))) (cdr empty)))
(right (when right-position (nthcdr right-position alist)))
(left (if right (ldiff alist right) alist)))
(deletef "" middle :key #'car :test #'string=)
(return (make-instance 'ternary-node
:label label :left left
:data data :priority priority
:middle middle :right right)))))))))
(defmacro set-if-changed (place new-value &environment environment)
(multiple-value-bind (temps vals news writer reader) (get-setf-expansion place environment)
(unless (endp (cdr news)) (error "Multiple values aren't supported in SET-IF-CHANGED."))
(with-gensyms (old-temp new-temp)
`(let* (,.(mapcar #'list temps vals) (,old-temp ,reader) (,new-temp ,new-value))
(if (eq ,new-temp ,old-temp) ,old-temp (let ((,(car news) ,new-temp)) ,writer))))))
(defmacro %ternary-insert (key data position place)
`(cond ((listp ,place) (setq ,place (alist-insert ,key ,data ,position ,place)))
(t (set-if-changed ,place (ternary-insert ,key ,data ,position ,place)))))
(defun ternary-insert (key data position node &aux (label (ternary-node-label node)))
(with-accessors ((priority ternary-node-priority) (left ternary-node-left)) node
(with-accessors ((middle ternary-node-middle) (right ternary-node-right)) node
(let ((current-label (char key position)))
(declare (type array-index position))
(declare (type character current-label))
(cond ((char-equal current-label label)
(if (= (the array-index (1+ position)) (length key))
(progn (setf (ternary-node-data node) data) node)
(progn (%ternary-insert key data (1+ position) middle)
(with-accessors ((middle-priority ternary-node-priority)) middle
(when (> middle-priority priority) (setq priority middle-priority))
node))))
((char-greaterp current-label label)
(%ternary-insert key data position right)
(let ((rotatep (> (ternary-node-priority right) priority)))
(if rotatep (ternary-rotate-with-right node) node)))
((%ternary-insert key data position left)
(let ((rotatep (> (ternary-node-priority left) priority)))
(if rotatep (ternary-rotate-with-left node) node))))))))
(defmacro delete-term (key place &environment environment)
(multiple-value-bind (temps vals news writer reader) (get-setf-expansion place environment)
(unless (endp (cdr news)) (error "Multiple values aren't supported in ternary search trees."))
(let ((temporary-bindings (mapcar #'list temps vals)) (new-place-store-variable (car news)))
(with-gensyms (key-temp place-temp)
`(let* ((,key-temp (string ,key))
,.temporary-bindings
(,place-temp ,reader))
(%ternary-delete ,key-temp 0 ,place-temp)
(when (or (listp ,place-temp) (not (eq ,place-temp ,reader)))
(let ((,new-place-store-variable ,place-temp)) ,writer))
,place-temp)))))
(defmacro %ternary-delete (key position place &aux (k `(subseq ,key ,position)))
`(cond ((listp ,place) (deletef ,k ,place :test #'string-equal :key #'car))
(t (set-if-changed ,place (ternary-delete ,key ,position ,place)))))
(defun ternary-delete (key position node)
(with-accessors ((priority ternary-node-priority) (left ternary-node-left)) node
(with-accessors ((middle ternary-node-middle) (right ternary-node-right)) node
(let ((current-label (char key position)) (label (ternary-node-label node)))
(declare (type array-index position))
(declare (type character current-label))
(if (char-equal current-label label)
(cond ((< (the array-index (1+ position)) (length key))
(let ((new-middle (%ternary-delete key (the array-index (1+ position)) middle)))
(when (eq (or (ternary-node-data node) new-middle) nil) (setq priority 0)))
(ternary-heapify-or-delete node))
((progn (setf (ternary-node-data node) nil) middle) node)
((progn (setq priority 0) (ternary-heapify-or-delete node))))
(prog1 node
(if (char-greaterp current-label label)
(%ternary-delete key position right)
(%ternary-delete key position left))))))))
(defun ternary-heapify-or-delete (node &aux (data (ternary-node-data node)))
(with-accessors ((priority ternary-node-priority) (left ternary-node-left)) node
(with-accessors ((middle ternary-node-middle) (right ternary-node-right)) node
(unless (or left middle right data) (return-from ternary-heapify-or-delete))
(with-accessors ((left-priority ternary-node-priority)) left
(with-accessors ((right-priority ternary-node-priority)) right
(cond ((>= priority (max right-priority left-priority)) node)
((> right-priority left-priority)
(let ((rotated-in-node (ternary-rotate-with-right node)))
(with-accessors ((left ternary-node-left)) rotated-in-node
(set-if-changed left (ternary-heapify-or-delete left)))
rotated-in-node))
((let ((rotated-in-node (ternary-rotate-with-left node)))
(with-accessors ((right ternary-node-right)) rotated-in-node
(set-if-changed right (ternary-heapify-or-delete right)))
rotated-in-node))))))))
(defun map-terms (function ternary-search-tree)
(cond ((typep ternary-search-tree 'ternary-node)
(map-terms function (ternary-node-left ternary-search-tree))
(whereas ((data (ternary-node-data ternary-search-tree)))
(funcall function data))
(map-terms function (ternary-node-middle ternary-search-tree))
(map-terms function (ternary-node-right ternary-search-tree))
ternary-search-tree)
((loop for entry in ternary-search-tree
do (funcall function (cdr entry))
finally (return ternary-search-tree)))))
(defmacro do-terms ((var ternary-search-tree &optional result-form) &body body)
`(block nil (map-terms #'(lambda (,var) (tagbody ,@body)) ,ternary-search-tree)
,result-form))