From cdfbcb4e69286eb00ab2516496731d6f14c6e7b9 Mon Sep 17 00:00:00 2001 From: Aaron Boodman Date: Thu, 4 Jun 2015 22:10:27 -0700 Subject: [PATCH] Add initial Map implementation --- types/flat_map.go | 97 ++++++++++++++++++++++++++++++++++++ types/flat_map_test.go | 108 +++++++++++++++++++++++++++++++++++++++++ types/map.go | 19 ++++++++ 3 files changed, 224 insertions(+) create mode 100644 types/flat_map.go create mode 100644 types/flat_map_test.go create mode 100644 types/map.go diff --git a/types/flat_map.go b/types/flat_map.go new file mode 100644 index 0000000000..b5764ceb56 --- /dev/null +++ b/types/flat_map.go @@ -0,0 +1,97 @@ +package types + +import ( + . "github.com/attic-labs/noms/dbg" +) + +var ( + emptyString = string("") + emptyValuePtr = (*Value)(nil) +) + +type internalMap map[string]Value + +type flatMap struct { + m internalMap +} + +func (fm flatMap) Len() uint64 { + return uint64(len(fm.m)) +} + +func (fm flatMap) Has(key string) bool { + _, ok := fm.m[key] + return ok +} + +func (fm flatMap) Get(key string) Value { + if v, ok := fm.m[key]; ok { + return v + } else { + return nil + } +} + +func (fm flatMap) Set(key string, val Value) Map { + return flatMap{buildMap(fm.m, key, val)} +} + +func (fm flatMap) SetM(kv ...interface{}) Map { + return flatMap{buildMap(fm.m, kv...)} +} + +func (fm flatMap) Remove(k string) Map { + m := buildMap(fm.m) + delete(m, k) + return flatMap{m} +} + +func (fm flatMap) Iter(cb IterCallback) { + for k, v := range fm.m { + if cb(k, v) { + break + } + } +} + +func (fm flatMap) Equals(other Value) (res bool) { + if other, ok := other.(Map); ok { + res = true + fm.Iter(func(k string, v Value) (stop bool) { + if other.Get(k) != v { + stop = true + res = false + } + return + }) + if !res { + return + } + other.Iter(func(k string, v Value) (stop bool) { + if !fm.Has(k) { + stop = true + res = false + } + return + }) + } + return +} + +func buildMap(initialData internalMap, kv ...interface{}) (m internalMap) { + Chk.Equal(0, len(kv)%2, "Must specify even number of key/value pairs") + m = internalMap{} + if initialData != nil { + for k, v := range initialData { + m[k] = v + } + } + for i := 0; i < len(kv); i += 2 { + k := kv[i] + v := kv[i+1] + Chk.IsType(emptyString, k) + Chk.Implements(emptyValuePtr, v) + m[k.(string)] = v.(Value) + } + return +} diff --git a/types/flat_map_test.go b/types/flat_map_test.go new file mode 100644 index 0000000000..a8cac149e7 --- /dev/null +++ b/types/flat_map_test.go @@ -0,0 +1,108 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewMap(t *testing.T) { + assert := assert.New(t) + m := NewMap() + assert.IsType(flatMap{}, m) + assert.Equal(uint64(0), m.Len()) + m = NewMap("foo", NewString("foo"), "bar", NewString("bar")) + assert.Equal(uint64(2), m.Len()) + assert.True(NewString("foo").Equals(m.Get("foo"))) + assert.True(NewString("bar").Equals(m.Get("bar"))) +} + +func TestFlatMapHasRemove(t *testing.T) { + assert := assert.New(t) + m1 := NewMap() + assert.False(m1.Has("foo")) + m2 := m1.Set("foo", NewString("foo")) + assert.False(m1.Has("foo")) + assert.True(m2.Has("foo")) + m3 := m1.Remove("foo") + assert.False(m1.Has("foo")) + assert.True(m2.Has("foo")) + assert.False(m3.Has("foo")) +} + +func TestFlatMapSetGet(t *testing.T) { + assert := assert.New(t) + m1 := NewMap() + assert.Nil(m1.Get("foo")) + m2 := m1.Set("foo", Int32(42)) + assert.Nil(m1.Get("foo")) + assert.True(Int32(42).Equals(m2.Get("foo"))) + m3 := m2.Set("foo", Int32(43)) + assert.Nil(m1.Get("foo")) + assert.True(Int32(42).Equals(m2.Get("foo"))) + assert.True(Int32(43).Equals(m3.Get("foo"))) + m4 := m3.Remove("foo") + assert.Nil(m1.Get("foo")) + assert.True(Int32(42).Equals(m2.Get("foo"))) + assert.True(Int32(43).Equals(m3.Get("foo"))) + assert.Nil(m4.Get("foo")) +} + +func TestFlatMapSetM(t *testing.T) { + assert := assert.New(t) + m1 := NewMap() + m2 := m1.SetM() + assert.True(m1.Equals(m2)) + m3 := m2.SetM("foo", NewString("bar"), "hot", NewString("dog")) + assert.Equal(uint64(2), m3.Len()) + assert.True(NewString("bar").Equals(m3.Get("foo"))) + assert.True(NewString("dog").Equals(m3.Get("hot"))) + m4 := m3.SetM("mon", NewString("key")) + assert.Equal(uint64(2), m3.Len()) + assert.Equal(uint64(3), m4.Len()) +} + +func TestFlatMapIter(t *testing.T) { + assert := assert.New(t) + m := NewMap() + got := map[string]Value{} + stop := false + cb := func(k string, v Value) bool { + got[k] = v + return stop + } + + m.Iter(cb) + assert.Equal(0, len(got)) + + m = m.SetM("a", Int32(0), "b", Int32(1)) + m.Iter(cb) + assert.Equal(2, len(got)) + assert.True(Int32(0).Equals(got["a"])) + assert.True(Int32(1).Equals(got["b"])) + + got = map[string]Value{} + stop = true + m.Iter(cb) + assert.Equal(1, len(got)) + assert.True(Int32(0).Equals(got["a"])) +} + +func TestFlatMapEquals(t *testing.T) { + assert := assert.New(t) + m1 := NewMap() + m2 := m1 + m3 := NewMap() + + assert.True(m1.Equals(m2)) + assert.True(m2.Equals(m1)) + assert.True(m3.Equals(m2)) + assert.True(m2.Equals(m3)) + + m1 = NewMap("foo", Float32(0.0), "bar", Float32(1.1)) + m2 = m2.SetM("foo", Float32(0.0), "bar", Float32(1.1)) + assert.True(m1.Equals(m2)) + assert.True(m2.Equals(m1)) + assert.False(m2.Equals(m3)) + assert.False(m3.Equals(m2)) +} diff --git a/types/map.go b/types/map.go new file mode 100644 index 0000000000..0c75afd7d6 --- /dev/null +++ b/types/map.go @@ -0,0 +1,19 @@ +package types + +type IterCallback func(k string, v Value) bool + +type Map interface { + Value + // TODO: keys should be able to be any noms type + Len() uint64 + Has(k string) bool + Get(k string) Value + Set(k string, v Value) Map + SetM(kv ...interface{}) Map + Remove(k string) Map + Iter(IterCallback) +} + +func NewMap(kv ...interface{}) Map { + return flatMap{buildMap(nil, kv...)} +}