stake: New package for fast access to live tickets. (#266)

Ticket voting selection and validation require fast access to a
lexicographically sorted list of all available tickets.  The current
implementation stores these tickets in bucketized maps which are
unordered.  This means the data has to be sorted every time it is needed
in order to select a ticket and validate a block.  On my hardware, the
process of removing tickets that have been voted and adding new tickets
takes ~27ms with the current implementation.

In order to speed this up, this introduces a new internal package named
tickettreap that provides a tailored treap data structure that is
intended to be used for the in-memory live ticket pool when the new
database and blockchain enhancements have been synced from upstream.
Since it maintains the data in sorted order, it provides a significant
speedup over the current map-based implementation.  On my hardware, this
implementation is able to remove the tickets that have voted and add the
new tickets in ~0.083ms and iterate the entire structure for selecting the
tickets in ~1.88ms for a total of ~1.96ms.  That equates to roughly 14x
faster access.

In addition, it would also be possible for the stake code to keep a map
for height-based lookups (while still relying on this new data structure
for keeping the lexicographic sort ordering requirements) for an even
greater speedup since it would eliminate the need to iterate the
structure to remove tickets at a given height.

Finally, when evaluating forks, the current code is quite inefficient
because it needs to make a copy of all of the live ticket maps.  This
new structure provides O(1) snapshot/copy capabilities so it is much
more efficient in that regard as well.

Benchmarks:

ImmutableCopy     30000000      40.7 ns/op   0 B/op   0 allocs/op
ImmutableIterate  1000       1881738 ns/op   0 B/op   0 allocs/op
This commit is contained in:
Dave Collins 2016-06-06 14:33:17 -05:00 committed by Alex Yocom-Piatt
parent b869593789
commit 92635a475e
9 changed files with 2121 additions and 0 deletions

View File

@ -0,0 +1,40 @@
tickettreap
===========
[![Build Status](https://img.shields.io/travis/decred/dcrd.svg)]
(https://travis-ci.org/decred/dcrd) [![ISC License]
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
(http://godoc.org/github.com/decred/dcrd/blockchain/stake/internal/tickettreap)
Package tickettreap implements a treap data structure that is used to hold
live tickets ordered by their key along with some associated data using a
combination of binary search tree and heap semantics. It is a self-organizing
and randomized data structure that doesn't require complex operations to
maintain balance. Search, insert, and delete operations are all O(log n).
Both mutable and immutable variants are provided.
The mutable variant is typically faster since it is able to simply update the
treap when modifications are made. However, a mutable treap is not safe for
concurrent access without careful use of locking by the caller and care must be
taken when iterating since it can change out from under the iterator.
The immutable variant works by creating a new version of the treap for all
mutations by replacing modified nodes with new nodes that have updated values
while sharing all unmodified nodes with the previous version. This is extremely
useful in concurrent applications since the caller only has to atomically
replace the treap pointer with the newly returned version after performing any
mutations. All readers can simply use their existing pointer as a snapshot
since the treap it points to is immutable. This effectively provides O(1)
snapshot capability with efficient memory usage characteristics since the old
nodes only remain allocated until there are no longer any references to them.
## Usage
This package is only used internally in the stake code and as such is not
available for use outside of it.
## License
Package tickettreap is licensed under the [copyfree](http://copyfree.org) ISC
License.

View File

@ -0,0 +1,144 @@
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package tickettreap
import (
"crypto/sha256"
"sync"
"testing"
)
// numTicketKeys is the number of keys to generate for use in the benchmarks.
const numTicketKeys = 42500
var (
// generatedTicketKeys is used to store ticket keys generated for use
// in the benchmarks so that they only need to be generatd once for all
// benchmarks that use them.
genTicketKeysLock sync.Mutex
generatedTicketKeys []Key
)
// genTicketKeys generates and returns 'numTicketKeys' along with memoizing them
// so that future calls return the cached data.
func genTicketKeys() []Key {
// Return generated keys if already done.
genTicketKeysLock.Lock()
defer genTicketKeysLock.Unlock()
if generatedTicketKeys != nil {
return generatedTicketKeys
}
// Generate the keys and cache them for future invocations.
ticketKeys := make([]Key, 0, numTicketKeys)
for i := 0; i < numTicketKeys; i++ {
key := Key(sha256.Sum256(serializeUint32(uint32(i))))
ticketKeys = append(ticketKeys, key)
}
generatedTicketKeys = ticketKeys
return ticketKeys
}
// BenchmarkMutableCopy benchmarks how long it takes to copy a mutable treap
// to another one when it contains 'numTicketKeys' entries.
func BenchmarkMutableCopy(b *testing.B) {
// Populate mutable treap with a bunch of key/value pairs.
testTreap := NewMutable()
ticketKeys := genTicketKeys()
for j := 0; j < len(ticketKeys); j++ {
hashBytes := ticketKeys[j]
value := &Value{Height: uint32(j)}
testTreap.Put(hashBytes, value)
}
b.ReportAllocs()
b.ResetTimer()
// Copying a mutable treap requires iterating all of the entries and
// populating them into a new treap all with a lock held for concurrency
// safety.
var mtx sync.RWMutex
for i := 0; i < b.N; i++ {
benchTreap := NewMutable()
mtx.Lock()
testTreap.ForEach(func(k Key, v *Value) bool {
benchTreap.Put(k, v)
return true
})
mtx.Unlock()
}
}
// BenchmarkImmutableCopy benchmarks how long it takes to copy an immutable
// treap to another one when it contains 'numTicketKeys' entries.
func BenchmarkImmutableCopy(b *testing.B) {
// Populate immutable treap with a bunch of key/value pairs.
testTreap := NewImmutable()
ticketKeys := genTicketKeys()
for j := 0; j < len(ticketKeys); j++ {
hashBytes := ticketKeys[j]
value := &Value{Height: uint32(j)}
testTreap = testTreap.Put(hashBytes, value)
}
b.ReportAllocs()
b.ResetTimer()
// Copying an immutable treap is just protecting access to the treap
// root and getting another reference to it.
var mtx sync.RWMutex
for i := 0; i < b.N; i++ {
mtx.RLock()
benchTreap := testTreap
mtx.RUnlock()
_ = benchTreap
}
}
// BenchmarkMutableCopy benchmarks how long it takes to iterate a mutable treap
// when it contains 'numTicketKeys' entries.
func BenchmarkMutableIterate(b *testing.B) {
// Populate mutable treap with a bunch of key/value pairs.
testTreap := NewMutable()
ticketKeys := genTicketKeys()
for j := 0; j < len(ticketKeys); j++ {
hashBytes := ticketKeys[j]
value := &Value{Height: uint32(j)}
testTreap.Put(hashBytes, value)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
testTreap.ForEach(func(k Key, v *Value) bool {
return true
})
}
}
// BenchmarkImmutableIterate benchmarks how long it takes to iterate an
// immutable treap when it contains 'numTicketKeys' entries.
func BenchmarkImmutableIterate(b *testing.B) {
// Populate immutable treap with a bunch of key/value pairs.
testTreap := NewImmutable()
ticketKeys := genTicketKeys()
for j := 0; j < len(ticketKeys); j++ {
hashBytes := ticketKeys[j]
value := &Value{Height: uint32(j)}
testTreap = testTreap.Put(hashBytes, value)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
testTreap.ForEach(func(k Key, v *Value) bool {
return true
})
}
}

View File

@ -0,0 +1,176 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package tickettreap
import (
"math/rand"
"sync"
"time"
"github.com/decred/dcrd/chaincfg/chainhash"
)
const (
// staticDepth is the size of the static array to use for keeping track
// of the parent stack during treap iteration. Since a treap has a very
// high probability that the tree height is logarithmic, it is
// exceedingly unlikely that the parent stack will ever exceed this size
// even for extremely large numbers of items.
staticDepth = 128
// nodeFieldsSize is the size the fields of each node takes excluding
// the contents of the key and value. It assumes 64-bit pointers so
// technically it is smaller on 32-bit platforms, but overestimating the
// size in that case is acceptable since it avoids the need to import
// unsafe. It consists of 8 bytes for each of the value, priority,
// left, and right fields (8*4).
nodeFieldsSize = 32
// nodeValueSize is the size of the fixed-size fields of a Value.
nodeValueSize = 4
)
// lockedSource is a rng source that is safe for concurrent access.
type lockedSource struct {
lock sync.Mutex
src rand.Source
}
// Int63 returns a non-negative pseudo-random 63-bit integer as an int64.
//
// This function is safe for concurrent access.
//
// This is part of the implementation for the rand.Source interface.
func (r *lockedSource) Int63() int64 {
r.lock.Lock()
n := r.src.Int63()
r.lock.Unlock()
return n
}
// Seed uses the provided seed value to initialize the generator to a
// deterministic state.
//
// This function is safe for concurrent access.
//
// This is part of the implementation for the rand.Source interface.
func (r *lockedSource) Seed(seed int64) {
r.lock.Lock()
r.src.Seed(seed)
r.lock.Unlock()
}
var (
// rng is a new random number generator that is local to the package.
rng = rand.New(&lockedSource{src: rand.NewSource(time.Now().UnixNano())})
)
// Key defines the key used to add an associated value to the treap.
type Key chainhash.Hash
// Value defines the information stored for a given key in the treap.
type Value struct {
// Height is the block height of the associated ticket.
Height uint32
}
// treapNode represents a node in the treap.
type treapNode struct {
key Key
value *Value
priority int
left *treapNode
right *treapNode
}
// nodeSize returns the number of bytes the specified node occupies including
// the struct fields and the contents of the key and value.
func nodeSize(node *treapNode) uint64 {
return nodeFieldsSize + uint64(len(node.key)+nodeValueSize)
}
// newTreapNode returns a new node from the given key, value, and priority. The
// node is not initially linked to any others.
func newTreapNode(key Key, value *Value, priority int) *treapNode {
return &treapNode{key: key, value: value, priority: priority}
}
// parentStack represents a stack of parent treap nodes that are used during
// iteration. It consists of a static array for holding the parents and a
// dynamic overflow slice. It is extremely unlikely the overflow will ever be
// hit during normal operation, however, since a treap's height is
// probabilistic, the overflow case needs to be handled properly. This approach
// is used because it is much more efficient for the majority case than
// dynamically allocating heap space every time the treap is iterated.
type parentStack struct {
index int
items [staticDepth]*treapNode
overflow []*treapNode
}
// Len returns the current number of items in the stack.
func (s *parentStack) Len() int {
return s.index
}
// At returns the item n number of items from the top of the stack, where 0 is
// the topmost item, without removing it. It returns nil if n exceeds the
// number of items on the stack.
func (s *parentStack) At(n int) *treapNode {
index := s.index - n - 1
if index < 0 {
return nil
}
if index < staticDepth {
return s.items[index]
}
return s.overflow[index-staticDepth]
}
// Pop removes the top item from the stack. It returns nil if the stack is
// empty.
func (s *parentStack) Pop() *treapNode {
if s.index == 0 {
return nil
}
s.index--
if s.index < staticDepth {
node := s.items[s.index]
s.items[s.index] = nil
return node
}
node := s.overflow[s.index-staticDepth]
s.overflow[s.index-staticDepth] = nil
return node
}
// Push pushes the passed item onto the top of the stack.
func (s *parentStack) Push(node *treapNode) {
if s.index < staticDepth {
s.items[s.index] = node
s.index++
return
}
// This approach is used over append because reslicing the slice to pop
// the item causes the compiler to make unneeded allocations. Also,
// since the max number of items is related to the tree depth which
// requires expontentially more items to increase, only increase the cap
// one item at a time. This is more intelligent than the generic append
// expansion algorithm which often doubles the cap.
index := s.index - staticDepth
if index+1 > cap(s.overflow) {
overflow := make([]*treapNode, index+1)
copy(overflow, s.overflow)
s.overflow = overflow
}
s.overflow[index] = node
s.index++
}

View File

@ -0,0 +1,128 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package tickettreap
import (
"encoding/binary"
"encoding/hex"
"reflect"
"testing"
)
// fromHex converts the passed hex string into a byte slice and will panic if
// there is an error. This is only provided for the hard-coded constants so
// errors in the source code can be detected. It will only (and must only) be
// called for initialization purposes.
func fromHex(s string) []byte {
r, err := hex.DecodeString(s)
if err != nil {
panic("invalid hex in source file: " + s)
}
return r
}
// serializeUint32 returns the big-endian encoding of the passed uint32.
func serializeUint32(ui uint32) []byte {
var ret [4]byte
binary.BigEndian.PutUint32(ret[:], ui)
return ret[:]
}
// uint32ToKey converts the provided uint32 to a treap Key.
func uint32ToKey(ui uint32) Key {
var key Key
binary.BigEndian.PutUint32(key[len(key)-4:], ui)
return key
}
// TestParentStack ensures the treapParentStack functionality works as intended.
func TestParentStack(t *testing.T) {
t.Parallel()
tests := []struct {
numNodes int
}{
{numNodes: 1},
{numNodes: staticDepth},
{numNodes: staticDepth + 1}, // Test dynamic code paths
}
testLoop:
for i, test := range tests {
nodes := make([]*treapNode, 0, test.numNodes)
for j := 0; j < test.numNodes; j++ {
key := uint32ToKey(uint32(j))
value := Value{Height: uint32(j)}
node := newTreapNode(key, &value, 0)
nodes = append(nodes, node)
}
// Push all of the nodes onto the parent stack while testing
// various stack properties.
stack := &parentStack{}
for j, node := range nodes {
stack.Push(node)
// Ensure the stack length is the expected value.
if stack.Len() != j+1 {
t.Errorf("Len #%d (%d): unexpected stack "+
"length - got %d, want %d", i, j,
stack.Len(), j+1)
continue testLoop
}
// Ensure the node at each index is the expected one.
for k := 0; k <= j; k++ {
atNode := stack.At(j - k)
if !reflect.DeepEqual(atNode, nodes[k]) {
t.Errorf("At #%d (%d): mismatched node "+
"- got %v, want %v", i, j-k,
atNode, nodes[k])
continue testLoop
}
}
}
// Ensure each popped node is the expected one.
for j := 0; j < len(nodes); j++ {
node := stack.Pop()
expected := nodes[len(nodes)-j-1]
if !reflect.DeepEqual(node, expected) {
t.Errorf("At #%d (%d): mismatched node - "+
"got %v, want %v", i, j, node, expected)
continue testLoop
}
}
// Ensure the stack is now empty.
if stack.Len() != 0 {
t.Errorf("Len #%d: stack is not empty - got %d", i,
stack.Len())
continue testLoop
}
// Ensure attempting to retrieve a node at an index beyond the
// stack's length returns nil.
if node := stack.At(2); node != nil {
t.Errorf("At #%d: did not give back nil - got %v", i,
node)
continue testLoop
}
// Ensure attempting to pop a node from an empty stack returns
// nil.
if node := stack.Pop(); node != nil {
t.Errorf("Pop #%d: did not give back nil - got %v", i,
node)
continue testLoop
}
}
}
func init() {
// Force the same pseudo random numbers for each test run.
rng.Seed(0)
}

View File

@ -0,0 +1,29 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
/*
Package tickettreap implements a treap data structure that is used to hold
live tickets ordered by their key along with some associated data using a
combination of binary search tree and heap semantics. It is a self-organizing
and randomized data structure that doesn't require complex operations to
maintain balance. Search, insert, and delete operations are all O(log n).
Both mutable and immutable variants are provided.
The mutable variant is typically faster since it is able to simply update the
treap when modifications are made. However, a mutable treap is not safe for
concurrent access without careful use of locking by the caller and care must be
taken when iterating since it can change out from under the iterator.
The immutable variant works by creating a new version of the treap for all
mutations by replacing modified nodes with new nodes that have updated values
while sharing all unmodified nodes with the previous version. This is extremely
useful in concurrent applications since the caller only has to atomically
replace the treap pointer with the newly returned version after performing any
mutations. All readers can simply use their existing pointer as a snapshot
since the treap it points to is immutable. This effectively provides O(1)
snapshot capability with efficient memory usage characteristics since the old
nodes only remain allocated until there are no longer any references to them.
*/
package tickettreap

View File

@ -0,0 +1,355 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package tickettreap
import "bytes"
// cloneTreapNode returns a shallow copy of the passed node.
func cloneTreapNode(node *treapNode) *treapNode {
return &treapNode{
key: node.key,
value: node.value,
priority: node.priority,
left: node.left,
right: node.right,
}
}
// Immutable represents a treap data structure which is used to hold ordered
// key/value pairs using a combination of binary search tree and heap semantics.
// It is a self-organizing and randomized data structure that doesn't require
// complex operations to maintain balance. Search, insert, and delete
// operations are all O(log n). In addition, it provides O(1) snapshots for
// multi-version concurrency control (MVCC).
//
// All operations which result in modifying the treap return a new version of
// the treap with only the modified nodes updated. All unmodified nodes are
// shared with the previous version. This is extremely useful in concurrent
// applications since the caller only has to atomically replace the treap
// pointer with the newly returned version after performing any mutations. All
// readers can simply use their existing pointer as a snapshot since the treap
// it points to is immutable. This effectively provides O(1) snapshot
// capability with efficient memory usage characteristics since the old nodes
// only remain allocated until there are no longer any references to them.
type Immutable struct {
root *treapNode
count int
// totalSize is the best estimate of the total size of of all data in
// the treap including the keys, values, and node sizes.
totalSize uint64
}
// newImmutable returns a new immutable treap given the passed parameters.
func newImmutable(root *treapNode, count int, totalSize uint64) *Immutable {
return &Immutable{root: root, count: count, totalSize: totalSize}
}
// Len returns the number of items stored in the treap.
func (t *Immutable) Len() int {
return t.count
}
// Size returns a best estimate of the total number of bytes the treap is
// consuming including all of the fields used to represent the nodes as well as
// the size of the keys and values. Shared values are not detected, so the
// returned size assumes each value is pointing to different memory.
func (t *Immutable) Size() uint64 {
return t.totalSize
}
// get returns the treap node that contains the passed key. It will return nil
// when the key does not exist.
func (t *Immutable) get(key Key) *treapNode {
for node := t.root; node != nil; {
// Traverse left or right depending on the result of the
// comparison.
compareResult := bytes.Compare(key[:], node.key[:])
if compareResult < 0 {
node = node.left
continue
}
if compareResult > 0 {
node = node.right
continue
}
// The key exists.
return node
}
// A nil node was reached which means the key does not exist.
return nil
}
// Has returns whether or not the passed key exists.
func (t *Immutable) Has(key Key) bool {
if node := t.get(key); node != nil {
return true
}
return false
}
// Get returns the value for the passed key. The function will return nil when
// the key does not exist.
func (t *Immutable) Get(key Key) *Value {
if node := t.get(key); node != nil {
return node.value
}
return nil
}
// Put inserts the passed key/value pair. Passing a nil value will result in a
// NOOP.
func (t *Immutable) Put(key Key, value *Value) *Immutable {
// Nothing to do if a nil value is passed.
if value == nil {
return t
}
// The node is the root of the tree if there isn't already one.
if t.root == nil {
root := newTreapNode(key, value, rng.Int())
return newImmutable(root, 1, nodeSize(root))
}
// Find the binary tree insertion point and construct a replaced list of
// parents while doing so. This is done because this is an immutable
// data structure so regardless of where in the treap the new key/value
// pair ends up, all ancestors up to and including the root need to be
// replaced.
//
// When the key matches an entry already in the treap, replace the node
// with a new one that has the new value set and return.
var parents parentStack
var compareResult int
for node := t.root; node != nil; {
// Clone the node and link its parent to it if needed.
nodeCopy := cloneTreapNode(node)
if oldParent := parents.At(0); oldParent != nil {
if oldParent.left == node {
oldParent.left = nodeCopy
} else {
oldParent.right = nodeCopy
}
}
parents.Push(nodeCopy)
// Traverse left or right depending on the result of comparing
// the keys.
compareResult = bytes.Compare(key[:], node.key[:])
if compareResult < 0 {
node = node.left
continue
}
if compareResult > 0 {
node = node.right
continue
}
// The key already exists, so update its value.
nodeCopy.value = value
// Return new immutable treap with the replaced node and
// ancestors up to and including the root of the tree.
newRoot := parents.At(parents.Len() - 1)
return newImmutable(newRoot, t.count, t.totalSize)
}
// Link the new node into the binary tree in the correct position.
node := newTreapNode(key, value, rng.Int())
parent := parents.At(0)
if compareResult < 0 {
parent.left = node
} else {
parent.right = node
}
// Perform any rotations needed to maintain the min-heap and replace
// the ancestors up to and including the tree root.
newRoot := parents.At(parents.Len() - 1)
for parents.Len() > 0 {
// There is nothing left to do when the node's priority is
// greater than or equal to its parent's priority.
parent = parents.Pop()
if node.priority >= parent.priority {
break
}
// Perform a right rotation if the node is on the left side or
// a left rotation if the node is on the right side.
if parent.left == node {
node.right, parent.left = parent, node.right
} else {
node.left, parent.right = parent, node.left
}
// Either set the new root of the tree when there is no
// grandparent or relink the grandparent to the node based on
// which side the old parent the node is replacing was on.
grandparent := parents.At(0)
if grandparent == nil {
newRoot = node
} else if grandparent.left == parent {
grandparent.left = node
} else {
grandparent.right = node
}
}
return newImmutable(newRoot, t.count+1, t.totalSize+nodeSize(node))
}
// Delete removes the passed key from the treap and returns the resulting treap
// if it exists. The original immutable treap is returned if the key does not
// exist.
func (t *Immutable) Delete(key Key) *Immutable {
// Find the node for the key while constructing a list of parents while
// doing so.
var parents parentStack
var delNode *treapNode
for node := t.root; node != nil; {
parents.Push(node)
// Traverse left or right depending on the result of the
// comparison.
compareResult := bytes.Compare(key[:], node.key[:])
if compareResult < 0 {
node = node.left
continue
}
if compareResult > 0 {
node = node.right
continue
}
// The key exists.
delNode = node
break
}
// There is nothing to do if the key does not exist.
if delNode == nil {
return t
}
// When the only node in the tree is the root node and it is the one
// being deleted, there is nothing else to do besides removing it.
parent := parents.At(1)
if parent == nil && delNode.left == nil && delNode.right == nil {
return newImmutable(nil, 0, 0)
}
// Construct a replaced list of parents and the node to delete itself.
// This is done because this is an immutable data structure and
// therefore all ancestors of the node that will be deleted, up to and
// including the root, need to be replaced.
var newParents parentStack
for i := parents.Len(); i > 0; i-- {
node := parents.At(i - 1)
nodeCopy := cloneTreapNode(node)
if oldParent := newParents.At(0); oldParent != nil {
if oldParent.left == node {
oldParent.left = nodeCopy
} else {
oldParent.right = nodeCopy
}
}
newParents.Push(nodeCopy)
}
delNode = newParents.Pop()
parent = newParents.At(0)
// Perform rotations to move the node to delete to a leaf position while
// maintaining the min-heap while replacing the modified children.
var child *treapNode
newRoot := newParents.At(newParents.Len() - 1)
for delNode.left != nil || delNode.right != nil {
// Choose the child with the higher priority.
var isLeft bool
if delNode.left == nil {
child = delNode.right
} else if delNode.right == nil {
child = delNode.left
isLeft = true
} else if delNode.left.priority >= delNode.right.priority {
child = delNode.left
isLeft = true
} else {
child = delNode.right
}
// Rotate left or right depending on which side the child node
// is on. This has the effect of moving the node to delete
// towards the bottom of the tree while maintaining the
// min-heap.
child = cloneTreapNode(child)
if isLeft {
child.right, delNode.left = delNode, child.right
} else {
child.left, delNode.right = delNode, child.left
}
// Either set the new root of the tree when there is no
// grandparent or relink the grandparent to the node based on
// which side the old parent the node is replacing was on.
//
// Since the node to be deleted was just moved down a level, the
// new grandparent is now the current parent and the new parent
// is the current child.
if parent == nil {
newRoot = child
} else if parent.left == delNode {
parent.left = child
} else {
parent.right = child
}
// The parent for the node to delete is now what was previously
// its child.
parent = child
}
// Delete the node, which is now a leaf node, by disconnecting it from
// its parent.
if parent.right == delNode {
parent.right = nil
} else {
parent.left = nil
}
return newImmutable(newRoot, t.count-1, t.totalSize-nodeSize(delNode))
}
// ForEach invokes the passed function with every key/value pair in the treap
// in ascending order.
func (t *Immutable) ForEach(fn func(k Key, v *Value) bool) {
// Add the root node and all children to the left of it to the list of
// nodes to traverse and loop until they, and all of their child nodes,
// have been traversed.
var parents parentStack
for node := t.root; node != nil; node = node.left {
parents.Push(node)
}
for parents.Len() > 0 {
node := parents.Pop()
if !fn(node.key, node.value) {
return
}
// Extend the nodes to traverse by all children to the left of
// the current node's right child.
for node := node.right; node != nil; node = node.left {
parents.Push(node)
}
}
}
// NewImmutable returns a new empty immutable treap ready for use. See the
// documentation for the Immutable structure for more details.
func NewImmutable() *Immutable {
return &Immutable{}
}

View File

@ -0,0 +1,501 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package tickettreap
import (
"bytes"
"crypto/sha256"
"reflect"
"testing"
)
// TestImmutableEmpty ensures calling functions on an empty immutable treap
// works as expected.
func TestImmutableEmpty(t *testing.T) {
t.Parallel()
// Ensure the treap length is the expected value.
testTreap := NewImmutable()
if gotLen := testTreap.Len(); gotLen != 0 {
t.Fatalf("Len: unexpected length - got %d, want %d", gotLen, 0)
}
// Ensure the reported size is 0.
if gotSize := testTreap.Size(); gotSize != 0 {
t.Fatalf("Size: unexpected byte size - got %d, want 0",
gotSize)
}
// Ensure there are no errors with requesting keys from an empty treap.
key := uint32ToKey(0)
if gotVal := testTreap.Has(key); gotVal != false {
t.Fatalf("Has: unexpected result - got %v, want false", gotVal)
}
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get: unexpected result - got %v, want nil", gotVal)
}
// Ensure there are no panics when deleting keys from an empty treap.
testTreap.Delete(key)
// Ensure the number of keys iterated by ForEach on an empty treap is
// zero.
var numIterated int
testTreap.ForEach(func(k Key, v *Value) bool {
numIterated++
return true
})
if numIterated != 0 {
t.Fatalf("ForEach: unexpected iterate count - got %d, want 0",
numIterated)
}
}
// TestImmutableSequential ensures that putting keys into an immutable treap in
// sequential order works as expected.
func TestImmutableSequential(t *testing.T) {
t.Parallel()
// Insert a bunch of sequential keys while checking several of the treap
// functions work as expected.
expectedSize := uint64(0)
numItems := 1000
testTreap := NewImmutable()
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(i))
value := &Value{Height: uint32(i)}
testTreap = testTreap.Put(key, value)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != i+1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, i+1)
}
// Ensure the treap has the key.
if !testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is not in treap", i, key)
}
// Get the key from the treap and ensure it is the expected
// value.
if gotVal := testTreap.Get(key); !reflect.DeepEqual(gotVal, value) {
t.Fatalf("Get #%d: unexpected value - got %v, want %v",
i, gotVal, value)
}
// Ensure the expected size is reported.
expectedSize += (nodeFieldsSize + uint64(len(key)) + nodeValueSize)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
// Ensure the all keys are iterated by ForEach in order.
var numIterated int
testTreap.ForEach(func(k Key, v *Value) bool {
// Ensure the key is as expected.
wantKey := uint32ToKey(uint32(numIterated))
if !bytes.Equal(k[:], wantKey[:]) {
t.Fatalf("ForEach #%d: unexpected key - got %x, want %x",
numIterated, k, wantKey)
}
// Ensure the value is as expected.
wantValue := &Value{Height: uint32(numIterated)}
if !reflect.DeepEqual(v, wantValue) {
t.Fatalf("ForEach #%d: unexpected value - got %v, want %v",
numIterated, v, wantValue)
}
numIterated++
return true
})
// Ensure all items were iterated.
if numIterated != numItems {
t.Fatalf("ForEach: unexpected iterate count - got %d, want %d",
numIterated, numItems)
}
// Delete the keys one-by-one while checking several of the treap
// functions work as expected.
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(i))
testTreap = testTreap.Delete(key)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != numItems-i-1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, numItems-i-1)
}
// Ensure the treap no longer has the key.
if testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is in treap", i, key)
}
// Get the key that no longer exists from the treap and ensure
// it is nil.
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get #%d: unexpected value - got %v, want nil",
i, gotVal)
}
// Ensure the expected size is reported.
expectedSize -= (nodeFieldsSize + uint64(len(key)) + 4)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
}
// TestImmutableReverseSequential ensures that putting keys into an immutable
// treap in reverse sequential order works as expected.
func TestImmutableReverseSequential(t *testing.T) {
t.Parallel()
// Insert a bunch of sequential keys while checking several of the treap
// functions work as expected.
expectedSize := uint64(0)
numItems := 1000
testTreap := NewImmutable()
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(numItems - i - 1))
value := &Value{Height: uint32(numItems - i - 1)}
testTreap = testTreap.Put(key, value)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != i+1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, i+1)
}
// Ensure the treap has the key.
if !testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is not in treap", i, key)
}
// Get the key from the treap and ensure it is the expected
// value.
if gotVal := testTreap.Get(key); !reflect.DeepEqual(gotVal, value) {
t.Fatalf("Get #%d: unexpected value - got %v, want %v",
i, gotVal, value)
}
// Ensure the expected size is reported.
expectedSize += (nodeFieldsSize + uint64(len(key)) + 4)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
// Ensure the all keys are iterated by ForEach in order.
var numIterated int
testTreap.ForEach(func(k Key, v *Value) bool {
// Ensure the key is as expected.
wantKey := uint32ToKey(uint32(numIterated))
if !bytes.Equal(k[:], wantKey[:]) {
t.Fatalf("ForEach #%d: unexpected key - got %x, want %x",
numIterated, k, wantKey)
}
// Ensure the value is as expected.
wantValue := &Value{Height: uint32(numIterated)}
if !reflect.DeepEqual(v, wantValue) {
t.Fatalf("ForEach #%d: unexpected value - got %v, want %v",
numIterated, v, wantValue)
}
numIterated++
return true
})
// Ensure all items were iterated.
if numIterated != numItems {
t.Fatalf("ForEach: unexpected iterate count - got %d, want %d",
numIterated, numItems)
}
// Delete the keys one-by-one while checking several of the treap
// functions work as expected.
for i := 0; i < numItems; i++ {
// Intentionally use the reverse order they were inserted here.
key := uint32ToKey(uint32(i))
testTreap = testTreap.Delete(key)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != numItems-i-1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, numItems-i-1)
}
// Ensure the treap no longer has the key.
if testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is in treap", i, key)
}
// Get the key that no longer exists from the treap and ensure
// it is nil.
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get #%d: unexpected value - got %v, want nil",
i, gotVal)
}
// Ensure the expected size is reported.
expectedSize -= (nodeFieldsSize + uint64(len(key)) + 4)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
}
// TestImmutableUnordered ensures that putting keys into an immutable treap in
// no paritcular order works as expected.
func TestImmutableUnordered(t *testing.T) {
t.Parallel()
// Insert a bunch of out-of-order keys while checking several of the
// treap functions work as expected.
expectedSize := uint64(0)
numItems := 1000
testTreap := NewImmutable()
for i := 0; i < numItems; i++ {
// Hash the serialized int to generate out-of-order keys.
key := Key(sha256.Sum256(serializeUint32(uint32(i))))
value := &Value{Height: uint32(i)}
testTreap = testTreap.Put(key, value)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != i+1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, i+1)
}
// Ensure the treap has the key.
if !testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is not in treap", i, key)
}
// Get the key from the treap and ensure it is the expected
// value.
if gotVal := testTreap.Get(key); !reflect.DeepEqual(gotVal, value) {
t.Fatalf("Get #%d: unexpected value - got %v, want %v",
i, gotVal, value)
}
// Ensure the expected size is reported.
expectedSize += nodeFieldsSize + uint64(len(key)) + 4
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
// Delete the keys one-by-one while checking several of the treap
// functions work as expected.
for i := 0; i < numItems; i++ {
// Hash the serialized int to generate out-of-order keys.
key := Key(sha256.Sum256(serializeUint32(uint32(i))))
testTreap = testTreap.Delete(key)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != numItems-i-1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, numItems-i-1)
}
// Ensure the treap no longer has the key.
if testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is in treap", i, key)
}
// Get the key that no longer exists from the treap and ensure
// it is nil.
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get #%d: unexpected value - got %v, want nil",
i, gotVal)
}
// Ensure the expected size is reported.
expectedSize -= (nodeFieldsSize + uint64(len(key)) + 4)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
}
// TestImmutableDuplicatePut ensures that putting a duplicate key into an
// immutable treap works as expected.
func TestImmutableDuplicatePut(t *testing.T) {
t.Parallel()
expectedVal := &Value{Height: 10000}
expectedSize := uint64(0)
numItems := 1000
testTreap := NewImmutable()
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(i))
value := &Value{Height: uint32(i)}
testTreap = testTreap.Put(key, value)
expectedSize += nodeFieldsSize + uint64(len(key)) + 4
// Put a duplicate key with the the expected final value.
testTreap = testTreap.Put(key, expectedVal)
// Ensure the key still exists and is the new value.
if gotVal := testTreap.Has(key); gotVal != true {
t.Fatalf("Has: unexpected result - got %v, want false",
gotVal)
}
if gotVal := testTreap.Get(key); !reflect.DeepEqual(gotVal, expectedVal) {
t.Fatalf("Get: unexpected result - got %v, want %v",
gotVal, expectedVal)
}
// Ensure the expected size is reported.
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size: unexpected byte size - got %d, want %d",
gotSize, expectedSize)
}
}
}
// TestImmutableNilValue ensures that putting a nil value into an immutable
// treap results in a NOOP.
func TestImmutableNilValue(t *testing.T) {
t.Parallel()
key := uint32ToKey(0)
// Put the key with a nil value.
testTreap := NewImmutable()
testTreap = testTreap.Put(key, nil)
// Ensure the key does NOT exist.
if gotVal := testTreap.Has(key); gotVal == true {
t.Fatalf("Has: unexpected result - got %v, want false", gotVal)
}
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get: unexpected result - got %v, want nil", gotVal)
}
}
// TestImmutableForEachStopIterator ensures that returning false from the ForEach
// callback on an immutable treap stops iteration early.
func TestImmutableForEachStopIterator(t *testing.T) {
t.Parallel()
// Insert a few keys.
numItems := 10
testTreap := NewImmutable()
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(i))
value := &Value{Height: uint32(i)}
testTreap = testTreap.Put(key, value)
}
// Ensure ForEach exits early on false return by caller.
var numIterated int
testTreap.ForEach(func(k Key, v *Value) bool {
numIterated++
if numIterated == numItems/2 {
return false
}
return true
})
if numIterated != numItems/2 {
t.Fatalf("ForEach: unexpected iterate count - got %d, want %d",
numIterated, numItems/2)
}
}
// TestImmutableSnapshot ensures that immutable treaps are actually immutable by
// keeping a reference to the previous treap, performing a mutation, and then
// ensuring the referenced treap does not have the mutation applied.
func TestImmutableSnapshot(t *testing.T) {
t.Parallel()
// Insert a bunch of sequential keys while checking several of the treap
// functions work as expected.
expectedSize := uint64(0)
numItems := 1000
testTreap := NewImmutable()
for i := 0; i < numItems; i++ {
treapSnap := testTreap
key := uint32ToKey(uint32(i))
value := &Value{Height: uint32(i)}
testTreap = testTreap.Put(key, value)
// Ensure the length of the treap snapshot is the expected
// value.
if gotLen := treapSnap.Len(); gotLen != i {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, i)
}
// Ensure the treap snapshot does not have the key.
if treapSnap.Has(key) {
t.Fatalf("Has #%d: key %q is in treap", i, key)
}
// Get the key that doesn't exist in the treap snapshot and
// ensure it is nil.
if gotVal := treapSnap.Get(key); gotVal != nil {
t.Fatalf("Get #%d: unexpected value - got %v, want nil",
i, gotVal)
}
// Ensure the expected size is reported.
if gotSize := treapSnap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
expectedSize += (nodeFieldsSize + uint64(len(key)) + 4)
}
// Delete the keys one-by-one while checking several of the treap
// functions work as expected.
for i := 0; i < numItems; i++ {
treapSnap := testTreap
key := uint32ToKey(uint32(i))
value := &Value{Height: uint32(i)}
testTreap = testTreap.Delete(key)
// Ensure the length of the treap snapshot is the expected
// value.
if gotLen := treapSnap.Len(); gotLen != numItems-i {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, numItems-i)
}
// Ensure the treap snapshot still has the key.
if !treapSnap.Has(key) {
t.Fatalf("Has #%d: key %q is not in treap", i, key)
}
// Get the key from the treap snapshot and ensure it is still
// the expected value.
if gotVal := treapSnap.Get(key); !reflect.DeepEqual(gotVal, value) {
t.Fatalf("Get #%d: unexpected value - got %v, want %v",
i, gotVal, value)
}
// Ensure the expected size is reported.
if gotSize := treapSnap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
expectedSize -= (nodeFieldsSize + uint64(len(key)) + 4)
}
}

View File

@ -0,0 +1,273 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package tickettreap
import "bytes"
// Mutable represents a treap data structure which is used to hold ordered
// key/value pairs using a combination of binary search tree and heap semantics.
// It is a self-organizing and randomized data structure that doesn't require
// complex operations to maintain balance. Search, insert, and delete
// operations are all O(log n).
type Mutable struct {
root *treapNode
count int
// totalSize is the best estimate of the total size of of all data in
// the treap including the keys, values, and node sizes.
totalSize uint64
}
// Len returns the number of items stored in the treap.
func (t *Mutable) Len() int {
return t.count
}
// Size returns a best estimate of the total number of bytes the treap is
// consuming including all of the fields used to represent the nodes as well as
// the size of the keys and values. Shared values are not detected, so the
// returned size assumes each value is pointing to different memory.
func (t *Mutable) Size() uint64 {
return t.totalSize
}
// get returns the treap node that contains the passed key and its parent. When
// the found node is the root of the tree, the parent will be nil. When the key
// does not exist, both the node and the parent will be nil.
func (t *Mutable) get(key Key) (*treapNode, *treapNode) {
var parent *treapNode
for node := t.root; node != nil; {
// Traverse left or right depending on the result of the
// comparison.
compareResult := bytes.Compare(key[:], node.key[:])
if compareResult < 0 {
parent = node
node = node.left
continue
}
if compareResult > 0 {
parent = node
node = node.right
continue
}
// The key exists.
return node, parent
}
// A nil node was reached which means the key does not exist.
return nil, nil
}
// Has returns whether or not the passed key exists.
func (t *Mutable) Has(key Key) bool {
if node, _ := t.get(key); node != nil {
return true
}
return false
}
// Get returns the value for the passed key. The function will return nil when
// the key does not exist.
func (t *Mutable) Get(key Key) *Value {
if node, _ := t.get(key); node != nil {
return node.value
}
return nil
}
// relinkGrandparent relinks the node into the treap after it has been rotated
// by changing the passed grandparent's left or right pointer, depending on
// where the old parent was, to point at the passed node. Otherwise, when there
// is no grandparent, it means the node is now the root of the tree, so update
// it accordingly.
func (t *Mutable) relinkGrandparent(node, parent, grandparent *treapNode) {
// The node is now the root of the tree when there is no grandparent.
if grandparent == nil {
t.root = node
return
}
// Relink the grandparent's left or right pointer based on which side
// the old parent was.
if grandparent.left == parent {
grandparent.left = node
} else {
grandparent.right = node
}
}
// Put inserts the passed key/value pair. Passing a nil value will result in a
// NOOP.
func (t *Mutable) Put(key Key, value *Value) {
// Nothing to do if a nil value is passed.
if value == nil {
return
}
// The node is the root of the tree if there isn't already one.
if t.root == nil {
node := newTreapNode(key, value, rng.Int())
t.count = 1
t.totalSize = nodeSize(node)
t.root = node
return
}
// Find the binary tree insertion point and construct a list of parents
// while doing so. When the key matches an entry already in the treap,
// just update its value and return.
var parents parentStack
var compareResult int
for node := t.root; node != nil; {
parents.Push(node)
compareResult = bytes.Compare(key[:], node.key[:])
if compareResult < 0 {
node = node.left
continue
}
if compareResult > 0 {
node = node.right
continue
}
// The key already exists, so update its value.
node.value = value
return
}
// Link the new node into the binary tree in the correct position.
node := newTreapNode(key, value, rng.Int())
t.count++
t.totalSize += nodeSize(node)
parent := parents.At(0)
if compareResult < 0 {
parent.left = node
} else {
parent.right = node
}
// Perform any rotations needed to maintain the min-heap.
for parents.Len() > 0 {
// There is nothing left to do when the node's priority is
// greater than or equal to its parent's priority.
parent = parents.Pop()
if node.priority >= parent.priority {
break
}
// Perform a right rotation if the node is on the left side or
// a left rotation if the node is on the right side.
if parent.left == node {
node.right, parent.left = parent, node.right
} else {
node.left, parent.right = parent, node.left
}
t.relinkGrandparent(node, parent, parents.At(0))
}
}
// Delete removes the passed key if it exists.
func (t *Mutable) Delete(key Key) {
// Find the node for the key along with its parent. There is nothing to
// do if the key does not exist.
node, parent := t.get(key)
if node == nil {
return
}
// When the only node in the tree is the root node and it is the one
// being deleted, there is nothing else to do besides removing it.
if parent == nil && node.left == nil && node.right == nil {
t.root = nil
t.count = 0
t.totalSize = 0
return
}
// Perform rotations to move the node to delete to a leaf position while
// maintaining the min-heap.
var isLeft bool
var child *treapNode
for node.left != nil || node.right != nil {
// Choose the child with the higher priority.
if node.left == nil {
child = node.right
isLeft = false
} else if node.right == nil {
child = node.left
isLeft = true
} else if node.left.priority >= node.right.priority {
child = node.left
isLeft = true
} else {
child = node.right
isLeft = false
}
// Rotate left or right depending on which side the child node
// is on. This has the effect of moving the node to delete
// towards the bottom of the tree while maintaining the
// min-heap.
if isLeft {
child.right, node.left = node, child.right
} else {
child.left, node.right = node, child.left
}
t.relinkGrandparent(child, node, parent)
// The parent for the node to delete is now what was previously
// its child.
parent = child
}
// Delete the node, which is now a leaf node, by disconnecting it from
// its parent.
if parent.right == node {
parent.right = nil
} else {
parent.left = nil
}
t.count--
t.totalSize -= nodeSize(node)
}
// ForEach invokes the passed function with every key/value pair in the treap
// in ascending order.
func (t *Mutable) ForEach(fn func(k Key, v *Value) bool) {
// Add the root node and all children to the left of it to the list of
// nodes to traverse and loop until they, and all of their child nodes,
// have been traversed.
var parents parentStack
for node := t.root; node != nil; node = node.left {
parents.Push(node)
}
for parents.Len() > 0 {
node := parents.Pop()
if !fn(node.key, node.value) {
return
}
// Extend the nodes to traverse by all children to the left of
// the current node's right child.
for node := node.right; node != nil; node = node.left {
parents.Push(node)
}
}
}
// Reset efficiently removes all items in the treap.
func (t *Mutable) Reset() {
t.count = 0
t.totalSize = 0
t.root = nil
}
// NewMutable returns a new empty mutable treap ready for use. See the
// documentation for the Mutable structure for more details.
func NewMutable() *Mutable {
return &Mutable{}
}

View File

@ -0,0 +1,475 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package tickettreap
import (
"bytes"
"crypto/sha256"
"reflect"
"testing"
)
// TestMutableEmpty ensures calling functions on an empty mutable treap works as
// expected.
func TestMutableEmpty(t *testing.T) {
t.Parallel()
// Ensure the treap length is the expected value.
testTreap := NewMutable()
if gotLen := testTreap.Len(); gotLen != 0 {
t.Fatalf("Len: unexpected length - got %d, want %d", gotLen, 0)
}
// Ensure the reported size is 0.
if gotSize := testTreap.Size(); gotSize != 0 {
t.Fatalf("Size: unexpected byte size - got %d, want 0",
gotSize)
}
// Ensure there are no errors with requesting keys from an empty treap.
key := uint32ToKey(0)
if gotVal := testTreap.Has(key); gotVal != false {
t.Fatalf("Has: unexpected result - got %v, want false", gotVal)
}
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get: unexpected result - got %v, want nil", gotVal)
}
// Ensure there are no panics when deleting keys from an empty treap.
testTreap.Delete(key)
// Ensure the number of keys iterated by ForEach on an empty treap is
// zero.
var numIterated int
testTreap.ForEach(func(k Key, v *Value) bool {
numIterated++
return true
})
if numIterated != 0 {
t.Fatalf("ForEach: unexpected iterate count - got %d, want 0",
numIterated)
}
}
// TestMutableReset ensures that resetting an existing mutable treap works as
// expected.
func TestMutableReset(t *testing.T) {
t.Parallel()
// Insert a few keys.
numItems := 10
testTreap := NewMutable()
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(i))
value := &Value{Height: uint32(i)}
testTreap.Put(key, value)
}
// Reset it.
testTreap.Reset()
// Ensure the treap length is now 0.
if gotLen := testTreap.Len(); gotLen != 0 {
t.Fatalf("Len: unexpected length - got %d, want %d", gotLen, 0)
}
// Ensure the reported size is now 0.
if gotSize := testTreap.Size(); gotSize != 0 {
t.Fatalf("Size: unexpected byte size - got %d, want 0",
gotSize)
}
// Ensure the treap no longer has any of the keys.
for i := 0; i < numItems; i++ {
// Ensure the treap no longer has the key.
key := uint32ToKey(uint32(i))
if testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is in treap", i, key)
}
// Get the key that no longer exists from the treap and ensure
// it is nil.
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get #%d: unexpected value - got %v, want nil",
i, gotVal)
}
}
// Ensure the number of keys iterated by ForEach is zero.
var numIterated int
testTreap.ForEach(func(k Key, v *Value) bool {
numIterated++
return true
})
if numIterated != 0 {
t.Fatalf("ForEach: unexpected iterate count - got %d, want 0",
numIterated)
}
}
// TestMutableSequential ensures that putting keys into a mutable treap in
// sequential order works as expected.
func TestMutableSequential(t *testing.T) {
t.Parallel()
// Insert a bunch of sequential keys while checking several of the treap
// functions work as expected.
expectedSize := uint64(0)
numItems := 1000
testTreap := NewMutable()
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(i))
value := &Value{Height: uint32(i)}
testTreap.Put(key, value)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != i+1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, i+1)
}
// Ensure the treap has the key.
if !testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is not in treap", i, key)
}
// Get the key from the treap and ensure it is the expected
// value.
if gotVal := testTreap.Get(key); !reflect.DeepEqual(gotVal, value) {
t.Fatalf("Get #%d: unexpected value - got %v, want %v",
i, gotVal, value)
}
// Ensure the expected size is reported.
expectedSize += (nodeFieldsSize + uint64(len(key)) + 4)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
// Ensure the all keys are iterated by ForEach in order.
var numIterated int
testTreap.ForEach(func(k Key, v *Value) bool {
// Ensure the key is as expected.
wantKey := uint32ToKey(uint32(numIterated))
if !bytes.Equal(k[:], wantKey[:]) {
t.Fatalf("ForEach #%d: unexpected key - got %x, want %x",
numIterated, k, wantKey)
}
// Ensure the value is as expected.
wantValue := &Value{Height: uint32(numIterated)}
if !reflect.DeepEqual(v, wantValue) {
t.Fatalf("ForEach #%d: unexpected value - got %v, want %v",
numIterated, v, wantValue)
}
numIterated++
return true
})
// Ensure all items were iterated.
if numIterated != numItems {
t.Fatalf("ForEach: unexpected iterate count - got %d, want %d",
numIterated, numItems)
}
// Delete the keys one-by-one while checking several of the treap
// functions work as expected.
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(i))
testTreap.Delete(key)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != numItems-i-1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, numItems-i-1)
}
// Ensure the treap no longer has the key.
if testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is in treap", i, key)
}
// Get the key that no longer exists from the treap and ensure
// it is nil.
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get #%d: unexpected value - got %v, want nil",
i, gotVal)
}
// Ensure the expected size is reported.
expectedSize -= (nodeFieldsSize + uint64(len(key)) + 4)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
}
// TestMutableReverseSequential ensures that putting keys into a mutable treap
// in reverse sequential order works as expected.
func TestMutableReverseSequential(t *testing.T) {
t.Parallel()
// Insert a bunch of sequential keys while checking several of the treap
// functions work as expected.
expectedSize := uint64(0)
numItems := 1000
testTreap := NewMutable()
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(numItems - i - 1))
value := &Value{Height: uint32(numItems - i - 1)}
testTreap.Put(key, value)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != i+1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, i+1)
}
// Ensure the treap has the key.
if !testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is not in treap", i, key)
}
// Get the key from the treap and ensure it is the expected
// value.
if gotVal := testTreap.Get(key); !reflect.DeepEqual(gotVal, value) {
t.Fatalf("Get #%d: unexpected value - got %v, want %v",
i, gotVal, value)
}
// Ensure the expected size is reported.
expectedSize += (nodeFieldsSize + uint64(len(key)) + 4)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
// Ensure the all keys are iterated by ForEach in order.
var numIterated int
testTreap.ForEach(func(k Key, v *Value) bool {
// Ensure the key is as expected.
wantKey := uint32ToKey(uint32(numIterated))
if !bytes.Equal(k[:], wantKey[:]) {
t.Fatalf("ForEach #%d: unexpected key - got %x, want %x",
numIterated, k, wantKey)
}
// Ensure the value is as expected.
wantValue := &Value{Height: uint32(numIterated)}
if !reflect.DeepEqual(v, wantValue) {
t.Fatalf("ForEach #%d: unexpected value - got %v, want %v",
numIterated, v, wantValue)
}
numIterated++
return true
})
// Ensure all items were iterated.
if numIterated != numItems {
t.Fatalf("ForEach: unexpected iterate count - got %d, want %d",
numIterated, numItems)
}
// Delete the keys one-by-one while checking several of the treap
// functions work as expected.
for i := 0; i < numItems; i++ {
// Intentionally use the reverse order they were inserted here.
key := uint32ToKey(uint32(i))
testTreap.Delete(key)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != numItems-i-1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, numItems-i-1)
}
// Ensure the treap no longer has the key.
if testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is in treap", i, key)
}
// Get the key that no longer exists from the treap and ensure
// it is nil.
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get #%d: unexpected value - got %v, want nil",
i, gotVal)
}
// Ensure the expected size is reported.
expectedSize -= (nodeFieldsSize + uint64(len(key)) + 4)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
}
// TestMutableUnordered ensures that putting keys into a mutable treap in no
// paritcular order works as expected.
func TestMutableUnordered(t *testing.T) {
t.Parallel()
// Insert a bunch of out-of-order keys while checking several of the
// treap functions work as expected.
expectedSize := uint64(0)
numItems := 1000
testTreap := NewMutable()
for i := 0; i < numItems; i++ {
// Hash the serialized int to generate out-of-order keys.
key := Key(sha256.Sum256(serializeUint32(uint32(i))))
value := &Value{Height: uint32(i)}
testTreap.Put(key, value)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != i+1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, i+1)
}
// Ensure the treap has the key.
if !testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is not in treap", i, key)
}
// Get the key from the treap and ensure it is the expected
// value.
if gotVal := testTreap.Get(key); !reflect.DeepEqual(gotVal, value) {
t.Fatalf("Get #%d: unexpected value - got %v, want %v",
i, gotVal, value)
}
// Ensure the expected size is reported.
expectedSize += nodeFieldsSize + uint64(len(key)) + 4
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
// Delete the keys one-by-one while checking several of the treap
// functions work as expected.
for i := 0; i < numItems; i++ {
// Hash the serialized int to generate out-of-order keys.
key := Key(sha256.Sum256(serializeUint32(uint32(i))))
testTreap.Delete(key)
// Ensure the treap length is the expected value.
if gotLen := testTreap.Len(); gotLen != numItems-i-1 {
t.Fatalf("Len #%d: unexpected length - got %d, want %d",
i, gotLen, numItems-i-1)
}
// Ensure the treap no longer has the key.
if testTreap.Has(key) {
t.Fatalf("Has #%d: key %q is in treap", i, key)
}
// Get the key that no longer exists from the treap and ensure
// it is nil.
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get #%d: unexpected value - got %v, want nil",
i, gotVal)
}
// Ensure the expected size is reported.
expectedSize -= (nodeFieldsSize + uint64(len(key)) + 4)
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size #%d: unexpected byte size - got %d, "+
"want %d", i, gotSize, expectedSize)
}
}
}
// TestMutableDuplicatePut ensures that putting a duplicate key into a mutable
// treap updates the existing value.
func TestMutableDuplicatePut(t *testing.T) {
t.Parallel()
expectedVal := &Value{Height: 10000}
expectedSize := uint64(0)
numItems := 1000
testTreap := NewMutable()
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(i))
value := &Value{Height: uint32(i)}
testTreap.Put(key, value)
expectedSize += nodeFieldsSize + uint64(len(key)) + 4
// Put a duplicate key with the the expected final value.
testTreap.Put(key, expectedVal)
// Ensure the key still exists and is the new value.
if gotVal := testTreap.Has(key); gotVal != true {
t.Fatalf("Has: unexpected result - got %v, want false",
gotVal)
}
if gotVal := testTreap.Get(key); !reflect.DeepEqual(gotVal, expectedVal) {
t.Fatalf("Get: unexpected result - got %v, want %v",
gotVal, expectedVal)
}
// Ensure the expected size is reported.
if gotSize := testTreap.Size(); gotSize != expectedSize {
t.Fatalf("Size: unexpected byte size - got %d, want %d",
gotSize, expectedSize)
}
}
}
// TestMutableNilValue ensures that putting a nil value into a mutable treap
// results in a NOOP.
func TestMutableNilValue(t *testing.T) {
t.Parallel()
key := uint32ToKey(0)
// Put the key with a nil value.
testTreap := NewMutable()
testTreap.Put(key, nil)
// Ensure the key does NOT exist.
if gotVal := testTreap.Has(key); gotVal == true {
t.Fatalf("Has: unexpected result - got %v, want false", gotVal)
}
if gotVal := testTreap.Get(key); gotVal != nil {
t.Fatalf("Get: unexpected result - got %v, want nil", gotVal)
}
}
// TestMutableForEachStopIterator ensures that returning false from the ForEach
// callback of a mutable treap stops iteration early.
func TestMutableForEachStopIterator(t *testing.T) {
t.Parallel()
// Insert a few keys.
numItems := 10
testTreap := NewMutable()
for i := 0; i < numItems; i++ {
key := uint32ToKey(uint32(i))
value := &Value{Height: uint32(i)}
testTreap.Put(key, value)
}
// Ensure ForEach exits early on false return by caller.
var numIterated int
testTreap.ForEach(func(k Key, v *Value) bool {
numIterated++
if numIterated == numItems/2 {
return false
}
return true
})
if numIterated != numItems/2 {
t.Fatalf("ForEach: unexpected iterate count - got %d, want %d",
numIterated, numItems/2)
}
}