日日是好日

Go实现String Set

April 1, 2020

基于map实现sets.go

package sets

// An Empty struct does not take up any memory
type Empty struct{}

// sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption.
type String map[string]Empty


func NewString(items ...string) String {
	s := String{}
	s.Insert(items...)
	return s
}

func (s String) Insert(items ...string) {
	for _, item := range items {
		s[item] = Empty{}
	}
}

func (s String) Delete(items ...string) {
	for _, item := range items {
		delete(s, item)
	}
}

func (s String) Has(item string) bool {
	_, res := s[item]
	return res
}

// HasAll returns true if and only if all items are contained in the set.
func (s String) HasAll(items ...string) bool {
	for _, item := range items {
		if !s.Has(item) {
			return false
		}
	}
	return true
}

// HasAny returns true if any items are contained in the set.
func (s String) HasAny(items ...string) bool {
	for _, item := range items {
		if s.Has(item) {
			return true
		}
	}
	return false
}

// Difference returns a set of objects that are not in s2
func (s String) Difference(s2 String) String {
	res := NewString()
	for item := range s {
		if !s2.Has(item) {
			res.Insert(item)
		}
	}
	return res
}

// Union returns a new set which includes items in either s1 or s2.
func (s1 String) Union(s2 String) String {
	res := NewString()
	for item := range s1 {
		res.Insert(item)
	}
	for item := range s2 {
		res.Insert(item)
	}
	return res
}

// Intersection returns a new set which includes the item in BOTH s1 and s2
func (s1 String) Intersection(s2 String) String {
	res := NewString()
	if s1.Len() < s2.Len() {
		for item := range s1 {
			if s2.Has(item) {
				res.Insert(item)
			}
		}
	} else {
		for item := range s2 {
			if s1.Has(item) {
				res.Insert(item)
			}
		}
	}
	return res
}

func (s String) Len() int {
	return len(s)
}

func (s String) List() []string {
	res := make([]string, 0, len(s))
	for item := range s {
		res = append(res, item)
    }
	return res
}

单元测试 sets_test.go

package sets

import (
    "testing"
)

func TestInsert(t *testing.T) {
	ss := NewString()
	
	// Insert 1 item\
	ss.Insert("a")
	if !ss.Has("a") {
		t.Errorf("Insert failed.")
	}

	// Insert several items
	ss.Insert("a", "bb", "ccc", "dddd")
	if !ss.HasAll("a", "bb", "ccc", "dddd") || ss.Len() != 4 {
		t.Errorf("Insert failed.")
	}

}

func TestDelete(t *testing.T) {
	ss := NewString("a", "b", "c", "d")

	// Delete 1 item
	ss.Delete("a")
	if ss.Has("a") {
		t.Errorf("Delete failed.")
	}

	// Delete several items
	ss.Delete("b", "c", "d")
	if ss.HasAny("b", "c", "d") || ss.Len() != 0 {
		t.Errorf("Delete failed.")
	}
}

func TestHas(t *testing.T) {
	s := "abc"
	ss := NewString(s)
	_, res1 := ss[s]
	res2 := ss.Has(s)
	if (res1 && !res2) || (!res1 && res2) {
		t.Errorf("Has Check failed.")
	}
}

func TestHasAny(t *testing.T) {
	ss := NewString("a", "b", "c", "d")
	_, resA := ss["a"]
	_, resB := ss["b"]
	res1 := resA || resB
	res2 := ss.HasAny("a", "b")
	if (res1 && !res2) || (!res1 && res2) {
		t.Errorf("HasAny Check failed.")
	}

}

func TestHasAll(t *testing.T) {
	ss := NewString("a", "b", "c")
	_, resA := ss["a"]
	_, resB := ss["b"]
	_, resC := ss["c"]
	res1 := resA && resB && resC
	res2 := ss.HasAll("a", "b", "c")
	if (res1 && !res2) || (!res1 && res2) {
		t.Errorf("HasAll Check failed.")
	}
}

func TestDifference(t *testing.T) {
	ss1 := NewString("a", "b")
	ss2 := NewString("a", "c")
	res := ss1.Difference(ss2)
	if !res.Has("b") {
		t.Errorf("Difference Check failed.")
	}
}

func TestUnion(t *testing.T) {
	ss1 := NewString("a")
	ss2 := NewString("b")
	res := ss1.Union(ss2)
	if !res.HasAll("a", "b") {
		t.Errorf("Union Check failed.")
	}
}

func TestIntersection(t *testing.T) {
	ss1 := NewString("a", "b")
	ss2 := NewString("a", "c")
	res := ss1.Intersection(ss2)
	if !res.Has("a") {
		t.Errorf("Intersection Check failed.")
	}
}

func TestLen(t *testing.T) {
	ss1 := NewString("a", "b")
	if ss1.Len() != 2 {
		t.Errorf("Len Check failed.")
	}
}