From 4188d0b84a9b36e0f3cfb3b254ef543afd8dea6f Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sat, 18 Oct 2014 20:03:16 -0400 Subject: [PATCH] make Elem interface have a method on it, implement that in seq (bleh), implement GoType --- seq/hashmap.go | 45 +++++++++++++++- seq/hashmap_test.go | 76 ++++++++++++++++++--------- seq/hashset.go | 116 +++++++++++++++++++++-------------------- seq/hashset_test.go | 122 +++++++++++++++++++++++++++----------------- seq/lazy.go | 14 +++++ seq/lazy_test.go | 17 ++++-- seq/list.go | 30 +++++++++++ seq/list_test.go | 70 +++++++++++++++++-------- seq/seq_test.go | 82 +++++++++++++++-------------- seq/util.go | 29 +++++++++-- types/types.go | 27 ++++++++++ 11 files changed, 432 insertions(+), 196 deletions(-) diff --git a/seq/hashmap.go b/seq/hashmap.go index 92af30b..157573f 100644 --- a/seq/hashmap.go +++ b/seq/hashmap.go @@ -28,9 +28,18 @@ func (kv *KV) Hash(i uint32) uint32 { // compared to another KV, only compares the other key as well. func (kv *KV) Equal(v types.Elem) bool { if kv2, ok := v.(*KV); ok { - return equal(kv.Key, kv2.Key) + return kv.Key.Equal(kv2.Key) } - return equal(kv.Key, v) + return kv.Key.Equal(v) +} + +func (kv *KV) fullEqual(v types.Elem) bool { + kv2, ok := v.(*KV) + if !ok { + return false + } + + return kv.Key.Equal(kv2.Key) && kv.Val.Equal(kv2.Val) } // Implementation of String for Stringer @@ -65,6 +74,38 @@ func (hm *HashMap) FirstRest() (types.Elem, Seq, bool) { return el, &HashMap{nset.(*Set)}, ok } +// Implementation of Equal for types.Elem interface. Completes in O(Nlog(M)) +// time if e is another HashMap, where M is the size of the given HashMap +func (hm *HashMap) Equal(e types.Elem) bool { + // This can't just use Set's Equal because that would end up using KeyVal's + // Equal, which is not a true Equal + + hm2, ok := e.(*HashMap) + if !ok { + return false + } + + var el types.Elem + s := Seq(hm) + size := uint64(0) + + for { + el, s, ok = s.FirstRest() + if !ok { + return size == hm2.Size() + } + size++ + + kv := el.(*KV) + k, v := kv.Key, kv.Val + + v2, ok := hm2.Get(k) + if !ok || !v.Equal(v2) { + return false + } + } +} + // Returns a new HashMap with the given value set on the given key. Also returns // whether or not this was the first time setting that key (false if it was // already there and was overwritten). Has the same complexity as Set's SetVal diff --git a/seq/hashmap_test.go b/seq/hashmap_test.go index 6b0368f..1951b83 100644 --- a/seq/hashmap_test.go +++ b/seq/hashmap_test.go @@ -14,11 +14,16 @@ func kvints(kvs ...*KV) ([]*KV, []types.Elem) { return kvs, ints } +// Test that HashMap implements types.Elem (compile-time check) +func TestHashMapElem(t *T) { + _ = types.Elem(NewHashMap()) +} + // Test creating a Set and calling the Seq interface methods on it func TestHashMapSeq(t *T) { kvs, ints := kvints( - KeyVal(1, "one"), - KeyVal(2, "two"), + keyValV(1, "one"), + keyValV(2, "two"), ) // Testing creation and Seq interface methods @@ -29,27 +34,50 @@ func TestHashMapSeq(t *T) { assertEmpty(ms, t) } +// Test that the Equal method on HashMaps works +func TestHashMapEqual(t *T) { + hm, hm2 := NewHashMap(), NewHashMap() + assertValue(hm.Equal(hm2), true, t) + assertValue(hm2.Equal(hm), true, t) + + hm = NewHashMap(keyValV(1, "one"), keyValV(2, "two")) + assertValue(hm.Equal(hm2), false, t) + assertValue(hm2.Equal(hm), false, t) + + hm2 = NewHashMap(keyValV(1, "one")) + assertValue(hm.Equal(hm2), false, t) + assertValue(hm2.Equal(hm), false, t) + + hm2 = NewHashMap(keyValV(1, "one"), keyValV(2, "three?")) + assertValue(hm.Equal(hm2), false, t) + assertValue(hm2.Equal(hm), false, t) + + hm2 = NewHashMap(keyValV(1, "one"), keyValV(2, "two")) + assertValue(hm.Equal(hm2), true, t) + assertValue(hm2.Equal(hm), true, t) +} + // Test getting values from a HashMap func TestHashMapGet(t *T) { kvs := []*KV{ - KeyVal(1, "one"), - KeyVal(2, "two"), + keyValV(1, "one"), + keyValV(2, "two"), } // Degenerate case m := NewHashMap() assertEmpty(m, t) - v, ok := m.Get(1) + v, ok := m.Get(types.GoType{1}) assertValue(v, nil, t) assertValue(ok, false, t) m = NewHashMap(kvs...) - v, ok = m.Get(1) + v, ok = m.Get(types.GoType{1}) assertSeqContentsHashMap(m, kvs, t) - assertValue(v, "one", t) + assertValue(v, types.GoType{"one"}, t) assertValue(ok, true, t) - v, ok = m.Get(3) + v, ok = m.Get(types.GoType{3}) assertSeqContentsHashMap(m, kvs, t) assertValue(v, nil, t) assertValue(ok, false, t) @@ -60,21 +88,21 @@ func TestHashMapSet(t *T) { // Set on empty m := NewHashMap() - m1, ok := m.Set(1, "one") + m1, ok := m.Set(types.GoType{1}, types.GoType{"one"}) assertEmpty(m, t) - assertSeqContentsHashMap(m1, []*KV{KeyVal(1, "one")}, t) + assertSeqContentsHashMap(m1, []*KV{keyValV(1, "one")}, t) assertValue(ok, true, t) // Set on same key - m2, ok := m1.Set(1, "wat") - assertSeqContentsHashMap(m1, []*KV{KeyVal(1, "one")}, t) - assertSeqContentsHashMap(m2, []*KV{KeyVal(1, "wat")}, t) + m2, ok := m1.Set(types.GoType{1}, types.GoType{"wat"}) + assertSeqContentsHashMap(m1, []*KV{keyValV(1, "one")}, t) + assertSeqContentsHashMap(m2, []*KV{keyValV(1, "wat")}, t) assertValue(ok, false, t) // Set on second new key - m3, ok := m2.Set(2, "two") - assertSeqContentsHashMap(m2, []*KV{KeyVal(1, "wat")}, t) - assertSeqContentsHashMap(m3, []*KV{KeyVal(1, "wat"), KeyVal(2, "two")}, t) + m3, ok := m2.Set(types.GoType{2}, types.GoType{"two"}) + assertSeqContentsHashMap(m2, []*KV{keyValV(1, "wat")}, t) + assertSeqContentsHashMap(m3, []*KV{keyValV(1, "wat"), keyValV(2, "two")}, t) assertValue(ok, true, t) } @@ -83,31 +111,31 @@ func TestHashMapSet(t *T) { func TestHashMapDel(t *T) { kvs := []*KV{ - KeyVal(1, "one"), - KeyVal(2, "two"), - KeyVal(3, "three"), + keyValV(1, "one"), + keyValV(2, "two"), + keyValV(3, "three"), } kvs1 := []*KV{ - KeyVal(2, "two"), - KeyVal(3, "three"), + keyValV(2, "two"), + keyValV(3, "three"), } // Degenerate case m := NewHashMap() - m1, ok := m.Del(1) + m1, ok := m.Del(types.GoType{1}) assertEmpty(m, t) assertEmpty(m1, t) assertValue(ok, false, t) // Delete actual key m = NewHashMap(kvs...) - m1, ok = m.Del(1) + m1, ok = m.Del(types.GoType{1}) assertSeqContentsHashMap(m, kvs, t) assertSeqContentsHashMap(m1, kvs1, t) assertValue(ok, true, t) // Delete it again! - m2, ok := m1.Del(1) + m2, ok := m1.Del(types.GoType{1}) assertSeqContentsHashMap(m1, kvs1, t) assertSeqContentsHashMap(m2, kvs1, t) assertValue(ok, false, t) diff --git a/seq/hashset.go b/seq/hashset.go index 5923d3e..f5458ca 100644 --- a/seq/hashset.go +++ b/seq/hashset.go @@ -30,63 +30,41 @@ func hash(v types.Elem, i uint32) uint32 { case Setable: return vt.Hash(i) % ARITY - case uint: - return uint32(vt) % ARITY - case uint8: - return uint32(vt) % ARITY - case uint32: - return uint32(vt) % ARITY - case uint64: - return uint32(vt) % ARITY - case int: - return uint32(vt) % ARITY - case int8: - return uint32(vt) % ARITY - case int16: - return uint32(vt) % ARITY - case int32: - return uint32(vt) % ARITY - case int64: - return uint32(vt) % ARITY - case float32: - return uint32(vt) % ARITY - case float64: - return uint32(vt) % ARITY + case types.GoType: + switch gvt := vt.V.(type) { + case uint: + return uint32(gvt) % ARITY + case uint8: + return uint32(gvt) % ARITY + case uint32: + return uint32(gvt) % ARITY + case uint64: + return uint32(gvt) % ARITY + case int: + return uint32(gvt) % ARITY + case int8: + return uint32(gvt) % ARITY + case int16: + return uint32(gvt) % ARITY + case int32: + return uint32(gvt) % ARITY + case int64: + return uint32(gvt) % ARITY + case float32: + return uint32(gvt) % ARITY + case float64: + return uint32(gvt) % ARITY - case string: - return crc32.ChecksumIEEE([]byte(vt)) % ARITY + case string: + return crc32.ChecksumIEEE([]byte(gvt)) % ARITY - case []byte: - return crc32.ChecksumIEEE(vt) % ARITY - - default: - err := fmt.Sprintf("%s not hashable", reflect.TypeOf(v)) - panic(err) - } -} - -// Returns whether two values (potentially Setable's) are equivalent -func equal(v1, v2 types.Elem) bool { - if v1t, ok := v1.(Setable); ok { - return v1t.Equal(v2) - } else if v2t, ok := v2.(Setable); ok { - return v2t.Equal(v1) - } else if v1t, ok := v1.([]byte); ok { - if v2t, ok := v2.([]byte); ok { - if len(v1t) != len(v2t) { - return false - } - for i := range v1t { - if v1t[i] != v2t[i] { - return false - } - } - return true + case []byte: + return crc32.ChecksumIEEE(gvt) % ARITY } - return false - } else { - return v1 == v2 } + + err := fmt.Sprintf("%s not hashable", reflect.TypeOf(v)) + panic(err) } // The number of children each node in Set (implemented as a hash tree) can have @@ -142,7 +120,7 @@ func (set *Set) shallowTrySetOrInit(val types.Elem) (bool, bool) { set.val = val set.full = true return true, false - } else if equal(set.val, val) { + } else if set.val.Equal(val) { set.val = val set.full = true return true, true @@ -215,7 +193,7 @@ func (set *Set) SetVal(val types.Elem) (*Set, bool) { func (set *Set) internalDelVal(val types.Elem, i uint32) (*Set, bool) { if set == nil { return nil, false - } else if set.full && equal(val, set.val) { + } else if set.full && set.val.Equal(val) { cset := set.clone() cset.val = nil cset.full = false @@ -247,7 +225,7 @@ func (set *Set) DelVal(val types.Elem) (*Set, bool) { func (set *Set) internalGetVal(val types.Elem, i uint32) (types.Elem, bool) { if set == nil { return nil, false - } else if set.full && equal(val, set.val) { + } else if set.full && set.val.Equal(val) { return set.val, true } else if set.kids == nil { return nil, false @@ -301,6 +279,32 @@ func (set *Set) FirstRest() (types.Elem, Seq, bool) { return el, Seq(restSet), ok } +// Implementation of Equal for types.Elem interface. Completes in O(Nlog(M)) +// time if e is another Set, where M is the size of the given Set +func (set *Set) Equal(e types.Elem) bool { + set2, ok := e.(*Set) + if !ok { + return false + } + + var el types.Elem + s := Seq(set) + size := uint64(0) + + for { + el, s, ok = s.FirstRest() + if !ok { + return size == set2.Size() + } + size++ + + _, ok = set2.GetVal(el) + if !ok { + return false + } + } +} + // Implementation of String for Stringer interface func (set *Set) String() string { return ToString(set, "#{", "}#") diff --git a/seq/hashset_test.go b/seq/hashset_test.go index 75872f8..80e83e0 100644 --- a/seq/hashset_test.go +++ b/seq/hashset_test.go @@ -6,9 +6,14 @@ import ( "github.com/mediocregopher/ginger/types" ) +// Test that HashSet implements types.Elem (compile-time check) +func TestSetElem(t *T) { + _ = types.Elem(NewSet()) +} + // Test creating a Set and calling the Seq interface methods on it func TestSetSeq(t *T) { - ints := []types.Elem{1, "a", 5.0} + ints := elemSliceV(nil, 1, "a", 5.0) // Testing creation and Seq interface methods s := NewSet(ints...) @@ -22,25 +27,48 @@ func TestSetSeq(t *T) { assertValue(len(ToSlice(s)), 0, t) } +// Test that the Equal method on Sets works +func TestSetEqual(t *T) { + s, s2 := NewSet(), NewSet() + assertValue(s.Equal(s2), true, t) + assertValue(s2.Equal(s), true, t) + + s = NewSet(elemSliceV(0, 1, 2)...) + assertValue(s.Equal(s2), false, t) + assertValue(s2.Equal(s), false, t) + + s2 = NewSet(elemSliceV(0, 1)...) + assertValue(s.Equal(s2), false, t) + assertValue(s2.Equal(s), false, t) + + s2 = NewSet(elemSliceV(0, 1, 3)...) + assertValue(s.Equal(s2), false, t) + assertValue(s2.Equal(s), false, t) + + s2 = NewSet(elemSliceV(0, 1, 2)...) + assertValue(s.Equal(s2), true, t) + assertValue(s2.Equal(s), true, t) +} + // Test setting a value on a Set func TestSetVal(t *T) { - ints := []types.Elem{0, 1, 2, 3, 4} - ints1 := []types.Elem{0, 1, 2, 3, 4, 5} + ints := elemSliceV(0, 1, 2, 3, 4) + ints1 := elemSliceV(0, 1, 2, 3, 4, 5) // Degenerate case s := NewSet() assertEmpty(s, t) - s, ok := s.SetVal(0) - assertSeqContentsSet(s, []types.Elem{0}, t) + s, ok := s.SetVal(types.GoType{0}) + assertSeqContentsSet(s, elemSliceV(0), t) assertValue(ok, true, t) s = NewSet(ints...) - s1, ok := s.SetVal(5) + s1, ok := s.SetVal(types.GoType{5}) assertSeqContentsSet(s, ints, t) assertSeqContentsSet(s1, ints1, t) assertValue(ok, true, t) - s2, ok := s1.SetVal(5) + s2, ok := s1.SetVal(types.GoType{5}) assertSeqContentsSet(s1, ints1, t) assertSeqContentsSet(s2, ints1, t) assertValue(ok, false, t) @@ -48,41 +76,41 @@ func TestSetVal(t *T) { // Test deleting a value from a Set func TestDelVal(t *T) { - ints := []types.Elem{0, 1, 2, 3, 4} - ints1 := []types.Elem{0, 1, 2, 3} - ints2 := []types.Elem{1, 2, 3, 4} - ints3 := []types.Elem{1, 2, 3, 4, 5} + ints := elemSliceV(0, 1, 2, 3, 4) + ints1 := elemSliceV(0, 1, 2, 3) + ints2 := elemSliceV(1, 2, 3, 4) + ints3 := elemSliceV(1, 2, 3, 4, 5) // Degenerate case s := NewSet() assertEmpty(s, t) - s, ok := s.DelVal(0) + s, ok := s.DelVal(types.GoType{0}) assertEmpty(s, t) assertValue(ok, false, t) s = NewSet(ints...) - s1, ok := s.DelVal(4) + s1, ok := s.DelVal(types.GoType{4}) assertSeqContentsSet(s, ints, t) assertSeqContentsSet(s1, ints1, t) assertValue(ok, true, t) - s1, ok = s1.DelVal(4) + s1, ok = s1.DelVal(types.GoType{4}) assertSeqContentsSet(s1, ints1, t) assertValue(ok, false, t) // 0 is the value on the root node of s, which is kind of a special case. We // want to test deleting it and setting a new value (which should get put on // the root node). - s2, ok := s.DelVal(0) + s2, ok := s.DelVal(types.GoType{0}) assertSeqContentsSet(s, ints, t) assertSeqContentsSet(s2, ints2, t) assertValue(ok, true, t) - s2, ok = s2.DelVal(0) + s2, ok = s2.DelVal(types.GoType{0}) assertSeqContentsSet(s2, ints2, t) assertValue(ok, false, t) - s3, ok := s2.SetVal(5) + s3, ok := s2.SetVal(types.GoType{5}) assertSeqContentsSet(s2, ints2, t) assertSeqContentsSet(s3, ints3, t) assertValue(ok, true, t) @@ -92,36 +120,36 @@ func TestDelVal(t *T) { func GetVal(t *T) { //Degenerate case s := NewSet() - v, ok := s.GetVal(1) + v, ok := s.GetVal(types.GoType{1}) assertValue(v, nil, t) assertValue(ok, false, t) - s = NewSet(0, 1, 2, 3, 4) - v, ok = s.GetVal(1) + s = NewSet(elemSliceV(0, 1, 2, 3, 4)...) + v, ok = s.GetVal(types.GoType{1}) assertValue(v, 1, t) assertValue(ok, true, t) // After delete - s, _ = s.DelVal(1) - v, ok = s.GetVal(1) + s, _ = s.DelVal(types.GoType{1}) + v, ok = s.GetVal(types.GoType{1}) assertValue(v, nil, t) assertValue(ok, false, t) // After set - s, _ = s.SetVal(1) - v, ok = s.GetVal(1) + s, _ = s.SetVal(types.GoType{1}) + v, ok = s.GetVal(types.GoType{1}) assertValue(v, 1, t) assertValue(ok, true, t) // After delete root node - s, _ = s.DelVal(0) - v, ok = s.GetVal(0) + s, _ = s.DelVal(types.GoType{0}) + v, ok = s.GetVal(types.GoType{0}) assertValue(v, nil, t) assertValue(ok, false, t) // After set root node - s, _ = s.SetVal(5) - v, ok = s.GetVal(5) + s, _ = s.SetVal(types.GoType{5}) + v, ok = s.GetVal(types.GoType{5}) assertValue(v, 5, t) assertValue(ok, true, t) } @@ -133,25 +161,25 @@ func TestSetSize(t *T) { assertValue(s.Size(), uint64(0), t) // Initialization case - s = NewSet(0, 1, 2) + s = NewSet(elemSliceV(0, 1, 2)...) assertValue(s.Size(), uint64(3), t) // Setting (both value not in and a value already in) - s, _ = s.SetVal(3) + s, _ = s.SetVal(types.GoType{3}) assertValue(s.Size(), uint64(4), t) - s, _ = s.SetVal(3) + s, _ = s.SetVal(types.GoType{3}) assertValue(s.Size(), uint64(4), t) // Deleting (both value already in and a value not in) - s, _ = s.DelVal(3) + s, _ = s.DelVal(types.GoType{3}) assertValue(s.Size(), uint64(3), t) - s, _ = s.DelVal(3) + s, _ = s.DelVal(types.GoType{3}) assertValue(s.Size(), uint64(3), t) // Deleting and setting the root node - s, _ = s.DelVal(0) + s, _ = s.DelVal(types.GoType{0}) assertValue(s.Size(), uint64(2), t) - s, _ = s.SetVal(5) + s, _ = s.SetVal(types.GoType{5}) assertValue(s.Size(), uint64(3), t) } @@ -162,8 +190,8 @@ func TestUnion(t *T) { empty := NewSet() assertEmpty(empty.Union(empty), t) - ints1 := []types.Elem{0, 1, 2} - ints2 := []types.Elem{3, 4, 5} + ints1 := elemSliceV(0, 1, 2) + ints2 := elemSliceV(3, 4, 5) intsu := append(ints1, ints2...) s1 := NewSet(ints1...) s2 := NewSet(ints2...) @@ -183,10 +211,10 @@ func TestIntersection(t *T) { empty := NewSet() assertEmpty(empty.Intersection(empty), t) - ints1 := []types.Elem{0, 1, 2} - ints2 := []types.Elem{1, 2, 3} - ints3 := []types.Elem{4, 5, 6} - intsi := []types.Elem{1, 2} + ints1 := elemSliceV(0, 1, 2) + ints2 := elemSliceV(1, 2, 3) + ints3 := elemSliceV(4, 5, 6) + intsi := elemSliceV(1, 2) s1 := NewSet(ints1...) s2 := NewSet(ints2...) s3 := NewSet(ints3...) @@ -208,9 +236,9 @@ func TestDifference(t *T) { empty := NewSet() assertEmpty(empty.Difference(empty), t) - ints1 := []types.Elem{0, 1, 2, 3} - ints2 := []types.Elem{2, 3, 4} - intsd := []types.Elem{0, 1} + ints1 := elemSliceV(0, 1, 2, 3) + ints2 := elemSliceV(2, 3, 4) + intsd := elemSliceV(0, 1) s1 := NewSet(ints1...) s2 := NewSet(ints2...) @@ -229,9 +257,9 @@ func TestSymDifference(t *T) { empty := NewSet() assertEmpty(empty.SymDifference(empty), t) - ints1 := []types.Elem{0, 1, 2, 3} - ints2 := []types.Elem{2, 3, 4} - intsd := []types.Elem{0, 1, 4} + ints1 := elemSliceV(0, 1, 2, 3) + ints2 := elemSliceV(2, 3, 4) + intsd := elemSliceV(0, 1, 4) s1 := NewSet(ints1...) s2 := NewSet(ints2...) diff --git a/seq/lazy.go b/seq/lazy.go index 97aa1ec..adc1323 100644 --- a/seq/lazy.go +++ b/seq/lazy.go @@ -50,6 +50,20 @@ func (l *Lazy) FirstRest() (types.Elem, Seq, bool) { } } +// Implementation of Equal for types.Elem interface. Treats a List as another +// Lazy. Completes in O(N) time if e is another List or List. +func (l *Lazy) Equal(e types.Elem) bool { + var ls2 *List + if l2, ok := e.(*Lazy); ok { + ls2 = ToList(l2) + } else if ls2, ok = e.(*List); ok { + } else { + return false + } + ls := ToList(l) + return ls.Equal(ls2) +} + // Implementation of String for Stringer func (l *Lazy) String() string { return ToString(l, "<<", ">>") diff --git a/seq/lazy_test.go b/seq/lazy_test.go index 98cc1a9..ba9fb77 100644 --- a/seq/lazy_test.go +++ b/seq/lazy_test.go @@ -7,19 +7,26 @@ import ( "github.com/mediocregopher/ginger/types" ) +// Test that Lazy implements types.Elem (compile-time check) +func TestLazyElem(t *T) { + _ = types.Elem(NewLazy(nil)) +} + // Test lazy operation and thread-safety func TestLazyBasic(t *T) { - ch := make(chan int) + ch := make(chan types.GoType) mapfn := func(el types.Elem) types.Elem { - i := el.(int) + i := el.(types.GoType) ch <- i return i } - intl := []types.Elem{0, 1, 2, 3, 4} + intl := elemSliceV(0, 1, 2, 3, 4) l := NewList(intl...) ml := LMap(mapfn, l) + // ml is a lazy list of intl, which will write to ch the first time any of + // the elements are read. This for loop ensures ml is thread-safe for i := 0; i < 10; i++ { go func() { mlintl := ToSlice(ml) @@ -29,6 +36,8 @@ func TestLazyBasic(t *T) { }() } + // This loop and subsequent close ensure that ml only ever "creates" each + // element once for _, el := range intl { select { case elch := <-ch: @@ -42,7 +51,7 @@ func TestLazyBasic(t *T) { // Test that arbitrary Seqs can turn into Lazy func TestToLazy(t *T) { - intl := []types.Elem{0, 1, 2, 3, 4} + intl := elemSliceV(0, 1, 2, 3, 4) l := NewList(intl...) ll := ToLazy(l) assertSeqContents(ll, intl, t) diff --git a/seq/list.go b/seq/list.go index 86c71db..46ae6a7 100644 --- a/seq/list.go +++ b/seq/list.go @@ -39,6 +39,36 @@ func (l *List) FirstRest() (types.Elem, Seq, bool) { } } +// Implementation of Equal for types.Elem interface. Completes in O(N) time if e +// is another List. +func (l *List) Equal(e types.Elem) bool { + l2, ok := e.(*List) + if !ok { + return false + } + + var el, el2 types.Elem + var ok2 bool + + s, s2 := Seq(l), Seq(l2) + + for { + el, s, ok = s.FirstRest() + el2, s2, ok2 = s2.FirstRest() + + if !ok && !ok2 { + return true + } + if ok != ok2 { + return false + } + + if !el.Equal(el2) { + return false + } + } +} + // Implementation of String for Stringer interface. func (l *List) String() string { return ToString(l, "(", ")") diff --git a/seq/list_test.go b/seq/list_test.go index 05796cf..cec136a 100644 --- a/seq/list_test.go +++ b/seq/list_test.go @@ -6,6 +6,11 @@ import ( "github.com/mediocregopher/ginger/types" ) +// Test that List implements types.Elem (compile-time check) +func TestListElem(t *T) { + _ = types.Elem(NewList()) +} + // Asserts that the given list is properly formed and has all of its size fields // filled in correctly func assertSaneList(l *List, t *T) { @@ -22,7 +27,7 @@ func assertSaneList(l *List, t *T) { // Test creating a list and calling the Seq interface methods on it func TestListSeq(t *T) { - ints := []types.Elem{1, "a", 5.0} + ints := elemSliceV(1, "a", 5.0) // Testing creation and Seq interface methods l := NewList(ints...) @@ -40,39 +45,62 @@ func TestListSeq(t *T) { assertValue(emptyl, nilpointer, t) } +// Test that the Equal method on Lists works +func TestListEqual(t *T) { + l, l2 := NewList(), NewList() + assertValue(l.Equal(l2), true, t) + assertValue(l2.Equal(l), true, t) + + l2 = NewList(elemSliceV(1, 2, 3)...) + assertValue(l.Equal(l2), false, t) + assertValue(l2.Equal(l), false, t) + + l = NewList(elemSliceV(1, 2, 3, 4)...) + assertValue(l.Equal(l2), false, t) + assertValue(l2.Equal(l), false, t) + + l2 = NewList(elemSliceV(1, 2, 3, 4)...) + assertValue(l.Equal(l2), true, t) + assertValue(l2.Equal(l), true, t) +} + // Test the string representation of a List func TestStringSeq(t *T) { - l := NewList(0, 1, 2, 3) + l := NewList(elemSliceV(0, 1, 2, 3)...) assertValue(l.String(), "( 0 1 2 3 )", t) - l = NewList(0, 1, 2, NewList(3, 4), 5, NewList(6, 7, 8)) + l = NewList(elemSliceV( + 0, 1, 2, + NewList(elemSliceV(3, 4)...), + 5, + NewList(elemSliceV(6, 7, 8)...))...) assertValue(l.String(), "( 0 1 2 ( 3 4 ) 5 ( 6 7 8 ) )", t) } // Test prepending an element to the beginning of a list func TestPrepend(t *T) { // Normal case - intl := []types.Elem{3, 2, 1, 0} + intl := elemSliceV(3, 2, 1, 0) l := NewList(intl...) - nl := l.Prepend(4) + nl := l.Prepend(types.GoType{4}) assertSaneList(l, t) assertSaneList(nl, t) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{4, 3, 2, 1, 0}, t) + assertSeqContents(nl, elemSliceV(4, 3, 2, 1, 0), t) // Degenerate case l = NewList() - nl = l.Prepend(0) + nl = l.Prepend(types.GoType{0}) assertEmpty(l, t) assertSaneList(nl, t) - assertSeqContents(nl, []types.Elem{0}, t) + assertSeqContents(nl, elemSliceV(0), t) } // Test prepending a Seq to the beginning of a list func TestPrependSeq(t *T) { //Normal case - intl1 := []types.Elem{3, 4} - intl2 := []types.Elem{0, 1, 2} + intl1 := elemSliceV(3, 4) + intl2 := elemSliceV(0, 1, 2) l1 := NewList(intl1...) l2 := NewList(intl2...) nl := l1.PrependSeq(l2) @@ -81,7 +109,7 @@ func TestPrependSeq(t *T) { assertSaneList(nl, t) assertSeqContents(l1, intl1, t) assertSeqContents(l2, intl2, t) - assertSeqContents(nl, []types.Elem{0, 1, 2, 3, 4}, t) + assertSeqContents(nl, elemSliceV(0, 1, 2, 3, 4), t) // Degenerate cases blank1 := NewList() @@ -105,34 +133,34 @@ func TestPrependSeq(t *T) { // Test appending to the end of a List func TestAppend(t *T) { // Normal case - intl := []types.Elem{3, 2, 1} + intl := elemSliceV(3, 2, 1) l := NewList(intl...) - nl := l.Append(0) + nl := l.Append(types.GoType{0}) assertSaneList(l, t) assertSaneList(nl, t) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{3, 2, 1, 0}, t) + assertSeqContents(nl, elemSliceV(3, 2, 1, 0), t) // Edge case (algorithm gets weird here) - l = NewList(1) - nl = l.Append(0) + l = NewList(elemSliceV(1)...) + nl = l.Append(types.GoType{0}) assertSaneList(l, t) assertSaneList(nl, t) - assertSeqContents(l, []types.Elem{1}, t) - assertSeqContents(nl, []types.Elem{1, 0}, t) + assertSeqContents(l, elemSliceV(1), t) + assertSeqContents(nl, elemSliceV(1, 0), t) // Degenerate case l = NewList() - nl = l.Append(0) + nl = l.Append(types.GoType{0}) assertEmpty(l, t) assertSaneList(nl, t) - assertSeqContents(nl, []types.Elem{0}, t) + assertSeqContents(nl, elemSliceV(0), t) } // Test retrieving items from a List func TestNth(t *T) { // Normal case, in bounds - intl := []types.Elem{0, 2, 4, 6, 8} + intl := elemSliceV(0, 2, 4, 6, 8) l := NewList(intl...) r, ok := l.Nth(3) assertSaneList(l, t) diff --git a/seq/seq_test.go b/seq/seq_test.go index 991e8c9..cd9915f 100644 --- a/seq/seq_test.go +++ b/seq/seq_test.go @@ -50,11 +50,11 @@ func testSeqNoOrderGen(t *T, s Seq, ints []types.Elem) Seq { // Test reversing a Seq func TestReverse(t *T) { // Normal case - intl := []types.Elem{3, 2, 1} + intl := elemSliceV(3, 2, 1) l := NewList(intl...) nl := Reverse(l) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{1, 2, 3}, t) + assertSeqContents(nl, elemSliceV(1, 2, 3), t) // Degenerate case l = NewList() @@ -65,15 +65,15 @@ func TestReverse(t *T) { func testMapGen(t *T, mapFn func(func(types.Elem) types.Elem, Seq) Seq) { fn := func(n types.Elem) types.Elem { - return n.(int) + 1 + return types.GoType{n.(types.GoType).V.(int) + 1} } // Normal case - intl := []types.Elem{1, 2, 3} + intl := elemSliceV(1, 2, 3) l := NewList(intl...) nl := mapFn(fn, l) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{2, 3, 4}, t) + assertSeqContents(nl, elemSliceV(2, 3, 4), t) // Degenerate case l = NewList() @@ -95,27 +95,31 @@ func TestLMap(t *T) { // Test reducing over a Seq func TestReduce(t *T) { fn := func(acc, el types.Elem) (types.Elem, bool) { - return acc.(int) + el.(int), false + acci := acc.(types.GoType).V.(int) + eli := el.(types.GoType).V.(int) + return types.GoType{acci + eli}, false } // Normal case - intl := []types.Elem{1, 2, 3, 4} + intl := elemSliceV(1, 2, 3, 4) l := NewList(intl...) - r := Reduce(fn, 0, l) + r := Reduce(fn, types.GoType{0}, l) assertSeqContents(l, intl, t) assertValue(r, 10, t) // Short-circuit case fns := func(acc, el types.Elem) (types.Elem, bool) { - return acc.(int) + el.(int), el.(int) > 2 + acci := acc.(types.GoType).V.(int) + eli := el.(types.GoType).V.(int) + return types.GoType{acci + eli}, eli > 2 } - r = Reduce(fns, 0, l) + r = Reduce(fns, types.GoType{0}, l) assertSeqContents(l, intl, t) assertValue(r, 6, t) // Degenerate case l = NewList() - r = Reduce(fn, 0, l) + r = Reduce(fn, types.GoType{0}, l) assertEmpty(l, t) assertValue(r, 0, t) } @@ -123,11 +127,11 @@ func TestReduce(t *T) { // Test the Any function func TestAny(t *T) { fn := func(el types.Elem) bool { - return el.(int) > 3 + return el.(types.GoType).V.(int) > 3 } // Value found case - intl := []types.Elem{1, 2, 3, 4} + intl := elemSliceV(1, 2, 3, 4) l := NewList(intl...) r, ok := Any(fn, l) assertSeqContents(l, intl, t) @@ -135,7 +139,7 @@ func TestAny(t *T) { assertValue(ok, true, t) // Value not found case - intl = []types.Elem{1, 2, 3} + intl = elemSliceV(1, 2, 3) l = NewList(intl...) r, ok = Any(fn, l) assertSeqContents(l, intl, t) @@ -153,18 +157,18 @@ func TestAny(t *T) { // Test the All function func TestAll(t *T) { fn := func(el types.Elem) bool { - return el.(int) > 3 + return el.(types.GoType).V.(int) > 3 } // All match case - intl := []types.Elem{4, 5, 6} + intl := elemSliceV(4, 5, 6) l := NewList(intl...) ok := All(fn, l) assertSeqContents(l, intl, t) assertValue(ok, true, t) // Not all match case - intl = []types.Elem{3, 4, 2, 5} + intl = elemSliceV(3, 4, 2, 5) l = NewList(intl...) ok = All(fn, l) assertSeqContents(l, intl, t) @@ -179,15 +183,15 @@ func TestAll(t *T) { func testFilterGen(t *T, filterFn func(func(types.Elem) bool, Seq) Seq) { fn := func(el types.Elem) bool { - return el.(int)%2 != 0 + return el.(types.GoType).V.(int)%2 != 0 } // Normal case - intl := []types.Elem{1, 2, 3, 4, 5} + intl := elemSliceV(1, 2, 3, 4, 5) l := NewList(intl...) r := filterFn(fn, l) assertSeqContents(l, intl, t) - assertSeqContents(r, []types.Elem{1, 3, 5}, t) + assertSeqContents(r, elemSliceV(1, 3, 5), t) // Degenerate cases l = NewList() @@ -209,19 +213,19 @@ func TestLFilter(t *T) { // Test Flatten-ing of a Seq func TestFlatten(t *T) { // Normal case - intl1 := []types.Elem{0, 1, 2} - intl2 := []types.Elem{3, 4, 5} + intl1 := elemSliceV(0, 1, 2) + intl2 := elemSliceV(3, 4, 5) l1 := NewList(intl1...) l2 := NewList(intl2...) blank := NewList() - intl := []types.Elem{-1, l1, l2, 6, blank, 7} + intl := elemSliceV(-1, l1, l2, 6, blank, 7) l := NewList(intl...) nl := Flatten(l) assertSeqContents(l1, intl1, t) assertSeqContents(l2, intl2, t) assertEmpty(blank, t) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{-1, 0, 1, 2, 3, 4, 5, 6, 7}, t) + assertSeqContents(nl, elemSliceV(-1, 0, 1, 2, 3, 4, 5, 6, 7), t) // Degenerate case nl = Flatten(blank) @@ -231,11 +235,11 @@ func TestFlatten(t *T) { func testTakeGen(t *T, takeFn func(uint64, Seq) Seq) { // Normal case - intl := []types.Elem{0, 1, 2, 3, 4} + intl := elemSliceV(0, 1, 2, 3, 4) l := NewList(intl...) nl := takeFn(3, l) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{0, 1, 2}, t) + assertSeqContents(nl, elemSliceV(0, 1, 2), t) // Edge cases nl = takeFn(5, l) @@ -269,28 +273,28 @@ func TestLTake(t *T) { func testTakeWhileGen(t *T, takeWhileFn func(func(types.Elem) bool, Seq) Seq) { pred := func(el types.Elem) bool { - return el.(int) < 3 + return el.(types.GoType).V.(int) < 3 } // Normal case - intl := []types.Elem{0, 1, 2, 3, 4, 5} + intl := elemSliceV(0, 1, 2, 3, 4, 5) l := NewList(intl...) nl := takeWhileFn(pred, l) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{0, 1, 2}, t) + assertSeqContents(nl, elemSliceV(0, 1, 2), t) // Edge cases - intl = []types.Elem{5, 5, 5} + intl = elemSliceV(5, 5, 5) l = NewList(intl...) nl = takeWhileFn(pred, l) assertSeqContents(l, intl, t) assertEmpty(nl, t) - intl = []types.Elem{0, 1, 2} + intl = elemSliceV(0, 1, 2) l = NewList(intl...) nl = takeWhileFn(pred, l) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{0, 1, 2}, t) + assertSeqContents(nl, elemSliceV(0, 1, 2), t) // Degenerate case l = NewList() @@ -312,11 +316,11 @@ func TestLTakeWhile(t *T) { // Test dropping from a Seq func TestDrop(t *T) { // Normal case - intl := []types.Elem{0, 1, 2, 3, 4} + intl := elemSliceV(0, 1, 2, 3, 4) l := NewList(intl...) nl := Drop(3, l) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{3, 4}, t) + assertSeqContents(nl, elemSliceV(3, 4), t) // Edge cases nl = Drop(5, l) @@ -341,24 +345,24 @@ func TestDrop(t *T) { // Test dropping from a Seq until a given condition func TestDropWhile(t *T) { pred := func(el types.Elem) bool { - return el.(int) < 3 + return el.(types.GoType).V.(int) < 3 } // Normal case - intl := []types.Elem{0, 1, 2, 3, 4, 5} + intl := elemSliceV(0, 1, 2, 3, 4, 5) l := NewList(intl...) nl := DropWhile(pred, l) assertSeqContents(l, intl, t) - assertSeqContents(nl, []types.Elem{3, 4, 5}, t) + assertSeqContents(nl, elemSliceV(3, 4, 5), t) // Edge cases - intl = []types.Elem{5, 5, 5} + intl = elemSliceV(5, 5, 5) l = NewList(intl...) nl = DropWhile(pred, l) assertSeqContents(l, intl, t) assertSeqContents(nl, intl, t) - intl = []types.Elem{0, 1, 2} + intl = elemSliceV(0, 1, 2) l = NewList(intl...) nl = DropWhile(pred, l) assertSeqContents(l, intl, t) diff --git a/seq/util.go b/seq/util.go index d482e96..4b8f6de 100644 --- a/seq/util.go +++ b/seq/util.go @@ -19,6 +19,18 @@ func intSlicesEq(a, b []types.Elem) bool { return true } +func elemSliceV(a ...interface{}) []types.Elem { + ret := make([]types.Elem, 0, len(a)) + for i := range a { + if e, ok := a[i].(types.Elem); ok { + ret = append(ret, e) + } else { + ret = append(ret, types.GoType{a[i]}) + } + } + return ret +} + // Asserts that the given Seq is empty (contains no elements) func assertEmpty(s Seq, t *testing.T) { if Size(s) != 0 { @@ -59,7 +71,7 @@ func assertSeqContentsSet(s Seq, ints []types.Elem, t *testing.T) { } func assertSeqContentsHashMap(s Seq, kvs []*KV, t *testing.T) { - m := map[types.Elem]bool{} + m := map[KV]bool{} for i := range kvs { m[*kvs[i]] = true } @@ -76,9 +88,16 @@ func assertSeqContentsHashMap(s Seq, kvs []*KV, t *testing.T) { } // Asserts that v1 is the same as v2 -func assertValue(v1, v2 types.Elem, t *testing.T) { +func assertValue(v1, v2 interface{}, t *testing.T) { + if gv1, ok := v1.(types.GoType); ok { + v1 = gv1.V + } + if gv2, ok := v2.(types.GoType); ok { + v2 = gv2.V + } if v1 != v2 { - t.Fatalf("Value wrong: %v not %v", v1, v2) + t.Logf("Value wrong: %v not %v", v1, v2) + panic("bail") } } @@ -88,3 +107,7 @@ func assertInMap(v1 types.Elem, m map[types.Elem]bool, t *testing.T) { t.Fatalf("Value not in set: %v not in %v", v1, m) } } + +func keyValV(k, v interface{}) *KV { + return KeyVal(types.GoType{k}, types.GoType{v}) +} diff --git a/types/types.go b/types/types.go index b865d17..185e784 100644 --- a/types/types.go +++ b/types/types.go @@ -1,11 +1,38 @@ // This package describes ginger's base types and the interfaces covered by them package types +import ( + "fmt" +) + // Elem is a generic type which can be used as a wrapper type for all ginger // types, both base types and data structures type Elem interface { + + // Returns whether one element is equal to another. Since all ginger values + // are immutable, this must be a deep-equals check. + Equal(Elem) bool } // Number can either be either an Int or a Float type Number interface { + Elem +} + +// Wraps a go type like int, string, or []byte. GoType is a struct whose only +// field is an interface{}, so using a pointer to is not necessary. Just pass +// around the value type. +type GoType struct { + V interface{} +} + +func (g GoType) Equal(e Elem) bool { + if g2, ok := e.(GoType); ok { + return g.V == g2.V + } + return false +} + +func (g GoType) String() string { + return fmt.Sprintf("%v", g.V) }