Skip to content

Commit

Permalink
[TASK] TRK-574 Add Set[T] type to gofp (#25)
Browse files Browse the repository at this point in the history
* [TASK] TRK-574 Add Set[T] type to gofp

* [TASK] TRK-574 Add ToSliceWithSort method to Set type

* [TASK] TRK-574 Add test for ToSliceWithSort method of gofp.Set type
  • Loading branch information
hsequeda authored Nov 23, 2022
1 parent 7234908 commit 2dc5fc8
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 0 deletions.
65 changes: 65 additions & 0 deletions gofp/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gofp

import (
"fmt"
"sort"
)

// Set[T] represents a unordered list of unique 'T' elements.
// NOTE: only works with comparable types because the underlying implementation
// uses maps and map keys can only be 'comparables'.
// Should be builded using make. Example:
// integerSet:= make(Set[int]).
type Set[T comparable] map[T]struct{}

// Add adds a new element to the set.
func (s Set[T]) Add(val ...T) {
for _, v := range val {
s[v] = struct{}{}
}
}

// Has verifies if an element exists in the set.
func (s Set[T]) Has(val T) bool {
_, ok := s[val]
return ok
}

// Remove removes an element from the set.
func (s Set[T]) Remove(val T) {
delete(s, val)
}

// String implements Stringer
func (s Set[T]) String() string {
keys := make([]T, 0, len(s))
for k := range s {
keys = append(keys, k)
}

return fmt.Sprintf("%v", keys)
}

// ToSlice convert the set in a slice.
func (s Set[T]) ToSlice() []T {
keys := make([]T, 0, len(s))
for k := range s {
keys = append(keys, k)
}
return keys
}

// ToSlice convert the set in a slice applying the sort function.
func (s Set[T]) ToSliceWithSort(sortFunc func(a, b T) bool) []T {
keys := make([]T, 0, len(s))

for k := range s {
keys = append(keys, k)
}

sort.Slice(keys, func(i, j int) bool {
return sortFunc(keys[i], keys[j])
})

return keys
}
121 changes: 121 additions & 0 deletions gofp/set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package gofp_test

import (
"fmt"
"testing"

"github.com/msales/gox/gofp"
"github.com/stretchr/testify/assert"
)

func TestInitializeSet(t *testing.T) {
testCases := []struct {
name string
initFunc func() gofp.Set[int]
withErr bool
}{
{
name: "Initialize with 'make'",
initFunc: func() gofp.Set[int] {
return make(gofp.Set[int])
},
},
{
name: "Initialize with empty value",
initFunc: func() gofp.Set[int] {
return gofp.Set[int]{}
},
},
{
name: "Initialize with var",
initFunc: func() gofp.Set[int] {
var val gofp.Set[int]
return val
},
withErr: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
set := tc.initFunc()
if tc.withErr {
assert.Panics(t, func() { set.Add(1) })
return
}

assert.NotPanics(t, func() { set.Add(1) })
})
}
}

func TestToString(t *testing.T) {
intSet := gofp.Set[int]{1: struct{}{}, 2: struct{}{}}
strVal := fmt.Sprint(intSet)
// Using Contains because the iteration over golang maps is not sortered.
assert.Contains(t, []string{"[2 1]", "[1 2]"}, strVal)

type simpleStruct struct {
Foo int
Bar string
}
simpleStructSet := gofp.Set[simpleStruct]{{Foo: 1, Bar: "abc"}: struct{}{}, {Foo: 35, Bar: "xyz"}: struct{}{}}
// Using Contains because the iteration over golang maps is not sortered.
strVal = fmt.Sprint(simpleStructSet)
assert.Contains(t, []string{"[{1 abc} {35 xyz}]", "[{35 xyz} {1 abc}]"}, strVal)
}

func TestHas(t *testing.T) {
intSet := gofp.Set[int]{1: struct{}{}, 2: struct{}{}}
assert.True(t, intSet.Has(1))

type simpleStruct struct {
Foo int
Bar string
}
simpleStructSet := gofp.Set[simpleStruct]{{Foo: 1, Bar: "abc"}: struct{}{}, {Foo: 35, Bar: "xyz"}: struct{}{}}
// Using Contains because the iteration over golang maps is not sortered.
assert.True(t, simpleStructSet.Has(simpleStruct{Foo: 1, Bar: "abc"}))
}

func TestAdd(t *testing.T) {
intSet := make(gofp.Set[int])
// add unique elements
intSet.Add(1, 2, 3, 4)
assert.Len(t, intSet, 4)
// add already existing elements plus new elements
intSet.Add(2, 3, 24)
assert.Len(t, intSet, 5)
}

func TestRemove(t *testing.T) {
intSet := gofp.Set[int]{1: struct{}{}, 2: struct{}{}, 3: struct{}{}, 4: struct{}{}}
// remove existing element
intSet.Remove(3)
assert.Len(t, intSet, 3)

// remove no-existing element
intSet.Remove(3423)
assert.Len(t, intSet, 3)
}

func TestToSlice(t *testing.T) {
intSet := gofp.Set[int]{1: struct{}{}, 2: struct{}{}, 3: struct{}{}, 4: struct{}{}}
intSlice := intSet.ToSlice()
assert.Len(t, intSlice, len(intSet)) // verify that both length are the same.

for _, v := range intSlice {
assert.True(t, intSet.Has(v))
}
}

func TestToSliceWithSort(t *testing.T) {
intSet := gofp.Set[int]{1: struct{}{}, 2: struct{}{}, 3: struct{}{}, 4: struct{}{}}
expectedSlice := []int{1, 2, 3, 4}
intSlice := intSet.ToSliceWithSort(func(a, b int) bool { return a < b })
assert.Len(t, intSlice, len(intSet)) // verify that both length are the same.

for i := 0; i < len(expectedSlice); i++ {
assert.Equal(t, expectedSlice[i], intSlice[i])
}
}

0 comments on commit 2dc5fc8

Please sign in to comment.