在Go语言中对共享的嵌套结构属性进行排序。

huangapple go评论99阅读模式
英文:

Sorting on a shared nested struct property in Go

问题

我有一组从rethink数据库中提取的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)
}

如你所见,我有两个结构体,Apples和Bananas,它们都共享来自Fruit结构体的属性;还有一个排序(包括接口函数Len、Swap、Less)和主函数,用于设置apples和bananas的数据结构,然后尝试对它们进行排序。

我希望对于Apples和Bananas都能有一个排序(类型为ByNumSeeds,包含Len、Swap、Less函数),能够分别对Apples和Bananas进行排序,根据它们从Fruit结构体继承的AvgNumSeeds属性。

我在这段代码中创建的排序函数接受一个Apples切片作为接口,并且确实能够按照AvgNumSeeds对我的apples数组进行排序。然而,我无法找到一种方法使其适用于Apple和Banana结构体。

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

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

接下来,我考虑通过某种方式将Apples/Bananas切片转换为Fruit切片来修复此错误,但这不太像是正确的做法。

在寻找解决方案时,我发现了一个名为sortutil的包,其中有一个名为AscByField的函数,它接受结构体和要排序的字段的名称。我还没有尝试过,但该包明确表示它不是高效的,因为它使用反射,并尝试首先使用标准接口方法。

有没有办法在不必为每个“子”结构体类型重复排序的情况下实现对嵌套结构体的排序?

英文:

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 (
&quot;fmt&quot;
&quot;sort&quot;
)
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 &lt; p[j].AvgNumSeeds
}
func main() {
apples := []Apple{
Apple{Fruit: Fruit{AvgNumSeeds: 4, Name: &quot;Cox&quot;}, Diameter: 10},
Apple{Fruit: Fruit{AvgNumSeeds: 6, Name: &quot;Granny Smith&quot;}, Diameter: 20},
Apple{Fruit: Fruit{AvgNumSeeds: 5, Name: &quot;Pink Lady&quot;}, Diameter: 21},
Apple{Fruit: Fruit{AvgNumSeeds: 2, Name: &quot;Russett&quot;}, Diameter: 15},
Apple{Fruit: Fruit{AvgNumSeeds: 1, Name: &quot;Crab&quot;}, Diameter: 7},
Apple{Fruit: Fruit{AvgNumSeeds: 7, Name: &quot;Brambley&quot;}, Diameter: 40},
Apple{Fruit: Fruit{AvgNumSeeds: 3, Name: &quot;Braeburn&quot;}, Diameter: 25},
}
bananas := []Banana{
Banana{Fruit: Fruit{AvgNumSeeds: 40, Name: &quot;Lacatan&quot;}, Length: 20},
Banana{Fruit: Fruit{AvgNumSeeds: 60, Name: &quot;Lady Finger&quot;}, Length: 22},
Banana{Fruit: Fruit{AvgNumSeeds: 50, Name: &quot;Senorita&quot;}, Length: 25},
Banana{Fruit: Fruit{AvgNumSeeds: 20, Name: &quot;Cavendish&quot;}, Length: 30},
Banana{Fruit: Fruit{AvgNumSeeds: 10, Name: &quot;Goldfinger&quot;}, Length: 27},
Banana{Fruit: Fruit{AvgNumSeeds: 70, Name: &quot;Gros Michel&quot;}, Length: 15},
Banana{Fruit: Fruit{AvgNumSeeds: 30, Name: &quot;Red Dacca&quot;}, Length: 19},
}
fmt.Println(&quot;Apples&quot;)
fmt.Printf(&quot;%+v\n\n&quot;, apples)
sort.Sort(ByNumSeeds(apples))
fmt.Printf(&quot;%+v\n\n\n&quot;, apples)
fmt.Println(&quot;Bananas&quot;)
fmt.Printf(&quot;%+v\n\n&quot;, bananas)
//sort.Sort(ByNumSeeds(bananas))
fmt.Printf(&quot;%+v\n\n&quot;, 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?

答案1

得分: 3

多态的解决方案在Go语言中是使用接口。仅仅使用嵌入是不太适用的,因为你仍然有不同的类型。以下是你的示例的重新编写,帮助你入门: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互换。如果你需要转换这两者,你必须创建一个新的切片并复制值。

英文:

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 (
&quot;fmt&quot;
&quot;sort&quot;
)
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() &lt; p[j].AvgNumSeeds()
}
func main() {
apples := []Fruit{
Apple{fruit: fruit{avgNumSeeds: 4, name: &quot;Cox&quot;}, Diameter: 10},
Apple{fruit: fruit{avgNumSeeds: 6, name: &quot;Granny Smith&quot;}, Diameter: 20},
Apple{fruit: fruit{avgNumSeeds: 5, name: &quot;Pink Lady&quot;}, Diameter: 21},
Apple{fruit: fruit{avgNumSeeds: 2, name: &quot;Russett&quot;}, Diameter: 15},
Apple{fruit: fruit{avgNumSeeds: 1, name: &quot;Crab&quot;}, Diameter: 7},
Apple{fruit: fruit{avgNumSeeds: 7, name: &quot;Brambley&quot;}, Diameter: 40},
Apple{fruit: fruit{avgNumSeeds: 3, name: &quot;Braeburn&quot;}, Diameter: 25},
}
bananas := []Fruit{
Banana{fruit: fruit{avgNumSeeds: 40, name: &quot;Lacatan&quot;}, Length: 20},
Banana{fruit: fruit{avgNumSeeds: 60, name: &quot;Lady Finger&quot;}, Length: 22},
Banana{fruit: fruit{avgNumSeeds: 50, name: &quot;Senorita&quot;}, Length: 25},
Banana{fruit: fruit{avgNumSeeds: 20, name: &quot;Cavendish&quot;}, Length: 30},
Banana{fruit: fruit{avgNumSeeds: 10, name: &quot;Goldfinger&quot;}, Length: 27},
Banana{fruit: fruit{avgNumSeeds: 70, name: &quot;Gros Michel&quot;}, Length: 15},
Banana{fruit: fruit{avgNumSeeds: 30, name: &quot;Red Dacca&quot;}, Length: 19},
}
fmt.Println(&quot;Apples&quot;)
fmt.Printf(&quot;%+v\n\n&quot;, apples)
sort.Sort(ByNumSeeds(apples))
fmt.Printf(&quot;%+v\n\n\n&quot;, apples)
fmt.Println(&quot;Bananas&quot;)
fmt.Printf(&quot;%+v\n\n&quot;, bananas)
sort.Sort(ByNumSeeds(bananas))
fmt.Printf(&quot;%+v\n\n&quot;, 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.

huangapple
  • 本文由 发表于 2014年3月6日 23:26:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/22228791.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定