函数式随机访问列表
如题,一种 lisp 列表的增强版,支持
#lang racket/base
(define (length-geq? l n)
(let loop ([cur l]
[len 0])
(cond
[(>= len n) #t]
[(null? cur) #f]
[else (loop (cdr cur) (add1 len))])))
(struct alist (roots) #:transparent)
(struct single-tree (root size) #:transparent)
(struct tree-node (val l r) #:transparent)
(define (alist-cons head l)
(let ([rt (alist-roots l)])
(if (length-geq? rt 2)
(let ([a (car rt)]
[b (cadr rt)]
[rest (cddr rt)])
(if (= (single-tree-size a) (single-tree-size b))
(alist
(cons (single-tree
(tree-node head (single-tree-root a) (single-tree-root b))
(add1 (* 2 (single-tree-size b))))
rest))
(alist (cons (single-tree (tree-node head #f #f) 1) rt))))
(alist (cons (single-tree (tree-node head #f #f) 1) rt)))))
(define (alist-car l)
(let ([head (car (alist-roots l))])
(tree-node-val
(single-tree-root head))))
(define (alist-cdr l)
(let ([head (car (alist-roots l))]
[rest (cdr (alist-roots l))])
(if (= (single-tree-size head) 1)
(alist rest)
(let* ([root (single-tree-root head)]
[lson (tree-node-l root)]
[rson (tree-node-r root)]
[size (arithmetic-shift (single-tree-size head) -1)])
(alist (cons (single-tree lson size) (cons (single-tree rson size) rest)))))))
(define (get-binary x)
(define width (integer-length x))
(define res (make-vector width #f))
(let loop ([i (sub1 width)])
(when (>= i 0)
(when (not (zero? (bitwise-and 1 (arithmetic-shift x (- i)))))
(vector-set! res i #t))
(loop (sub1 i))))
res)
(define (access-in-single-tree p idx)
(define width (integer-length idx))
(define bin (get-binary idx))
(let loop ([i (- width 2)]
[cur p])
(if (>= i 0)
(if (vector-ref bin i)
(loop (sub1 i) (tree-node-r cur))
(loop (sub1 i) (tree-node-l cur)))
(tree-node-val cur))))
(define (random-access l pos)
(set! pos (add1 pos)) ;; 0-base => 1-base
(let ([roots (alist-roots l)])
(let loop ([cur roots]
[i pos])
(let ([rt (car cur)])
(if (> i (single-tree-size rt))
(loop (cdr cur) (- i (single-tree-size rt)))
(access-in-single-tree (single-tree-root rt) i))))))
(define (set-in-single-tree p idx v)
(define width (integer-length idx))
(define bin (get-binary idx))
(let loop ([i (- width 2)]
[cur p])
(let ([org (tree-node-val cur)]
[lson (tree-node-l cur)]
[rson (tree-node-r cur)])
(if (< i 0)
(tree-node v lson rson)
(if (vector-ref bin i)
(tree-node org lson (loop (sub1 i) rson))
(tree-node org (loop (sub1 i) lson) rson))))))
(define (random-set l pos v)
(set! pos (add1 pos))
(define res
(let ([roots (alist-roots l)])
(let loop ([cur roots]
[i pos])
(let ([rt (car cur)])
(if (> i (single-tree-size rt))
(cons rt (loop (cdr cur) (- i (single-tree-size rt))))
(cons (single-tree (set-in-single-tree (single-tree-root rt) i v) (single-tree-size rt)) (cdr cur)))))))
(alist res))
;; tests
(define a (alist '()))
(set! a (alist-cons 3 a))
(set! a (alist-cons 5 a))
(displayln (random-access a 0))
(set! a (alist-cons 7 a))
(set! a (alist-cons 9 a))
(displayln (random-access a 2))
(set! a (random-set a 2 'a))
(displayln (random-access a 2))
(displayln (random-access (alist-cdr a) 1))
概述
一种基于完美二叉树以及斜二进制分解的结构。lisp 列表的上位替代,支持
后面的几点才是真正的优势。如果只是要常数级别头部添加以及高效随机访问,vector 快得多(类似 C++ std::vector 的结构,但是反转过来)。然而我们使用列表时常用的是结构共享和可持久化的功能,也要求不可变性,vector 就无法胜任。
权值线段树不支持 cons/cdr,而平衡树不支持
原理
维护一组完美二叉树组成的列表。
我们将列表中的元素存储在这些树中,靠前的树中任意元素的下标小于靠后的树中元素的下标(形式化的,对于任意
对于一棵树内部,下标最小的元素占据根节点,它的左儿子是下标第二小的,右儿子是第三小的。下标第
这些二叉树的大小从前往后单调不降,并且除了第一二个可能相等外其余的树大小严格递增。
不难发现,由于每多一棵树节点总数翻倍,树的总数是
cons
如果原来的前两棵树大小相等,就将这两棵树分别作为新节点的左右儿子组合成一棵更大的树。
否则造一棵新的大小为 1 的树,插进列表最前面。
car
取第一棵树的根节点。
cdr
cons 的逆过程,如果第一棵树大小为 1,直接删掉。否则删去它的根节点,将它分裂成两棵大小相同的树。
access
在列表中顺序查找到访问下标所处的树,然后在树中进行访问。
假设这棵树的根节点的下标是 i,你要访问 x,那么设 idx=x-i+1,将它转为二进制位序列(高位在前,从最高有效位开始。比如 10 对应的序列是 1010)。
这个序列就是寻找到目标节点的操作步骤指示。假如说你现在在深度为 d 的节点(一开始你在根节点,深度为 0),那么这个序列的第 d+1(0-based)位为 0 代表你应该去当前节点的左儿子,为 1 说明你应该去右儿子。
而如果 d+1 已经等于序列长度了(也就是说操作全部做完了),你就到达目标节点了。
复杂度
set
同样的,在列表中顺序查找到访问下标所处的树,然后对它进行可持久化修改即可。返回新的列表,共用其他没有被修改的树,中间 cons 上修改后返回的新树。
可持久化修改也很简单:找到你要修改的节点(方法和 access 是一样的),把它换成一个新的节点,上面存的值是你修改后的新值,但是两个儿子和之前一样。
对于它的所有祖先节点,都重造一个新的节点,存的值以及指向没有被修改的那个子树的指针不变,但指向被修改了的子树的指针指向修改后的新子树节点。
具体都可以见代码。
应用场景
没什么应用场景。如果你发现你需要用到大量的 list-ref,可以考虑这个。或者如果你需要一个能够 push_back 的可持久化数组。