initial work #1

Merged
suyono merged 4 commits from suyono.dev/goset into main 2023-07-03 15:46:29 +10:00
6 changed files with 227 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/.idea/

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
golang 1.18.10

View File

@ -1,2 +1,10 @@
# goset
One would argue that implementing set in go is easy. Thus, a library for a set is unnecessary. I agree, but if you have
to implement it over and over again for many projects, it becomes tedious. So, I wrote a simple library, hoping it would
save me a few lines in my projects.
As any other go library, you can install it by
```shell
go get gitea.suyono.dev/suyono/goset
```

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module gitea.suyono.dev/suyono/goset
go 1.18

184
goset.go Normal file
View File

@ -0,0 +1,184 @@
package goset
import (
"errors"
"fmt"
"reflect"
)
type Set struct {
m map[any]any
}
type StrictSet struct {
m map[any]any
typ reflect.Type
}
var ErrMismatchType = errors.New("must have same type for all the members")
func NewSet(v ...any) *Set {
return NewSetSlice(v)
}
func NewSetSlice(v []any) *Set {
mapper := make(map[any]any)
for _, item := range v {
mapper[item] = nil
}
m := &Set{
m: mapper,
}
return m
}
func (s *Set) Add(v any) {
mapper := s.m
mapper[v] = nil
s.m = mapper
}
func (s *Set) Remove(v any) {
mapper := s.m
delete(mapper, v)
s.m = mapper
}
func (s *Set) Has(v any) bool {
_, ok := s.m[v]
return ok
}
func (s *Set) Len() int {
return len(s.m)
}
func NewStrictSetErr(v ...any) (*StrictSet, error) {
return NewStrictSetSliceErr(v)
}
func NewStrictSetSliceErr(v []any) (set *StrictSet, err error) {
defer func() {
if r := recover(); r != nil {
set = nil
err = r.(error)
}
}()
set = NewStrictSetSlice(v)
return
}
func NewStrictSet(v ...any) *StrictSet {
return NewStrictSetSlice(v)
}
func NewStrictSetSlice(v []any) *StrictSet {
if len(v) > 0 {
mapper := make(map[any]any)
typ := reflect.TypeOf(v[0])
for _, item := range v {
if reflect.TypeOf(item) != typ {
panic(ErrMismatchType)
}
mapper[item] = nil
}
return &StrictSet{
m: mapper,
typ: typ,
}
}
return &StrictSet{}
}
func (s *StrictSet) AddErr(v any) (err error) {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
s.Add(v)
return
}
func (s *StrictSet) Add(v any) {
if s.typ == nil {
s.typ = reflect.TypeOf(v)
s.m = map[any]any{v: nil}
return
}
if reflect.TypeOf(v) != s.typ {
panic(ErrMismatchType)
}
mapper := s.m
mapper[v] = nil
s.m = mapper
}
func (s *StrictSet) Remove(v any) {
if _, ok := s.m[v]; ok {
mapper := s.m
delete(mapper, v)
s.m = mapper
}
}
func (s *StrictSet) Has(v any) bool {
_, ok := s.m[v]
return ok
}
func (s *StrictSet) Len() int {
return len(s.m)
}
func (s *StrictSet) Distinct(v any) (int, error) {
val := reflect.ValueOf(v)
if val.IsNil() {
return 0, errors.New("the output parameter is nil")
}
if val.Kind() == reflect.Pointer {
val = val.Elem()
if val.Kind() != reflect.Slice {
return 0, fmt.Errorf("unsupported type pointer of %v", val.Type())
}
if s.typ != val.Type().Elem() {
return 0, fmt.Errorf("invalid output parameter type %v", val.Type().Elem())
}
for item := range s.m {
val.Set(reflect.Append(val, reflect.ValueOf(item)))
}
return val.Len(), nil
} else if val.Kind() == reflect.Slice {
if len(s.m) > val.Len() {
return 0, errors.New("insufficient length of the output parameter")
}
if s.typ != val.Type().Elem() {
return 0, fmt.Errorf("invalid output parameter type %v", val.Type())
}
ctr := 0
for item := range s.m {
val.Index(ctr).Set(reflect.ValueOf(item))
ctr++
}
return ctr, nil
} else {
return 0, fmt.Errorf("invalid type %v", val.Type())
}
}

30
goset_test.go Normal file
View File

@ -0,0 +1,30 @@
package goset
import "testing"
func TestInitial(t *testing.T) {
NewSet("hello", "world", 1, 1.5, "world")
}
func TestDua(t *testing.T) {
s := NewStrictSet("hello", "world", "go", "further")
s.Remove("hello")
}
func TestDistinct(t *testing.T) {
var (
out []string
err error
len int
)
s := NewStrictSet("hello", "world", "go", "further")
len, err = s.Distinct(&out)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if len != 4 {
t.Fatalf("unexpected length %v", len)
}
}