Last active
August 29, 2015 14:21
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
note - for clients generally it's faster to use an array of strings with gzip as encoding / decoding and memory become the issue