tools: introduce tools.OrderedSet
This commit is contained in:
parent
42661cdeed
commit
cc351fcba5
217
tools/ordered_set.go
Normal file
217
tools/ordered_set.go
Normal file
@ -0,0 +1,217 @@
|
||||
package tools
|
||||
|
||||
// OrderedSet is a unique set of strings that maintains insertion order.
|
||||
type OrderedSet struct {
|
||||
// s is the set of strings that we're keeping track of.
|
||||
s []string
|
||||
// m is a mapping of string value "s" into the index "i" that that
|
||||
// string is present in in the given "s".
|
||||
m map[string]int
|
||||
}
|
||||
|
||||
// NewOrderedSet creates an ordered set with no values.
|
||||
func NewOrderedSet() *OrderedSet {
|
||||
return NewOrderedSetWithCapacity(0)
|
||||
}
|
||||
|
||||
// NewOrderedSetWithCapacity creates a new ordered set with no values. The
|
||||
// returned ordered set can be appended to "capacity" number of times before it
|
||||
// grows internally.
|
||||
func NewOrderedSetWithCapacity(capacity int) *OrderedSet {
|
||||
return &OrderedSet{
|
||||
s: make([]string, 0, capacity),
|
||||
m: make(map[string]int, capacity),
|
||||
}
|
||||
}
|
||||
|
||||
// NewOrderedSetFromSlice returns a new ordered set with the elements given in
|
||||
// the slice "s".
|
||||
func NewOrderedSetFromSlice(s []string) *OrderedSet {
|
||||
set := NewOrderedSetWithCapacity(len(s))
|
||||
for _, e := range s {
|
||||
set.Add(e)
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
// Add adds the given element "i" to the ordered set, unless the element is
|
||||
// already present. It returns whether or not the element was added.
|
||||
func (s *OrderedSet) Add(i string) bool {
|
||||
if _, ok := s.m[i]; ok {
|
||||
return false
|
||||
}
|
||||
|
||||
s.s = append(s.s, i)
|
||||
s.m[i] = len(s.s) - 1
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Contains returns whether or not the given "i" is contained in this ordered
|
||||
// set. It is a constant-time operation.
|
||||
func (s *OrderedSet) Contains(i string) bool {
|
||||
if _, ok := s.m[i]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsAll returns whether or not all of the given items in "i" are present
|
||||
// in the ordered set.
|
||||
func (s *OrderedSet) ContainsAll(i ...string) bool {
|
||||
for _, item := range i {
|
||||
if !s.Contains(item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSubset returns whether other is a subset of this ordered set. In other
|
||||
// words, it returns whether or not all of the elements in "other" are also
|
||||
// present in this set.
|
||||
func (s *OrderedSet) IsSubset(other *OrderedSet) bool {
|
||||
for _, i := range other.s {
|
||||
if !s.Contains(i) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSuperset returns whether or not this set is a superset of "other". In other
|
||||
// words, it returns whether or not all of the elements in this set are also in
|
||||
// the set "other".
|
||||
func (s *OrderedSet) IsSuperset(other *OrderedSet) bool {
|
||||
return other.IsSubset(s)
|
||||
}
|
||||
|
||||
// Union returns a union of this set with the given set "other". It returns the
|
||||
// items that are in either set while maintaining uniqueness constraints. It
|
||||
// preserves ordered within each set, and orders the elements in this set before
|
||||
// the elements in "other".
|
||||
//
|
||||
// It is an O(n+m) operation.
|
||||
func (s *OrderedSet) Union(other *OrderedSet) *OrderedSet {
|
||||
union := NewOrderedSetWithCapacity(other.Cardinality() + s.Cardinality())
|
||||
|
||||
for _, e := range s.s {
|
||||
union.Add(e)
|
||||
}
|
||||
for _, e := range other.s {
|
||||
union.Add(e)
|
||||
}
|
||||
|
||||
return union
|
||||
}
|
||||
|
||||
// Intersect returns the elements that are in both this set and then given
|
||||
// "ordered" set. It is an O(min(n, m)) (in other words, O(n)) operation.
|
||||
func (s *OrderedSet) Intersect(other *OrderedSet) *OrderedSet {
|
||||
intersection := NewOrderedSetWithCapacity(MinInt(
|
||||
s.Cardinality(), other.Cardinality()))
|
||||
|
||||
if s.Cardinality() < other.Cardinality() {
|
||||
for _, elem := range s.s {
|
||||
if other.Contains(elem) {
|
||||
intersection.Add(elem)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, elem := range other.s {
|
||||
if s.Contains(elem) {
|
||||
intersection.Add(elem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return intersection
|
||||
}
|
||||
|
||||
// Difference returns the elements that are in this set, but not included in
|
||||
// other.
|
||||
func (s *OrderedSet) Difference(other *OrderedSet) *OrderedSet {
|
||||
diff := NewOrderedSetWithCapacity(s.Cardinality())
|
||||
for _, e := range s.s {
|
||||
if !other.Contains(e) {
|
||||
diff.Add(e)
|
||||
}
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
// SymmetricDifference returns the elements that are not present in both sets.
|
||||
func (s *OrderedSet) SymmetricDifference(other *OrderedSet) *OrderedSet {
|
||||
left := s.Difference(other)
|
||||
right := other.Difference(s)
|
||||
|
||||
return left.Union(right)
|
||||
}
|
||||
|
||||
// Clear removes all elements from this set.
|
||||
func (s *OrderedSet) Clear() {
|
||||
s.s = make([]string, 0)
|
||||
s.m = make(map[string]int, 0)
|
||||
}
|
||||
|
||||
// Remove removes the given element "i" from this set.
|
||||
func (s *OrderedSet) Remove(i string) {
|
||||
idx, ok := s.m[i]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
rest := MinInt(idx+1, len(s.s)-1)
|
||||
|
||||
s.s = append(s.s[:idx], s.s[rest:]...)
|
||||
for _, e := range s.s[rest:] {
|
||||
s.m[e] = s.m[e] - 1
|
||||
}
|
||||
delete(s.m, i)
|
||||
}
|
||||
|
||||
// Cardinality returns the cardinality of this set.
|
||||
func (s *OrderedSet) Cardinality() int {
|
||||
return len(s.s)
|
||||
}
|
||||
|
||||
// Iter returns a channel which yields the elements in this set in insertion
|
||||
// order.
|
||||
func (s *OrderedSet) Iter() <-chan string {
|
||||
c := make(chan string)
|
||||
go func() {
|
||||
for _, i := range s.s {
|
||||
c <- i
|
||||
}
|
||||
close(c)
|
||||
}()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Equal returns whether this element has the same number, identity and ordering
|
||||
// elements as given in "other".
|
||||
func (s *OrderedSet) Equal(other *OrderedSet) bool {
|
||||
if s.Cardinality() != other.Cardinality() {
|
||||
return false
|
||||
}
|
||||
|
||||
for e, i := range s.m {
|
||||
if ci, ok := other.m[e]; !ok || ci != i {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of this set.
|
||||
func (s *OrderedSet) Clone() *OrderedSet {
|
||||
clone := NewOrderedSetWithCapacity(s.Cardinality())
|
||||
for _, i := range s.s {
|
||||
clone.Add(i)
|
||||
}
|
||||
return clone
|
||||
}
|
217
tools/ordered_set_test.go
Normal file
217
tools/ordered_set_test.go
Normal file
@ -0,0 +1,217 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOrderedSetAddAddsElements(t *testing.T) {
|
||||
s := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
assert.False(t, s.Contains("d"),
|
||||
"tools: did not expected s to contain \"d\"")
|
||||
|
||||
assert.True(t, s.Add("d"))
|
||||
|
||||
assert.True(t, s.Contains("d"),
|
||||
"tools: expected s to contain \"d\"")
|
||||
}
|
||||
|
||||
func TestOrderedSetContainsReturnsTrueForItemsItContains(t *testing.T) {
|
||||
s := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
assert.True(t, s.Contains("b"),
|
||||
"tools: expected s to contain element \"b\"")
|
||||
}
|
||||
|
||||
func TestOrderedSetContainsReturnsFalseForItemsItDoesNotContains(t *testing.T) {
|
||||
s := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
assert.False(t, s.Contains("d"),
|
||||
"tools: did not expect s to contain element \"d\"")
|
||||
}
|
||||
|
||||
func TestOrderedSetContainsAllReturnsTrueWhenAllElementsAreContained(t *testing.T) {
|
||||
s := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
assert.True(t, s.ContainsAll("b", "c"),
|
||||
"tools: expected s to contain element \"b\" and \"c\"")
|
||||
}
|
||||
|
||||
func TestOrderedSetContainsAllReturnsFalseWhenAllElementsAreNotContained(t *testing.T) {
|
||||
s := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
assert.False(t, s.ContainsAll("b", "c", "d"),
|
||||
"tools: did not expect s to contain element \"b\", \"c\" and \"d\"")
|
||||
}
|
||||
|
||||
func TestOrderedSetIsSubsetReturnsTrueWhenOtherContainsAllElements(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
|
||||
assert.True(t, s1.IsSubset(s2),
|
||||
"tools: expected [a, b] to be a subset of [a, b, c]")
|
||||
}
|
||||
|
||||
func TestOrderedSetIsSubsetReturnsFalseWhenOtherDoesNotContainAllElements(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
assert.False(t, s1.IsSubset(s2),
|
||||
"tools: did not expect [a, b, c] to be a subset of [a, b]")
|
||||
}
|
||||
|
||||
func TestOrderedSetIsSupersetReturnsTrueWhenContainsAllElementsOfOther(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
assert.True(t, s1.IsSuperset(s2),
|
||||
"tools: expected [a, b, c] to be a superset of [a, b]")
|
||||
}
|
||||
|
||||
func TestOrderedSetIsSupersetReturnsFalseWhenDoesNotContainAllElementsOfOther(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
|
||||
assert.False(t, s1.IsSuperset(s2),
|
||||
"tools: did not expect [a, b] to be a superset of [a, b, c]")
|
||||
}
|
||||
|
||||
func TestOrderedSetUnion(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"b", "a"})
|
||||
|
||||
elems := make([]string, 0)
|
||||
for e := range s1.Union(s2).Iter() {
|
||||
elems = append(elems, e)
|
||||
}
|
||||
|
||||
require.Len(t, elems, 2)
|
||||
assert.Equal(t, "a", elems[0])
|
||||
assert.Equal(t, "b", elems[1])
|
||||
}
|
||||
|
||||
func TestOrderedSetIntersect(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"b", "a"})
|
||||
|
||||
elems := make([]string, 0)
|
||||
for e := range s1.Intersect(s2).Iter() {
|
||||
elems = append(elems, e)
|
||||
}
|
||||
|
||||
require.Len(t, elems, 1)
|
||||
assert.Equal(t, "a", elems[0])
|
||||
}
|
||||
|
||||
func TestOrderedSetDifference(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"a"})
|
||||
|
||||
elems := make([]string, 0)
|
||||
for e := range s1.Difference(s2).Iter() {
|
||||
elems = append(elems, e)
|
||||
}
|
||||
|
||||
require.Len(t, elems, 1)
|
||||
assert.Equal(t, "b", elems[0])
|
||||
}
|
||||
|
||||
func TestOrderedSetSymmetricDifference(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"b", "c"})
|
||||
|
||||
elems := make([]string, 0)
|
||||
for e := range s1.SymmetricDifference(s2).Iter() {
|
||||
elems = append(elems, e)
|
||||
}
|
||||
|
||||
require.Len(t, elems, 2)
|
||||
assert.Equal(t, "a", elems[0])
|
||||
assert.Equal(t, "c", elems[1])
|
||||
}
|
||||
|
||||
func TestOrderedSetClear(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
|
||||
assert.Equal(t, 2, s1.Cardinality())
|
||||
|
||||
s1.Clear()
|
||||
|
||||
assert.Equal(t, 0, s1.Cardinality())
|
||||
}
|
||||
|
||||
func TestOrderedSetRemove(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
|
||||
assert.True(t, s1.Contains("a"), "tools: expected [a, b] to contain 'a'")
|
||||
assert.True(t, s1.Contains("b"), "tools: expected [a, b] to contain 'b'")
|
||||
|
||||
s1.Remove("a")
|
||||
|
||||
assert.False(t, s1.Contains("a"), "tools: did not expect to find 'a' in [b]")
|
||||
assert.True(t, s1.Contains("b"), "tools: expected [b] to contain 'b'")
|
||||
}
|
||||
|
||||
func TestOrderedSetCardinality(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
|
||||
assert.Equal(t, 2, s1.Cardinality(),
|
||||
"tools: expected cardinality of [a, b] to equal 2")
|
||||
}
|
||||
|
||||
func TestOrderedSetIter(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
elems := make([]string, 0)
|
||||
for e := range s1.Iter() {
|
||||
elems = append(elems, e)
|
||||
}
|
||||
|
||||
require.Len(t, elems, 3)
|
||||
assert.Equal(t, "a", elems[0])
|
||||
assert.Equal(t, "b", elems[1])
|
||||
assert.Equal(t, "c", elems[2])
|
||||
}
|
||||
|
||||
func TestOrderedSetEqualReturnsTrueWhenSameElementsInSameOrder(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
assert.True(t, s1.Equal(s2),
|
||||
"tools: expected [a, b, c] to equal [a, b, c]")
|
||||
}
|
||||
|
||||
func TestOrderedSetEqualReturnsFalseWhenSameElementsInDifferentOrder(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"a", "c", "b"})
|
||||
|
||||
assert.False(t, s1.Equal(s2),
|
||||
"tools: did not expect [a, b, c] to equal [a, c, b]")
|
||||
}
|
||||
|
||||
func TestOrderedSetEqualReturnsFalseWithDifferentCardinalities(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a"})
|
||||
s2 := NewOrderedSetFromSlice([]string{"a", "b"})
|
||||
|
||||
assert.False(t, s1.Equal(s2),
|
||||
"tools: did not expect [a] to equal [a, b]")
|
||||
}
|
||||
|
||||
func TestOrderedSetClone(t *testing.T) {
|
||||
s1 := NewOrderedSetFromSlice([]string{"a", "b", "c"})
|
||||
|
||||
s2 := s1.Clone()
|
||||
|
||||
elems := make([]string, 0)
|
||||
for e := range s2.Iter() {
|
||||
elems = append(elems, e)
|
||||
}
|
||||
|
||||
require.Len(t, elems, 3)
|
||||
assert.Equal(t, "a", elems[0])
|
||||
assert.Equal(t, "b", elems[1])
|
||||
assert.Equal(t, "c", elems[2])
|
||||
}
|
Loading…
Reference in New Issue
Block a user