Sek: Catenable, Splittable, Transient Sequences
| (require sek) | package: sek-lib |
An implementation of the sequence data structure of Charguéraud and Pottier [Chargueraud26].
The Sek library provides efficient persistent sequences and ephemeral sequences, together with cheap conversions between the two. Both support random access, pushing and popping at either end, concatenation and splitting.
Conversions between these two flavors are cheap: pseq-edit produces an ephemeral sequence in constant time, and eseq-snapshot produces a persistent sequence in logarithmic time. In contrast, snapshotting and editing other data structures typically takes linear time relative to the size of the data.
This approach is called transience, following terminology originally developed in Clojure.
1 Overview
> (define p (list->pseq '(1 2 3 4 5))) > (pseq->list (pseq-push-front p 0)) '(0 1 2 3 4 5)
; p itself is unchanged > (pseq->list p) '(1 2 3 4 5)
; switch to in-place updates, in O(1) time > (define e (pseq-edit p)) > (eseq-push-back! e 6) > (eseq-set! e 0 'a) ; and back again > (define q (eseq-snapshot e)) > (pseq->list q) '(a 2 3 4 5 6)
> (pseq->list p) '(1 2 3 4 5)
2 Sequences
A sequence is a finite ordered collection of elements, in either of two flavors: a persistent sequence is immutable, and an ephemeral sequence is updated in place. A sequence stores its elements in chunks of up to K elements.
Throughout this manual, N is the length of the sequence, K the chunk capacity (by default 128 at the leaves and 16 at internal nodes), and T the length up to which a persistent sequence is held in a plain vector. Unless otherwise specified, operations on a sequence of length N take O(logK N) time. As with treelists, the base of the log is large enough that it is effectively constant-time for many purposes: with the default settings, a sequence of up to a hundred million elements is at most six levels deep.
2.1 Comparison with treelists
Racket’s treelists
solve a similar problem—
Sequences from this library are more efficient at the beginning and the end. Most pushes and pops at either end take O(1) time, for either flavor (see pseq-add for when a persistent push costs O(K)). In the worst case a push takes O(K logK N) time and a pop O(logK N), and a series of pushes and pops on an ephemeral sequence takes amortized O(logK N) time per operation. The corresponding treelist operations take O(log N) time.
Conversions are cheaper too. treelist-copy and mutable-treelist-snapshot each take O(N) time, where pseq-edit takes O(1) and eseq-snapshot O(K logK N). Likewise mutable-treelist-append! takes O(N) time in the length of its second argument, where eseq-append! takes O(K logK N + logK2 N).
Traversal is O(N) for both. This library also provides segments, which give a caller the elements a run at a time.
2.2 Persistent sequences
A persistent sequence is immutable: an operation on one produces a new sequence and leaves the original intact.
A persistent sequence can be used as a single-valued sequence, whose elements are the elements of the sequence; see also in-pseq. It can also be used as a stream, and it is serializable?. Two persistent sequences are equal? when their elements are.
Adding elements to the same persistent sequence from several threads at once requires synchronization.
> (pseq 1 "a" 'apple) (pseq 1 "a" 'apple)
procedure
(pseq-empty? s) → boolean?
s : pseq?
value
procedure
s : pseq?
> (pseq-length (pseq 1 "a" 'apple)) 3
procedure
s : pseq? v : any/c
procedure
s : pseq? v : any/c
procedure
(pseq-push-back s v) → pseq?
s : pseq? v : any/c
procedure
(pseq-push-front s v) → pseq?
s : pseq? v : any/c
These take O(K logK N) time in the worst case. Most take O(1) time, or O(K) time if s has already had an element added at that end or is the result of removing one from it.
> (define s (pseq 1 2 3)) > (pseq-cons s 0) (pseq 0 1 2 3)
> (pseq-add s 4) (pseq 1 2 3 4)
> s (pseq 1 2 3)
procedure
(pseq-pop-front s) →
any/c pseq? s : pseq?
procedure
(pseq-pop-back s) →
any/c pseq? s : pseq?
procedure
s : pseq? i : exact-nonnegative-integer?
procedure
s : pseq? i : exact-nonnegative-integer? v : any/c
pseq-ref takes O(K logK N) time in general, and O(logK N) time for a sequence built without concatenation. pseq-set takes O(K logK N) time.
> (define s (list->pseq '(a b c d))) > (pseq-ref s 2) 'c
> (pseq->list (pseq-set s 2 'C)) '(a b C d)
> (pseq->list s) '(a b c d)
procedure
(pseq-append s1 s2) → pseq?
s1 : pseq? s2 : pseq?
> (pseq->list (pseq-append (pseq 1 2) (pseq 3 4))) '(1 2 3 4)
procedure
(pseq-split s i) →
pseq? pseq? s : pseq? i : exact-nonnegative-integer?
> (define-values (before after) (pseq-split (list->pseq '(a b c d e)) 2)) > (pseq->list before) '(a b)
> (pseq->list after) '(c d e)
procedure
s : pseq? i : exact-nonnegative-integer?
procedure
s : pseq? i : exact-nonnegative-integer?
> (define s (list->pseq '(a b c d e))) > (pseq->list (pseq-take s 2)) '(a b)
> (pseq->list (pseq-drop s 2)) '(c d e)
procedure
(pseq->list s) → list?
s : pseq?
procedure
(list->pseq xs) → pseq?
xs : list?
procedure
(pseq->vector s) → vector?
s : pseq?
procedure
(vector->pseq v) → pseq?
v : vector?
procedure
(pseq-for-each s proc) → void?
s : pseq? proc : (-> any/c any)
procedure
s : pseq? proc : (-> any/c any/c)
2.3 Ephemeral sequences
An ephemeral sequence changes in place. Where an operation on a persistent sequence returns a new sequence, the corresponding operation here modifies the sequence it is given and returns void.
An ephemeral sequence can be used as a single-valued sequence; see also in-eseq. It is serializable?, and two ephemeral sequences are equal? when their elements are. It is not a stream.
> (eseq 1 "a" 'apple) (eseq 1 "a" 'apple)
procedure
n : exact-nonnegative-integer? = 0 v : any/c = #f
procedure
(eseq-empty? e) → boolean?
e : eseq?
procedure
e : eseq?
> (eseq-length (eseq 1 "a" 'apple)) 3
procedure
e : eseq? v : any/c
procedure
(eseq-cons! e v) → void?
e : eseq? v : any/c
procedure
(eseq-push-back! e v) → void?
e : eseq? v : any/c
procedure
(eseq-push-front! e v) → void?
e : eseq? v : any/c
procedure
(eseq-pop-back! e) → any/c
e : eseq?
procedure
(eseq-pop-front! e) → any/c
e : eseq?
In the worst case a push takes O(K logK N) time and a pop O(logK N). A series of pushes and pops takes amortized O(logK N) time per operation, and most take O(1).
> (define items (eseq 1 2 3)) > (eseq-cons! items 0) > (eseq-add! items 4) > items (eseq 0 1 2 3 4)
> (eseq-pop-front! items) 0
> (eseq-pop-back! items) 4
> items (eseq 1 2 3)
procedure
e : eseq? i : exact-nonnegative-integer?
procedure
e : eseq? i : exact-nonnegative-integer? v : any/c
eseq-ref takes O(K logK N) time in general, and O(logK N) time for a sequence built without concatenation. eseq-set! takes O(K logK N) time. For a sequence built without concatenation it takes O(logK N), except that after a snapshot or an edit the first update near each index costs the full O(K logK N).
> (define items (eseq 1 "a" 'apple)) > (eseq-ref items 2) 'apple
> (eseq-set! items 2 'pear) > items (eseq 1 "a" 'pear)
The five operations that follow rearrange ephemeral sequences in place, and they consume the sequences they are given, leaving each empty. Use sek-take, sek-drop and sek-sub when the input must survive.
procedure
(eseq-append! e other [side]) → void?
e : eseq? other : (or/c eseq? pseq?) side : (or/c 'front 'back) = 'back
procedure
(eseq-concat! e1 e2) → eseq?
e1 : eseq? e2 : eseq?
procedure
(eseq-split! e i) →
eseq? eseq? e : eseq? i : exact-nonnegative-integer?
procedure
(eseq-carve! e i [side]) → eseq?
e : eseq? i : exact-nonnegative-integer? side : (or/c 'front 'back) = 'back
procedure
(eseq-take! e i [side]) → void?
e : eseq? i : exact-nonnegative-integer? side : (or/c 'front 'back) = 'front
procedure
(eseq-drop! e i [side]) → void?
e : eseq? i : exact-nonnegative-integer? side : (or/c 'front 'back) = 'front
procedure
(eseq-clear! e) → void?
e : eseq?
procedure
(eseq-assign! e1 e2) → void?
e1 : eseq? e2 : eseq?
procedure
(eseq->list e) → list?
e : eseq?
procedure
(list->eseq xs) → eseq?
xs : list?
procedure
(eseq->vector e) → vector?
e : eseq?
procedure
(eseq-for-each e proc) → void?
e : eseq? proc : (-> any/c any)
procedure
(eseq-fill! e v [start end]) → void?
e : eseq? v : any/c start : exact-nonnegative-integer? = 0 end : exact-nonnegative-integer? = (eseq-length e)
procedure
(eseq-copy! dst dst-start src [ src-start src-end]) → void? dst : eseq? dst-start : exact-nonnegative-integer? src : sek? src-start : exact-nonnegative-integer? = 0 src-end : exact-nonnegative-integer? = (sek-length src)
src may be of either flavor; only the destination is modified. eseq-copy! handles the case where src and dst are the same sequence and the ranges overlap.
For a sequence built without concatenation, both cost O(size + K logK N) time, even after a snapshot, and O((size + K) logK N) in general.
> (define items (eseq 1 2 3 4 5)) > (eseq-fill! items 'x 1 3) > items (eseq 1 'x 'x 4 5)
> (eseq-copy! items 0 (pseq 'a 'b)) > items (eseq 'a 'b 'x 4 5)
2.4 Converting between the two flavors
procedure
(eseq-snapshot e) → pseq?
e : eseq?
This operation takes O(K logK N) time in the worst case. Later updates to e may cost more than they otherwise would.
> (define e (list->eseq '(1 2 3))) > (define snap (eseq-snapshot e)) > (eseq-push-back! e 4) > (eseq->list e) '(1 2 3 4)
; the snapshot does not see the push > (pseq->list snap) '(1 2 3)
This operation takes O(1) time.
> (define s (pseq 1 2 3)) > (define e (pseq-edit s)) > (eseq-set! e 0 'changed) > (eseq->list e) '(changed 2 3)
> (pseq->list s) '(1 2 3)
procedure
e : eseq?
3 Iterators
An iterator is a cursor into a sequence. Its position is an integer in [-1, N]: the indices in [0, N) designate elements, and the two extremes are sentinels, one just before the sequence and one just after. An iterator that sits on a sentinel is sek-iter-finished?.
Moving one step costs O(1) amortized and O(logK N) in the worst case, so a full traversal costs O(N), where repeated pseq-ref pays for a lookup at every element.
Iterating an ephemeral sequence is checked: any update to the sequence invalidates every iterator on it, and using an invalidated iterator raises an exception. sek-configure! turns the check off, after which using an invalidated iterator is undefined. Iterators on persistent sequences are never invalidated.
procedure
(sek-iterator s [dir]) → sek-iter?
s : (or/c pseq? eseq?) dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iterator-at-sentinel s [side]) → sek-iter?
s : (or/c pseq? eseq?) side : (or/c 'front 'back) = 'front
procedure
(sek-iter-sequence it) → (or/c pseq? eseq?)
it : sek-iter?
procedure
it : sek-iter?
procedure
(sek-iter-index it) → exact-integer?
it : sek-iter?
procedure
(sek-iter-finished? it) → boolean?
it : sek-iter?
procedure
(sek-iter-valid? it) → boolean?
it : sek-iter?
procedure
(sek-iter-get it) → any/c
it : sek-iter?
procedure
(sek-iter-get* it) → any/c
it : sek-iter?
Throughout this section, a name ending in * is the variant that returns #f at a sentinel instead of raising.
procedure
(sek-iter-move! it [dir]) → void?
it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-get-and-move! it [dir]) → any/c
it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-get-and-move*! it [dir]) → any/c
it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-jump! it dir n) → void?
it : sek-iter? dir : (or/c 'forward 'backward) n : exact-nonnegative-integer?
procedure
(sek-iter-reach! it i) → void?
it : sek-iter? i : exact-integer?
procedure
(sek-iter-copy it) → sek-iter?
it : sek-iter?
procedure
(sek-iter-reset! it [dir]) → void?
it : sek-iter? dir : (or/c 'forward 'backward 'sentinel) = 'forward
procedure
(sek-iter-check it) → sek-iter?
it : sek-iter?
3.1 Segments
A segment is a run of up to K consecutive elements of a sequence, given as a vector, a start index and a length. An iterator can return the run it is on as a segment.
A segment is a view into the sequence, not a copy. It is valid only as long as the iterator that produced it is, and writing through one writes into the sequence.
procedure
(sek-iter-segment it [dir]) → segment?
it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-segment* it [dir]) → (or/c segment? #f)
it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-segment-and-jump! it [dir]) → segment?
it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-segment-and-jump*! it [dir]) → (or/c segment? #f)
it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
v : vector? start : exact-nonnegative-integer? len : exact-nonnegative-integer?
procedure
v : any/c
procedure
(segment-valid? s) → boolean?
s : any/c
procedure
(segment-vector s) → vector?
s : segment?
procedure
s : segment?
procedure
s : segment?
procedure
(segment-empty? s) → boolean?
s : segment?
procedure
(segment-ref s i) → any/c
s : segment? i : exact-nonnegative-integer?
procedure
(segment-set! s i v) → void?
s : segment? i : exact-nonnegative-integer? v : any/c
procedure
(segment-for-each s proc [dir]) → void?
s : segment? proc : (-> any/c any) dir : (or/c 'forward 'backward) = 'forward
procedure
(segment-for-each2 s1 s2 proc [dir]) → void?
s1 : segment? s2 : segment? proc : (-> any/c any/c any) dir : (or/c 'forward 'backward) = 'forward
procedure
(in-segment s) → sequence?
s : segment?
procedure
(segment->list s) → list?
s : segment?
procedure
(segment->vector s) → vector?
s : segment?
3.2 Writing through an iterator
procedure
(sek-iter-set! it v) → void?
it : sek-iter? v : any/c
procedure
(sek-iter-set-and-move! it v [dir]) → void?
it : sek-iter? v : any/c dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-writable-segment it [dir]) → segment?
it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-writable-segment* it [dir]) → (or/c segment? #f)
it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-writable-segment-and-jump! it [ dir]) → segment? it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-iter-writable-segment-and-jump*! it [ dir]) → (or/c segment? #f) it : sek-iter? dir : (or/c 'forward 'backward) = 'forward
The first write into a chunk that is shared with a snapshot costs O(K logK N) in the worst case, and later writes into the same chunk cost O(1). For a sequence built without concatenation, a sweep that writes every element costs O(N + K logK N), even after a snapshot.
4 Operations on either flavor
The operations in this section accept a persistent or an ephemeral sequence. Those that build a new sequence return the same flavor they were given.
procedure
v : any/c
procedure
s : sek?
procedure
(sek-empty? s) → boolean?
s : sek?
procedure
s : sek? i : exact-nonnegative-integer?
procedure
s : sek?
procedure
s : sek?
4.1 Traversal
procedure
(sek-for-each s proc [dir]) → void?
s : sek? proc : (-> any/c any) dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-for-each/index s proc [dir]) → void?
s : sek? proc : (-> exact-nonnegative-integer? any/c any) dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-segments-for-each s proc [dir]) → void?
s : sek? proc : (-> segment? any) dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-segments-for-each2 s1 s2 proc [dir]) → void?
s1 : sek? s2 : sek? proc : (-> segment? segment? any) dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-fold-left s proc init) → any/c
s : sek? proc : (-> any/c any/c any/c) init : any/c
procedure
(sek-fold-right s proc init) → any/c
s : sek? proc : (-> any/c any/c any/c) init : any/c
procedure
s : sek? dir : (or/c 'forward 'backward) = 'forward
procedure
(sek->vector s) → vector?
s : sek?
4.2 Searching
procedure
s : sek? pred : (-> any/c any/c) dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-find-index s pred [dir])
→ (or/c exact-nonnegative-integer? #f) s : sek? pred : (-> any/c any/c) dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-find-map s proc [dir]) → any/c
s : sek? proc : (-> any/c any/c) dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-for-all? s pred) → boolean?
s : sek? pred : (-> any/c any/c)
procedure
(sek-exists? s pred) → boolean?
s : sek? pred : (-> any/c any/c)
procedure
(sek-member? s v [same?]) → boolean?
s : sek? v : any/c same? : (-> any/c any/c any/c) = equal?
procedure
s : sek? v : any/c
4.3 Building new sequences
procedure
s : sek? proc : (-> any/c any/c)
procedure
(sek-map/index s proc) → sek?
s : sek? proc : (-> exact-nonnegative-integer? any/c any/c)
procedure
(sek-filter s pred) → sek?
s : sek? pred : (-> any/c any/c)
procedure
(sek-filter-map s proc) → sek?
s : sek? proc : (-> any/c any/c)
procedure
(sek-partition s pred) →
sek? sek? s : sek? pred : (-> any/c any/c)
procedure
(sek-reverse s) → sek?
s : sek?
procedure
(sek-append* s) → sek?
s : sek?
procedure
(sek-append-map s proc) → sek?
s : sek? proc : (-> any/c sek?)
procedure
s : sek? start : exact-nonnegative-integer? size : exact-nonnegative-integer?
procedure
s : sek? n : exact-nonnegative-integer?
procedure
s : sek? n : exact-nonnegative-integer?
procedure
s : sek? mode : (or/c 'share 'copy) = 'copy
procedure
(sek-take-right s n) → sek?
s : sek? n : exact-nonnegative-integer?
procedure
(sek-drop-right s n) → sek?
s : sek? n : exact-nonnegative-integer?
> (sek-take-right (pseq 1 2 3 4 5) 2) (pseq 4 5)
> (sek-drop-right (pseq 1 2 3 4 5) 2) (pseq 1 2 3)
procedure
(sek-insert s i v) → sek?
s : sek? i : exact-nonnegative-integer? v : any/c
procedure
(sek-delete s i) → sek?
s : sek? i : exact-nonnegative-integer?
Each takes O(K logK N + logK2 N) time, and neither modifies s.
> (sek-insert (pseq 1 2 3) 1 'x) (pseq 1 'x 2 3)
> (sek-insert (pseq 1 2 3) 3 'x) (pseq 1 2 3 'x)
> (sek-delete (pseq 1 2 3) 1) (pseq 1 3)
procedure
(sek-index-of s v [same?]) → (or/c exact-nonnegative-integer? #f)
s : sek? v : any/c same? : (-> any/c any/c any/c) = equal?
> (sek-index-of (pseq 'a 'b 'c) 'b) 1
> (sek-index-of (pseq 'a 'b 'c) 'z) #f
4.4 Ordering
procedure
s : sek? less? : (-> any/c any/c any/c)
procedure
s : sek? same? : (-> any/c any/c any/c) = equal?
procedure
s1 : sek? s2 : sek? less? : (-> any/c any/c any/c)
4.5 Two sequences at once
procedure
(sek-for-each2 s1 s2 proc [dir]) → void?
s1 : sek? s2 : sek? proc : (-> any/c any/c any) dir : (or/c 'forward 'backward) = 'forward
procedure
(sek-fold-left2 s1 s2 proc init) → any/c
s1 : sek? s2 : sek? proc : (-> any/c any/c any/c any/c) init : any/c
procedure
(sek-fold-right2 s1 s2 proc init) → any/c
s1 : sek? s2 : sek? proc : (-> any/c any/c any/c any/c) init : any/c
procedure
s1 : sek? s2 : sek? proc : (-> any/c any/c any/c)
procedure
s1 : sek? s2 : sek?
procedure
(sek-unzip s) →
sek? sek? s : sek?
procedure
(sek-for-all2? s1 s2 pred) → boolean?
s1 : sek? s2 : sek? pred : (-> any/c any/c any/c)
procedure
(sek-exists2? s1 s2 pred) → boolean?
s1 : sek? s2 : sek? pred : (-> any/c any/c any/c)
procedure
(sek-equal? s1 s2 [same?]) → boolean?
s1 : sek? s2 : sek? same? : (-> any/c any/c any/c) = equal?
procedure
(sek-compare s1 s2 cmp) → (or/c -1 0 1)
s1 : sek? s2 : sek? cmp : (-> any/c any/c real?)
4.6 Construction
procedure
(build-pseq n proc) → pseq?
n : exact-nonnegative-integer? proc : (-> exact-nonnegative-integer? any/c)
procedure
(build-eseq n proc) → eseq?
n : exact-nonnegative-integer? proc : (-> exact-nonnegative-integer? any/c)
procedure
n : exact-nonnegative-integer? v : any/c = #f
procedure
(sequence->pseq s [n]) → pseq?
s : sequence? n : (or/c exact-nonnegative-integer? #f) = #f
procedure
(sequence->eseq s [n]) → eseq?
s : sequence? n : (or/c exact-nonnegative-integer? #f) = #f
syntax
(for/eseq (for-clause ...) body ...+)
syntax
(for*/eseq (for-clause ...) body ...+)
syntax
(for/pseq (for-clause ...) body ...+)
syntax
(for*/pseq (for-clause ...) body ...+)
5 Configuration
procedure
(sek-configure! #:leaf-capacity k0 #:node-capacity k1 #:short-threshold t #:overwrite-empty-slots? overwrite? #:check-iterator-validity? check?) → void? k0 : (and/c exact-integer? (>=/c 2)) k1 : (and/c exact-integer? (>=/c 2)) t : exact-nonnegative-integer? overwrite? : any/c check? : any/c
k0 and k1 are the chunk capacities used at the leaves and at internal nodes, and t is the length up to which a persistent sequence is represented by a plain vector. The defaults are 128, 16 and 32.
overwrite? controls whether popping from an ephemeral sequence overwrites the slot it empties. Leaving it alone saves one write per pop but lets the garbage collector retain a value that the sequence no longer holds; overwriting is the default. Popping from a persistent sequence never overwrites, so the popped value stays reachable for as long as the result does.
check? controls whether the library detects the use of an invalidated iterator at runtime. The check is on by default; with it off, using an invalidated iterator is undefined rather than an error.
Set the capacities and the threshold before building any sequences: a sequence built under other settings fails sek-validate-pseq and does not meet the bounds given here. Small capacities are useful mainly for testing.
6 Validation
procedure
(sek-validate-pseq s) → pseq?
s : pseq?
procedure
(sek-validate-eseq e) → eseq?
e : eseq?
7 Differences from the paper
This library follows the paper, and where the paper is silent, the authors’ OCaml library Sek.
The conformance directory holds a harness that checks this library against the OCaml library on generated scripts, the operation-by-operation mapping between the two APIs, and a record of what has been checked. The differences are these.
No operation takes a default value, where every constructor in the OCaml library does.
The two flavors are one set of names rather than two parallel modules: an operation that builds a sequence returns the same flavor it was given.
sek-sort is stable, so it covers stable_sort too; sort makes no such promise.
sek-sub on a persistent sequence always shares the sequence’s chunks, where the OCaml library copies a slice of at most T elements.
pseq-edit takes O(1) time, and so does eseq-copy in 'share mode, where the OCaml library’s versions take O(K).
The settings are global and are changed at run time with sek-configure!, where the OCaml library fixes them when its functor Make is applied. The chunk capacity takes one value for the leaves and one for every level above them, where the OCaml library takes a function of the depth.
This library supports a #:short-threshold of 0, which the OCaml library rejects.
Bibliography
| [Chargueraud26] | Arthur Charguéraud and François Pottier, “A Catenable, Splittable, Transient Sequence Data Structure,” International Conference on Functional Programming, 2026. https://doi.org/10.1145/3828706 | |
| [Stucki15] | Nicolas Stucki, Tiark Rompf, Vlad Ureche, and Phil Bagwell, “RRB Vector: A Practical General Purpose Immutable Sequence,” International Conference on Functional Programming, 2015. https://dl.acm.org/doi/abs/10.1145/2784731.2784739 |