Skip to content
Open
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
6 changes: 0 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
22 changes: 22 additions & 0 deletions slices/slices.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,25 @@ func Grow[S ~[]E, E any](s S, n int) S {
func Clip[S ~[]E, E any](s S) S {
return s[:len(s):len(s)]
}

// Intersection finds elements that are present in both slices, returning new slice without duplicates
func Intersection[E comparable](s1, s2 []E) []E {
var result []E
s2len := len(s2)
s1len := len(s1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not make sense. The built-in len() function is inlined, and instead of len(), the value of len from the slice structure is substituted.

if s1len == 0 || s2len == 0 {
return result
}
s2Map := make(map[E]bool, s2len)
for i := 0; i < s2len; i++ {
el := s2[i]
s2Map[el] = true
}
for i := 0; i < s1len; i++ {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is necessary to create maps for each slice in order to avoid duplication due to duplicates in one of the slices.

element := s1[i]
if _, ok := s2Map[element]; ok {
result = append(result, element)
}
}
return result
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a result, the implementation may look like this:

func Intersect[T comparable](a, b []T) []T {
	if len(a) == 0 || len(b) == 0 {
		return nil
	}

	base, other := a, b
	if len(b) < len(a) {
		base, other = b, a
	}

	// Base sequence -- unique elements of the smaller slice (preserve order)
	intersection := Unique(base)
	otherSet := HashSet(other)
	writeIdx := 0
	for _, v := range intersection {
		if _, ok := otherSet[v]; ok {
			intersection[writeIdx] = v
			writeIdx++
		}
	}

	intersection = intersection[:writeIdx]
	if len(intersection) == 0 {
		return nil
	}

	return intersection
}

// Stable function
func Unique[V comparable](slice []V) []V {
	if len(slice) == 0 {
		return nil
	}

	hashSet := make(map[V]struct{}, len(slice))
	result := make([]V, 0, len(slice))
	for _, elem := range slice {
		if _, ok := hashSet[elem]; !ok {
			result = append(result, elem)
			hashSet[elem] = struct{}{}
		}
	}

	return result
}

func HashSet[V comparable](slice []V) map[V]struct{} {
	if len(slice) == 0 {
		return nil
	}

	result := make(map[V]struct{}, len(slice))
	for _, elem := range slice {
		result[elem] = struct{}{}
	}

	return result
}

77 changes: 77 additions & 0 deletions slices/slices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,3 +767,80 @@ func BenchmarkReplace(b *testing.B) {
}

}

func TestIntersection(t *testing.T) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add a unit test for the case of duplicates within a single slice.
It is also customary to add benchmarks for functions of this type.

intTypeCases := []struct {
name string
s, v []int
want []int
}{
{
name: "empty first slice",
s: []int{},
v: []int{1, 4},
want: []int{},
},
{
name: "empty second slice",
s: []int{1, 3},
v: []int{},
want: []int{},
},
{
name: "duplicates int",
s: []int{2, 4, 1},
v: []int{1, 3, 1},
want: []int{1},
},
{
name: "regular use",
s: []int{1, 2, 3},
v: []int{3, 4, 5},
want: []int{3},
},
{
name: "nil value",
s: nil,
v: nil,
want: nil,
},
{
name: "different size",
s: []int{1, 5},
v: []int{5, 10, 25},
want: []int{5},
},
}
strTypeCases := []struct {
name string
s, v []string
want []string
}{
{
name: "duplicates string",
s: []string{"a", "b", "c"},
v: []string{"b", "b", "z"},
want: []string{"b"},
},
{
name: "contain substring",
s: []string{"abc", "h", "i"},
v: []string{"ab", "g", "z"},
want: []string{},
},
}
for _, test := range intTypeCases {
t.Run(test.name, func(tt *testing.T) {
if got := Intersection(test.s, test.v); !Equal(got, test.want) {
tt.Errorf("Intersection(%v) = %v, want %v", test.s, got, test.want)
}
})
}
for _, test := range strTypeCases {
t.Run(test.name, func(tt *testing.T) {
if got := Intersection(test.s, test.v); !Equal(got, test.want) {
tt.Errorf("Intersection(%v) = %v, want %v", test.s, got, test.want)
}
})
}
}