【问题标题】:What is the shortest way to simply sort an array of structs by (arbitrary) field names?按(任意)字段名称对结构数组进行简单排序的最短方法是什么?
【发布时间】:2015-03-12 00:07:48
【问题描述】:

我只是遇到了一个问题,我有一个结构数组,例如

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 很方便,但它不会胜过专用排序。就性能而言的接口。实现排序。嵌入例如 []MyStruct 并执行 sort.Sort 的类型 ByName 的接口需要高性能时应考虑 (ByName{MySlice})。"

标签: sorting go


【解决方案1】:

从 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 函数对相同数据进行操作。为此,您必须键入 planets 三次(DRY)。
  • planets[:] 至关重要。但我不明白为什么。虽然有效。
  • @STEEL 通常你应该首先使用切片而不是数组。那你就不需要[:]了。
【解决方案2】:

更新:此答案与 go 的旧版本有关。对于 Go 1.8 及更高版本,请参阅AndreKR's answer above


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

使用此包,您可以执行排序:

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

planets[:] 部分是生成覆盖数组的切片所必需的。如果您将 planets 设为切片而不是数组,则可以跳过该部分。

【讨论】:

  • 我必须使用第三方包对数组进行排序(除非我想编写大量冗长的代码)?这种语言有什么问题?我的意思是......这只是排序!没有黑魔法。
  • @jendas Go 意味着简单,并不容易。红宝石很容易。即使不确切知道某事是如何工作的,您也可以尝试,它会按预期工作。但是你不敢尝试理解语言的规范并构建一个解释器,或者在学习 ruby​​ 的同时阅读 rails 的代码。去很简单。建议您在游览结束后阅读语言规范 - 即使是初学者也可以。他们可以阅读最先进的代码并获得它。因为它很简单。
  • @kik 这确实没有意义。简单并不意味着没有特色。排序是库可以拥有的最重要和最基本的但简单功能之一。 Golang 有一个用于 html 模板、crc32 哈希、打印机、扫描仪等的标准库。这确实使它变得不简单。在您的标准库中没有排序不是简单的问题,而是缺少所有语言都认为是标准的基本功能的问题。甚至 C 也有排序功能。不要再成为 Golang 的精英主义者,开始考虑 Golang 可能只是在这个问题上是错误的(如果它确实没有的话)。
  • func Slice在go1.8中添加
【解决方案3】:

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


您可以实现一个集合类型,它实现了sort interface

Here's an example 有两种允许您按轴或名称排序的类型:

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】:

您可以在包含集合和将进行比较的闭包的类型上实现,而不是在 []Planet 上实现 Sort interface。您必须为每个属性提供比较闭包的实现。

我觉得这种方法比为结构的每个属性都实现一个排序类型要好。

这个答案几乎是从sort docs 撕下来的,所以我不能太相信它

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

Here is a Demo

【讨论】:

    【解决方案5】:

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

    Here is a Demo

    所有的魔法都发生在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}
    }
    

    【讨论】:

      【解决方案6】:

      您也可以使用快速排序来实现,在分区函数内,您可以选择要排序的字段,例如我选择名称。

      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
      }
      

      【讨论】:

        猜你喜欢
        • 2019-08-17
        • 2023-03-11
        • 2010-09-12
        • 1970-01-01
        • 2014-01-03
        • 2011-12-09
        • 2015-03-31
        • 2012-08-18
        • 1970-01-01
        相关资源
        最近更新 更多