From 23db2eeb0ac5af83fd28c37b5e5fc6276865f540 Mon Sep 17 00:00:00 2001 From: magical Date: Wed, 26 Jan 2022 19:25:09 +0000 Subject: [PATCH] start of a pvec implementation just the tree part and some functions to convert to/from slice, no tail yet and no operations on the pvec. just wanted to see what it looked like. --- pvec.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ pvec_test.go | 51 ++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 pvec.go create mode 100644 pvec_test.go diff --git a/pvec.go b/pvec.go new file mode 100644 index 0000000..ee20497 --- /dev/null +++ b/pvec.go @@ -0,0 +1,116 @@ +package pmap + +import ( + "fmt" + "math/bits" + "unsafe" +) + +const vecChunk = 16 + +type pvec struct { + head *vnode + tail []Value + len int +} + +type vnode struct { + car, cdr *vnode +} + +type vleaf struct { + //vnode // for debugging + + elem [vecChunk]Value +} + +func getleaf(n *vnode, index, size int) (*vleaf, int) { + // precondition: 0 <= index < size + // cut is the largest power of 2 less than size + // change the loop condition as follows for trees where the leaves are always at the same depth (incl the unbalanced ones) + // for cut := floor(size); cut >= vecChunk; cut >>= 1 + for size > vecChunk { + cut := floor(size) + if index < cut { + n = n.car + //size = cut + + // optimization: size is now a power of two, so this subtree is complete and + // the loop can be a lot simpler. small thing, but makes a difference when + // you're doing a lot of gets + for cut >>= 1; cut >= vecChunk; cut >>= 1 { + if index < cut { + n = n.car + } else { + n = n.cdr + index -= cut + } + } + break + } else { + n = n.cdr + index -= cut + size -= cut + } + } + return (*vleaf)(unsafe.Pointer(n)), index +} + +func list2pvec(elem []Value) *pvec { + // 0, 8 == depth 0 + // 9, 16 == depth 1 + // 17, 32 == depth 2 + // etc + depth := 0 + if len(elem) > 0 { + depth = bits.Len((uint(len(elem)) - 1) / vecChunk) + } + _ = depth + return &pvec{ + head: mkvnode(elem, len(elem)), + tail: nil, + len: len(elem), + } +} + +func mkvnode(elem []Value, size int) *vnode { + if len(elem) == 0 { + return nil + } + if size <= vecChunk { + leaf := new(vleaf) + copy(leaf.elem[:], elem) + return (*vnode)(unsafe.Pointer(leaf)) + } + cut := floor(size) + n := new(vnode) + n.car = mkvnode(elem[:], cut) + if cut < size { + n.cdr = mkvnode(elem[cut:], size-cut) + } + return n +} + +func pvec2list(p *pvec) []Value { + var l = make([]Value, p.len) + for i := range l { + leaf, j := getleaf(p.head, i, p.len) + if leaf == nil { + panic(fmt.Sprint("nil leaf [index=", i, ", len=", p.len, "]")) + } + if j != i%vecChunk { + panic("index mismatch") + } + l[i] = leaf.elem[j] + } + return l +} + +// floor returns the largest power of 2 less than n +// returns 0 if n <= 0 +func floor(n int) int { + if n <= 1 { + return 0 + } + return 1 << bits.Len(uint(n-1)) >> 1 +} diff --git a/pvec_test.go b/pvec_test.go new file mode 100644 index 0000000..dfc62fb --- /dev/null +++ b/pvec_test.go @@ -0,0 +1,51 @@ +package pmap + +import ( + "testing" +) + +func TestPvec(t *testing.T) { + for n := 4; n < 1024*1024; { + list := make([]Value, n) + for i := range list { + list[i] = len(list) - i + } + + p := list2pvec(list) + got := pvec2list(p) + if !eq(list, got) { + t.Error("mismatch with n =", n) + if n < 128 { + t.Error(" want =", list) + t.Error(" got =", got) + } + break + } + if n < 1024 { + n++ + } else { + n += n / 8 + } + } +} + +func eq(a, b []Value) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func TestFloor(t *testing.T) { + if got := floor(8); got != 4 { + t.Errorf("floor(%v) = %v, want %v", 8, got, 4) + } + if got := floor(9); got != 8 { + t.Errorf("floor(%v) = %v, want %v", 9, got, 8) + } +}