(cond-expand (r7rs) (chicken (import r7rs))) (define-library (avl-trees) ;; ;; This library implements ‘persistent’ (that is, ‘immutable’) AVL ;; trees for R7RS Scheme. ;; ;; Included are generators of the key-data pairs in a tree. Because ;; the trees are persistent (‘immutable’), these generators are safe ;; from alterations of the tree. ;; ;; References: ;; ;; * Niklaus Wirth, 1976. Algorithms + Data Structures = ;; Programs. Prentice-Hall, Englewood Cliffs, New Jersey. ;; ;; * Niklaus Wirth, 2004. Algorithms and Data Structures. Updated ;; by Fyodor Tkachov, 2014. ;; ;; Note that the references do not discuss persistent ;; implementations. It seems worthwhile to compare the methods of ;; implementation. ;; (export avl) (export alist->avl) (export avl->alist) (export avl?) (export avl-empty?) (export avl-size) (export avl-insert) (export avl-delete) (export avl-delete-values) (export avl-has-key?) (export avl-search) (export avl-search-values) (export avl-make-generator) (export avl-pretty-print) (export avl-check-avl-condition) (export avl-check-usage) (import (scheme base)) (import (scheme case-lambda)) (import (scheme process-context)) (import (scheme write)) (cond-expand (chicken (import (only (chicken base) define-record-printer)) (import (only (chicken format) format))) ; For debugging. (else)) (begin ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ;; ;; Tools for making generators. These use call/cc and so might be ;; inefficient in your Scheme. I am using CHICKEN, in which ;; call/cc is not so inefficient. ;; ;; Often I have made &fail a unique object rather than #f, but in ;; this case #f will suffice. ;; (define &fail #f) (define *suspend* (make-parameter (lambda (x) x))) (define (suspend v) ((*suspend*) v)) (define (fail-forever) (let loop () (suspend &fail) (loop))) (define (make-generator-procedure thunk) ;; Make a suspendable procedure that takes no arguments. The ;; result is a simple generator of values. (This can be ;; elaborated upon for generators to take values on resumption, ;; in the manner of Icon co-expressions.) (define (next-run return) (define (my-suspend v) (set! return (call/cc (lambda (resumption-point) (set! next-run resumption-point) (return v))))) (parameterize ((*suspend* my-suspend)) (suspend (thunk)) (fail-forever))) (lambda () (call/cc next-run))) ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (define-syntax avl-check-usage (syntax-rules () ((_ pred msg) (or pred (usage-error msg))))) ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (define-record-type (%avl key data bal left right) avl? (key %key) (data %data) (bal %bal) (left %left) (right %right)) (cond-expand (chicken (define-record-printer ( rt out) (display "#" out))) (else)) ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (define avl (case-lambda (() (%avl #f #f #f #f #f)) ((predavl predavl predavl expects a procedure as first argument") (let loop ((tree (avl)) (lst alst)) (if (null? lst) tree (let ((head (car lst))) (loop (avl-insert predalist tree) ;; Go from AVL tree to association list. The output will be in ;; order. (define (traverse p lst) ;; Reverse in-order traversal of the tree, to produce an ;; in-order cons-list. (if (not p) lst (traverse (%left p) (cons (cons (%key p) (%data p)) (traverse (%right p) lst))))) (if (avl-empty? tree) '() (traverse tree '()))) (define (avl-insert predalist tree) (cdr p))) ((null? p)) (display-key-data (caar p) (cdar p)) (newline))) (define (error-stop) (display "*** ERROR STOP ***\n" (current-error-port)) (emergency-exit 1)) (define n 20) (define keys (make-vector (+ n 1))) (do ((i 0 (+ i 1))) ((= i n)) ;; To keep things more like Fortran, do not use index zero. (vector-set! keys (+ i 1) (+ i 1))) (fisher-yates-shuffle keys) ;; Insert key-data pairs in the shuffled order. (define tree (avl)) (avl-check-avl-condition tree) (do ((i 1 (+ i 1))) ((= i (+ n 1))) (let ((ix (vector-ref keys i))) (set! tree (avl-insert < tree ix (inexact ix))) (avl-check-avl-condition tree) (do ((j 1 (+ j 1))) ((= j (+ n 1))) (let*-values (((k) (vector-ref keys j)) ((has-key?) (avl-has-key? < tree k)) ((data) (avl-search < tree k)) ((data^ has-key?^) (avl-search-values < tree k))) (unless (exact? k) (error-stop)) (if (<= j i) (unless (and has-key? data data^ has-key?^ (inexact? data) (= data k) (inexact? data^) (= data^ k)) (error-stop)) (when (or has-key? data data^ has-key?^) (error-stop))))))) (display "----------------------------------------------------------------------\n") (display "keys = ") (write (cdr (vector->list keys))) (newline) (display "----------------------------------------------------------------------\n") (avl-pretty-print tree) (display "----------------------------------------------------------------------\n") (display "tree size = ") (display (avl-size tree)) (newline) (display-tree-contents tree) (display "----------------------------------------------------------------------\n") ;; ;; Reshuffle the keys, and change the data from inexact numbers ;; to strings. ;; (fisher-yates-shuffle keys) (do ((i 1 (+ i 1))) ((= i (+ n 1))) (let ((ix (vector-ref keys i))) (set! tree (avl-insert < tree ix (number->string ix))) (avl-check-avl-condition tree))) (avl-pretty-print tree) (display "----------------------------------------------------------------------\n") (display "tree size = ") (display (avl-size tree)) (newline) (display-tree-contents tree) (display "----------------------------------------------------------------------\n") ;; ;; Reshuffle the keys, and delete the contents of the tree, but ;; also keep the original tree by saving it in a variable. Check ;; persistence of the tree. ;; (fisher-yates-shuffle keys) (define saved-tree tree) (do ((i 1 (+ i 1))) ((= i (+ n 1))) (let ((ix (vector-ref keys i))) (set! tree (avl-delete < tree ix)) (avl-check-avl-condition tree) (unless (= (avl-size tree) (- n i)) (error-stop)) ;; Try deleting a second time. (set! tree (avl-delete < tree ix)) (avl-check-avl-condition tree) (unless (= (avl-size tree) (- n i)) (error-stop)) (do ((j 1 (+ j 1))) ((= j (+ n 1))) (let ((jx (vector-ref keys j))) (unless (eq? (avl-has-key? < tree jx) (< i j)) (error-stop)) (let ((data (avl-search < tree jx))) (unless (eq? (not (not data)) (< i j)) (error-stop)) (unless (or (not data) (= (string->number data) jx)) (error-stop))) (let-values (((data found?) (avl-search-values < tree jx))) (unless (eq? found? (< i j)) (error-stop)) (unless (or (and (not data) (<= j i)) (and data (= (string->number data) jx))) (error-stop))))))) (do ((i 1 (+ i 1))) ((= i (+ n 1))) ;; Is save-tree the persistent value of the tree we just ;; deleted? (let ((ix (vector-ref keys i))) (unless (equal? (avl-search < saved-tree ix) (number->string ix)) (error-stop)))) (display "forwards generator:\n") (let ((gen (avl-make-generator saved-tree))) (do ((pair (gen) (gen))) ((not pair)) (display-key-data (car pair) (cdr pair)) (newline))) (display "----------------------------------------------------------------------\n") (display "backwards generator:\n") (let ((gen (avl-make-generator saved-tree -1))) (do ((pair (gen) (gen))) ((not pair)) (display-key-data (car pair) (cdr pair)) (newline))) (display "----------------------------------------------------------------------\n") )) (else))