perf: rbtree contains? O(log n) via rbtree-ref; size/min/max drop rbtree->list materialization
ober
5334fcb924ba6836f8b208fba8a05d9de56339a1
--- a/powers.ss +++ b/powers.ss @@ -77,13 +77,24 @@ (def rbtree-insert rbtree-put) ; (rbtree-insert t k v) -> new tree (def rbtree-lookup rbtree-get) ; (rbtree-lookup t k) -> value | #f (def rbtree-delete rbtree-remove) -(def (rbtree-contains? t k) ; rbtree->list yields sorted (k . v) - (let loop ((l (rbtree->list t))) - (and (pair? l) (or (equal? (caar l) k) (loop (cdr l)))))) -(def (rbtree-size t) (length (rbtree->list t))) +;; contains? walks the tree via rbtree-ref (O(log n)) instead of scanning +;; rbtree->list (O(n)); a sentinel distinguishes an absent key from a #f value. +(def +rbtree-absent+ (list 'rbtree-absent)) +(def (rbtree-contains? t k) + (not (eq? (rbtree-ref t k +rbtree-absent+) +rbtree-absent+))) +;; size/min/max avoid materializing rbtree->list: a single allocation-free +;; for-each pass (for-each visits keys in ascending order, so the first key seen +;; is min and the last is max). Gerbil's rbtree is opaque (no root/node access, +;; no size field), so these stay O(n) time but no longer allocate the full list. +(def (rbtree-size t) + (let (n 0) (rbtree-for-each (lambda (k v) (set! n (+ n 1))) t) n)) (def (rbtree-min t) - (let (l (rbtree->list t)) (and (pair? l) (car l)))) + (let ((k* #f) (v* #f) (any? #f)) + (rbtree-for-each (lambda (k v) + (unless any? (set! k* k) (set! v* v) (set! any? #t))) + t) + (and any? (cons k* v*)))) (def (rbtree-max t) - (let (l (rbtree->list t)) - (and (pair? l) - (let loop ((l l)) (if (null? (cdr l)) (car l) (loop (cdr l))))))) + (let ((k* #f) (v* #f) (any? #f)) + (rbtree-for-each (lambda (k v) (set! k* k) (set! v* v) (set! any? #t)) t) + (and any? (cons k* v*))))