在Go中对共享的嵌套struct属性进行排序

 2020-01-14 

Sorting on a shared nested struct property in Go

我已经从重新思考数据库中提取了json数据集,然后使用rethinkgo将数据序列化为结构。我需要能够处理此数据的子集,并根据其属性之一的值对其进行排序。

为了避免使上述问题变得更加复杂,我创建了一个简化的(基于水果的)示例,该示例说明了所使用的结构以及我要实现的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package main

import (
   "fmt"
   "sort"
)

type Fruit struct {
    AvgNumSeeds int
    Name        string
}

type Apple struct {
    Fruit
    Diameter int
}

type Banana struct {
    Fruit
    Length int
}

type ByNumSeeds []Apple //[]Fruit

func (p ByNumSeeds) Len() int {
    return len(p)
}

func (p ByNumSeeds) Swap(i, j int) {
    p[i], p[j] = p[j], p[i]
}

func (p ByNumSeeds) Less(i, j int) bool {
    return p[i].AvgNumSeeds < p[j].AvgNumSeeds
}

func main() {
    apples := []Apple{
        Apple{Fruit: Fruit{AvgNumSeeds: 4, Name:"Cox"}, Diameter: 10},
        Apple{Fruit: Fruit{AvgNumSeeds: 6, Name:"Granny Smith"}, Diameter: 20},
        Apple{Fruit: Fruit{AvgNumSeeds: 5, Name:"Pink Lady"}, Diameter: 21},
        Apple{Fruit: Fruit{AvgNumSeeds: 2, Name:"Russett"}, Diameter: 15},
        Apple{Fruit: Fruit{AvgNumSeeds: 1, Name:"Crab"}, Diameter: 7},
        Apple{Fruit: Fruit{AvgNumSeeds: 7, Name:"Brambley"}, Diameter: 40},
        Apple{Fruit: Fruit{AvgNumSeeds: 3, Name:"Braeburn"}, Diameter: 25},
    }

    bananas := []Banana{
        Banana{Fruit: Fruit{AvgNumSeeds: 40, Name:"Lacatan"}, Length: 20},
        Banana{Fruit: Fruit{AvgNumSeeds: 60, Name:"Lady Finger"}, Length: 22},
        Banana{Fruit: Fruit{AvgNumSeeds: 50, Name:"Senorita"}, Length: 25},
        Banana{Fruit: Fruit{AvgNumSeeds: 20, Name:"Cavendish"}, Length: 30},
        Banana{Fruit: Fruit{AvgNumSeeds: 10, Name:"Goldfinger"}, Length: 27},
        Banana{Fruit: Fruit{AvgNumSeeds: 70, Name:"Gros Michel"}, Length: 15},
        Banana{Fruit: Fruit{AvgNumSeeds: 30, Name:"Red Dacca"}, Length: 19},
    }

    fmt.Println("Apples")
    fmt.Printf("%+v

", apples)
    sort.Sort(ByNumSeeds(apples))
    fmt.Printf("%+v


", apples)

    fmt.Println("Bananas")
    fmt.Printf("%+v

", bananas)
    //sort.Sort(ByNumSeeds(bananas))
    fmt.Printf("%+v

", bananas)
}

http://play.golang.org/p/EjWOf58N3x

如您所见,我有两个结构,Apples和Bananas,它们都共享Fruit结构中的属性。排序(包括接口函数Len,Swap,Less)和主要函数,它们设置了苹果和香蕉的数据结构,然后尝试对它们进行排序。

我要苹果和香蕉都需要的一种类型(类型ByNumSeeds,Len,Swap,Less)能够在Apple和Bananas都从Fruit结构AvgNumSeeds共享的属性上分别对苹果和香蕉进行排序。

我在这段代码中创建的排序将一片苹果作为其接口,并且确实通过AvgNumSeeds对我的一系列苹果进行了排序。但是,我找不到找到使其与Apple和Banana结构一起使用的方法。

我最初的想法是将接口视为"水果"切片,但是可以理解的是,我得到了错误:

1
60: cannot convert apples (type []Apple) to type ByNumSeeds

我的下一个想法是通过以某种方式将一小块苹果/香蕉浇铸成一小块水果来解决此错误,但这并不是一件正确的事情。

在寻找解决方案的过程中,我遇到了一个名为sortutil的软件包,该软件包具有一个称为AscByField的函数,该函数采用结构和字段名称作为排序依据。我还没有尝试过,但是该软件包很清楚地表明它效率不高,因为它通过使用反射并首先尝试使用标准接口方法来工作。

有没有一种方法可以实现嵌套结构的排序,而不必为每个"子"结构类型重复排序?


多态的解决方案是接口。 如您所见,单独嵌入在这里实际上并不起作用,因为您仍然具有不同的类型。 这是对示例的重做,以帮助您开始使用http://play.golang.org/p/7HV_HJ3Gw0,或者这可能更易于阅读(通常将未导出的结构隐藏在导出的界面后面) )http://play.golang.org/p/z3CHj002Jq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package main

import (
   "fmt"
   "sort"
)

type fruit struct {
    avgNumSeeds int
    name        string
}

type Fruit interface {
    Name() string
    AvgNumSeeds() int
}

func (f fruit) Name() string {
    return f.name
}

func (f fruit) AvgNumSeeds() int {
    return f.avgNumSeeds
}

type Apple struct {
    fruit
    Diameter int
}

type Banana struct {
    fruit
    Length int
}

type ByNumSeeds []Fruit

func (p ByNumSeeds) Len() int {
    return len(p)
}

func (p ByNumSeeds) Swap(i, j int) {
    p[i], p[j] = p[j], p[i]
}

func (p ByNumSeeds) Less(i, j int) bool {
    return p[i].AvgNumSeeds() < p[j].AvgNumSeeds()
}

func main() {
    apples := []Fruit{
        Apple{fruit: fruit{avgNumSeeds: 4, name:"Cox"}, Diameter: 10},
        Apple{fruit: fruit{avgNumSeeds: 6, name:"Granny Smith"}, Diameter: 20},
        Apple{fruit: fruit{avgNumSeeds: 5, name:"Pink Lady"}, Diameter: 21},
        Apple{fruit: fruit{avgNumSeeds: 2, name:"Russett"}, Diameter: 15},
        Apple{fruit: fruit{avgNumSeeds: 1, name:"Crab"}, Diameter: 7},
        Apple{fruit: fruit{avgNumSeeds: 7, name:"Brambley"}, Diameter: 40},
        Apple{fruit: fruit{avgNumSeeds: 3, name:"Braeburn"}, Diameter: 25},
    }

    bananas := []Fruit{
        Banana{fruit: fruit{avgNumSeeds: 40, name:"Lacatan"}, Length: 20},
        Banana{fruit: fruit{avgNumSeeds: 60, name:"Lady Finger"}, Length: 22},
        Banana{fruit: fruit{avgNumSeeds: 50, name:"Senorita"}, Length: 25},
        Banana{fruit: fruit{avgNumSeeds: 20, name:"Cavendish"}, Length: 30},
        Banana{fruit: fruit{avgNumSeeds: 10, name:"Goldfinger"}, Length: 27},
        Banana{fruit: fruit{avgNumSeeds: 70, name:"Gros Michel"}, Length: 15},
        Banana{fruit: fruit{avgNumSeeds: 30, name:"Red Dacca"}, Length: 19},
    }

    fmt.Println("Apples")
    fmt.Printf("%+v

", apples)
    sort.Sort(ByNumSeeds(apples))
    fmt.Printf("%+v


", apples)

    fmt.Println("Bananas")
    fmt.Printf("%+v

", bananas)
    sort.Sort(ByNumSeeds(bananas))
    fmt.Printf("%+v

", bananas)
}

但是,从您的示例中我还是很警惕,您试图使合成像继承一样工作(但这可能只是来自简化的示例)。 嵌入不会像继承那样为您提供"是"关系,而只会给您"具有"。 让您的类型提供一个通用接口,使您可以通过相同的排序功能运行所有兼容类型。

在您的示例中唯一真正的陷阱是[]struct[]interface不可互换。 如果需要转换两者,则必须创建一个新的切片并复制值。