Skip to content

Commit f6f28de

Browse files
authored
Merge pull request #132 from rebuy-de/add-dsutil
add typeutil
2 parents 6273918 + 10e659e commit f6f28de

File tree

6 files changed

+306
-0
lines changed

6 files changed

+306
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ require (
2828
github.com/spf13/cobra v1.6.1
2929
github.com/stretchr/testify v1.8.1
3030
github.com/tidwall/pretty v1.2.1
31+
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
3132
golang.org/x/oauth2 v0.4.0
3233
golang.org/x/sync v0.1.0
3334
golang.org/x/tools v0.5.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
595595
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
596596
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
597597
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
598+
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
599+
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
598600
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
599601
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
600602
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

pkg/typeutil/pointer.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package typeutil
2+
3+
// Pointer creates a pointer from the given value. This is useful, when
4+
// creating a pointer from a loop variable [1] or when creating a pointer from
5+
// a return value of another function, since both would require assigning the
6+
// value to an intermediate variable first.
7+
//
8+
// [1]: https://github.com/golang/go/discussions/56010
9+
func Pointer[T any](v T) *T {
10+
return &v
11+
}
12+
13+
// Value returns the value from a pointer. If the pointer is nil, it will
14+
// return the zero value instead.
15+
func Value[T any](p *T) T {
16+
if p != nil {
17+
return *p
18+
}
19+
20+
var zero T
21+
return zero
22+
}

pkg/typeutil/set.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package typeutil
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"sort"
7+
8+
"golang.org/x/exp/constraints"
9+
)
10+
11+
// Set implements a set data structure based on built-in maps. This is not
12+
// optimized for large data, but to be convient.
13+
type Set[T constraints.Ordered] struct {
14+
data map[T]struct{}
15+
}
16+
17+
// NewSet initializes a new Set with the given values. If there are no values
18+
// provides this is equivalent to new(Set[T]).
19+
func NewSet[T constraints.Ordered](values ...T) *Set[T] {
20+
s := new(Set[T])
21+
for i := range values {
22+
s.Add(values[i])
23+
}
24+
return s
25+
}
26+
27+
// Add puts a single value to the set. The set will be the same, if it already
28+
// contains the value.
29+
func (s *Set[T]) Add(value T) {
30+
if s.data == nil {
31+
s.data = map[T]struct{}{}
32+
}
33+
34+
s.data[value] = struct{}{}
35+
}
36+
37+
// Contains returns true, if the given value is part of the set.
38+
func (s *Set[T]) Contains(value T) bool {
39+
if s.data == nil {
40+
return false
41+
}
42+
43+
_, found := s.data[value]
44+
return found
45+
}
46+
47+
// Remove removes the given value from the set. The set will be the same, if
48+
// the value is not part of it.
49+
func (s *Set[T]) Remove(value T) {
50+
if s.data == nil {
51+
return
52+
}
53+
54+
delete(s.data, value)
55+
}
56+
57+
// Substract removes every element from the given set from the set.
58+
func (s *Set[T]) Subtract(other *Set[T]) {
59+
if s.data == nil {
60+
return
61+
}
62+
63+
for value := range other.data {
64+
delete(s.data, value)
65+
}
66+
}
67+
68+
// Len returns the number of all values in the set.
69+
func (s Set[T]) Len() int {
70+
return len(s.data)
71+
}
72+
73+
// ToList converts the set into a slice. Since the set uses a map as an
74+
// underlying data structure, this will copy each value. So it might be memory
75+
// intensive. Also it sorts the slice to ensure a cosistent result.
76+
func (s *Set[T]) ToList() []T {
77+
if s == nil || s.data == nil {
78+
return nil
79+
}
80+
81+
list := make([]T, 0, len(s.data))
82+
83+
if len(s.data) > 0 {
84+
for v := range s.data {
85+
list = append(list, v)
86+
}
87+
}
88+
89+
sort.Slice(list, func(i, j int) bool { return list[i] < list[j] })
90+
91+
return list
92+
}
93+
94+
// AddSet adds each value from the given set to the set.
95+
func (s *Set[T]) AddSet(other *Set[T]) {
96+
if other == nil {
97+
return
98+
}
99+
100+
for o := range other.data {
101+
s.Add(o)
102+
}
103+
}
104+
105+
// MarshalJSON adds support for mashaling the set into a JSON list.
106+
func (s Set[T]) MarshalJSON() ([]byte, error) {
107+
list := s.ToList()
108+
return json.Marshal(list)
109+
}
110+
111+
// MarshalJSON adds support for unmashaling the set from a JSON list.
112+
func (s *Set[T]) UnmarshalJSON(data []byte) error {
113+
list := []T{}
114+
err := json.Unmarshal(data, &list)
115+
if err != nil {
116+
return fmt.Errorf("unmarshal set: %w", err)
117+
}
118+
119+
for _, v := range list {
120+
s.Add(v)
121+
}
122+
123+
return nil
124+
}
125+
126+
func SetUnion[T constraints.Ordered](sets ...*Set[T]) *Set[T] {
127+
result := new(Set[T])
128+
129+
for s := range sets {
130+
result.AddSet(sets[s])
131+
}
132+
133+
return result
134+
}
135+
136+
// SetIntersect returns a set that only contains elements which exist in all
137+
// sets.
138+
func SetIntersect[T constraints.Ordered](sets ...*Set[T]) *Set[T] {
139+
result := new(Set[T])
140+
141+
for _, s := range sets {
142+
if s == nil || s.data == nil {
143+
return result
144+
}
145+
}
146+
147+
if len(sets) == 0 {
148+
return result
149+
}
150+
151+
result.AddSet(sets[0])
152+
153+
for _, s := range sets[1:] {
154+
for e := range result.data {
155+
if !s.Contains(e) {
156+
delete(result.data, e)
157+
}
158+
}
159+
}
160+
161+
return result
162+
}

pkg/typeutil/set_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package typeutil
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestSet(t *testing.T) {
12+
toJSON := func(t *testing.T, set Set[string]) string {
13+
data, err := json.Marshal(set)
14+
assert.NoError(t, err)
15+
return string(data)
16+
}
17+
18+
fromJSON := func(t *testing.T, data string) Set[string] {
19+
var set Set[string]
20+
err := json.Unmarshal([]byte(data), &set)
21+
assert.NoError(t, err)
22+
return set
23+
}
24+
25+
t.Run("Simple", func(t *testing.T) {
26+
var set Set[string]
27+
set.Add("foo")
28+
set.Add("bar")
29+
set.Add("bar")
30+
assert.Equal(t, `["bar","foo"]`, toJSON(t, set))
31+
})
32+
33+
t.Run("JSON", func(t *testing.T) {
34+
in := `["bar","foo"]`
35+
set := fromJSON(t, `["bar","foo"]`)
36+
out := toJSON(t, set)
37+
38+
assert.Equal(t, in, out)
39+
})
40+
}
41+
42+
func TestSetUnion(t *testing.T) {
43+
cases := []struct {
44+
Name string
45+
A, B *Set[string]
46+
Want *Set[string]
47+
}{
48+
{
49+
Name: "Simple",
50+
A: NewSet("a", "b", "c"),
51+
B: NewSet("c", "d", "e"),
52+
Want: NewSet("a", "b", "c", "d", "e"),
53+
},
54+
{
55+
Name: "NilB",
56+
A: NewSet("a", "b", "c"),
57+
B: nil,
58+
Want: NewSet("a", "b", "c"),
59+
},
60+
}
61+
62+
for _, tc := range cases {
63+
t.Run(tc.Name, func(t *testing.T) {
64+
have := SetUnion(tc.A, tc.B)
65+
require.Equal(t, tc.Want.ToList(), have.ToList())
66+
})
67+
}
68+
}
69+
70+
func TestSetIntersection(t *testing.T) {
71+
cases := []struct {
72+
Name string
73+
A, B *Set[string]
74+
Want *Set[string]
75+
}{
76+
{
77+
Name: "Simple",
78+
A: NewSet("a", "b", "c", "d"),
79+
B: NewSet("c", "d", "e"),
80+
Want: NewSet("c", "d"),
81+
},
82+
{
83+
Name: "NilB",
84+
A: NewSet("a", "b", "c"),
85+
B: nil,
86+
Want: NewSet[string](),
87+
},
88+
}
89+
90+
for _, tc := range cases {
91+
t.Run(tc.Name, func(t *testing.T) {
92+
have := SetIntersect(tc.A, tc.B)
93+
require.Equal(t, tc.Want.ToList(), have.ToList())
94+
})
95+
}
96+
}

pkg/typeutil/slice.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package typeutil
2+
3+
func LimitSlice[T any](slice []T, limit int) ([]T, int) {
4+
l := len(slice)
5+
6+
if l <= limit {
7+
return slice, 0
8+
}
9+
10+
return slice[0:limit], l - limit
11+
}
12+
13+
func FilterSlice[T any](in []T, fn func(T) bool) []T {
14+
result := []T{}
15+
16+
for i := range in {
17+
if fn(in[i]) {
18+
result = append(result, in[i])
19+
}
20+
}
21+
22+
return result
23+
}

0 commit comments

Comments
 (0)