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

时间:2014-03-06 15:26:49

标签: sorting struct go nested

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

为了避免使我上面提到的工作问题复杂化,我创建了一个简化的(基于水果的)示例,说明正在使用的结构以及我想要实现的目标。

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\n\n", apples)
    sort.Sort(ByNumSeeds(apples))
    fmt.Printf("%+v\n\n\n", apples)

    fmt.Println("Bananas")
    fmt.Printf("%+v\n\n", bananas)
    //sort.Sort(ByNumSeeds(bananas))
    fmt.Printf("%+v\n\n", bananas)
}

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

正如你所看到的,我有两个结构,苹果和香蕉,它们都共享来自结构Fruit的属性; sort(包括接口函数Len,Swap,Less)和主函数,它设置苹果和香蕉的数据结构,然后尝试对它们进行排序。

我想要的苹果和香蕉是一种(类型ByNumSeeds,Len,Swap,Less)能够分别对苹果和香蕉进行分类,它们都是从Fruit结构,AvgNumSeeds共享的属性。

我在这段代码中创建的排序采用了一些Apples作为其界面,并确实通过AvgNumSeeds对我的苹果数组进行排序。但是我无法找到一种方法来使它与Apple和Banana结构一起使用。

我最初的想法是将界面视为一片水果,但可以理解的是,我得到了错误:

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

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

在我对一个解决方案的调查中,我遇到了一个名为sortutil的包,它有一个名为AscByField的函数,它接受字段的结构和名称进行排序。我还没有尝试过,但是这个软件包很清楚它的效率不高,因为它使用反射并首先尝试使用标准接口方法。

有没有办法可以实现嵌套结构的排序,而不必为每个'子'结构类型复制一个排序?

1 个答案:

答案 0 :(得分:2)

多态性的解决方案是接口。如你所见,嵌入它自己并不真正起作用,因为你仍然有不同的类型。这是你的例子的重新开始,以帮助你开始http://play.golang.org/p/7HV_HJ3Gw0,或者这可能更容易阅读(通常隐藏导出的界面后面的未导出的结构)http://play.golang.org/p/z3CHj002Jq

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\n\n", apples)
    sort.Sort(ByNumSeeds(apples))
    fmt.Printf("%+v\n\n\n", apples)

    fmt.Println("Bananas")
    fmt.Printf("%+v\n\n", bananas)
    sort.Sort(ByNumSeeds(bananas))
    fmt.Printf("%+v\n\n", bananas)
}

我对你的例子很谨慎,你试图强迫组合像继承一样工作(但这可能只是来自简化的例子)。嵌入不会给你一个像继承这样的“是一种”关系,只有“有一个”。让您的类型提供通用接口允许您通过相同的排序功能运行所有兼容类型。

您的示例中唯一真正的问题是[]struct不能与[]interface互换。如果需要转换这两个,则必须创建一个新切片并复制值。