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


129

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

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에 비슷한 간단한 것이 있습니까?)


여기에 또 다른 타사 github.com/patrickmn/sortutil 패키지가 있습니다. 일반 정렬 및 중첩 정렬을 수행 할 수 있습니다. 여기에서 성능에 대한 문서를 인용합니다. "sortutil이 편리하지만 전용 정렬을 능가하지는 않습니다. 성능 측면에서 인터페이스. 정렬 구현. 예를 들어 [] MyStruct를 포함하고 sort.Sort를 수행하는 ByName 유형에 대한 인터페이스 고성능이 필요할 때는 (ByName {MySlice})를 고려해야합니다. "
Tutompita

답변:


63

업데이트 : 이 답변은 이전 버전의 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

8
@jendas Go는 간단하지 않고 간단합니다. 루비는 쉽습니다. 작동 방식을 정확히 알지 못하는 경우에도 시도해 볼 수 있으며 예상대로 작동합니다. 그러나 언어의 사양을 이해하고 인터프리터를 구축하거나 루비를 배우면서 레일스의 코드를 읽으려고하지 마십시오. Go는 간단합니다. 투어 후 언어 사양을 읽어 보는 것이 좋습니다. 초보자도 가능합니다. 그리고 그들은 가장 진보 된 코드를 읽고 얻을 수 있습니다. 간단하기 때문입니다.
kik

4
@kik 말이 안돼. 단순하다는 것이 특징이 없다는 것을 의미하지는 않습니다. 정렬은 라이브러리가 가질 수있는 가장 중요하고 기본적이지만 간단한 기능 중 하나입니다 . Golang은 html 템플릿, crc32 해시, 프린터, 스캐너 등을위한 표준 라이브러리를 가지고 있습니다. 그렇기 때문에 간단하지 않습니다. 표준 라이브러리에 정렬을하지 않는 것은 단순성의 문제가 아니며 모든 언어가 표준으로 간주하는 기본 기능이 누락 된 문제입니다. C조차도 정렬 기능이 있습니다. Golang과 함께 엘리트 주의자가되는 것을 그만두고 Golang이 이것에 대해 틀렸을 수도 있다고 생각하기 시작하십시오 (실제로 가지고 있지 않다면).
Eksapsy

319

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).
Brent Bradburn

planets[:]중요합니다. 그러나 나는 이유를 이해하지 못한다. 그래도 작동합니다.
STEEL

@STEEL 일반적으로 처음에는 배열 대신 슬라이스를 사용해야합니다. 그렇다면 [:].
AndreKR

37

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

이것이 제가 연결 한 장황한 솔루션입니다. 그렇지 않습니까?
Martin Thoma 2015 년

1
내가이 글을 쓰는 동안 당신은 그것을 연결했습니다. 죄송합니다. 그러나 표준 도구 만 사용하면 더 짧은 방법이 없습니다.
jimt

5

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

다음은 데모입니다.


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}
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.