英文:
What is the shortest way to simply sort an array of structs by (arbitrary) field names?
问题
我刚刚遇到了一个问题,我有一个结构体数组,例如:
package main
import "log"
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"axis"` // in km
Radius float64 `json:"radius"`
}
func main() {
var mars = new(Planet)
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth = new(Planet)
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus = new(Planet)
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
planets := [...]Planet{*mars, *venus, *earth}
log.Println(planets)
}
假设你想按Axis
进行排序。你该如何做到这一点?
(注意:我已经看过http://golang.org/pkg/sort/,它似乎可以工作,但我需要添加大约20行代码来实现按一个非常简单的键进行排序。我有Python的背景,用Python可以很简单地实现sorted(planets, key=lambda n: n.Axis)
- 在Go语言中有类似简单的方法吗?)
英文:
I just had a problem where I had an array of structs, e.g.
package main
import "log"
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
func main() {
var mars = new(Planet)
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth = new(Planet)
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus = new(Planet)
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
planets := [...]Planet{*mars, *venus, *earth}
log.Println(planets)
}
Lets say you want to sort it by Axis
. How do you do that?
(Note: I have seen http://golang.org/pkg/sort/ and it seems to work, but I have to add about 20 lines just for simple sorting by a very simple key. I have a python background where it is as simple as sorted(planets, key=lambda n: n.Axis)
- is there something similar simple in Go?)
答案1
得分: 588
从Go 1.8开始,你现在可以使用sort.Slice来对切片进行排序:
sort.Slice(planets, func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
通常情况下,没有理由使用数组而不是切片,但在你的示例中,你确实使用了一个数组,所以你需要用切片覆盖它(添加[:]
)以使其与sort.Slice
一起工作:
sort.Slice(planets[:], func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
排序会改变数组,所以如果你真的想要的话,可以在排序后继续使用数组而不是切片。
英文:
As of Go 1.8 you can now use sort.Slice to sort a slice:
sort.Slice(planets, func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
There is normally no reason to use an array instead of a slice, but in your example you are using an array, so you have to overlay it with a slice (add [:]
) to make it work with sort.Slice
:
sort.Slice(planets[:], func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
The sorting changes the array, so if you really want you can continue to use the array instead of the slice after the sorting.
答案2
得分: 99
**更新:**此答案与较旧版本的go
相关。对于Go 1.8及更高版本,请参阅上面的AndreKR的答案。
如果你想要比标准库sort
包更简洁的解决方案,你可以使用第三方的github.com/bradfitz/slice
包。它使用一些技巧来生成排序切片所需的Len
和Swap
方法,因此你只需要提供一个Less
方法。
使用这个包,你可以进行排序:
slice.Sort(planets[:], func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
planets[:]
部分是必要的,以生成覆盖你的数组的切片。如果你将planets
定义为切片而不是数组,你可以跳过这部分。
英文:
UPDATE: This answer relates to older versions of go
. For Go 1.8 and newer, see the AndreKR's answer above.
If you want something a bit less verbose than the standard library sort
package, you could use the third party github.com/bradfitz/slice
package. It uses some tricks to generate the Len
and Swap
methods needed to sort your slice, so you only need to provide a Less
method.
With this package, you can perform the sort with:
slice.Sort(planets[:], func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
The planets[:]
part is necessary to produce a slice covering your array. If you make planets
a slice instead of an array you could skip that part.
答案3
得分: 50
截至Go 1.8版本,@AndreKR的答案是更好的解决方案。
你可以实现一个实现了sort接口的集合类型。
这里有一个示例,其中有两种类型,可以按照Axis或Name进行排序:
package main
import "log"
import "sort"
// AxisSorter按照轴排序行星。
type AxisSorter []Planet
func (a AxisSorter) Len() int { return len(a) }
func (a AxisSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a AxisSorter) Less(i, j int) bool { return a[i].Axis < a[j].Axis }
// NameSorter按照名称排序行星。
type NameSorter []Planet
func (a NameSorter) Len() int { return len(a) }
func (a NameSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a NameSorter) Less(i, j int) bool { return a[i].Name < a[j].Name }
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"axis"` // in km
Radius float64 `json:"radius"`
}
func main() {
var mars Planet
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth Planet
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus Planet
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
planets := []Planet{mars, venus, earth}
log.Println("unsorted:", planets)
sort.Sort(AxisSorter(planets))
log.Println("by axis:", planets)
sort.Sort(NameSorter(planets))
log.Println("by name:", planets)
}
英文:
As of Go 1.8, @AndreKR's answer is the better solution.
You can implement a collection type which implements the sort interface.
Here's an example of two such types which allow you to sort either by Axis or Name:
package main
import "log"
import "sort"
// AxisSorter sorts planets by axis.
type AxisSorter []Planet
func (a AxisSorter) Len() int { return len(a) }
func (a AxisSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a AxisSorter) Less(i, j int) bool { return a[i].Axis < a[j].Axis }
// NameSorter sorts planets by name.
type NameSorter []Planet
func (a NameSorter) Len() int { return len(a) }
func (a NameSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a NameSorter) Less(i, j int) bool { return a[i].Name < a[j].Name }
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
func main() {
var mars Planet
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth Planet
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus Planet
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
planets := []Planet{mars, venus, earth}
log.Println("unsorted:", planets)
sort.Sort(AxisSorter(planets))
log.Println("by axis:", planets)
sort.Sort(NameSorter(planets))
log.Println("by name:", planets)
}
答案4
得分: 7
你可以在一个包含集合和闭包的类型上实现Sort
接口,而不是在[]Planet
上实现。你需要为每个属性提供比较闭包的实现。
我觉得这种方法比为结构体的每个属性实现一个Sort类型要好。
这个答案几乎是从sort文档中抄袭过来的,所以我不能为此太多的功劳。
package main
import (
"log"
"sort"
)
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
type By func(p1, p2 *Planet) bool
func (by By) Sort(planets []Planet) {
ps := &planetSorter{
planets: planets,
by: by,
}
sort.Sort(ps)
}
type planetSorter struct {
planets []Planet
by func(p1, p2 *Planet) bool
}
func (s *planetSorter) Len() int {
return len(s.planets)
}
func (s *planetSorter) Swap(i, j int) {
s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}
func (s *planetSorter) Less(i, j int) bool {
return s.by(&s.planets[i], &s.planets[j])
}
func main() {
/* Same code as in the question */
planets := []Planet{*mars, *venus, *earth}
By(func(p1, p2 *Planet) bool {
return p1.Name < p2.Name
}).Sort(planets)
log.Println(planets)
By(func(p1, p2 *Planet) bool {
return p1.Axis < p2.Axis
}).Sort(planets)
log.Println(planets)
}
[这里有一个演示][2]
[1]: http://golang.org/pkg/sort/
[2]: https://play.golang.org/p/1hqYY6D24Z
如何调用它:
func main() {
/* Same code as in the question */
planets := []Planet{*mars, *venus, *earth}
By(func(p1, p2 *Planet) bool {
return p1.Name < p2.Name
}).Sort(planets)
log.Println(planets)
By(func(p1, p2 *Planet) bool {
return p1.Axis < p2.Axis
}).Sort(planets)
log.Println(planets)
}
英文:
You can, instead of implementing the Sort interface
on []Planet
you implement on a type that contains the collection and a closure that will do the comparison. You have to provide the implementation for the comparison closure for each property.
This method I feel is better than implementing a Sort type for each property of the struct.
This answer is almost ripped right from the sort docs so I can't take to much credit for it
package main
import (
"log"
"sort"
)
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
type By func(p1, p2 *Planet) bool
func (by By) Sort(planets []Planet) {
ps := &planetSorter{
planets: planets,
by: by,
}
sort.Sort(ps)
}
type planetSorter struct {
planets []Planet
by func(p1, p2 *Planet) bool
}
func (s *planetSorter) Len() int {
return len(s.planets)
}
func (s *planetSorter) Swap(i, j int) {
s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}
func (s *planetSorter) Less(i, j int) bool {
return s.by(&s.planets[i], &s.planets[j])
}
How to call it.
func main() {
/* Same code as in the question */
planets := []Planet{*mars, *venus, *earth}
By(func(p1, p2 *Planet) bool {
return p1.Name < p2.Name
}).Sort(planets)
log.Println(planets)
By(func(p1, p2 *Planet) bool {
return p1.Axis < p2.Axis
}).Sort(planets)
log.Println(planets)
}
答案5
得分: 3
这里有另一种减少一些样板代码的方法。免责声明,它使用反射并且会损失类型安全性。
所有的魔法都发生在Prop
函数中。它接受要排序的结构体属性和排序顺序(升序、降序),并返回一个执行比较的函数。
package main
import (
"log"
"reflect"
"sort"
)
func test(planets []Planet) {
log.Println("Sort Name")
By(Prop("Name", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Aphelion")
By(Prop("Aphelion", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Perihelion")
By(Prop("Perihelion", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Axis")
By(Prop("Axis", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Radius")
By(Prop("Radius", true)).Sort(planets)
log.Println(planets)
}
func Prop(field string, asc bool) func(p1, p2 *Planet) bool {
return func(p1, p2 *Planet) bool {
v1 := reflect.Indirect(reflect.ValueOf(p1)).FieldByName(field)
v2 := reflect.Indirect(reflect.ValueOf(p2)).FieldByName(field)
ret := false
switch v1.Kind() {
case reflect.Int64:
ret = int64(v1.Int()) < int64(v2.Int())
case reflect.Float64:
ret = float64(v1.Float()) < float64(v2.Float())
case reflect.String:
ret = string(v1.String()) < string(v2.String())
}
if asc {
return ret
}
return !ret
}
}
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
type By func(p1, p2 *Planet) bool
func (by By) Sort(planets []Planet) {
ps := &planetSorter{
planets: planets,
by: by, // The Sort method's receiver is the function (closure) that defines the sort order.
}
sort.Sort(ps)
}
type planetSorter struct {
planets []Planet
by func(p1, p2 *Planet) bool // Closure used in the Less method.
}
// Len is part of sort.Interface.
func (s *planetSorter) Len() int { return len(s.planets) }
// Swap is part of sort.Interface.
func (s *planetSorter) Swap(i, j int) {
s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s *planetSorter) Less(i, j int) bool {
return s.by(&s.planets[i], &s.planets[j])
}
func main() {
test(dataSet())
}
func dataSet() []Planet {
var mars = new(Planet)
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth = new(Planet)
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus = new(Planet)
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
return []Planet{*mars, *venus, *earth}
}
英文:
Here is another way to reduce some of the boiler plate. Disclaimer, it uses reflection and losses type safety.
All the magic happens in the Prop
function. It takes the struct property to sort on and the order it which you want to sort (ascending, descending) and returns a function that will perform the comparisons.
package main
import (
"log"
"reflect"
"sort"
)
func test(planets []Planet) {
log.Println("Sort Name")
By(Prop("Name", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Aphelion")
By(Prop("Aphelion", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Perihelion")
By(Prop("Perihelion", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Axis")
By(Prop("Axis", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Radius")
By(Prop("Radius", true)).Sort(planets)
log.Println(planets)
}
func Prop(field string, asc bool) func(p1, p2 *Planet) bool {
return func(p1, p2 *Planet) bool {
v1 := reflect.Indirect(reflect.ValueOf(p1)).FieldByName(field)
v2 := reflect.Indirect(reflect.ValueOf(p2)).FieldByName(field)
ret := false
switch v1.Kind() {
case reflect.Int64:
ret = int64(v1.Int()) < int64(v2.Int())
case reflect.Float64:
ret = float64(v1.Float()) < float64(v2.Float())
case reflect.String:
ret = string(v1.String()) < string(v2.String())
}
if asc {
return ret
}
return !ret
}
}
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
type By func(p1, p2 *Planet) bool
func (by By) Sort(planets []Planet) {
ps := &planetSorter{
planets: planets,
by: by, // The Sort method's receiver is the function (closure) that defines the sort order.
}
sort.Sort(ps)
}
type planetSorter struct {
planets []Planet
by func(p1, p2 *Planet) bool // Closure used in the Less method.
}
// Len is part of sort.Interface.
func (s *planetSorter) Len() int { return len(s.planets) }
// Swap is part of sort.Interface.
func (s *planetSorter) Swap(i, j int) {
s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s *planetSorter) Less(i, j int) bool {
return s.by(&s.planets[i], &s.planets[j])
}
func main() {
test(dataSet())
}
func dataSet() []Planet {
var mars = new(Planet)
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth = new(Planet)
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus = new(Planet)
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
return []Planet{*mars, *venus, *earth}
}
答案6
得分: 1
你也可以使用快速排序来实现,在分区函数中选择要按哪个字段进行排序,例如我选择了按照名称排序。
package main
import (
"fmt"
)
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"axis"` // in km
Radius float64 `json:"radius"`
}
func main() {
var mars Planet
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth Planet
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus Planet
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
planets := []Planet{mars, venus, earth}
fmt.Println(quickSort(&planets, 0, len(planets)-1))
}
func quickSort(arr *[]Planet, start, end int) []Planet {
if start < end {
partitionIndex := partition(*arr, start, end)
quickSort(arr, start, partitionIndex-1)
quickSort(arr, partitionIndex+1, end)
}
return *arr
}
func partition(arr []Planet, start, end int) int {
pivot := arr[end].Name
pIndex := start
for i := start; i < end; i++ {
if arr[i].Name <= pivot {
// swap
arr[i], arr[pIndex] = arr[pIndex], arr[i]
pIndex++
}
}
arr[pIndex], arr[end] = arr[end], arr[pIndex]
return pIndex
}
英文:
You can implement using quick sort as well and inside the partition func, you choose which field to sort by, I choose Name for example.
package main
import (
"fmt"
)
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
func main() {
var mars Planet
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth Planet
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus Planet
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
planets := []Planet{mars, venus, earth}
fmt.Println(quickSort(&planets,0,len(planets)-1))
}
func quickSort(arr *[]Planet, start, end int)[]Planet{
if start < end{
partitionIndex := partition(*arr,start,end)
quickSort(arr,start,partitionIndex-1)
quickSort(arr,partitionIndex+1, end)
}
return *arr
}
func partition(arr []Planet, start, end int) int{
pivot := arr[end].Name
pIndex := start
for i:= start; i<end; i++{
if arr[i].Name <= pivot{
// swap
arr[i],arr[pIndex] = arr[pIndex],arr[i]
pIndex++
}
}
arr[pIndex],arr[end] = arr[end],arr[pIndex]
return pIndex
}
答案7
得分: 0
其他答案已经非常好了。
我想为按照一个字符串值进行字母排序的结构体添加一个示例。在Go中基本上是相同的,对于有其他编程语言背景的人可能会有所帮助
type My struct {
Val string
}
func main() {
m1 := My{Val: "B"}
m2 := My{Val: "C"}
m3 := My{Val: "A"}
m4 := My{Val: "D"}
mList := []My{m1, m2, m3, m4}
sort.Slice(mList, func(i, j int) bool {
return mList[i].Val < mList[j].Val
})
fmt.Println("Sorted:", mList[0], mList[1], mList[2], mList[3])
}
英文:
The other answers are already pretty good.
I want to add this example for sorting alphabetically by a structs Value that is a string. Pretty much the same in Go, might be helpful to someone with other programming language background
type My struct {
Val string
}
func main() {
m1 := My{Val: "B"}
m2 := My{Val: "C"}
m3 := My{Val: "A"}
m4 := My{Val: "D"}
mList := []My{m1, m2, m3, m4}
sort.Slice(mList, func(i, j int) bool {
return mList[i].Val < mList[j].Val
})
fmt.Println("Sorted:", mList[0], mList[1], mList[2], mList[3])
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论