Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set operations: intersection, union, difference #195

Merged
merged 6 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type Container interface {
Size() int
Clear()
Values() []interface{}
String() string
}
```

Expand Down Expand Up @@ -110,6 +111,7 @@ type List interface {
// Size() int
// Clear()
// Values() []interface{}
// String() string
}
```

Expand Down Expand Up @@ -228,19 +230,25 @@ func main() {

A set is a data structure that can store elements and has no repeated values. It is a computer implementation of the mathematical concept of a finite set. Unlike most other collection types, rather than retrieving a specific element from a set, one typically tests an element for membership in a set. This structure is often used to ensure that no duplicates are present in a container.

Set additionally allow set operations such as [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory)), [union](https://en.wikipedia.org/wiki/Union_(set_theory)), [difference](https://proofwiki.org/wiki/Definition:Set_Difference), etc.

Implements [Container](#containers) interface.

```go
type Set interface {
Add(elements ...interface{})
Remove(elements ...interface{})
Contains(elements ...interface{}) bool

// Intersection(another *Set) *Set
// Union(another *Set) *Set
// Difference(another *Set) *Set

containers.Container
// Empty() bool
// Size() int
// Clear()
// Values() []interface{}
// String() string
}
```

Expand Down Expand Up @@ -343,6 +351,7 @@ type Stack interface {
// Size() int
// Clear()
// Values() []interface{}
// String() string
}
```

Expand Down Expand Up @@ -418,6 +427,7 @@ type Map interface {
// Size() int
// Clear()
// Values() []interface{}
// String() string
}
```

Expand Down Expand Up @@ -591,6 +601,7 @@ type Tree interface {
// Size() int
// Clear()
// Values() []interface{}
// String() string
}
```

Expand Down Expand Up @@ -1348,7 +1359,7 @@ func main() {

### Serialization

All data structures can be serialized (marshalled) and deserialized (unmarshalled). Currently only JSON support is available.
All data structures can be serialized (marshalled) and deserialized (unmarshalled). Currently, only JSON support is available.

#### JSONSerializer

Expand Down Expand Up @@ -1481,7 +1492,7 @@ Container specific operations:

```go
// Returns sorted container''s elements with respect to the passed comparator.
// Does not effect the ordering of elements within the container.
// Does not affect the ordering of elements within the container.
func GetSortedValues(container Container, comparator utils.Comparator) []interface{}
```

Expand Down
4 changes: 0 additions & 4 deletions containers/enumerable.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ type EnumerableWithIndex interface {

// Map invokes the given function once for each element and returns a
// container containing the values returned by the given function.
// TODO would appreciate help on how to enforce this in containers (don't want to type assert when chaining)
// Map(func(index int, value interface{}) interface{}) Container

// Select returns a new container containing all elements for which the given function returns a true value.
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
// Select(func(index int, value interface{}) bool) Container

// Any passes each element of the container to the given function and
Expand All @@ -39,11 +37,9 @@ type EnumerableWithKey interface {

// Map invokes the given function once for each element and returns a container
// containing the values returned by the given function as key/value pairs.
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
// Map(func(key interface{}, value interface{}) (interface{}, interface{})) Container

// Select returns a new container containing all elements for which the given function returns a true value.
// TODO need help on how to enforce this in containers (don't want to type assert when chaining)
// Select(func(key interface{}, value interface{}) bool) Container

// Any passes each element of the container to the given function and
Expand Down
55 changes: 55 additions & 0 deletions sets/hashset/hashset.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,58 @@ func (set *Set) String() string {
str += strings.Join(items, ", ")
return str
}

// Intersection returns the intersection between two sets.
// The new set consists of all elements that are both in "set" and "another".
// Ref: https://en.wikipedia.org/wiki/Intersection_(set_theory)
func (set *Set) Intersection(another *Set) *Set {
result := New()

// Iterate over smaller set (optimization)
if set.Size() <= another.Size() {
for item := range set.items {
if _, contains := another.items[item]; contains {
result.Add(item)
}
}
} else {
for item := range another.items {
if _, contains := set.items[item]; contains {
result.Add(item)
}
}
}

return result
}

// Union returns the union of two sets.
// The new set consists of all elements that are in "set" or "another" (possibly both).
// Ref: https://en.wikipedia.org/wiki/Union_(set_theory)
func (set *Set) Union(another *Set) *Set {
result := New()

for item := range set.items {
result.Add(item)
}
for item := range another.items {
result.Add(item)
}

return result
}

// Difference returns the difference between two sets.
// The new set consists of all elements that are in "set" but not in "another".
// Ref: https://proofwiki.org/wiki/Definition:Set_Difference
func (set *Set) Difference(another *Set) *Set {
result := New()

for item := range set.items {
if _, contains := another.items[item]; !contains {
result.Add(item)
}
}

return result
}
66 changes: 66 additions & 0 deletions sets/hashset/hashset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,72 @@ func TestSetSerialization(t *testing.T) {
}
}

func TestSetIntersection(t *testing.T) {
set := New()
another := New()

intersection := set.Intersection(another)
if actualValue, expectedValue := intersection.Size(), 0; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}

set.Add("a", "b", "c", "d")
another.Add("c", "d", "e", "f")

intersection = set.Intersection(another)

if actualValue, expectedValue := intersection.Size(), 2; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}
if actualValue := intersection.Contains("c", "d"); actualValue != true {
t.Errorf("Got %v expected %v", actualValue, true)
}
}

func TestSetUnion(t *testing.T) {
set := New()
another := New()

union := set.Union(another)
if actualValue, expectedValue := union.Size(), 0; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}

set.Add("a", "b", "c", "d")
another.Add("c", "d", "e", "f")

union = set.Union(another)

if actualValue, expectedValue := union.Size(), 6; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}
if actualValue := union.Contains("a", "b", "c", "d", "e", "f"); actualValue != true {
t.Errorf("Got %v expected %v", actualValue, true)
}
}

func TestSetDifference(t *testing.T) {
set := New()
another := New()

difference := set.Difference(another)
if actualValue, expectedValue := difference.Size(), 0; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}

set.Add("a", "b", "c", "d")
another.Add("c", "d", "e", "f")

difference = set.Difference(another)

if actualValue, expectedValue := difference.Size(), 2; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}
if actualValue := difference.Contains("a", "b"); actualValue != true {
t.Errorf("Got %v expected %v", actualValue, true)
}
}

func benchmarkContains(b *testing.B, set *Set, size int) {
for i := 0; i < b.N; i++ {
for n := 0; n < size; n++ {
Expand Down
55 changes: 55 additions & 0 deletions sets/linkedhashset/linkedhashset.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,58 @@ func (set *Set) String() string {
str += strings.Join(items, ", ")
return str
}

// Intersection returns the intersection between two sets.
// The new set consists of all elements that are both in "set" and "another".
// Ref: https://en.wikipedia.org/wiki/Intersection_(set_theory)
func (set *Set) Intersection(another *Set) *Set {
result := New()

// Iterate over smaller set (optimization)
if set.Size() <= another.Size() {
for item := range set.table {
if _, contains := another.table[item]; contains {
result.Add(item)
}
}
} else {
for item := range another.table {
if _, contains := set.table[item]; contains {
result.Add(item)
}
}
}

return result
}

// Union returns the union of two sets.
// The new set consists of all elements that are in "set" or "another" (possibly both).
// Ref: https://en.wikipedia.org/wiki/Union_(set_theory)
func (set *Set) Union(another *Set) *Set {
result := New()

for item := range set.table {
result.Add(item)
}
for item := range another.table {
result.Add(item)
}

return result
}

// Difference returns the difference between two sets.
// The new set consists of all elements that are in "set" but not in "another".
// Ref: https://proofwiki.org/wiki/Definition:Set_Difference
func (set *Set) Difference(another *Set) *Set {
result := New()

for item := range set.table {
if _, contains := another.table[item]; !contains {
result.Add(item)
}
}

return result
}
66 changes: 66 additions & 0 deletions sets/linkedhashset/linkedhashset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,72 @@ func TestSetSerialization(t *testing.T) {
}
}

func TestSetIntersection(t *testing.T) {
set := New()
another := New()

intersection := set.Intersection(another)
if actualValue, expectedValue := intersection.Size(), 0; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}

set.Add("a", "b", "c", "d")
another.Add("c", "d", "e", "f")

intersection = set.Intersection(another)

if actualValue, expectedValue := intersection.Size(), 2; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}
if actualValue := intersection.Contains("c", "d"); actualValue != true {
t.Errorf("Got %v expected %v", actualValue, true)
}
}

func TestSetUnion(t *testing.T) {
set := New()
another := New()

union := set.Union(another)
if actualValue, expectedValue := union.Size(), 0; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}

set.Add("a", "b", "c", "d")
another.Add("c", "d", "e", "f")

union = set.Union(another)

if actualValue, expectedValue := union.Size(), 6; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}
if actualValue := union.Contains("a", "b", "c", "d", "e", "f"); actualValue != true {
t.Errorf("Got %v expected %v", actualValue, true)
}
}

func TestSetDifference(t *testing.T) {
set := New()
another := New()

difference := set.Difference(another)
if actualValue, expectedValue := difference.Size(), 0; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}

set.Add("a", "b", "c", "d")
another.Add("c", "d", "e", "f")

difference = set.Difference(another)

if actualValue, expectedValue := difference.Size(), 2; actualValue != expectedValue {
t.Errorf("Got %v expected %v", actualValue, expectedValue)
}
if actualValue := difference.Contains("a", "b"); actualValue != true {
t.Errorf("Got %v expected %v", actualValue, true)
}
}

func benchmarkContains(b *testing.B, set *Set, size int) {
for i := 0; i < b.N; i++ {
for n := 0; n < size; n++ {
Expand Down
3 changes: 3 additions & 0 deletions sets/sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ type Set interface {
Add(elements ...interface{})
Remove(elements ...interface{})
Contains(elements ...interface{}) bool
// Intersection(another *Set) *Set
// Union(another *Set) *Set
// Difference(another *Set) *Set

containers.Container
// Empty() bool
Expand Down
Loading