Cách ngắn nhất để sắp xếp đơn giản một mảng cấu trúc theo tên trường (tùy ý) là gì?


129

Tôi vừa gặp sự cố trong đó tôi có một mảng cấu trúc, ví dụ:

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

Giả sử bạn muốn sắp xếp nó theo Axis. Làm thế nào để bạn làm điều đó?

(Lưu ý: Tôi đã xem http://golang.org/pkg/sort/ và nó có vẻ hoạt động, nhưng tôi phải thêm khoảng 20 dòng chỉ để phân loại đơn giản bằng một phím rất đơn giản. Tôi có nền python ở đó đơn giản như sorted(planets, key=lambda n: n.Axis)- có điều gì đó tương tự đơn giản trong cờ vây không?)


Đây là một gói github.com/patrickmn/sortutil của bên thứ ba khác . Nó có thể phân loại bình thường và cũng có thể phân loại lồng nhau. Ở đây tôi trích dẫn từ tài liệu về hiệu suất: "Mặc dù sortutil rất tiện lợi, nhưng nó sẽ không đánh bại một loại chuyên dụng. Giao diện về mặt hiệu suất. Thực hiện sắp xếp. Giao diện cho một loại ByName nhúng ví dụ như [] MyStruct và thực hiện sắp xếp. Sắp xếp. (ByName {MySlice}) nên được xem xét khi yêu cầu hiệu suất cao. "
Tutompita

Câu trả lời:


63

CẬP NHẬT: Câu trả lời này liên quan đến các phiên bản cũ hơn của go. Đối với Go 1.8 và mới hơn, hãy xem câu trả lời của AndreKR bên dưới .


Nếu bạn muốn thứ gì đó ít dài hơn một chút so với sortgói thư viện tiêu chuẩn , bạn có thể sử dụng github.com/bradfitz/slicegói của bên thứ ba . Nó sử dụng một số thủ thuật để tạo LenSwapcác phương thức cần thiết để sắp xếp lát cắt của bạn, vì vậy bạn chỉ cần cung cấp một Lessphương thức.

Với gói này, bạn có thể thực hiện sắp xếp với:

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

Phần planets[:]này cần thiết để tạo ra một lát cắt bao phủ mảng của bạn. Nếu bạn tạo planetsmột lát thay vì một mảng, bạn có thể bỏ qua phần đó.


28
Tôi phải sử dụng gói của bên thứ ba để sắp xếp một mảng (trừ khi tôi muốn viết số lượng mã dài dòng đáng kinh ngạc)? Có gì sai với ngôn ngữ này? Ý tôi là ... Nó chỉ là sự sắp xếp! Không có ma thuật đen.
Jendas

8
@jendas Go có nghĩa là đơn giản, không dễ dàng. Ruby rất dễ dàng. Ngay cả khi không biết chính xác cách nào đó hoạt động, bạn có thể thử và nó sẽ hoạt động như mong đợi. Nhưng bạn không dám cố gắng hiểu các thông số kỹ thuật của ngôn ngữ và xây dựng một trình thông dịch, hoặc đọc mã rails trong khi học ruby. Go là đơn giản. Sau chuyến tham quan, bạn nên đọc thông số ngôn ngữ - ngay cả người mới bắt đầu cũng có thể. Và họ có thể đọc mã tiên tiến nhất và lấy nó. Bởi vì nó đơn giản.
kik

4
@kik Điều đó không có ý nghĩa. Đơn giản không có nghĩa là kỳ công. Sắp xếp là một trong những tính năng quan trọng và cơ bản nhưng đơn giản mà thư viện có thể có. Golang có một thư viện tiêu chuẩn cho các mẫu html, mã băm crc32, máy in, máy quét, v.v. Điều đó làm cho nó KHÔNG CÓ ÍT NHẤT. Không có sự sắp xếp trong thư viện tiêu chuẩn của bạn không phải là vấn đề đơn giản mà là vấn đề thiếu các tính năng cơ bản mà tất cả các ngôn ngữ đều coi là tiêu chuẩn. Ngay cả C cũng có chức năng Sắp xếp. Hãy dừng lại những người theo chủ nghĩa tinh hoa với Golang và bắt đầu xem xét rằng Golang có thể đã sai trong điều này (nếu nó thực sự không mắc phải).
Eksapsy

319

Kể từ Go 1.8, bây giờ bạn có thể sử dụng sort.Slice để sắp xếp một lát:

sort.Slice(planets, func(i, j int) bool {
  return planets[i].Axis < planets[j].Axis
})

Thông thường không có lý do gì để sử dụng một mảng thay vì một lát cắt, nhưng trong ví dụ của bạn, bạn đang sử dụng một mảng, vì vậy bạn phải phủ lên nó một lát cắt (thêm [:]) để làm cho nó hoạt động với sort.Slice:

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

Việc sắp xếp thay đổi mảng, vì vậy nếu bạn thực sự muốn, bạn có thể tiếp tục sử dụng mảng thay vì lát sau khi sắp xếp.


sort.Slicelà loại đáng ngạc nhiên. Các lesschức năng chỉ mất chỉ số vì vậy nó phải (trong câu trả lời này) sử dụng một cách riêng biệt-bắt planetsmảng. Dường như không có gì bắt buộc rằng lát cắt được sắp xếp và lesshàm đang hoạt động trên cùng một dữ liệu. Để làm việc này, bạn phải gõ planetsba lần (DRY).
Brent Bradburn

planets[:]quan trọng. Nhưng tôi không hiểu tại sao. Hoạt động mặc dù.
STEEL

@STEEL Thông thường, bạn nên sử dụng một lát cắt thay vì một mảng ngay từ đầu. Sau đó, bạn không cần [:].
AndreKR

37

Đối với Go 1.8, câu trả lời của @ AndreKR là giải pháp tốt hơn.


Bạn có thể triển khai một kiểu tập hợp thực hiện giao diện sắp xếp .

Dưới đây là một ví dụ về hai kiểu như vậy cho phép bạn sắp xếp theo Trục hoặc Tên:

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

Đây chính xác là giải pháp dài dòng mà tôi đã liên kết, phải không?
Martin Thoma

1
Bạn đã liên kết nó trong khi tôi viết bài này. Lời xin lỗi của tôi. Nhưng chỉ sử dụng các công cụ tiêu chuẩn, không có cách nào ngắn hơn để làm điều này.
jimt

5

Bạn có thể, thay vì triển khai Sort interfacetrên []Planetbạn triển khai trên một kiểu có chứa tập hợp và một bao đóng sẽ thực hiện so sánh. Bạn phải cung cấp việc triển khai để đóng so sánh cho từng thuộc tính.

Phương pháp này tôi cảm thấy tốt hơn là triển khai kiểu Sắp xếp cho mỗi thuộc tính của struct.

Câu trả lời này gần như được tách ra ngay từ các tài liệu sắp xếp nên tôi không thể coi thường nó

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

Làm thế nào để gọi nó.

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

Đây là một Demo


3

Đây là một cách khác để giảm bớt một số tấm lò hơi. Tuyên bố từ chối trách nhiệm, nó sử dụng sự phản ánh và kiểu mất an toàn.

Đây là một Demo

Tất cả những điều kỳ diệu xảy ra trong Prophàm. Nó có thuộc tính struct để sắp xếp và thứ tự mà bạn muốn sắp xếp (tăng dần, giảm dần) và trả về một hàm sẽ thực hiện các phép so sánh.

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}
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.