อะไรคือวิธีที่สั้นที่สุดในการจัดเรียงอาร์เรย์ของโครงสร้างตามชื่อฟิลด์ (ตามอำเภอใจ)


130

ฉันเพิ่งมีปัญหาที่ฉันมีอาร์เรย์ของโครงสร้างเช่น

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 จะสะดวก แต่ก็ไม่สามารถเอาชนะการเรียงลำดับเฉพาะได้อินเทอร์เฟซในแง่ของประสิทธิภาพการใช้งานการจัดเรียงอินเทอร์เฟซสำหรับประเภท ByName ซึ่งฝังเช่น [] MyStruct และการจัดเรียงลำดับ (ByName {MySlice}) ควรได้รับการพิจารณาเมื่อต้องการประสิทธิภาพสูง "
Tutompita

คำตอบ:


63

UPDATE:คำตอบนี้เกี่ยวข้องกับเวอร์ชันเก่าของgo. เพื่อไป 1.8 และใหม่กว่าให้ดูคำตอบ AndreKR ด้านล่าง


หากคุณต้องการบางสิ่งบางอย่างที่ละเอียดน้อยกว่าsortแพ็คเกจไลบรารีมาตรฐานคุณสามารถใช้github.com/bradfitz/sliceแพ็คเกจของบุคคลที่สามได้ ใช้เทคนิคบางอย่างเพื่อสร้างLenและSwapวิธีการที่จำเป็นในการจัดเรียงชิ้นส่วนของคุณดังนั้นคุณต้องระบุLessวิธีการเท่านั้น

ด้วยแพ็คเกจนี้คุณสามารถดำเนินการจัดเรียงด้วย:

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

planets[:]ส่วนหนึ่งเป็นสิ่งที่จำเป็นในการผลิตชิ้นครอบคลุมอาร์เรย์ของคุณ หากคุณสร้างสplanetsไลซ์แทนอาร์เรย์คุณสามารถข้ามส่วนนั้นได้


28
ฉันต้องใช้แพ็คเกจของบุคคลที่สามเพื่อจัดเรียงอาร์เรย์ (เว้นแต่ฉันต้องการเขียนโค้ด verbose จำนวนมากอย่างไม่น่าเชื่อ)? ภาษานี้ผิดอะไร ฉันหมายถึง ... มันก็แค่จัดเรียง! ไม่มีมนต์ดำ.
Jendas

8
@jendas Go ตั้งใจจะง่ายไม่ง่าย ทับทิมเป็นเรื่องง่าย แม้ว่าจะไม่รู้ว่าบางอย่างทำงานอย่างไรคุณก็สามารถลองใช้งานได้ตามที่คาดไว้ แต่คุณไม่กล้าที่จะพยายามทำความเข้าใจข้อกำหนดของภาษาและสร้างล่ามหรืออ่านโค้ดของรางในขณะที่เรียนรู้ทับทิม Go เป็นเรื่องง่าย ขอแนะนำให้คุณอ่านข้อมูลจำเพาะของภาษาหลังทัวร์ - แม้แต่ผู้เริ่มต้นก็สามารถทำได้ และพวกเขาสามารถอ่านโค้ดขั้นสูงสุดและรับมันได้ เพราะมันเรียบง่าย
กิ๊ก

4
@kik นั่นไม่สมเหตุสมผล เรียบง่ายไม่ได้หมายความว่าไม่มีคุณลักษณะ การเรียงลำดับเป็นหนึ่งในคุณสมบัติที่สำคัญที่สุดและเป็นพื้นฐาน แต่เรียบง่ายที่ห้องสมุดสามารถมีได้ Golang มีไลบรารีมาตรฐานสำหรับเทมเพลต html แฮช crc32 เครื่องพิมพ์สแกนเนอร์และอื่น ๆ ซึ่งทำให้มันไม่ง่ายเลยแม้แต่น้อย การไม่มีการจัดเรียงในไลบรารีมาตรฐานของคุณไม่ใช่เรื่องง่าย แต่เป็นเรื่องของคุณสมบัติพื้นฐานที่ขาดหายไปซึ่งทุกภาษาถือว่าเป็นมาตรฐาน แม้แต่ C ก็มีฟังก์ชันการเรียงลำดับ หยุดทำตัวเป็นชนชั้นสูงกับ Golang และเริ่มพิจารณาว่า Golang อาจจะคิดผิดในเรื่องนี้ (ถ้ามันไม่มี)
Eksapsy

320

ตั้งแต่ 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)
}

นี่คือโซลูชัน verbose ที่ฉันเชื่อมโยงใช่ไหม
Martin Thoma

1
คุณเชื่อมโยงในขณะที่ฉันเขียนสิ่งนี้ ขอโทษด้วย. แต่การใช้เครื่องมือมาตรฐานจะไม่มีวิธีใดที่สั้นกว่านี้
jimt

5

คุณสามารถแทนที่จะนำสิ่งSort interfaceที่[]Planetคุณใช้กับประเภทที่มีคอลเลกชันและการปิดที่จะทำการเปรียบเทียบ คุณต้องดำเนินการสำหรับการปิดการเปรียบเทียบสำหรับแต่ละคุณสมบัติ

วิธีนี้ฉันรู้สึกว่าดีกว่าการใช้ประเภทการเรียงลำดับสำหรับคุณสมบัติแต่ละอย่างของโครงสร้าง

คำตอบนี้เกือบจะถูกฉีกออกจากเอกสารการจัดเรียงดังนั้นฉันจึงไม่สามารถรับเครดิตได้มากนัก

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

นี่เป็นอีกวิธีหนึ่งในการลดจานหม้อไอน้ำบางส่วน Disclaimer จะใช้ความปลอดภัยประเภทสะท้อนและการสูญเสีย

นี่คือการสาธิต

ความมหัศจรรย์ทั้งหมดเกิดขึ้นใน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.