CafeM0ca

[Go] sort In struct and map 본문

Programming/Go

[Go] sort In struct and map

M0ca 2021. 7. 23. 23:51
반응형

Go언어 Sort. 구조체 정렬, map 정렬

Go의 Sort를 알아보자.

https://pkg.go.dev/sort

기본적으로 int형 slice와 float64형 slice, string slice는 Sort를 지원한다.

구조체 정렬

그렇다면 구조체를 정렬하기 위해서는 어떻게 해야할까? 다행히 공식 문서에 사용법에 대한 예제 코드가 존재한다.

// 정렬할 구조체다. Name과 Age 멤버변수가 있다.
type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s: %d", p.Name, p.Age)
}
// Go의 sort.Sort()는 `func Sort(data Interface)`로 정의되어 있다.
// 따라서 커스텀타입을 정렬하기 위해서는 인터페이스가 필요하다.
// ByAge는 Age를 기준으로 정렬하기 위해 Person slice타입이다.
type ByAge []Person

// Sort Interface를 충족시키기 위한 Len(), Swap(int, int), Less(int, int)다. 
func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func main() {
    people := []Person{
        {"Bob", 31},
        {"John", 42},
        {"Michael", 17},
        {"Jenny", 26},
    }

    fmt.Println(people)


    // sort하는 방법에는 2가지가 있다. 
  // 첫번째는 slice타입의 인터페이스를 구현하고 sort.Sort로 정렬하는 방법이다.
    sort.Sort(ByAge(people))
    fmt.Println(people)

  // 두번째 방법은 sort.Slice로 Less() 함수를 커스텀해서 구현하는 방법인데 closure를 사용하면 구현할 수 있다. 이 경우에는 slice타입의 인터페이스를 만족시키기 위해 메소드들을 구현할 필요가 없다. 
  // 여기서는 정렬된 people slice를 다시 뒤집어본다.
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age > people[j].Age
    })
    fmt.Println(people)
}

/* output
[Bob: 31 John: 42 Michael: 17 Jenny: 26] // 정렬안된 데이터
[Michael: 17 Jenny: 26 Bob: 31 John: 42] // 정렬된 데이터
[John: 42 Bob: 31 Jenny: 26 Michael: 17] // 뒤집은 데이터
*/

전체코드

package main

import (
    "fmt"
    "sort"
)

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s: %d", p.Name, p.Age)
}

// ByAge implements sort.Interface for []Person based on
// the Age field.
type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

func main() {
    people := []Person{
        {"Bob", 31},
        {"John", 42},
        {"Michael", 17},
        {"Jenny", 26},
    }

    fmt.Println(people)
    // There are two ways to sort a slice. First, one can define
    // a set of methods for the slice type, as with ByAge, and
    // call sort.Sort. In this first example we use that technique.
    sort.Sort(ByAge(people))
    fmt.Println(people)

    // The other way is to use sort.Slice with a custom Less
    // function, which can be provided as a closure. In this
    // case no methods are needed. (And if they exist, they
    // are ignored.) Here we re-sort in reverse order: compare
    // the closure with ByAge.Less.
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age > people[j].Age
    })
    fmt.Println(people)
}
반응형

map 정렬

map에 든 데이터를 정렬하는 일이 발생할 수 있다. map은 key와 value로 이루어져 있다.

우선 key based sort를 먼저 보자.

func main() {
  // 아래에 string-int를 key-value로 갖는 map 데이터가 있고
    person := map[string]int{
        "cain":  25,
        "bob":   19,
        "alice": 21,
        "dela":  22,
    }

    var names []string

  // person을 순회하면서 key를 뽑아서 names 슬라이스에 저장한다. 
    for k, v := range person {
        // v == person[k]
        fmt.Printf("name:%s, age:%d is equal %d\n", k, v, person[k])
        names = append(names, k)
    }
    fmt.Println("==============")

    fmt.Println("key based sort")
  // names를 정렬하고
    sort.Strings(names)
  // names를 순회하면서 name을 map에 넣으면 name의 순서대로 정렬이 된다.
    for _, name := range names {
        fmt.Printf("name:%s, age:%d\n", name, person[name])
    }

위 방식은 정확히는 정렬이 된 데이터가 아니라 정렬이 될 데이터(names)로 뽑아내는 방법이다.

다음으로는 value based sort를 보자.

type Person struct {
    age  int
    name string
}

func main() {
    person := map[string]int{
        "cain":  25,
        "bob":   19,
        "alice": 21,
        "dela":  22,
    }

    var p []Person
  // person을 순회하면서 Person 구조체를 갖고 있는 p에 value와 key를 넣어준다.
    for k, v := range person {
        p = append(p, Person{v, k})
    }

    fmt.Println("value based sort")
  // slice인 Person을 정렬하기 위해 closure로 정렬했다.
    sort.Slice(p, func(i, j int) bool {
        return p[i].age < p[j].age
    })
    fmt.Println(p)
    fmt.Println("==============")
}

key based sort와 다른점은 value based sort는 정렬할 데이터가 아니라 정렬된 데이터 를 갖게 된다.

전체코드는 아래와 같다.

//mapsort.go
package main

import (
    "fmt"
    "sort"
)

type Person struct {
    age  int
    name string
}

func main() {
    person := map[string]int{
        "cain":  25,
        "bob":   19,
        "alice": 21,
        "dela":  22,
    }

    var names []string
    var p []Person
    for k, v := range person {
        // v == person[k]
        fmt.Printf("name:%s, age:%d is equal %d\n", k, v, person[k])
        names = append(names, k)
        p = append(p, Person{v, k})
    }
    fmt.Println("==============")

    fmt.Println("key based sort")
    sort.Strings(names)
    for _, name := range names {
        fmt.Printf("name:%s, age:%d\n", name, person[name])
    }
    fmt.Println("==============")

    fmt.Println("value based sort")
    sort.Slice(p, func(i, j int) bool {
        return p[i].age < p[j].age
    })
    fmt.Println(p)
    fmt.Println("==============")
}

mapsort.go 실행결과

연습문제

프로그래머스의 https://programmers.co.kr/learn/courses/30/lessons/42579 문제를 Go로 풀면서 연습해보자. sort 인터페이스를 만족시키며 구현할 수 있겠지만 코드량을 줄이기 위해 closure로 구현하는 것을 권장한다.

 

반응형
Comments