0

假设我有很多自定义结构,

type MyStruct struct{
  Name string
}
type MyStruct2 struct{
  Port int
}
type MyStruct3 struct{
  Person MyStruct
}
// ..other custom structs

我想有一个功能来实现这样的事情:

package main

import (
    "reflect"
)

func main(){
  A1s := []MyStruct{}{
    {
       Name: "a1",
    }, 
    {
       Name: "a2",
    }, 
  }
  A2s := []MyStruct{}{
    {
       Name: "a2",
    }, 
    {
       Name: "a3",
    }, 
  }
  MergeSlices(A1s, A2s, func(i1 interface{}, i2 interface{}) bool {
                    e1 := i1.(MyStruct)
                    e2 := i2.(MyStruct)
                    if e1.Name == e2.Name{
                        return true
                    }
                    return false
                })
}

这应该导致:

A1s := []MyStruct{}{
    {
       Name: "a1",
    }, 
    {
       Name: "a2",
    }, 
   {
       Name: "a3",
    }, 
  }

这样一个简单的函数就可以合并任何类型的自定义结构,包括 string/int,只要你在equalMatcherfunc 中有自己的实现。

实现此目的的函数接受两个切片和一个 equalMatcher 函数(如 Java 比较器),它修改第一个切片,然后合并第二个切片中的元素,但唯一。


func MergeSlices(base interface{}, guest interface{}, equalMatcher func(interface{}, interface{}) bool)  {
    if reflect.TypeOf(base).Kind() == reflect.Slice && reflect.TypeOf(guest).Kind() == reflect.Slice {
        b := reflect.ValueOf(base)
        g := reflect.ValueOf(guest)
        found := make([]bool, g.Len())
        for i := 0; i < b.Len(); i++ {
            for j := 0; j < g.Len(); j++ {
                if equalMatcher(b.Index(i).Interface(), g.Index(j).Interface()) {
                    found[j] = true
                }
            }
        }
        bElem := reflect.ValueOf(&base).Elem()
        for i := range found {
            if !found[i] {
                bElem.Set(reflect.Append(bElem, g.Index(i)))
            }
        }
    }
}

但是我很恐慌:

call of reflect.Append on interface Value

我想这是因为func MergeSlices接受interface{}而不是[]interface{}

我希望我的MergeSlices功能有效。可以func MergeSlices有一个返回值。也欢迎任何第三方软件包建议。

先感谢您。

编辑:感谢@Aristofanio Garcia,我做了它有一个返回值。

func MergeSlices(base interface{}, guest interface{}, equalMatcher func(interface{}, interface{}) bool) interface{} {
    if reflect.TypeOf(base).Kind() == reflect.Slice && reflect.TypeOf(guest).Kind() == reflect.Slice {
        b := reflect.ValueOf(base)
        g := reflect.ValueOf(guest)
        found := make([]bool, g.Len())
        for i := 0; i < b.Len(); i++ {
            for j := 0; j < g.Len(); j++ {
                if equalMatcher(b.Index(i).Interface(), g.Index(j).Interface()) {
                    found[j] = true
                }
            }
        }
        for i := range found {
            if !found[i] {
                b = reflect.Append(b, g.Index(i))
            }
        }
        return b.Interface()
    }
    return base
}

以下是一些测试:

func TestMergeSlices_String(t *testing.T) {
    type args struct {
        base         interface{}
        guest        interface{}
        equalMatcher func(interface{}, interface{}) bool
        expectedBase interface{}
    }
    tests := []struct {
        name string
        args args
    }{
        {
            name: "string success test full merge",
            args: args{
                base:         []string{"a", "b"},
                guest:        []string{"c", "d"},
                equalMatcher: func(i interface{}, i2 interface{}) bool {
                    s1 := i.(string)
                    s2 := i2.(string)
                    if s1 == s2{
                        return true
                    }
                    return false
                },
                expectedBase :[]string{"a", "b", "c", "d"},
            },
        },
        {
            name: "string success test partial merge",
            args: args{
                base:         []string{"a", "b", "c"},
                guest:        []string{"c", "d"},
                equalMatcher: func(i interface{}, i2 interface{}) bool {
                    s1 := i.(string)
                    s2 := i2.(string)
                    if s1 == s2{
                        return true
                    }
                    return false
                },
                expectedBase :[]string{"a", "b", "c", "d"},
            },
        },
        {
            name: "string success test with empty guest",
            args: args{
                base:         []string{"a", "b", "c"},
                guest:        []string{},
                equalMatcher: func(i interface{}, i2 interface{}) bool {
                    s1 := i.(string)
                    s2 := i2.(string)
                    if s1 == s2{
                        return true
                    }
                    return false
                },
                expectedBase :[]string{"a", "b", "c"},
            },
        },
        {
            name: "string success test with empty base",
            args: args{
                base:         []string{},
                guest:        []string{"a", "b", "c"},
                equalMatcher: func(i interface{}, i2 interface{}) bool {
                    s1 := i.(string)
                    s2 := i2.(string)
                    if s1 == s2{
                        return true
                    }
                    return false
                },
                expectedBase :[]string{"a", "b", "c"},
            },
        },
        {
            name: "string success test with both empty",
            args: args{
                base:         []string{},
                guest:        []string{},
                equalMatcher: func(i interface{}, i2 interface{}) bool {
                    s1 := i.(string)
                    s2 := i2.(string)
                    if s1 == s2{
                        return true
                    }
                    return false
                },
                expectedBase :[]string{},
            },
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            gotInterface := MergeSlices(tt.args.base, tt.args.guest, tt.args.equalMatcher)
            got := gotInterface.([]string)
            assert.Equal(t, tt.args.expectedBase, got)
        })
    }
}

func TestMergeSlices_MyStruct(t *testing.T) {
    type args struct {
        base         interface{}
        guest        interface{}
        equalMatcher func(interface{}, interface{}) bool
        expectedBase interface{}
    }
    type S struct{
        S1 int
    }
    tests := []struct {
        name string
        args args
    }{
        {
            name: "int success test",
            args: args{
                base:         []S{
                    {
                        S1: 1,
                    },
                    {
                        S1: 2,
                    },
                },
                guest:        []S{
                    {
                        S1: 2,
                    },
                    {
                        S1: 3,
                    },
                },
                equalMatcher: func(i interface{}, i2 interface{}) bool {
                    s1 := i.(S)
                    s2 := i2.(S)
                    if s1.S1 == s2.S1{
                        return true
                    }
                    return false
                },
                expectedBase :[]S{
                    {
                        S1: 1,
                    },
                    {
                        S1: 2,
                    },
                    {
                        S1: 3,
                    },
                },
            },
        },

    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            gotInterface := MergeSlices(tt.args.base, tt.args.guest, tt.args.equalMatcher)
            got := gotInterface.([]S)
            assert.Equal(t, tt.args.expectedBase, got)
        })
    }
}
4

2 回答 2

2

bElem := reflect.ValueOf(&base).Elem()您从输入界面元素而不是切片中检索指针时。

代码建议:

package main

import (
    "errors"
    "fmt"
    "reflect"
)

type MyStruct struct {
    Name string
}

func main() {
    a1s := []MyStruct{
        {
            Name: "a1",
        },
        {
            Name: "a2",
        },
    }
    a2s := []MyStruct{
        {
            Name: "a2",
        },
        {
            Name: "a3",
        },
    }
    
    // add new value like reference
    addValue(&a2s, MyStruct{
        Name: "a4",
    })
    fmt.Printf("New a2s: %v\n", a2s)
    
    // add values like value
    a3s, err := MergeSlices(a1s, a2s, func(i1 interface{}, i2 interface{}) bool {
        e1 := i1.(MyStruct)
        e2 := i2.(MyStruct)
        return e1.Name == e2.Name
    })
    if err != nil {
        panic(err)
    }

    // fmt.Printf("%v\n", a3s)
    res := a3s.([]MyStruct)
    for _, a := range res {
        fmt.Printf("a.Name: %s\n", a.Name)
    }
}

func MergeSlices(base interface{}, guest interface{}, equalMatcher func(interface{}, interface{}) bool) (interface{}, error) {
    if reflect.TypeOf(base).Kind() == reflect.Slice && reflect.TypeOf(guest).Kind() == reflect.Slice {
        b := reflect.ValueOf(base)
        g := reflect.ValueOf(guest)
        found := make([]bool, g.Len())
        for i := 0; i < b.Len(); i++ {
            for j := 0; j < g.Len(); j++ {
                if equalMatcher(b.Index(i).Interface(), g.Index(j).Interface()) {
                    found[j] = true
                }
            }
        }
        // extract slice
        bElem := b
        for i := range found {
            if !found[i] {
                e := g.Index(i)
                bElem = reflect.Append(bElem, e)
            }
        }
        //
        return bElem.Interface(), nil
    }
    return nil, errors.New("its is not slices")
}

func addValue(a *[]MyStruct, b MyStruct) {
    *a = append(*a, b)
}

游乐场演示

更详细的游乐场演示

于 2021-03-31T14:43:34.330 回答
0

您不需要使用反射来实现所需的功能。这是可能的,因为空interface{}值是可比较的。

因此,一个更简单的解决方案涉及声明一个新类型ider interface{ToID()interface{}},它以空的形式返回interface{}集合中每个元素的标识符。

在我看来,我还将代码重写为更简单的代码。

package main

import (
    "fmt"
)

type MyStruct struct {
    Name string
}

func (m MyStruct) ToID() interface{} {
    return m.Name
}

type ider interface {
    ToID() interface{}
}

func main() {
    a1s := []ider{
        MyStruct{
            Name: "a1",
        },
        MyStruct{
            Name: "a2",
        },
    }
    a2s := []ider{
        MyStruct{
            Name: "a2",
        },
        MyStruct{
            Name: "a3",
        },
    }

    // add new value like reference
    a2s = append(a2s, MyStruct{
        Name: "a4",
    })
    fmt.Printf("New a2s: %v\n", a2s)

    // add values like value
    a3s, err := MergeSlices(a1s, a2s)
    if err != nil {
        panic(err)
    }

    for _, a := range a3s {
        fmt.Printf("a.Name: %#v\n", a)
    }
}

func MergeSlices(base []ider, guest []ider) ([]ider, error) {
    for i := 0; i < len(guest); i++ {
        var found bool
        for j := 0; j < len(base); j++ {
            if base[j].ToID() == guest[i].ToID() {
                found = true
                break
            }
        }
        if !found {
            base = append(base, guest[i])
        }
    }
    return base, nil
}

参考 https://golang.org/ref/spec#Comparison_operators 接口值是可比较的。如果两个接口值具有相同的动态类型和相同的动态值,或者两者都具有值 nil,则它们是相等的。

如果您不喜欢此解决方案,因为您发现很难使用切片ider而不是interface{},我建议使用此复制功能来帮助解决。

/*

    t := []MyStruct{
        MyStruct{
            Name: "a1",
        },
        MyStruct{
            Name: "a4",
        },
    }
    g := make([]ider, 4)
    Copy(g, t)
    fmt.Printf("g %#v\n", g)
*/


func Copy(dst interface{}, src interface{}) {
    rdst := reflect.ValueOf(dst)
    rsrc := reflect.ValueOf(src)

    if rdst.Kind() != reflect.Slice {
        return
    }
    if rsrc.Kind() != reflect.Slice {
        return
    }

    for i := 0; i < rdst.Len(); i++ {
        if i < rsrc.Len() {
            rdst.Index(i).Set(rsrc.Index(i))
        }
    }
    return
}
于 2021-03-31T16:24:31.773 回答