developer tip

(임의의) 필드 이름으로 구조체 배열을 정렬하는 가장 짧은 방법은 무엇입니까?

copycodes 2020. 9. 17. 08:03
반응형

(임의의) 필드 이름으로 구조체 배열을 정렬하는 가장 짧은 방법은 무엇입니까?


방금 구조체 배열이있는 문제가 발생했습니다.

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 줄을 추가해야합니다. 여기에 파이썬 배경이 있습니다. 간단하게 sorted(planets, key=lambda n: n.Axis)-Go에 비슷한 간단한 것이 있습니까?)


업데이트 : 이 답변은 이전 버전의 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배열 대신 슬라이스 를 만드는 경우 해당 부분을 건너 뛸 수 있습니다.


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

정렬은 배열을 변경하므로 실제로 원하는 경우 정렬 후 슬라이스 대신 배열을 계속 사용할 수 있습니다.


Go 1.8부터 @AndreKR의 대답 이 더 나은 솔루션입니다.


정렬 인터페이스 를 구현하는 컬렉션 유형을 구현할 수 있습니다 .

다음 은 축 또는 이름별로 정렬 할 수있는 두 가지 유형 의 예 입니다.

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

Sort interfaceon []Planet구현하는 대신 컬렉션을 포함하는 유형과 비교를 수행 할 클로저를 구현할 수 있습니다 . 각 속성에 대한 비교 종료에 대한 구현을 제공해야합니다.

이 방법은 구조체의 각 속성에 대해 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)
}

다음은 데모입니다.


보일러 플레이트의 일부를 줄이는 또 다른 방법이 있습니다. 면책 조항, 반사 및 손실 유형 안전을 사용합니다.

다음은 데모입니다.

모든 마법은 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}
}

참고 URL : https://stackoverflow.com/questions/28999735/what-is-the-shortest-way-to-simply-sort-an-array-of-structs-by-arbitrary-field

반응형