315 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			315 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package pmap
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"math/bits"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	nodeDegree = 16 // branch factor of nodes
 | |
| 	nodeShift  = 4
 | |
| 	nodeMask   = 0b1111
 | |
| )
 | |
| 
 | |
| type Key = int
 | |
| type Value = int
 | |
| 
 | |
| type Map interface {
 | |
| 	Get(Key) (Value, bool)
 | |
| 	Set(Key, Value) Map
 | |
| 	Del(Key) Map
 | |
| 	Len() int
 | |
| }
 | |
| 
 | |
| type pmap struct {
 | |
| 	root interface{}
 | |
| 	len  int
 | |
| 	hash func(Key) uint32
 | |
| }
 | |
| 
 | |
| // A Map implemented as a hashed trie
 | |
| type node struct {
 | |
| 	child  []interface{}
 | |
| 	bitmap uint32
 | |
| }
 | |
| 
 | |
| func bitmask(shiftedHash uint32) uint32 {
 | |
| 	return uint32(1) << (shiftedHash & nodeMask)
 | |
| }
 | |
| 
 | |
| func (n *node) index(mask uint32) int {
 | |
| 	return bits.OnesCount32(n.bitmap & (mask - 1))
 | |
| }
 | |
| 
 | |
| type collision struct {
 | |
| 	hash uint32
 | |
| 	leaf []leaf
 | |
| }
 | |
| 
 | |
| type leaf struct {
 | |
| 	k Key
 | |
| 	v Value
 | |
| }
 | |
| 
 | |
| type HashFunc = func(Key) uint32
 | |
| 
 | |
| func New(hash HashFunc) Map {
 | |
| 	if hash == nil {
 | |
| 		panic("pmap.New: nil hash")
 | |
| 	}
 | |
| 	return pmap{hash: hash}
 | |
| }
 | |
| 
 | |
| func (p pmap) Len() int {
 | |
| 	return p.len
 | |
| }
 | |
| 
 | |
| func (p pmap) Get(k Key) (Value, bool) {
 | |
| 	var zero Value
 | |
| 	h := p.hash(k)
 | |
| 	return lookup(p.root, h, k, zero)
 | |
| }
 | |
| 
 | |
| func (p pmap) Set(k Key, v Value) Map {
 | |
| 	h := p.hash(k)
 | |
| 	root, added := insert(p.root, h, k, v, p.hash)
 | |
| 	p.root = root
 | |
| 	if added {
 | |
| 		p.len++
 | |
| 	}
 | |
| 	//pretty.Println(p)
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| func (p pmap) Del(k Key) Map {
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| func (n *node) check() {
 | |
| 	if bits.OnesCount32(n.bitmap) != len(n.child) {
 | |
| 		panic(fmt.Sprintf("pmap: corrupt bitmap b=%#b len=%d", n.bitmap, len(n.child)))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (n *node) getNode(shift, hash uint32, key Key) interface{} {
 | |
| 	n.check()
 | |
| 	m := bitmask(hash >> shift)
 | |
| 	if n.bitmap&m == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return n.child[n.index(m)]
 | |
| }
 | |
| 
 | |
| func (n *collision) getNode(hash uint32, key Key) interface{} {
 | |
| 	if hash != n.hash {
 | |
| 		return nil
 | |
| 	}
 | |
| 	for i := range n.leaf {
 | |
| 		if key == n.leaf[i].k {
 | |
| 			return n.leaf[i]
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func lookup(root interface{}, hash uint32, key Key, zero Value) (Value, bool) {
 | |
| 	shift := 0
 | |
| 	cur := root
 | |
| 	for {
 | |
| 		switch n := cur.(type) {
 | |
| 		case nil:
 | |
| 			return zero, false
 | |
| 		case leaf:
 | |
| 			if n.k == key {
 | |
| 				return n.v, true
 | |
| 			} else {
 | |
| 				return zero, false
 | |
| 			}
 | |
| 		case *node:
 | |
| 			cur = n.getNode(shift, hash, key)
 | |
| 			shift += nodeShift
 | |
| 		case *collision:
 | |
| 			cur = n.getNode(hash, key)
 | |
| 		default:
 | |
| 			panic("pmap: unhandled case in lookup")
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func singleton(key Key, val Value, hash, shift uint32) *node {
 | |
| 	return newnode(leaf{key, val}, hash, shift)
 | |
| }
 | |
| 
 | |
| func newnode(child interface{}, hash, shift uint32) *node {
 | |
| 	n := &node{}
 | |
| 	a := [1]interface{}{child}
 | |
| 	n.child = a[:]
 | |
| 	n.bitmap = bitmask(hash >> shift)
 | |
| 	n.check()
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| func insert(n interface{}, hash uint32, key Key, val Value, hashFn HashFunc) (newNode interface{}, added bool) {
 | |
| 	if n == nil {
 | |
| 		return leaf{key, val}, true
 | |
| 	}
 | |
| 	var _insert func(n interface{}, shift uint32) interface{}
 | |
| 	_insert = func(n interface{}, shift uint32) interface{} {
 | |
| 		//fmt.Printf("insert %v %x %#v\n", shift, hash, n)
 | |
| 		switch n := n.(type) {
 | |
| 		//case nil:
 | |
| 		//	added = true
 | |
| 		//	return leaf{key, val}
 | |
| 		case leaf:
 | |
| 			if n.k == key {
 | |
| 				// replace existing entry
 | |
| 				added = false
 | |
| 				return leaf{key, val}
 | |
| 			} else if h := hashFn(n.k); h == hash {
 | |
| 				// collision
 | |
| 				added = true
 | |
| 				return &collision{hash, []leaf{{key, val}, n}}
 | |
| 			} else {
 | |
| 				if h>>shift == hash>>shift {
 | |
| 					panic("pmap: infinite loop in insert")
 | |
| 				}
 | |
| 				// not a collision, so we must still have some hash bits left
 | |
| 				// split the trie
 | |
| 				x := newnode(n, h, shift)
 | |
| 				return _insert(x, shift)
 | |
| 			}
 | |
| 		case *node:
 | |
| 			c := n.getNode(shift, hash, key)
 | |
| 			if c == nil {
 | |
| 				// new node
 | |
| 				c = leaf{key, val}
 | |
| 				added = true
 | |
| 
 | |
| 				m := bitmask(hash >> shift)
 | |
| 				x := &node{bitmap: n.bitmap | m}
 | |
| 				i := x.index(m)
 | |
| 				x.child = make([]interface{}, len(n.child)+1)
 | |
| 				copy(x.child[:i], n.child[:i])
 | |
| 				x.child[i] = c
 | |
| 				copy(x.child[i+1:], n.child[i:])
 | |
| 				x.check()
 | |
| 				return x
 | |
| 			} else {
 | |
| 				c = _insert(c, shift+nodeShift)
 | |
| 				// TODO: short circuit if c unchanged?
 | |
| 				m := bitmask(hash >> shift)
 | |
| 				x := &node{bitmap: n.bitmap}
 | |
| 				i := x.index(m)
 | |
| 				x.child = make([]interface{}, len(n.child))
 | |
| 				copy(x.child, n.child)
 | |
| 				x.child[i] = c
 | |
| 				x.check()
 | |
| 				return x
 | |
| 			}
 | |
| 		case *collision:
 | |
| 			if n.hash != hash {
 | |
| 				// not a collision, so we must still have some hash bits left
 | |
| 				// split the trie
 | |
| 				x := newnode(n, n.hash, shift)
 | |
| 				return _insert(x, shift)
 | |
| 			}
 | |
| 			for i := range n.leaf {
 | |
| 				if key == n.leaf[i].k {
 | |
| 					// replace existing entry
 | |
| 					l := make([]leaf, len(n.leaf))
 | |
| 					l[0] = leaf{key, val}
 | |
| 					copy(l[1:], n.leaf[:i])
 | |
| 					copy(l[1+i:], n.leaf[i+1:])
 | |
| 					added = false
 | |
| 					return &collision{hash, l}
 | |
| 				}
 | |
| 			}
 | |
| 			// new collision
 | |
| 			added = true
 | |
| 			return &collision{hash, append([]leaf{{key, val}}, n.leaf...)}
 | |
| 		default:
 | |
| 			panic("pmap: unhandled case in insert")
 | |
| 		}
 | |
| 	}
 | |
| 	newNode = _insert(n, 0)
 | |
| 	return
 | |
| }
 | |
| 
 | |
| type stats struct {
 | |
| 	count          int
 | |
| 	maxHeight      int
 | |
| 	avgHeight      float64
 | |
| 	avgDegree      float64
 | |
| 	nodeCount      int
 | |
| 	leafCount      int
 | |
| 	collisionCount int
 | |
| 	collidedKeys   int
 | |
| 	emptySlots     int
 | |
| }
 | |
| 
 | |
| func (p pmap) stats() stats {
 | |
| 	var s stats
 | |
| 	var th float64
 | |
| 	var td float64
 | |
| 	var visit func(n interface{}, h int)
 | |
| 	visit = func(n interface{}, h int) {
 | |
| 		switch n := n.(type) {
 | |
| 		case leaf:
 | |
| 			s.count++
 | |
| 			s.leafCount++
 | |
| 			th += float64(h)
 | |
| 			if s.maxHeight < h {
 | |
| 				s.maxHeight = h
 | |
| 			}
 | |
| 		case *node:
 | |
| 			s.count++
 | |
| 			s.nodeCount++
 | |
| 			for i := range n.child {
 | |
| 				if n.child[i] != nil {
 | |
| 					td += 1.0
 | |
| 					visit(n.child[i], h+1)
 | |
| 				} else {
 | |
| 					s.emptySlots++
 | |
| 				}
 | |
| 			}
 | |
| 		case *collision:
 | |
| 			s.count++
 | |
| 			s.collisionCount++
 | |
| 			s.collidedKeys += len(n.leaf)
 | |
| 		default:
 | |
| 			panic("pmap: unhandled case in stats")
 | |
| 		}
 | |
| 	}
 | |
| 	visit(p.root, 1)
 | |
| 	if s.leafCount > 0 {
 | |
| 		s.avgHeight = th / float64(s.leafCount)
 | |
| 	}
 | |
| 	if s.nodeCount > 0 {
 | |
| 		s.avgDegree = td / float64(s.nodeCount)
 | |
| 	}
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| func (s stats) String() string {
 | |
| 	return fmt.Sprintf(
 | |
| 		"count          = %d\n"+
 | |
| 			"maxHeight      = %d\n"+
 | |
| 			"avgHeight      = %g\n"+
 | |
| 			"avgDegree      = %g\n"+
 | |
| 			"nodeCount      = %d\n"+
 | |
| 			"leafCount      = %d\n"+
 | |
| 			"collisionCount = %d\n"+
 | |
| 			"collidedKeys   = %d\n"+
 | |
| 			"emptySlots     = %d\n",
 | |
| 		s.count,
 | |
| 		s.maxHeight,
 | |
| 		s.avgHeight,
 | |
| 		s.avgDegree,
 | |
| 		s.nodeCount,
 | |
| 		s.leafCount,
 | |
| 		s.collisionCount,
 | |
| 		s.collidedKeys,
 | |
| 		s.emptySlots,
 | |
| 	)
 | |
| }
 |