Skip to content

Instantly share code, notes, and snippets.

@robert-king
Last active August 29, 2015 14:21
Show Gist options
  • Save robert-king/1a5d7aaa3377e6546d0c to your computer and use it in GitHub Desktop.
Save robert-king/1a5d7aaa3377e6546d0c to your computer and use it in GitHub Desktop.
golang implementation of Trie datastructure with Load & Save to persist to google appengine datastore
package trie
import (
"appengine/datastore"
"appengine"
"encoding/gob"
"bytes"
)
//@robertkingNZ
type Trie struct {
R rune `json:"r" endpoints:"req,desc=character rune"`
Children []*Trie `json:"l" endpoints:"req,desc=child trie"`
Ends int64 `json:"e" endpoints:"req,desc=number of endings"`
}
func NewTrie() *Trie {
return new(Trie)
}
func (t *Trie) find(r rune) *Trie {
for _, child := range t.Children {
if child.R == r {
return child
}
}
return nil
}
func (root *Trie) Insert(s string) {
for _, r := range s {
child := root.find(r)
if child == nil {
child = NewTrie()
child.R = r
root.Children = append(root.Children, child)
}
root = child
}
if len(s) > 0 {
root.Ends++
}
}
func GobMarshal(v interface{}) ([]byte, error) {
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(v); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func GobUnmarshal(data []byte, v interface{}) error {
return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v)
}
func (root *Trie) encode() ([]byte, error) {
return GobMarshal(root)
}
func (t *Trie) decode(data []byte) error {
return GobUnmarshal(data, &t)
}
func (t *Trie) Key(c appengine.Context,stringID string) *datastore.Key {
return datastore.NewKey(c, "trie", stringID, 0, nil)
}
func (t *Trie) Save(c appengine.Context, stringID string) error {
data, err := t.encode()
if err != nil {
c.Infof("error encoding trie")
return err
}
gobbed := &struct{ Bytes []byte }{data}
if len(gobbed.Bytes) > 200000 {
c.Infof("storing trie of %d bytes", len(gobbed.Bytes))
}
_, putErr := datastore.Put(c, t.Key(c, stringID), gobbed)
if putErr != nil {
c.Infof("error saving trie to datastore: %v", putErr)
}
return putErr
}
func Load(c appengine.Context, stringID string) (*Trie, error) {
t := new(Trie)
gobbed := struct{ Bytes []byte }{}
if err := datastore.Get(c, t.Key(c, stringID), &gobbed); err != nil {
if err != datastore.ErrNoSuchEntity {
c.Infof("error fetching trie %s, %v", stringID, err)
}
return nil, err
}
if err := t.decode(gobbed.Bytes); err != nil {
c.Infof("error decoding trie %v", err)
return nil, err
}
return t, nil
}
package trie
import (
"github.com/robert-king/studentcoursereview/backends/gopackages/testutil"
"reflect"
"testing"
)
func TestTrieSaving(test *testing.T) {
c := testutil.GetContext()
t1 := NewTrie()
t1.Insert("abc")
err := t1.Save(c, "someKey")
if err != nil {
test.Error("error saving trie", err)
}
t2, err2 := Load(c, "someKey")
if err2 != nil {
test.Error("error loading trie")
}
if !reflect.DeepEqual(t1, t2) {
test.Error("not the same", t1, t2)
}
}
func TestTrie(test *testing.T) {
root := NewTrie()
root.Insert("abc")
if len(root.Children) != 1 {
test.Error("didn't insert")
}
if root.find('a').find('b').find('c') == nil {
test.Error("didn't insert")
}
if root.find('a').Ends != 0 {
test.Error("not zero")
}
if root.find('a').find('b').Ends != 0 {
test.Error("not zero")
}
if root.find('a').find('b').find('c').Ends != 1 {
test.Error("not one")
}
root.Insert("")
root.Insert("w")
root.Insert("xyz")
root.Insert("abc")
root.Insert("abb")
if root.find('a').find('b').find('c') == nil {
test.Error("didn't insert")
}
if root.find('w') == nil {
test.Error("didn't insert")
}
if root.find('x').find('y').find('z') == nil {
test.Error("didn't insert")
}
if root.find('a').find('b').find('b') == nil {
test.Error("didn't insert")
}
if len(root.Children) != 3 {
test.Error("error")
}
if root.find('a').find('b').find('c').Ends != 2 {
test.Error("not two")
}
if root.Ends != 0 {
test.Error("not zero")
}
if root.find('a').find('b').find('b').Ends != 1 {
test.Error("not one")
}
if root.find('w').Ends != 1 {
test.Error("not one")
}
}
@robert-king
Copy link
Author

note - for clients generally it's faster to use an array of strings with gzip as encoding / decoding and memory become the issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment