From a4554494e38447c559eb7afd1891da22fb935953 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 6 Oct 2014 18:29:52 -0400 Subject: [PATCH] add in seq package, borrowed from github.com/mediocregopher/seq --- .gitignore | 2 +- .go.yaml | 2 + seq/hashmap.go | 125 +++++++++++++ seq/hashmap_test.go | 115 ++++++++++++ seq/hashset.go | 414 ++++++++++++++++++++++++++++++++++++++++++++ seq/hashset_test.go | 245 ++++++++++++++++++++++++++ seq/lazy.go | 146 ++++++++++++++++ seq/lazy_test.go | 49 ++++++ seq/list.go | 142 +++++++++++++++ seq/list_test.go | 156 +++++++++++++++++ seq/seq.go | 281 ++++++++++++++++++++++++++++++ seq/seq_test.go | 372 +++++++++++++++++++++++++++++++++++++++ seq/util.go | 90 ++++++++++ types/types.go | 6 +- 14 files changed, 2141 insertions(+), 4 deletions(-) create mode 100644 .go.yaml create mode 100644 seq/hashmap.go create mode 100644 seq/hashmap_test.go create mode 100644 seq/hashset.go create mode 100644 seq/hashset_test.go create mode 100644 seq/lazy.go create mode 100644 seq/lazy_test.go create mode 100644 seq/list.go create mode 100644 seq/list_test.go create mode 100644 seq/seq.go create mode 100644 seq/seq_test.go create mode 100644 seq/util.go diff --git a/.gitignore b/.gitignore index 423e89b..588b9f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -*.gng +.goat diff --git a/.go.yaml b/.go.yaml new file mode 100644 index 0000000..e82fa5a --- /dev/null +++ b/.go.yaml @@ -0,0 +1,2 @@ +--- +path: github.com/mediocregopher/ginger diff --git a/seq/hashmap.go b/seq/hashmap.go new file mode 100644 index 0000000..92af30b --- /dev/null +++ b/seq/hashmap.go @@ -0,0 +1,125 @@ +package seq + +import ( + "fmt" + + "github.com/mediocregopher/ginger/types" +) + +// Hash maps are built on top of hash sets. KeyVal implements Setable, but the +// Hash and Equal methods only apply to the key and ignore the value. + +// Container for a key/value pair, used by HashMap to hold its data +type KV struct { + Key types.Elem + Val types.Elem +} + +func KeyVal(key, val types.Elem) *KV { + return &KV{key, val} +} + +// Implementation of Hash for Setable. Only actually hashes the Key field +func (kv *KV) Hash(i uint32) uint32 { + return hash(kv.Key, i) +} + +// Implementation of Equal for Setable. Only actually compares the key field. If +// 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 equal(kv.Key, v) +} + +// Implementation of String for Stringer +func (kv *KV) String() string { + return fmt.Sprintf("%v -> %v", kv.Key, kv.Val) +} + +// HashMaps are actually built on top of Sets, just with some added convenience +// methods for interacting with them as actual key/val stores +type HashMap struct { + set *Set +} + +// Returns a new HashMap of the given KVs (or possibly just an empty HashMap) +func NewHashMap(kvs ...*KV) *HashMap { + ints := make([]types.Elem, len(kvs)) + for i := range kvs { + ints[i] = kvs[i] + } + return &HashMap{ + set: NewSet(ints...), + } +} + +// Implementation of FirstRest for Seq interface. First return value will +// always be a *KV or nil. Completes in O(log(N)) time. +func (hm *HashMap) FirstRest() (types.Elem, Seq, bool) { + if hm == nil { + return nil, nil, false + } + el, nset, ok := hm.set.FirstRest() + return el, &HashMap{nset.(*Set)}, ok +} + +// 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 +// method. +func (hm *HashMap) Set(key, val types.Elem) (*HashMap, bool) { + if hm == nil { + hm = NewHashMap() + } + + nset, ok := hm.set.SetVal(KeyVal(key, val)) + return &HashMap{nset}, ok +} + +// Returns a new HashMap with the given key removed from it. Also returns +// whether or not the key was already there (true if so, false if not). Has the +// same time complexity as Set's DelVal method. +func (hm *HashMap) Del(key types.Elem) (*HashMap, bool) { + if hm == nil { + hm = NewHashMap() + } + + nset, ok := hm.set.DelVal(KeyVal(key, nil)) + return &HashMap{nset}, ok +} + +// Returns a value for a given key from the HashMap, along with a boolean +// indicating whether or not the value was found. Has the same time complexity +// as Set's GetVal method. +func (hm *HashMap) Get(key types.Elem) (types.Elem, bool) { + if hm == nil { + return nil, false + } else if kv, ok := hm.set.GetVal(KeyVal(key, nil)); ok { + return kv.(*KV).Val, true + } else { + return nil, false + } +} + +// Same as FirstRest, but returns values already casted, which may be convenient +// in some cases. +func (hm *HashMap) FirstRestKV() (*KV, *HashMap, bool) { + if el, nhm, ok := hm.FirstRest(); ok { + return el.(*KV), nhm.(*HashMap), true + } else { + return nil, nil, false + } +} + +// Implementation of String for Stringer interface +func (hm *HashMap) String() string { + return ToString(hm, "{", "}") +} + +// Returns the number of KVs in the HashMap. Has the same complexity as Set's +// Size method. +func (hm *HashMap) Size() uint64 { + return hm.set.Size() +} diff --git a/seq/hashmap_test.go b/seq/hashmap_test.go new file mode 100644 index 0000000..6b0368f --- /dev/null +++ b/seq/hashmap_test.go @@ -0,0 +1,115 @@ +package seq + +import ( + . "testing" + + "github.com/mediocregopher/ginger/types" +) + +func kvints(kvs ...*KV) ([]*KV, []types.Elem) { + ints := make([]types.Elem, len(kvs)) + for i := range kvs { + ints[i] = kvs[i] + } + return kvs, ints +} + +// 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"), + ) + + // Testing creation and Seq interface methods + m := NewHashMap(kvs...) + ms := testSeqNoOrderGen(t, m, ints) + + // ms should be empty at this point + assertEmpty(ms, t) +} + +// Test getting values from a HashMap +func TestHashMapGet(t *T) { + kvs := []*KV{ + KeyVal(1, "one"), + KeyVal(2, "two"), + } + + // Degenerate case + m := NewHashMap() + assertEmpty(m, t) + v, ok := m.Get(1) + assertValue(v, nil, t) + assertValue(ok, false, t) + + m = NewHashMap(kvs...) + v, ok = m.Get(1) + assertSeqContentsHashMap(m, kvs, t) + assertValue(v, "one", t) + assertValue(ok, true, t) + + v, ok = m.Get(3) + assertSeqContentsHashMap(m, kvs, t) + assertValue(v, nil, t) + assertValue(ok, false, t) +} + +// Test setting values on a HashMap +func TestHashMapSet(t *T) { + + // Set on empty + m := NewHashMap() + m1, ok := m.Set(1, "one") + assertEmpty(m, t) + assertSeqContentsHashMap(m1, []*KV{KeyVal(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) + 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) + assertValue(ok, true, t) + +} + +// Test deleting keys from sets +func TestHashMapDel(t *T) { + + kvs := []*KV{ + KeyVal(1, "one"), + KeyVal(2, "two"), + KeyVal(3, "three"), + } + kvs1 := []*KV{ + KeyVal(2, "two"), + KeyVal(3, "three"), + } + + // Degenerate case + m := NewHashMap() + m1, ok := m.Del(1) + assertEmpty(m, t) + assertEmpty(m1, t) + assertValue(ok, false, t) + + // Delete actual key + m = NewHashMap(kvs...) + m1, ok = m.Del(1) + assertSeqContentsHashMap(m, kvs, t) + assertSeqContentsHashMap(m1, kvs1, t) + assertValue(ok, true, t) + + // Delete it again! + m2, ok := m1.Del(1) + assertSeqContentsHashMap(m1, kvs1, t) + assertSeqContentsHashMap(m2, kvs1, t) + assertValue(ok, false, t) + +} diff --git a/seq/hashset.go b/seq/hashset.go new file mode 100644 index 0000000..5923d3e --- /dev/null +++ b/seq/hashset.go @@ -0,0 +1,414 @@ +package seq + +import ( + "fmt" + "hash/crc32" + "reflect" + + "github.com/mediocregopher/ginger/types" +) + +// This is an implementation of a persistent tree, which will then be used as +// the basis for vectors, hash maps, and hash sets. + +type Setable interface { + + // Returns an integer for the value. For two equivalent values (as defined + // by ==) Hash(i) should always return the same number. For multiple values + // of i, Hash should return different values if possible. + Hash(uint32) uint32 + + // Given an arbitrary value found in a Set, returns whether or not the two + // are equal + Equal(types.Elem) bool +} + +// Returns an arbitrary integer for the given value/iteration tuple +func hash(v types.Elem, i uint32) uint32 { + switch vt := v.(type) { + + 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 string: + return crc32.ChecksumIEEE([]byte(vt)) % 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 + } + return false + } else { + return v1 == v2 + } +} + +// The number of children each node in Set (implemented as a hash tree) can have +const ARITY = 32 + +// A Set is an implementation of Seq in the form of a persistant hash-tree. All +// public operations on it return a new, immutable form of the modified +// variable, leaving the old one intact. Immutability is implemented through +// node sharing, so operations aren't actually copying the entire hash-tree +// everytime, only the nodes which change, making the implementation very +// efficient compared to just copying. +// +// Items in sets need to be hashable and comparable. This means they either need +// to be some real numeric type (int, float32, etc...), string, []byte, or +// implement the Setable interface. +type Set struct { + + // The value being held + val types.Elem + + // Whether or not the held value has been set yet. Needed because the value + // could be nil + full bool + + // Slice of kids of this node. Could be an empty slice + kids []*Set + + // Number of values in this Set. + size uint64 +} + +// Returns a new Set of the given elements (or no elements, for an empty set) +func NewSet(vals ...types.Elem) *Set { + if len(vals) == 0 { + return nil + } + set := new(Set) + for i := range vals { + set.setValDirty(vals[i], 0) + } + set.size = uint64(len(vals)) + return set +} + +// Methods marked as "dirty" operate on the node in place, and potentially +// change it or its children. + +// Dirty. Tries to set the val on this Set node, or initialize the kids slice if +// it can't. Returns whether or not the value was set and whether or not it was +// already set. +func (set *Set) shallowTrySetOrInit(val types.Elem) (bool, bool) { + if !set.full { + set.val = val + set.full = true + return true, false + } else if equal(set.val, val) { + set.val = val + set.full = true + return true, true + } else if set.kids == nil { + set.kids = make([]*Set, ARITY) + } + return false, false +} + +// dirty (obviously). Sets a value on this node in place. Only used during +// initialization. +func (set *Set) setValDirty(val types.Elem, i uint32) { + if ok, _ := set.shallowTrySetOrInit(val); ok { + return + } + + h := hash(val, i) + if kid := set.kids[h]; kid != nil { + kid.setValDirty(val, i+1) + } else { + set.kids[h] = NewSet(val) + } +} + +// Returns a copy of this set node, including allocating and copying the kids +// slice. +func (set *Set) clone() *Set { + var newkids []*Set + if set.kids != nil { + newkids = make([]*Set, ARITY) + copy(newkids, set.kids) + } + cs := &Set{ + val: set.val, + full: set.full, + kids: newkids, + size: set.size, + } + return cs +} + +// The actual implementation of SetVal, because we need to pass i down the stack +func (set *Set) internalSetVal(val types.Elem, i uint32) (*Set, bool) { + if set == nil { + return NewSet(val), true + } + cset := set.clone() + if ok, prev := cset.shallowTrySetOrInit(val); ok { + return cset, !prev + } + + h := hash(val, i) + newkid, ok := cset.kids[h].internalSetVal(val, i+1) + cset.kids[h] = newkid + return cset, ok +} + +// Returns a new Set with the given value added to it. Also returns whether or +// not this is the first time setting this value (false if it was already there +// and was overwritten). Completes in O(log(N)) time. +func (set *Set) SetVal(val types.Elem) (*Set, bool) { + nset, ok := set.internalSetVal(val, 0) + if ok { + nset.size++ + } + return nset, ok +} + +// The actual implementation of DelVal, because we need to pass i down the stack +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) { + cset := set.clone() + cset.val = nil + cset.full = false + return cset, true + } else if set.kids == nil { + return set, false + } + + h := hash(val, i) + if newkid, ok := set.kids[h].internalDelVal(val, i+1); ok { + cset := set.clone() + cset.kids[h] = newkid + return cset, true + } + return set, false +} + +// Returns a new Set with the given value removed from it and whether or not the +// value was actually removed. Completes in O(log(N)) time. +func (set *Set) DelVal(val types.Elem) (*Set, bool) { + nset, ok := set.internalDelVal(val, 0) + if ok && nset != nil { + nset.size-- + } + return nset, ok +} + +// The actual implementation of GetVal, because we need to pass i down the stack +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) { + return set.val, true + } else if set.kids == nil { + return nil, false + } + + h := hash(val, i) + return set.kids[h].internalGetVal(val, i+1) +} + +// Returns a value from the Set, along with a boolean indiciating whether or +// not the value was found. Completes in O(log(N)) time. +func (set *Set) GetVal(val types.Elem) (types.Elem, bool) { + return set.internalGetVal(val, 0) +} + +// Actual implementation of FirstRest. Because we need it to return a *Set +// instead of Seq for one case. +func (set *Set) internalFirstRest() (types.Elem, *Set, bool) { + if set == nil { + return nil, nil, false + } + + if set.kids != nil { + var el types.Elem + var rest *Set + var ok bool + for i := range set.kids { + if el, rest, ok = set.kids[i].internalFirstRest(); ok { + cset := set.clone() + cset.kids[i] = rest + return el, cset, true + } + } + } + + // We're not nil, but we don't have a value and no kids had values. We might + // as well be nil. + if !set.full { + return nil, nil, false + } + + return set.val, nil, true +} + +// Implementation of FirstRest for Seq interface. Completes in O(log(N)) time. +func (set *Set) FirstRest() (types.Elem, Seq, bool) { + el, restSet, ok := set.internalFirstRest() + if ok && restSet != nil { + restSet.size-- + } + return el, Seq(restSet), ok +} + +// Implementation of String for Stringer interface +func (set *Set) String() string { + return ToString(set, "#{", "}#") +} + +// Returns the number of elements in the Set. Completes in O(1) time. +func (set *Set) Size() uint64 { + if set == nil { + return 0 + } + return set.size +} + +// Returns a Set with all of the elements of the original Set along with +// everything in the given Seq. If an element is present in both the Set and the +// Seq, the element in the Seq overwrites. Completes in O(M*log(N)), with M +// being the number of elements in the Seq and N the number of elements in the +// Set +func (set *Set) Union(s Seq) *Set { + if set == nil { + return ToSet(s) + } + + cset := set.clone() + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); !ok { + return cset + } else if cset, ok = cset.SetVal(el); ok { + cset.size++ + } + } +} + +// Returns a Set with all of the elements in Seq that are also in Set. Completes +// in O(M*log(N)), with M being the number of elements in the Seq and N the +// number of elements in the Set +func (set *Set) Intersection(s Seq) *Set { + if set == nil { + return nil + } + + iset := NewSet() + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); !ok { + return iset + } else if _, ok = set.GetVal(el); ok { + iset, _ = iset.SetVal(el) + } + } +} + +// Returns a Set of all elements in the original Set that aren't in the Seq. +// Completes in O(M*log(N)), with M being the number of elements in the Seq and +// N the number of elements in the Set +func (set *Set) Difference(s Seq) *Set { + if set == nil { + return nil + } + + cset := set.clone() + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); !ok { + return cset + } else { + cset, _ = cset.DelVal(el) + } + } +} + +// Returns a Set of all elements that are either in the original Set or the +// given Seq, but not in both. Completes in O(M*log(N)), with M being the number +// of elements in the Seq and N the number of elements in the Set. +func (set *Set) SymDifference(s Seq) *Set { + if set == nil { + return ToSet(s) + } + + cset := set.clone() + var cset2 *Set + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); !ok { + return cset + } else if cset2, ok = cset.DelVal(el); ok { + cset = cset2 + } else { + cset, _ = cset.SetVal(el) + } + } +} + +// Returns the elements in the Seq as a set. In general this completes in +// O(N*log(N)) time (I think...). If the given Seq is already a Set it will +// complete in O(1) time. If it is a HashMap it will complete in O(1) time, and +// the resultant Set will be comprised of all KVs +func ToSet(s Seq) *Set { + if set, ok := s.(*Set); ok { + return set + } else if hm, ok := s.(*HashMap); ok { + return hm.set + } + vals := ToSlice(s) + return NewSet(vals...) +} diff --git a/seq/hashset_test.go b/seq/hashset_test.go new file mode 100644 index 0000000..75872f8 --- /dev/null +++ b/seq/hashset_test.go @@ -0,0 +1,245 @@ +package seq + +import ( + . "testing" + + "github.com/mediocregopher/ginger/types" +) + +// Test creating a Set and calling the Seq interface methods on it +func TestSetSeq(t *T) { + ints := []types.Elem{1, "a", 5.0} + + // Testing creation and Seq interface methods + s := NewSet(ints...) + ss := testSeqNoOrderGen(t, s, ints) + + // ss should be empty at this point + s = ToSet(ss) + var nilpointer *Set + assertEmpty(s, t) + assertValue(s, nilpointer, t) + assertValue(len(ToSlice(s)), 0, 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} + + // Degenerate case + s := NewSet() + assertEmpty(s, t) + s, ok := s.SetVal(0) + assertSeqContentsSet(s, []types.Elem{0}, t) + assertValue(ok, true, t) + + s = NewSet(ints...) + s1, ok := s.SetVal(5) + assertSeqContentsSet(s, ints, t) + assertSeqContentsSet(s1, ints1, t) + assertValue(ok, true, t) + + s2, ok := s1.SetVal(5) + assertSeqContentsSet(s1, ints1, t) + assertSeqContentsSet(s2, ints1, t) + assertValue(ok, false, 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} + + // Degenerate case + s := NewSet() + assertEmpty(s, t) + s, ok := s.DelVal(0) + assertEmpty(s, t) + assertValue(ok, false, t) + + s = NewSet(ints...) + s1, ok := s.DelVal(4) + assertSeqContentsSet(s, ints, t) + assertSeqContentsSet(s1, ints1, t) + assertValue(ok, true, t) + + s1, ok = s1.DelVal(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) + assertSeqContentsSet(s, ints, t) + assertSeqContentsSet(s2, ints2, t) + assertValue(ok, true, t) + + s2, ok = s2.DelVal(0) + assertSeqContentsSet(s2, ints2, t) + assertValue(ok, false, t) + + s3, ok := s2.SetVal(5) + assertSeqContentsSet(s2, ints2, t) + assertSeqContentsSet(s3, ints3, t) + assertValue(ok, true, t) +} + +// Test getting values from a Set +func GetVal(t *T) { + //Degenerate case + s := NewSet() + v, ok := s.GetVal(1) + assertValue(v, nil, t) + assertValue(ok, false, t) + + s = NewSet(0, 1, 2, 3, 4) + v, ok = s.GetVal(1) + assertValue(v, 1, t) + assertValue(ok, true, t) + + // After delete + s, _ = s.DelVal(1) + v, ok = s.GetVal(1) + assertValue(v, nil, t) + assertValue(ok, false, t) + + // After set + s, _ = s.SetVal(1) + v, ok = s.GetVal(1) + assertValue(v, 1, t) + assertValue(ok, true, t) + + // After delete root node + s, _ = s.DelVal(0) + v, ok = s.GetVal(0) + assertValue(v, nil, t) + assertValue(ok, false, t) + + // After set root node + s, _ = s.SetVal(5) + v, ok = s.GetVal(5) + assertValue(v, 5, t) + assertValue(ok, true, t) +} + +// Test that Size functions properly for all cases +func TestSetSize(t *T) { + // Degenerate case + s := NewSet() + assertValue(s.Size(), uint64(0), t) + + // Initialization case + s = NewSet(0, 1, 2) + assertValue(s.Size(), uint64(3), t) + + // Setting (both value not in and a value already in) + s, _ = s.SetVal(3) + assertValue(s.Size(), uint64(4), t) + s, _ = s.SetVal(3) + assertValue(s.Size(), uint64(4), t) + + // Deleting (both value already in and a value not in) + s, _ = s.DelVal(3) + assertValue(s.Size(), uint64(3), t) + s, _ = s.DelVal(3) + assertValue(s.Size(), uint64(3), t) + + // Deleting and setting the root node + s, _ = s.DelVal(0) + assertValue(s.Size(), uint64(2), t) + s, _ = s.SetVal(5) + assertValue(s.Size(), uint64(3), t) + +} + +// Test that Union functions properly +func TestUnion(t *T) { + // Degenerate case + empty := NewSet() + assertEmpty(empty.Union(empty), t) + + ints1 := []types.Elem{0, 1, 2} + ints2 := []types.Elem{3, 4, 5} + intsu := append(ints1, ints2...) + s1 := NewSet(ints1...) + s2 := NewSet(ints2...) + + assertSeqContentsSet(s1.Union(empty), ints1, t) + assertSeqContentsSet(empty.Union(s1), ints1, t) + + su := s1.Union(s2) + assertSeqContentsSet(s1, ints1, t) + assertSeqContentsSet(s2, ints2, t) + assertSeqContentsSet(su, intsu, t) +} + +// Test that Intersection functions properly +func TestIntersection(t *T) { + // Degenerate case + 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} + s1 := NewSet(ints1...) + s2 := NewSet(ints2...) + s3 := NewSet(ints3...) + + assertEmpty(s1.Intersection(empty), t) + assertEmpty(empty.Intersection(s1), t) + + si := s1.Intersection(s2) + assertEmpty(s1.Intersection(s3), t) + assertSeqContentsSet(s1, ints1, t) + assertSeqContentsSet(s2, ints2, t) + assertSeqContentsSet(s3, ints3, t) + assertSeqContentsSet(si, intsi, t) +} + +// Test that Difference functions properly +func TestDifference(t *T) { + // Degenerate case + 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} + s1 := NewSet(ints1...) + s2 := NewSet(ints2...) + + assertSeqContentsSet(s1.Difference(empty), ints1, t) + assertEmpty(empty.Difference(s1), t) + + sd := s1.Difference(s2) + assertSeqContentsSet(s1, ints1, t) + assertSeqContentsSet(s2, ints2, t) + assertSeqContentsSet(sd, intsd, t) +} + +// Test that SymDifference functions properly +func TestSymDifference(t *T) { + // Degenerate case + 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} + s1 := NewSet(ints1...) + s2 := NewSet(ints2...) + + assertSeqContentsSet(s1.SymDifference(empty), ints1, t) + assertSeqContentsSet(empty.SymDifference(s1), ints1, t) + + sd := s1.SymDifference(s2) + assertSeqContentsSet(s1, ints1, t) + assertSeqContentsSet(s2, ints2, t) + assertSeqContentsSet(sd, intsd, t) +} diff --git a/seq/lazy.go b/seq/lazy.go new file mode 100644 index 0000000..97aa1ec --- /dev/null +++ b/seq/lazy.go @@ -0,0 +1,146 @@ +package seq + +import ( + "github.com/mediocregopher/ginger/types" +) + +// A Lazy is an implementation of a Seq which only actually evaluates its +// contents as those contents become needed. Lazys can be chained together, so +// if you have three steps in a pipeline there aren't two intermediate Seqs +// created, only the final resulting one. Lazys are also thread-safe, so +// multiple routines can interact with the same Lazy pointer at the same time +// but the contents will only be evalutated once. +type Lazy struct { + this types.Elem + next *Lazy + ok bool + ch chan struct{} +} + +// Given a Thunk, returns a Lazy around that Thunk. +func NewLazy(t Thunk) *Lazy { + l := &Lazy{ch: make(chan struct{})} + go func() { + l.ch <- struct{}{} + el, next, ok := t() + l.this = el + l.next = NewLazy(next) + l.ok = ok + close(l.ch) + }() + return l +} + +// Implementation of FirstRest for Seq interface. Completes in O(1) time. +func (l *Lazy) FirstRest() (types.Elem, Seq, bool) { + if l == nil { + return nil, l, false + } + + // Reading from the channel tells the Lazy to populate the data and prepare + // the next item in the seq, it closes the channel when it's done that. + if _, ok := <-l.ch; ok { + <-l.ch + } + + if l.ok { + return l.this, l.next, true + } else { + return nil, nil, false + } +} + +// Implementation of String for Stringer +func (l *Lazy) String() string { + return ToString(l, "<<", ">>") +} + +// Thunks are the building blocks a Lazy. A Thunk returns an element, another +// Thunk, and a boolean representing if the call yielded any results or if it +// was actually empty (true indicates it yielded results). +type Thunk func() (types.Elem, Thunk, bool) + +func mapThunk(fn func(types.Elem) types.Elem, s Seq) Thunk { + return func() (types.Elem, Thunk, bool) { + el, ns, ok := s.FirstRest() + if !ok { + return nil, nil, false + } + + return fn(el), mapThunk(fn, ns), true + } +} + +// Lazy implementation of Map +func LMap(fn func(types.Elem) types.Elem, s Seq) Seq { + return NewLazy(mapThunk(fn, s)) +} + +func filterThunk(fn func(types.Elem) bool, s Seq) Thunk { + return func() (types.Elem, Thunk, bool) { + for { + el, ns, ok := s.FirstRest() + if !ok { + return nil, nil, false + } + + if keep := fn(el); keep { + return el, filterThunk(fn, ns), true + } else { + s = ns + } + } + } +} + +// Lazy implementation of Filter +func LFilter(fn func(types.Elem) bool, s Seq) Seq { + return NewLazy(filterThunk(fn, s)) +} + +func takeThunk(n uint64, s Seq) Thunk { + return func() (types.Elem, Thunk, bool) { + el, ns, ok := s.FirstRest() + if !ok || n == 0 { + return nil, nil, false + } + return el, takeThunk(n-1, ns), true + } +} + +// Lazy implementation of Take +func LTake(n uint64, s Seq) Seq { + return NewLazy(takeThunk(n, s)) +} + +func takeWhileThunk(fn func(types.Elem) bool, s Seq) Thunk { + return func() (types.Elem, Thunk, bool) { + el, ns, ok := s.FirstRest() + if !ok || !fn(el) { + return nil, nil, false + } + return el, takeWhileThunk(fn, ns), true + } +} + +// Lazy implementation of TakeWhile +func LTakeWhile(fn func(types.Elem) bool, s Seq) Seq { + return NewLazy(takeWhileThunk(fn, s)) +} + +func toLazyThunk(s Seq) Thunk { + return func() (types.Elem, Thunk, bool) { + el, ns, ok := s.FirstRest() + if !ok { + return nil, nil, false + } + return el, toLazyThunk(ns), true + } +} + +// Returns the Seq as a Lazy. Pointless for linked-lists, but possibly useful +// for other implementations where FirstRest might be costly and the same Seq +// needs to be iterated over many times. +func ToLazy(s Seq) *Lazy { + return NewLazy(toLazyThunk(s)) +} diff --git a/seq/lazy_test.go b/seq/lazy_test.go new file mode 100644 index 0000000..98cc1a9 --- /dev/null +++ b/seq/lazy_test.go @@ -0,0 +1,49 @@ +package seq + +import ( + . "testing" + "time" + + "github.com/mediocregopher/ginger/types" +) + +// Test lazy operation and thread-safety +func TestLazyBasic(t *T) { + ch := make(chan int) + mapfn := func(el types.Elem) types.Elem { + i := el.(int) + ch <- i + return i + } + + intl := []types.Elem{0, 1, 2, 3, 4} + l := NewList(intl...) + ml := LMap(mapfn, l) + + for i := 0; i < 10; i++ { + go func() { + mlintl := ToSlice(ml) + if !intSlicesEq(mlintl, intl) { + panic("contents not right") + } + }() + } + + for _, el := range intl { + select { + case elch := <-ch: + assertValue(el, elch, t) + case <-time.After(1 * time.Millisecond): + t.Fatalf("Took too long reading result") + } + } + close(ch) +} + +// Test that arbitrary Seqs can turn into Lazy +func TestToLazy(t *T) { + intl := []types.Elem{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 new file mode 100644 index 0000000..86c71db --- /dev/null +++ b/seq/list.go @@ -0,0 +1,142 @@ +package seq + +import ( + "github.com/mediocregopher/ginger/types" +) + +// A List is an implementation of Seq in the form of a single-linked-list, and +// is used as the underlying structure for Seqs for most methods that return a +// Seq. It is probably the most efficient and simplest of the implementations. +// Even though, conceptually, all Seq operations return a new Seq, the old Seq +// can actually share nodes with the new Seq (if both are Lists), thereby saving +// memory and copies. +type List struct { + el types.Elem + next *List +} + +// Returns a new List comprised of the given elements (or no elements, for an +// empty list) +func NewList(els ...types.Elem) *List { + elsl := len(els) + if elsl == 0 { + return nil + } + + var cur *List + for i := 0; i < elsl; i++ { + cur = &List{els[elsl-i-1], cur} + } + return cur +} + +// Implementation of FirstRest for Seq interface. Completes in O(1) time. +func (l *List) FirstRest() (types.Elem, Seq, bool) { + if l == nil { + return nil, l, false + } else { + return l.el, l.next, true + } +} + +// Implementation of String for Stringer interface. +func (l *List) String() string { + return ToString(l, "(", ")") +} + +// Prepends the given element to the front of the list, returning a copy of the +// new list. Completes in O(1) time. +func (l *List) Prepend(el types.Elem) *List { + return &List{el, l} +} + +// Prepends the argument Seq to the beginning of the callee List, returning a +// copy of the new List. Completes in O(N) time, N being the length of the +// argument Seq +func (l *List) PrependSeq(s Seq) *List { + var first, cur, prev *List + var el types.Elem + var ok bool + for { + el, s, ok = s.FirstRest() + if !ok { + break + } + cur = &List{el, nil} + if first == nil { + first = cur + } + if prev != nil { + prev.next = cur + } + prev = cur + } + + // prev will be nil if s is empty + if prev == nil { + return l + } + + prev.next = l + return first +} + +// Appends the given element to the end of the List, returning a copy of the new +// List. While most methods on List don't actually copy much data, this one +// copies the entire list. Completes in O(N) time. +func (l *List) Append(el types.Elem) *List { + var first, cur, prev *List + for l != nil { + cur = &List{l.el, nil} + if first == nil { + first = cur + } + if prev != nil { + prev.next = cur + } + prev = cur + l = l.next + } + final := &List{el, nil} + if prev == nil { + return final + } + prev.next = final + return first +} + +// Returns the nth index element (starting at 0), with bool being false if i is +// out of bounds. Completes in O(N) time. +func (l *List) Nth(n uint64) (types.Elem, bool) { + var el types.Elem + var ok bool + s := Seq(l) + for i := uint64(0); ; i++ { + el, s, ok = s.FirstRest() + if !ok { + return nil, false + } else if i == n { + return el, true + } + } +} + +// Returns the elements in the Seq as a List. Has similar properties as +// ToSlice. In general this completes in O(N) time. If the given Seq is already +// a List it will complete in O(1) time. +func ToList(s Seq) *List { + var ok bool + var l *List + if l, ok = s.(*List); ok { + return l + } + + var el types.Elem + for ret := NewList(); ; { + if el, s, ok = s.FirstRest(); ok { + ret = ret.Prepend(el) + } else { + return Reverse(ret).(*List) + } + } +} diff --git a/seq/list_test.go b/seq/list_test.go new file mode 100644 index 0000000..05796cf --- /dev/null +++ b/seq/list_test.go @@ -0,0 +1,156 @@ +package seq + +import ( + . "testing" + + "github.com/mediocregopher/ginger/types" +) + +// Asserts that the given list is properly formed and has all of its size fields +// filled in correctly +func assertSaneList(l *List, t *T) { + if Size(l) == 0 { + var nilpointer *List + assertValue(l, nilpointer, t) + return + } + + size := Size(l) + assertValue(Size(l.next), size-1, t) + assertSaneList(l.next, t) +} + +// Test creating a list and calling the Seq interface methods on it +func TestListSeq(t *T) { + ints := []types.Elem{1, "a", 5.0} + + // Testing creation and Seq interface methods + l := NewList(ints...) + sl := testSeqGen(t, l, ints) + + // sl should be empty at this point + l = ToList(sl) + var nilpointer *List + assertEmpty(l, t) + assertValue(l, nilpointer, t) + assertValue(len(ToSlice(l)), 0, t) + + // Testing creation of empty List. + emptyl := NewList() + assertValue(emptyl, nilpointer, t) +} + +// Test the string representation of a List +func TestStringSeq(t *T) { + l := NewList(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)) + 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} + l := NewList(intl...) + nl := l.Prepend(4) + assertSaneList(l, t) + assertSaneList(nl, t) + assertSeqContents(l, intl, t) + assertSeqContents(nl, []types.Elem{4, 3, 2, 1, 0}, t) + + // Degenerate case + l = NewList() + nl = l.Prepend(0) + assertEmpty(l, t) + assertSaneList(nl, t) + assertSeqContents(nl, []types.Elem{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} + l1 := NewList(intl1...) + l2 := NewList(intl2...) + nl := l1.PrependSeq(l2) + assertSaneList(l1, t) + assertSaneList(l2, t) + assertSaneList(nl, t) + assertSeqContents(l1, intl1, t) + assertSeqContents(l2, intl2, t) + assertSeqContents(nl, []types.Elem{0, 1, 2, 3, 4}, t) + + // Degenerate cases + blank1 := NewList() + blank2 := NewList() + nl = blank1.PrependSeq(blank2) + assertEmpty(blank1, t) + assertEmpty(blank2, t) + assertEmpty(nl, t) + + nl = blank1.PrependSeq(l1) + assertEmpty(blank1, t) + assertSaneList(nl, t) + assertSeqContents(nl, intl1, t) + + nl = l1.PrependSeq(blank1) + assertEmpty(blank1, t) + assertSaneList(nl, t) + assertSeqContents(nl, intl1, t) +} + +// Test appending to the end of a List +func TestAppend(t *T) { + // Normal case + intl := []types.Elem{3, 2, 1} + l := NewList(intl...) + nl := l.Append(0) + assertSaneList(l, t) + assertSaneList(nl, t) + assertSeqContents(l, intl, t) + assertSeqContents(nl, []types.Elem{3, 2, 1, 0}, t) + + // Edge case (algorithm gets weird here) + l = NewList(1) + nl = l.Append(0) + assertSaneList(l, t) + assertSaneList(nl, t) + assertSeqContents(l, []types.Elem{1}, t) + assertSeqContents(nl, []types.Elem{1, 0}, t) + + // Degenerate case + l = NewList() + nl = l.Append(0) + assertEmpty(l, t) + assertSaneList(nl, t) + assertSeqContents(nl, []types.Elem{0}, t) +} + +// Test retrieving items from a List +func TestNth(t *T) { + // Normal case, in bounds + intl := []types.Elem{0, 2, 4, 6, 8} + l := NewList(intl...) + r, ok := l.Nth(3) + assertSaneList(l, t) + assertSeqContents(l, intl, t) + assertValue(r, 6, t) + assertValue(ok, true, t) + + // Normal case, out of bounds + r, ok = l.Nth(8) + assertSaneList(l, t) + assertSeqContents(l, intl, t) + assertValue(r, nil, t) + assertValue(ok, false, t) + + // Degenerate case + l = NewList() + r, ok = l.Nth(0) + assertEmpty(l, t) + assertValue(r, nil, t) + assertValue(ok, false, t) +} diff --git a/seq/seq.go b/seq/seq.go new file mode 100644 index 0000000..562bd1d --- /dev/null +++ b/seq/seq.go @@ -0,0 +1,281 @@ +// This package describes ginger datastructures, and many of the operations +// which can be used on those data structures +package seq + +import ( + "bytes" + "fmt" + + "github.com/mediocregopher/ginger/types" +) + +// The general interface which most operations will actually operate on. Acts as +// an interface onto any data structure +type Seq interface { + + // Returns the "first" element in the data structure as well as a Seq + // containing a copy of the rest of the elements in the data structure. The + // "first" element can be random for structures which don't have a concept + // of order (like Set). Calling FirstRest on an empty Seq (Size() == 0) will + // return "first" as nil, the same empty Seq , and false. The third return + // value is true in all other cases. + FirstRest() (types.Elem, Seq, bool) +} + +// Returns the number of elements contained in the data structure. In general +// this completes in O(N) time, except for Set and HashMap for which it +// completes in O(1) +func Size(s Seq) uint64 { + switch st := s.(type) { + case *Set: + return st.Size() + case *HashMap: + return st.Size() + default: + } + + var ok bool + for i := uint64(0); ; { + if _, s, ok = s.FirstRest(); ok { + i++ + } else { + return i + } + } +} + +// Returns the elements in the Seq as a slice. If the underlying Seq has any +// implicit order to it that order will be kept. An empty Seq will return an +// empty slice; nil is never returned. In general this completes in O(N) time. +func ToSlice(s Seq) []types.Elem { + var el types.Elem + var ok bool + for ret := make([]types.Elem, 0, 8); ; { + if el, s, ok = s.FirstRest(); ok { + ret = append(ret, el) + } else { + return ret + } + } +} + +// Turns a Seq into a string, with each element separated by a space and with a +// dstart and dend wrapping the whole thing +func ToString(s Seq, dstart, dend string) string { + buf := bytes.NewBufferString(dstart) + buf.WriteString(" ") + var el types.Elem + var strel fmt.Stringer + var rest Seq + var ok bool + for { + if el, rest, ok = s.FirstRest(); ok { + if strel, ok = el.(fmt.Stringer); ok { + buf.WriteString(strel.String()) + } else { + buf.WriteString(fmt.Sprintf("%v", el)) + } + buf.WriteString(" ") + s = rest + } else { + break + } + } + buf.WriteString(dend) + return buf.String() +} + +// Returns a reversed copy of the List. Completes in O(N) time. +func Reverse(s Seq) Seq { + l := NewList() + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); ok { + l = l.Prepend(el) + } else { + return l + } + } +} + +// Returns a Seq consisting of the result of applying fn to each element in the +// given Seq. Completes in O(N) time. +func Map(fn func(types.Elem) types.Elem, s Seq) Seq { + l := NewList() + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); ok { + l = l.Prepend(fn(el)) + } else { + break + } + } + return Reverse(l) +} + +// A function used in a reduce. The first argument is the accumulator, the +// second is an element from the Seq being reduced over. The ReduceFn returns +// the accumulator to be used in the next iteration, wherein that new +// accumulator will be called alongside the next element in the Seq. ReduceFn +// also returns a boolean representing whether or not the reduction should stop +// at this step. If true, the reductions will stop and any remaining elements in +// the Seq will be ignored. +type ReduceFn func(acc, el types.Elem) (types.Elem, bool) + +// Reduces over the given Seq using ReduceFn, with acc as the first accumulator +// value in the reduce. See ReduceFn for more details on how it works. The +// return value is the result of the reduction. Completes in O(N) time. +func Reduce(fn ReduceFn, acc types.Elem, s Seq) types.Elem { + var el types.Elem + var ok, stop bool + for { + if el, s, ok = s.FirstRest(); ok { + acc, stop = fn(acc, el) + if stop { + break + } + } else { + break + } + } + return acc +} + +// Returns the first element in Seq for which fn returns true, or nil. The +// returned boolean indicates whether or not a matching element was found. +// Completes in O(N) time. +func Any(fn func(el types.Elem) bool, s Seq) (types.Elem, bool) { + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); ok { + if fn(el) { + return el, true + } + } else { + return nil, false + } + } +} + +// Returns true if fn returns true for all elements in the Seq. Completes in +// O(N) time. +func All(fn func(types.Elem) bool, s Seq) bool { + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); ok { + if !fn(el) { + return false + } + } else { + return true + } + } +} + +// Returns a Seq containing all elements in the given Seq for which fn returned +// true. Completes in O(N) time. +func Filter(fn func(el types.Elem) bool, s Seq) Seq { + l := NewList() + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); ok { + if fn(el) { + l = l.Prepend(el) + } + } else { + return Reverse(l) + } + } +} + +// Flattens the given Seq into a single, one-dimensional Seq. This method only +// flattens Seqs found in the top level of the given Seq, it does not recurse +// down to multiple layers. Completes in O(N*M) time, where N is the number of +// elements in the Seq and M is how large the Seqs in those elements actually +// are. +func Flatten(s Seq) Seq { + l := NewList() + var el types.Elem + var ok bool + for { + if el, s, ok = s.FirstRest(); ok { + if els, ok := el.(Seq); ok { + l = l.PrependSeq(Reverse(els)) + } else { + l = l.Prepend(el) + } + } else { + return Reverse(l) + } + } +} + +// Returns a Seq containing the first n elements in the given Seq. If n is +// greater than the length of the given Seq then the whole Seq is returned. +// Completes in O(N) time. +func Take(n uint64, s Seq) Seq { + l := NewList() + var el types.Elem + var ok bool + for i := uint64(0); i < n; i++ { + el, s, ok = s.FirstRest() + if !ok { + break + } + l = l.Prepend(el) + } + return Reverse(l) +} + +// Goes through each item in the given Seq until an element returns false from +// pred. Returns a new Seq containing these truthful elements. Completes in O(N) +// time. +func TakeWhile(pred func(types.Elem) bool, s Seq) Seq { + l := NewList() + var el types.Elem + var ok bool + for { + el, s, ok = s.FirstRest() + if !ok || !pred(el) { + break + } + l = l.Prepend(el) + } + return Reverse(l) +} + +// Returns a Seq the is the previous Seq without the first n elements. If n is +// greater than the length of the Seq, returns an empty Seq. Completes in O(N) +// time. +func Drop(n uint64, s Seq) Seq { + var ok bool + for i := uint64(0); i < n; i++ { + _, s, ok = s.FirstRest() + if !ok { + break + } + } + return s +} + +// Drops elements from the given Seq until pred returns false for an element. +// Returns a Seq of the remaining elements (including the one which returned +// false). Completes in O(N) time. +func DropWhile(pred func(types.Elem) bool, s Seq) Seq { + var el types.Elem + var curs Seq + var ok bool + for { + el, curs, ok = s.FirstRest() + if !ok || !pred(el) { + break + } + s = curs + } + return s +} diff --git a/seq/seq_test.go b/seq/seq_test.go new file mode 100644 index 0000000..991e8c9 --- /dev/null +++ b/seq/seq_test.go @@ -0,0 +1,372 @@ +package seq + +import ( + . "testing" + + "github.com/mediocregopher/ginger/types" +) + +// Tests the FirstRest, Size, and ToSlice methods of a Seq +func testSeqGen(t *T, s Seq, ints []types.Elem) Seq { + intsl := uint64(len(ints)) + for i := range ints { + assertSaneList(ToList(s), t) + assertValue(Size(s), intsl-uint64(i), t) + assertSeqContents(s, ints[i:], t) + + first, rest, ok := s.FirstRest() + assertValue(ok, true, t) + assertValue(first, ints[i], t) + + s = rest + } + return s +} + +// Tests the FirstRest, Size, and ToSlice methods of an unordered Seq +func testSeqNoOrderGen(t *T, s Seq, ints []types.Elem) Seq { + intsl := uint64(len(ints)) + + m := map[types.Elem]bool{} + for i := range ints { + m[ints[i]] = true + } + + for i := range ints { + assertSaneList(ToList(s), t) + assertValue(Size(s), intsl-uint64(i), t) + assertSeqContentsNoOrderMap(s, m, t) + + first, rest, ok := s.FirstRest() + assertValue(ok, true, t) + assertInMap(first, m, t) + + delete(m, first) + s = rest + } + return s +} + +// Test reversing a Seq +func TestReverse(t *T) { + // Normal case + intl := []types.Elem{3, 2, 1} + l := NewList(intl...) + nl := Reverse(l) + assertSeqContents(l, intl, t) + assertSeqContents(nl, []types.Elem{1, 2, 3}, t) + + // Degenerate case + l = NewList() + nl = Reverse(l) + assertEmpty(l, t) + assertEmpty(nl, 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 + } + + // Normal case + intl := []types.Elem{1, 2, 3} + l := NewList(intl...) + nl := mapFn(fn, l) + assertSeqContents(l, intl, t) + assertSeqContents(nl, []types.Elem{2, 3, 4}, t) + + // Degenerate case + l = NewList() + nl = mapFn(fn, l) + assertEmpty(l, t) + assertEmpty(nl, t) +} + +// Test mapping over a Seq +func TestMap(t *T) { + testMapGen(t, Map) +} + +// Test lazily mapping over a Seq +func TestLMap(t *T) { + testMapGen(t, LMap) +} + +// Test reducing over a Seq +func TestReduce(t *T) { + fn := func(acc, el types.Elem) (types.Elem, bool) { + return acc.(int) + el.(int), false + } + + // Normal case + intl := []types.Elem{1, 2, 3, 4} + l := NewList(intl...) + r := Reduce(fn, 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 + } + r = Reduce(fns, 0, l) + assertSeqContents(l, intl, t) + assertValue(r, 6, t) + + // Degenerate case + l = NewList() + r = Reduce(fn, 0, l) + assertEmpty(l, t) + assertValue(r, 0, t) +} + +// Test the Any function +func TestAny(t *T) { + fn := func(el types.Elem) bool { + return el.(int) > 3 + } + + // Value found case + intl := []types.Elem{1, 2, 3, 4} + l := NewList(intl...) + r, ok := Any(fn, l) + assertSeqContents(l, intl, t) + assertValue(r, 4, t) + assertValue(ok, true, t) + + // Value not found case + intl = []types.Elem{1, 2, 3} + l = NewList(intl...) + r, ok = Any(fn, l) + assertSeqContents(l, intl, t) + assertValue(r, nil, t) + assertValue(ok, false, t) + + // Degenerate case + l = NewList() + r, ok = Any(fn, l) + assertEmpty(l, t) + assertValue(r, nil, t) + assertValue(ok, false, t) +} + +// Test the All function +func TestAll(t *T) { + fn := func(el types.Elem) bool { + return el.(int) > 3 + } + + // All match case + intl := []types.Elem{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} + l = NewList(intl...) + ok = All(fn, l) + assertSeqContents(l, intl, t) + assertValue(ok, false, t) + + // Degenerate case + l = NewList() + ok = All(fn, l) + assertEmpty(l, t) + assertValue(ok, true, t) +} + +func testFilterGen(t *T, filterFn func(func(types.Elem) bool, Seq) Seq) { + fn := func(el types.Elem) bool { + return el.(int)%2 != 0 + } + + // Normal case + intl := []types.Elem{1, 2, 3, 4, 5} + l := NewList(intl...) + r := filterFn(fn, l) + assertSeqContents(l, intl, t) + assertSeqContents(r, []types.Elem{1, 3, 5}, t) + + // Degenerate cases + l = NewList() + r = filterFn(fn, l) + assertEmpty(l, t) + assertEmpty(r, t) +} + +// Test the Filter function +func TestFilter(t *T) { + testFilterGen(t, Filter) +} + +// Test the lazy Filter function +func TestLFilter(t *T) { + testFilterGen(t, LFilter) +} + +// Test Flatten-ing of a Seq +func TestFlatten(t *T) { + // Normal case + intl1 := []types.Elem{0, 1, 2} + intl2 := []types.Elem{3, 4, 5} + l1 := NewList(intl1...) + l2 := NewList(intl2...) + blank := NewList() + intl := []types.Elem{-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) + + // Degenerate case + nl = Flatten(blank) + assertEmpty(blank, t) + assertEmpty(nl, t) +} + +func testTakeGen(t *T, takeFn func(uint64, Seq) Seq) { + // Normal case + intl := []types.Elem{0, 1, 2, 3, 4} + l := NewList(intl...) + nl := takeFn(3, l) + assertSeqContents(l, intl, t) + assertSeqContents(nl, []types.Elem{0, 1, 2}, t) + + // Edge cases + nl = takeFn(5, l) + assertSeqContents(l, intl, t) + assertSeqContents(nl, intl, t) + + nl = takeFn(6, l) + assertSeqContents(l, intl, t) + assertSeqContents(nl, intl, t) + + // Degenerate cases + empty := NewList() + nl = takeFn(1, empty) + assertEmpty(empty, t) + assertEmpty(nl, t) + + nl = takeFn(0, l) + assertSeqContents(l, intl, t) + assertEmpty(nl, t) +} + +// Test taking from a Seq +func TestTake(t *T) { + testTakeGen(t, Take) +} + +// Test lazily taking from a Seq +func TestLTake(t *T) { + testTakeGen(t, LTake) +} + +func testTakeWhileGen(t *T, takeWhileFn func(func(types.Elem) bool, Seq) Seq) { + pred := func(el types.Elem) bool { + return el.(int) < 3 + } + + // Normal case + intl := []types.Elem{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) + + // Edge cases + intl = []types.Elem{5, 5, 5} + l = NewList(intl...) + nl = takeWhileFn(pred, l) + assertSeqContents(l, intl, t) + assertEmpty(nl, t) + + intl = []types.Elem{0, 1, 2} + l = NewList(intl...) + nl = takeWhileFn(pred, l) + assertSeqContents(l, intl, t) + assertSeqContents(nl, []types.Elem{0, 1, 2}, t) + + // Degenerate case + l = NewList() + nl = takeWhileFn(pred, l) + assertEmpty(l, t) + assertEmpty(nl, t) +} + +// Test taking from a Seq until a given condition +func TestTakeWhile(t *T) { + testTakeWhileGen(t, TakeWhile) +} + +// Test lazily taking from a Seq until a given condition +func TestLTakeWhile(t *T) { + testTakeWhileGen(t, LTakeWhile) +} + +// Test dropping from a Seq +func TestDrop(t *T) { + // Normal case + intl := []types.Elem{0, 1, 2, 3, 4} + l := NewList(intl...) + nl := Drop(3, l) + assertSeqContents(l, intl, t) + assertSeqContents(nl, []types.Elem{3, 4}, t) + + // Edge cases + nl = Drop(5, l) + assertSeqContents(l, intl, t) + assertEmpty(nl, t) + + nl = Drop(6, l) + assertSeqContents(l, intl, t) + assertEmpty(nl, t) + + // Degenerate cases + empty := NewList() + nl = Drop(1, empty) + assertEmpty(empty, t) + assertEmpty(nl, t) + + nl = Drop(0, l) + assertSeqContents(l, intl, t) + assertSeqContents(nl, intl, t) +} + +// Test dropping from a Seq until a given condition +func TestDropWhile(t *T) { + pred := func(el types.Elem) bool { + return el.(int) < 3 + } + + // Normal case + intl := []types.Elem{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) + + // Edge cases + intl = []types.Elem{5, 5, 5} + l = NewList(intl...) + nl = DropWhile(pred, l) + assertSeqContents(l, intl, t) + assertSeqContents(nl, intl, t) + + intl = []types.Elem{0, 1, 2} + l = NewList(intl...) + nl = DropWhile(pred, l) + assertSeqContents(l, intl, t) + assertEmpty(nl, t) + + // Degenerate case + l = NewList() + nl = DropWhile(pred, l) + assertEmpty(l, t) + assertEmpty(nl, t) +} diff --git a/seq/util.go b/seq/util.go new file mode 100644 index 0000000..d482e96 --- /dev/null +++ b/seq/util.go @@ -0,0 +1,90 @@ +package seq + +import ( + "testing" + + "github.com/mediocregopher/ginger/types" +) + +// Returns whether or not two types.Elem slices contain the same elements +func intSlicesEq(a, b []types.Elem) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +// Asserts that the given Seq is empty (contains no elements) +func assertEmpty(s Seq, t *testing.T) { + if Size(s) != 0 { + t.Fatalf("Seq isn't empty: %v", ToSlice(s)) + } +} + +// Asserts that the given Seq has the given elements +func assertSeqContents(s Seq, intl []types.Elem, t *testing.T) { + if ls := ToSlice(s); !intSlicesEq(ls, intl) { + t.Fatalf("Slice contents wrong: %v not %v", ls, intl) + } +} + +// Asserts that the given Seq has all elements, and only the elements, in the +// given map +func assertSeqContentsNoOrderMap(s Seq, m map[types.Elem]bool, t *testing.T) { + ls := ToSlice(s) + if len(ls) != len(m) { + t.Fatalf("Slice contents wrong: %v not %v", ls, m) + } + for i := range ls { + if _, ok := m[ls[i]]; !ok { + t.Fatalf("Slice contents wrong: %v not %v", ls, m) + } + } +} + +// Asserts that the given Seq has all the elements, and only the elements +// (duplicates removed), in the given slice, although no necessarily in the +// order given in the slice +func assertSeqContentsSet(s Seq, ints []types.Elem, t *testing.T) { + m := map[types.Elem]bool{} + for i := range ints { + m[ints[i]] = true + } + assertSeqContentsNoOrderMap(s, m, t) +} + +func assertSeqContentsHashMap(s Seq, kvs []*KV, t *testing.T) { + m := map[types.Elem]bool{} + for i := range kvs { + m[*kvs[i]] = true + } + ls := ToSlice(s) + if len(ls) != len(m) { + t.Fatalf("Slice contents wrong: %v not %v", ls, m) + } + for i := range ls { + kv := ls[i].(*KV) + if _, ok := m[*kv]; !ok { + t.Fatalf("Slice contents wrong: %v not %v", ls, m) + } + } +} + +// Asserts that v1 is the same as v2 +func assertValue(v1, v2 types.Elem, t *testing.T) { + if v1 != v2 { + t.Fatalf("Value wrong: %v not %v", v1, v2) + } +} + +// Asserts that v1 is a key in the given map +func assertInMap(v1 types.Elem, m map[types.Elem]bool, t *testing.T) { + if _, ok := m[v1]; !ok { + t.Fatalf("Value not in set: %v not in %v", v1, m) + } +} diff --git a/types/types.go b/types/types.go index e47bbfd..c3df1b7 100644 --- a/types/types.go +++ b/types/types.go @@ -6,12 +6,12 @@ package types type Elem interface { } -type String string +type Str string -type Integer int +type Int int type Float float32 type Char rune -type Error error +type Err error