Update github.com/xiaq/persistent.

This commit is contained in:
Qi Xiao 2018-01-25 22:46:58 +00:00 committed by Qi Xiao
parent 99921ea222
commit 1cbef84551
5 changed files with 200 additions and 25 deletions

2
Gopkg.lock generated
View File

@ -23,7 +23,7 @@
branch = "master"
name = "github.com/xiaq/persistent"
packages = ["hash","hashmap","vector"]
revision = "3621ae09791316ac331a0cc984c6a1cbf9d4eabf"
revision = "8f8f5eb31fdd1340ad5abdf018d07448c6a768d7"
[[projects]]
branch = "master"

View File

@ -1,6 +1,15 @@
// Package hashmap implements persistent hashmap.
package hashmap
import (
"bytes"
"encoding"
"encoding/json"
"fmt"
"reflect"
"strconv"
)
const (
chunkBits = 5
nodeCap = 1 << chunkBits
@ -13,11 +22,12 @@ type Equal func(k1, k2 interface{}) bool
// Hash is the type of a function that returns the hash code of a key.
type Hash func(k interface{}) uint32
// HashMap is a persistent associative data structure mapping keys to values. It
// Map is a persistent associative data structure mapping keys to values. It
// is immutable, and supports near-O(1) operations to create modified version of
// the hashmap that shares the underlying data structure, making it suitable for
// concurrent access.
type HashMap interface {
type Map interface {
json.Marshaler
// Len returns the length of the hashmap.
Len() int
// Get returns whether there is a value associated with the given key, and
@ -25,10 +35,10 @@ type HashMap interface {
Get(k interface{}) (interface{}, bool)
// Assoc returns an almost identical hashmap, with the given key associated
// with the given value.
Assoc(k, v interface{}) HashMap
Assoc(k, v interface{}) Map
// Without returns an almost identical hashmap, with the given key
// associated with no value.
Without(k interface{}) HashMap
Without(k interface{}) Map
// Iterator returns an iterator over the map.
Iterator() Iterator
}
@ -49,8 +59,8 @@ type Iterator interface {
}
// New takes an equality function and a hash function, and returns an empty
// HashMap.
func New(e Equal, h Hash) HashMap {
// Map.
func New(e Equal, h Hash) Map {
return &hashMap{0, emptyBitmapNode, e, h}
}
@ -69,7 +79,7 @@ func (m *hashMap) Get(k interface{}) (interface{}, bool) {
return m.root.find(0, m.hash(k), k, m.equal)
}
func (m *hashMap) Assoc(k, v interface{}) HashMap {
func (m *hashMap) Assoc(k, v interface{}) Map {
newRoot, added := m.root.assoc(0, m.hash(k), k, v, m.hash, m.equal)
newCount := m.count
if added {
@ -78,7 +88,7 @@ func (m *hashMap) Assoc(k, v interface{}) HashMap {
return &hashMap{newCount, newRoot, m.equal, m.hash}
}
func (m *hashMap) Without(k interface{}) HashMap {
func (m *hashMap) Without(k interface{}) Map {
newRoot, deleted := m.root.without(0, m.hash(k), k, m.equal)
newCount := m.count
if deleted {
@ -91,6 +101,60 @@ func (m *hashMap) Iterator() Iterator {
return m.root.iterator()
}
func (m *hashMap) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
buf.WriteByte('{')
first := true
for it := m.Iterator(); it.HasElem(); it.Next() {
if first {
first = false
} else {
buf.WriteByte(',')
}
k, v := it.Elem()
kString, err := convertKey(k)
if err != nil {
return nil, err
}
kBytes, err := json.Marshal(kString)
if err != nil {
return nil, err
}
vBytes, err := json.Marshal(v)
if err != nil {
return nil, err
}
buf.Write(kBytes)
buf.WriteByte(':')
buf.Write(vBytes)
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
// convertKey converts a map key to a string. The implementation matches the
// behavior of how json.Marshal encodes keys of the builtin map type.
func convertKey(k interface{}) (string, error) {
kref := reflect.ValueOf(k)
if kref.Kind() == reflect.String {
return kref.String(), nil
}
if t, ok := k.(encoding.TextMarshaler); ok {
b2, err := t.MarshalText()
if err != nil {
return "", err
}
return string(b2), nil
}
switch kref.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(kref.Int(), 10), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(kref.Uint(), 10), nil
}
return "", fmt.Errorf("unsupported key type %T", k)
}
// node is an interface for all nodes in the hash map tree.
type node interface {
// assoc adds a new pair of key and value. It returns the new node, and

View File

@ -1,7 +1,6 @@
package hashmap
import (
"fmt"
"math/rand"
"strconv"
"testing"
@ -35,18 +34,13 @@ type anotherTestKey uint32
func equalFunc(k1, k2 interface{}) bool {
switch k1 := k1.(type) {
case uint32:
return k1 == k2
case string:
s2, ok := k2.(string)
return ok && k1 == s2
case testKey:
t2, ok := k2.(testKey)
return ok && k1 == t2
case anotherTestKey:
return false
default:
panic(fmt.Errorf("unknown key type %T", k1))
return k1 == k2
}
}
@ -63,7 +57,7 @@ func hashFunc(k interface{}) uint32 {
case anotherTestKey:
return uint32(k)
default:
panic(fmt.Errorf("unknown key type %T", k))
return 0
}
}
@ -152,9 +146,45 @@ func TestHashMapSmallRandom(t *testing.T) {
}
}
// testHashMapWithRefEntries tests the operations of a HashMap. It uses the
// supplied list of entries to build the hash map, and then test all its
// operations.
var marshalJSONTests = []struct {
in Map
wantOut string
wantErr bool
}{
{makeHashMap(uint32(1), "a", "2", "b"), `{"1":"a","2":"b"}`, false},
// Invalid key type
{makeHashMap([]interface{}{}, "x"), "", true},
}
func TestMarshalJSON(t *testing.T) {
for i, test := range marshalJSONTests {
out, err := test.in.MarshalJSON()
if string(out) != test.wantOut {
t.Errorf("m%d.MarshalJSON -> out %s, want %s", i, out, test.wantOut)
}
if (err != nil) != test.wantErr {
var wantErr string
if test.wantErr {
wantErr = "non-nil"
} else {
wantErr = "nil"
}
t.Errorf("m%d.MarshalJSON -> err %v, want %s", i, err, wantErr)
}
}
}
func makeHashMap(data ...interface{}) Map {
m := empty
for i := 0; i+1 < len(data); i += 2 {
k, v := data[i], data[i+1]
m = m.Assoc(k, v)
}
return m
}
// testHashMapWithRefEntries tests the operations of a Map. It uses the supplied
// list of entries to build the map, and then test all its operations.
func testHashMapWithRefEntries(t *testing.T, refEntries []refEntry) {
m := empty
// Len of Empty should be 0.
@ -210,7 +240,7 @@ func testHashMapWithRefEntries(t *testing.T, refEntries []refEntry) {
}
}
func testMapContent(t *testing.T, m HashMap, ref map[testKey]string) {
func testMapContent(t *testing.T, m Map, ref map[testKey]string) {
for k, v := range ref {
got, in := m.Get(k)
if !in {
@ -222,7 +252,7 @@ func testMapContent(t *testing.T, m HashMap, ref map[testKey]string) {
}
}
func testIterator(t *testing.T, m HashMap, ref map[testKey]string) {
func testIterator(t *testing.T, m Map, ref map[testKey]string) {
ref2 := map[interface{}]interface{}{}
for k, v := range ref {
ref2[k] = v
@ -258,7 +288,7 @@ func BenchmarkSequentialConsPersistent1(b *testing.B) { sequentialCons(b.N, N1)
func BenchmarkSequentialConsPersistent2(b *testing.B) { sequentialCons(b.N, N2) }
func BenchmarkSequentialConsPersistent3(b *testing.B) { sequentialCons(b.N, N3) }
// sequentialCons starts with an empty HashMap and adds elements 0...n-1 to the
// sequentialCons starts with an empty hash map and adds elements 0...n-1 to the
// map, using the same value as the key, repeating for N times.
func sequentialCons(N int, n uint32) {
for r := 0; r < N; r++ {

View File

@ -1,6 +1,12 @@
// Package vector implements persistent vector.
package vector
import (
"bytes"
"encoding/json"
"fmt"
)
const (
chunkBits = 5
nodeSize = 1 << chunkBits
@ -15,6 +21,7 @@ const (
// vector that shares the underlying data structure, making it suitable for
// concurrent access. The empty value is a valid empty vector.
type Vector interface {
json.Marshaler
// Len returns the length of the vector.
Len() int
// Nth returns the i-th element of the vector. It returns nil if the index
@ -244,6 +251,14 @@ func (v *vector) SubVector(begin, end int) Vector {
return &subVector{v, begin, end}
}
func (v *vector) Iterator() Iterator {
return newIterator(v)
}
func (v *vector) MarshalJSON() ([]byte, error) {
return marshalJSON(v.Iterator())
}
type subVector struct {
v *vector
begin int
@ -293,8 +308,8 @@ func (s *subVector) Iterator() Iterator {
return newIteratorWithRange(s.v, s.begin, s.end)
}
func (v *vector) Iterator() Iterator {
return newIterator(v)
func (s *subVector) MarshalJSON() ([]byte, error) {
return marshalJSON(s.Iterator())
}
type iterator struct {
@ -366,3 +381,31 @@ func (it *iterator) Next() {
}
it.index++
}
type marshalError struct {
index int
cause error
}
func (err *marshalError) Error() string {
return fmt.Sprintf("element %d: %s", err.index, err.cause)
}
func marshalJSON(it Iterator) ([]byte, error) {
var buf bytes.Buffer
buf.WriteByte('[')
index := 0
for ; it.HasElem(); it.Next() {
if index > 0 {
buf.WriteByte(',')
}
elemBytes, err := json.Marshal(it.Elem())
if err != nil {
return nil, &marshalError{index, err}
}
buf.Write(elemBytes)
index++
}
buf.WriteByte(']')
return buf.Bytes(), nil
}

View File

@ -1,6 +1,7 @@
package vector
import (
"errors"
"math/rand"
"testing"
"time"
@ -223,6 +224,43 @@ func eqVector(v1, v2 Vector) bool {
return true
}
var marshalJSONTests = []struct {
in Vector
wantOut string
wantErr error
}{
{makeVector("1", 2, nil), `["1",2,null]`, nil},
{makeVector("1", makeVector(2)), `["1",[2]]`, nil},
{makeVector(0, 1, 2, 3, 4, 5).SubVector(1, 5), `[1,2,3,4]`, nil},
{makeVector(0, func() {}), "", errors.New("element 1: json: unsupported type: func()")},
}
func TestMarshalJSON(t *testing.T) {
for i, test := range marshalJSONTests {
out, err := test.in.MarshalJSON()
if string(out) != test.wantOut {
t.Errorf("v%d.MarshalJSON -> out %q, want %q", i, out, test.wantOut)
}
if err == nil || test.wantErr == nil {
if err != test.wantErr {
t.Errorf("v%d.MarshalJSON -> err %v, want %v", i, err, test.wantErr)
}
} else {
if err.Error() != test.wantErr.Error() {
t.Errorf("v%d.MarshalJSON -> err %v, want %v", i, err, test.wantErr)
}
}
}
}
func makeVector(elements ...interface{}) Vector {
v := Empty
for _, element := range elements {
v = v.Cons(element)
}
return v
}
func BenchmarkConsNative1(b *testing.B) { benchmarkNativeAppend(b, N1) }
func BenchmarkConsNative2(b *testing.B) { benchmarkNativeAppend(b, N2) }
func BenchmarkConsNative3(b *testing.B) { benchmarkNativeAppend(b, N3) }