Initial Sublist

This commit is contained in:
Derek Collison
2012-10-30 16:09:11 -07:00
parent 599246e51f
commit 01885a9106
2 changed files with 448 additions and 0 deletions

277
sublist.go Normal file
View File

@@ -0,0 +1,277 @@
// Copyright 2012 Apcera Inc. All rights reserved.
package gnatsd
import (
// "bytes"
// "fmt"
// "github.com/apcera/gnatsd/hash"
"github.com/apcera/gnatsd/hashmap"
)
type node struct {
next *level
subs []interface{}
}
type level struct {
nodes *hashmap.HashMap
pwc, fwc *node
}
type Sublist struct {
root *level
count uint32
}
func newNode() *node {
return &node{subs: make([]interface{}, 0, 4)}
}
func newLevel() *level {
return &level{nodes: hashmap.New()}
}
func New() *Sublist {
return &Sublist{root: newLevel()}
}
var (
_PWC = byte('*')
_FWC = byte('>')
_SEP = byte('.')
)
func split(subject []byte, tokens [][]byte) [][]byte {
start := 0
for i, b := range subject {
if b == _SEP {
tokens = append(tokens, subject[start:i])
start = i + 1
}
}
return append(tokens, subject[start:])
}
func (s *Sublist) Insert(subject []byte, sub interface{}) {
tsa := [16][]byte{}
toks := split(subject, tsa[:0])
l := s.root
var n *node
for _, t := range toks {
switch t[0] {
case _PWC:
n = l.pwc
case _FWC:
n = l.fwc
default:
if v := l.nodes.Get(t); v == nil {
n = nil
} else {
n = v.(*node)
}
}
if n == nil {
n = newNode()
switch t[0] {
case _PWC:
l.pwc = n
case _FWC:
l.fwc = n
default:
l.nodes.Set(t, n)
}
}
if n.next == nil {
n.next = newLevel()
}
l = n.next
}
n.subs = append(n.subs, sub)
s.count++
}
func (s *Sublist) Match(subject []byte) []interface{} {
tsa := [16][]byte{}
toks := split(subject, tsa[:0])
// FIXME: Let them pass in?
results := make([]interface{}, 0, 4)
matchLevel(s.root, toks, &results)
return results
}
func matchLevel(l *level, toks [][]byte, results *[]interface{}) {
var pwc, n *node
for i, t := range toks {
if l == nil {
return
}
if l.fwc != nil {
*results = append(*results, l.fwc.subs...)
}
if pwc = l.pwc; pwc != nil {
matchLevel(pwc.next, toks[i+1:], results)
}
if v := l.nodes.Get(t); v == nil {
n = nil
} else {
n = v.(*node)
}
if n != nil {
l = n.next
} else {
l = nil
}
}
if n != nil {
*results = append(*results, n.subs...)
}
if pwc != nil {
*results = append(*results, pwc.subs...)
}
return
}
type lnt struct {
l *level
n *node
t []byte
}
func (s *Sublist) Remove(subject []byte, sub interface{}) {
tsa := [16][]byte{}
toks := split(subject, tsa[:0])
l := s.root
var n *node
var lnts [32]lnt
levels := lnts[:0]
for _, t := range toks {
if l == nil {
return
}
switch t[0] {
case _PWC:
n = l.pwc
case _FWC:
n = l.fwc
default:
if v := l.nodes.Get(t); v == nil {
n = nil
} else {
n = v.(*node)
}
}
if n != nil {
levels = append(levels, lnt{l, n, t})
l = n.next
} else {
l = nil
}
}
if !s.removeFromNode(n, sub) {
return
}
for i := len(levels) - 1; i >= 0; i-- {
l, n, t := levels[i].l, levels[i].n, levels[i].t
if n.isEmpty() {
l.pruneNode(n, t)
}
}
}
func (l *level) pruneNode(n *node, t []byte) {
if n == nil {
return
}
if n == l.fwc {
l.fwc = nil
} else if n == l.pwc {
l.pwc = nil
} else {
l.nodes.Remove(t)
}
}
func (n *node) isEmpty() bool {
if len(n.subs) == 0 {
if n.next == nil || n.next.numNodes() == 0 {
return true
}
}
return false
}
func (l *level) numNodes() uint32 {
num := l.nodes.Count()
if l.pwc != nil {
num += 1
}
if l.fwc != nil {
num += 1
}
return num
}
func (s *Sublist) removeFromNode(n *node, sub interface{}) bool {
if n == nil {
return false
}
for i, v := range n.subs {
if v == sub {
s.count--
num := len(n.subs)
a := n.subs
copy(a[i:num-1], a[i+1:num])
n.subs = a[0 : num-1]
return true
}
}
return false
}
func (s *Sublist) Count() uint32 { return s.count }
func (s *Sublist) DebugNumLevels() int {
return visitLevel(s.root, 0)
}
func visitLevel(l *level, depth int) int {
if l == nil || l.numNodes() == 0 {
return depth
}
depth += 1
maxDepth := depth
all := l.nodes.All()
for _, a := range all {
if a == nil {
continue
}
n := a.(*node)
newDepth := visitLevel(n.next, depth)
if newDepth > maxDepth {
maxDepth = newDepth
}
}
if l.pwc != nil {
pwcDepth := visitLevel(l.pwc.next, depth)
if pwcDepth > maxDepth {
maxDepth = pwcDepth
}
}
if l.fwc != nil {
fwcDepth := visitLevel(l.fwc.next, depth)
if fwcDepth > maxDepth {
maxDepth = fwcDepth
}
}
return maxDepth
}

171
sublist_test.go Normal file
View File

@@ -0,0 +1,171 @@
package gnatsd
import (
"bytes"
"testing"
)
func verifyCount(s *Sublist, count uint32, t *testing.T) {
if s.Count() != count {
t.Errorf("Count is %d, should be %d", s.Count(), count)
}
}
func verifyLen(r []interface{}, l int, t *testing.T) {
if len(r) != l {
t.Errorf("Results len is %d, should be %d", len(r), l)
}
}
func verifyMember(r []interface{}, val string, t *testing.T) {
for _, v := range r {
if v == nil {
continue
}
if v.(string) == val {
return
}
}
t.Errorf("Value '%s' not found in results", val)
}
func verifyNumLevels(s *Sublist, expected int, t *testing.T) {
dl := s.DebugNumLevels()
if dl != expected {
t.Errorf("NumLevels is %d, should be %d", dl, expected)
}
}
func TestInit(t *testing.T) {
s := New()
verifyCount(s, 0, t)
}
func TestInsertCount(t *testing.T) {
s := New()
s.Insert([]byte("foo"), "a")
s.Insert([]byte("bar"), "b")
s.Insert([]byte("foo.bar"), "b")
verifyCount(s, 3, t)
}
func TestSimple(t *testing.T) {
s := New()
val := "a"
sub := []byte("foo")
s.Insert(sub, val)
r := s.Match(sub)
verifyLen(r, 1, t)
verifyMember(r, val, t)
}
func TestSimpleMultiTokens(t *testing.T) {
s := New()
val := "a"
sub := []byte("foo.bar.baz")
s.Insert(sub, val)
r := s.Match(sub)
verifyLen(r, 1, t)
verifyMember(r, val, t)
}
func TestPartialWildcard(t *testing.T) {
s := New()
literal := []byte("a.b.c")
pwc := []byte("a.*.c")
a, b := "a", "b"
s.Insert(literal, a)
s.Insert(pwc, b)
r := s.Match(literal)
verifyLen(r, 2, t)
verifyMember(r, a, t)
verifyMember(r, b, t)
}
func TestPartialWildcardAtEnd(t *testing.T) {
s := New()
literal := []byte("a.b.c")
pwc := []byte("a.b.*")
a, b := "a", "b"
s.Insert(literal, a)
s.Insert(pwc, b)
r := s.Match(literal)
verifyLen(r, 2, t)
verifyMember(r, a, t)
verifyMember(r, b, t)
}
func TestFullWildcard(t *testing.T) {
s := New()
literal := []byte("a.b.c")
fwc := []byte("a.>")
a, b := "a", "b"
s.Insert(literal, a)
s.Insert(fwc, b)
r := s.Match(literal)
verifyLen(r, 2, t)
verifyMember(r, a, t)
verifyMember(r, b, t)
}
func TestRemove(t *testing.T) {
s := New()
literal := []byte("a.b.c.d")
value := "foo"
s.Insert(literal, value)
verifyCount(s, 1, t)
s.Remove(literal, "bar")
verifyCount(s, 1, t)
s.Remove([]byte("a.b.c"), value)
verifyCount(s, 1, t)
s.Remove(literal, value)
verifyCount(s, 0, t)
r := s.Match(literal)
verifyLen(r, 0, t)
}
func TestRemoveWildcard(t *testing.T) {
s := New()
literal := []byte("a.b.c.d")
pwc := []byte("a.b.*.d")
fwc := []byte("a.b.>")
value := "foo"
s.Insert(pwc, value)
s.Insert(fwc, value)
s.Insert(literal, value)
verifyCount(s, 3, t)
r := s.Match(literal)
verifyLen(r, 3, t)
s.Remove(literal, value)
verifyCount(s, 2, t)
s.Remove(fwc, value)
verifyCount(s, 1, t)
s.Remove(pwc, value)
verifyCount(s, 0, t)
}
func TestRemoveCleanup(t *testing.T) {
s := New()
literal := []byte("a.b.c.d.e.f")
depth := len(bytes.Split(literal, []byte(".")))
value := "foo"
verifyNumLevels(s, 0, t)
s.Insert(literal, value)
verifyNumLevels(s, depth, t)
s.Remove(literal, value)
verifyNumLevels(s, 0, t)
}
func TestRemoveCleanupWildcards(t *testing.T) {
s := New()
literal := []byte("a.b.*.d.e.>")
depth := len(bytes.Split(literal, []byte(".")))
value := "foo"
verifyNumLevels(s, 0, t)
s.Insert(literal, value)
verifyNumLevels(s, depth, t)
s.Remove(literal, value)
verifyNumLevels(s, 0, t)
}