Reputation: 328
I have a method Deduplicate that returns deduplicated copy of passed in slice as an interface{}. Is there a way to cast returned by this method interface{} value to the same type as I passed in this method without writing it explicitly? For example, if I change myStruct.RelatedIDs type from []int to []uint it will prevent code from compiling.
https://play.golang.org/p/8OT4xYZuwEn
package main
import (
"fmt"
"reflect"
)
type myStruct struct {
ID int
RelatedIDs []int
}
func main() {
s := &myStruct{
ID: 42,
RelatedIDs: []int{1, 1, 2, 3},
}
v, _ := Deduplicate(s.RelatedIDs)
s.RelatedIDs = v.([]int) // << can I assert type dynamically here?
// s.RelatedIDs = v.(reflect.TypeOf(s.RelatedIDs)) // does not work
fmt.Printf("%#v\n", s.RelatedIDs)
}
func Deduplicate(slice interface{}) (interface{}, error) {
if reflect.TypeOf(slice).Kind() != reflect.Slice {
return nil, fmt.Errorf("slice has wrong type: %T", slice)
}
s := reflect.ValueOf(slice)
res := reflect.MakeSlice(s.Type(), 0, s.Len())
seen := make(map[interface{}]struct{})
for i := 0; i < s.Len(); i++ {
v := s.Index(i)
if _, ok := seen[v.Interface()]; ok {
continue
}
seen[v.Interface()] = struct{}{}
res = reflect.Append(res, v)
}
return res.Interface(), nil
}
Upvotes: 0
Views: 471
Reputation: 66244
Since Go 1.21, you can rely on the Compact
function from the slices
package:
package main
import (
"fmt"
"slices"
)
func main() {
s := []int{1, 1, 2, 3} // assumed sorted
res := slices.Compact(s)
fmt.Printf("%#v\n", res)
}
Output:
[]int{1, 2, 3}
And if the compacted slice is much shorter than the original one, you may want to pass it through slices.Clone
in order to make the old (large) backing array (and its elements) eligible for GC.
For completeness, here is a generic version (Playground), which doesn't require reflection. Only open in February 2022! The usual caveats about NaN
apply, though.
package main
import (
"fmt"
)
func main() {
s := []int{1, 1, 2, 3}
res := Deduplicate(s)
fmt.Printf("%#v\n", res)
}
func Deduplicate[T comparable](s []T) []T {
seen := make(map[T]struct{})
res := make([]T, 0, len(s))
for _, elem := range s {
if _, exists := seen[elem]; exists {
continue
}
seen[elem] = struct{}{}
res = append(res, elem)
}
return res
}
Output:
[]int{1, 2, 3}
Upvotes: 2
Reputation:
Try this
package main
import (
"fmt"
"reflect"
)
type myStruct struct {
ID int
RelatedIDs []int
}
func main() {
s := &myStruct{
ID: 42,
RelatedIDs: []int{1, 1, 2, 3},
}
err := Deduplicate(&s.RelatedIDs)
fmt.Println(err)
// s.RelatedIDs = v.([]int) // << can I assert type dynamically here?
// s.RelatedIDs = v.(reflect.TypeOf(s.RelatedIDs)) // does not work
fmt.Printf("%#v\n", s.RelatedIDs)
}
func Deduplicate(slice interface{}) error {
rts := reflect.TypeOf(slice)
rtse := rts.Elem()
if rts.Kind() != reflect.Ptr && rtse.Kind() != reflect.Slice {
return fmt.Errorf("slice has wrong type: %T", slice)
}
rvs := reflect.ValueOf(slice)
rvse := rvs.Elem()
seen := make(map[interface{}]struct{})
var e int
for i := 0; i < rvse.Len(); i++ {
v := rvse.Index(i)
if _, ok := seen[v.Interface()]; ok {
continue
}
seen[v.Interface()] = struct{}{}
rvse.Index(e).Set(v)
e++
}
rvse.SetLen(e)
rvs.Elem().Set(rvse)
return nil
}
https://play.golang.org/p/hkEW4u1aGUi
with future generics, it might look like this https://go2goplay.golang.org/p/jobI5wKR8fU
Upvotes: 3