用(任意)字段名称对结构数组进行简单排序的最短方法是什么?


128

我只是有一个问题,我有一系列的结构,例如

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背景一样简单sorted(planets, key=lambda n: n.Axis)-Go中有类似的东西吗?)


这里是另一个第三方github.com/patrickmn/sortutil包。它可以进行常规排序,也可以进行嵌套排序。在这里,我从有关性能的文档中引用:“虽然sortutil很方便,但它不会击败专用的sort。就性能而言,接口。实现sort。用于嵌入例如[] MyStruct并执行sort.Sort的ByName类型的接口。 (ByName {MySlice})应该在需要高性能的情况下考虑。”
Tutompita

Answers:


62

更新:此答案与的旧版本有关go。对于Go 1.8和更高版本,请参见下面AndreKR答案


如果您想要的内容比标准库sort包少一些,可以使用第三方github.com/bradfitz/slice包。它使用一些技巧来生成对切片进行排序所需的LenSwap方法,因此您只需要提供一个Less方法即可。

使用此软件包,您可以使用以下命令执行排序:

slice.Sort(planets[:], func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

planets[:]零件是产生覆盖阵列的切片所必需的。如果您制作planets切片而不是数组,则可以跳过该部分。


28
我必须使用第三方程序包对数组进行排序(除非我想编写数量惊人的冗长代码)?这种语言有什么问题?我的意思是...挺好的!没有黑魔法。
Jendas '16

8
@jendas Go意味着简单而不容易。Ruby很简单。即使不知道确切的工作原理,您也可以尝试一下,并按预期工作。但是,您不敢尝试了解该语言的规范并构建解释器,或者在学习红宝石时阅读Rails的代码。去很简单。游览结束后,建议您阅读语言规范-甚至初学者也可以。他们可以阅读最高级的代码并获取它。因为很简单。
kik

4
@kik没有任何意义。简单并不意味着没有特色。排序是最重要和最基本的一个,但简单的提供图书馆可能有。Golang有一个用于html模板,crc32哈希,打印机,扫描仪等的标准库。这确实使其不那么简单。在标准库中不进行排序不是简单的问题,而是缺少所有语言都认为是标准的基本功能的问题。甚至C也具有排序功能。不再对Golang这么礼貌,开始考虑Golang在这方面可能是错的(如果确实没有)。
Eksapsy

317

从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
})

排序会更改数组,因此,如果您确实需要,可以在排序后继续使用数组而不是切片。


sort.Slice有点令人惊讶。该less函数仅采用索引,因此(在此答案中)必须使用单独捕获的planets数组。似乎没有什么可以强制要求排序后的分片和less函数对同一数据进行操作。为此,您必须键入planets3次(DRY)。
布伦特·布拉德本

planets[:]至关重要。但我不明白为什么。虽然有效。

@STEEL通常,首先应该使用切片而不是数组。然后,您不需要[:]
AndreKR

37

从Go 1.8开始,@ AndreKR的答案是更好的解决方案。


您可以实现一个实现排序接口的集合类型。

是两个这样的类型的示例,允许您按Axis或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)
}

这正是我链接的详细解决方案,不是吗?
Martin Thoma 2015年

1
我写这篇文章时,您已将其链接。我很抱歉。但是,仅使用标准工具,就没有更短的方法了。
jimt 2015年

5

您可以代替Sort interface[]Planet包含集合和将进行比较的闭包的类型上实施on实现。您必须为每个属性提供比较闭包的实现。

我觉得此方法比为结构的每个属性实现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)
}

这是一个演示


3

这是减少一些锅炉板的另一种方法。免责声明,它使用反射和损失类型的安全性。

这是一个演示

所有的魔术都发生在Prop函数中。它接受struct属性进行排序,并对排序顺序进行排序(升序,降序),并返回执行比较的函数。

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}
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.