简体   繁体   中英

Sorting on a shared nested struct property in Go

I have set of json data being pulled from a rethink database, the data is then serialised into structs using rethinkgo. I need to be able to work with a subset of this data and sort it based on the value of one of its properties.

To save complicating the problem with the workings of what I mentioned above, I've created a simplified (fruit based) example of the structs being used and what I'm trying to achieve.

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

As you can see I've two structs, Apples and Bananas, both of which share properties from the struct Fruit; a sort (inc. interface functions Len, Swap, Less) and the main function which sets up the data structures for apples and bananas and then tries to sort them.

What I am wanting for both Apples and Bananas is one sort (type ByNumSeeds, Len, Swap, Less) that is capable of sorting both Apples and Bananas separately, on a property that they both share from the Fruit struct, AvgNumSeeds.

The sort I have created in this code takes a slice of Apples as its interface and does indeed sort my array of apples by the AvgNumSeeds. However I am unable to find a way to get it to work with both Apple and Banana structs.

My initial thought was to treat the interface as a slice of Fruit but, understandably, I then get the error:

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

My next thought was to fix this error by somehow casting a slice of Apples/Bananas to a slice of Fruit but this doesn't quite feel like the right thing to do.

In my investigation for a solution I've come across a package called sortutil which has a function called AscByField that takes the struct and the name of the field to sort by. I've not tried it yet but the package makes it quite clear that its not efficient as it works by using reflection and to try to use the standard interface method first.

Is there a way I can achieve the sorting of a nested struct without having to duplicate a sort for each 'child' struct type?

The solution to polymorphism is go is interfaces. Embedding on its own doesn't really work here as you see, because you still have distinct types. Here's a re-work of your example to get you started http://play.golang.org/p/7HV_HJ3Gw0 , or maybe this is a little easier to read through (it's common to hide an un-exported struct behind the exported interface) 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)
}

I am wary from your example though, that your trying to force composition to work like inheritance (but that could just be from the simplified example). Embedding doesn't give you an "is a" relationship like inheritance, only a "has a". Having your types provide a common interface allows you to run all compliant types through the same sort function.

The only real gotcha in your example is going to be that []struct is not interchangeable with []interface . If you need to convert the two, you have to create a new slice and copy the values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM