[go] 키로 Go 맵 값 정렬

주제 함수에 의해 반환 된 코드에서 반환 된 맵을 반복 할 때 키가 순서대로 나타나지 않습니다.

키를 순서대로 가져 오거나 맵을 정렬하여 키가 정렬되고 값이 일치하도록하려면 어떻게해야합니까?

다음은 코드 입니다.



답변

이동 블로그 : 이동 행동에지도는 훌륭한 설명이 있습니다.

범위 루프가있는 맵을 반복 할 때 반복 순서가 지정되지 않으며 한 반복에서 다음 반복까지 동일하다고 보장 할 수 없습니다. Go 1 이후 프로그래머가 이전 구현의 안정적인 반복 순서에 의존 했으므로 런타임은 맵 반복 순서를 무작위로 지정합니다. 안정적인 반복 순서가 필요한 경우 해당 순서를 지정하는 별도의 데이터 구조를 유지해야합니다.

다음은 수정 된 예제 코드 버전입니다.
http://play.golang.org/p/dvqcGPYy3-

package main

import (
    "fmt"
    "sort"
)

func main() {
    // To create a map as input
    m := make(map[int]string)
    m[1] = "a"
    m[2] = "c"
    m[0] = "b"

    // To store the keys in slice in sorted order
    keys := make([]int, len(m))
    i := 0
    for k := range m {
        keys[i] = k
        i++
    }
    sort.Ints(keys)

    // To perform the opertion you want
    for _, k := range keys {
        fmt.Println("Key:", k, "Value:", m[k])
    }
}

산출:

Key: 0 Value: b
Key: 1 Value: a
Key: 2 Value: c


답변

Go 사양 에 따르면 맵에 대한 반복 순서는 정의되지 않으며 프로그램 실행마다 다를 수 있습니다. 실제로는 정의되지 않았을뿐만 아니라 실제로 의도적으로 무작위 화됩니다. 이는 예전에는 예측 가능했고 Go 언어 개발자는 사람들이 지정되지 않은 동작에 의존하는 것을 원하지 않았기 때문에 의도적으로 무작위 화하여이 동작에 의존하는 것이 불가능했습니다.

그런 다음해야 할 일은 키를 슬라이스로 가져 와서 정렬 한 다음 다음과 같이 슬라이스에 걸쳐 범위를 지정하는 것입니다.

var m map[keyType]valueType
keys := sliceOfKeys(m) // you'll have to implement this
for _, k := range keys {
    v := m[k]
    // k is the key and v is the value; do your computation here
}


답변

여기에있는 모든 답변에는 이제 맵의 이전 동작이 포함됩니다. Go 1.12+에서는지도 값을 인쇄하기 만하면 자동으로 키별로 정렬됩니다. 맵 값을 쉽게 테스트 할 수 있기 때문에 추가되었습니다.

func main() {
    m := map[int]int{3: 5, 2: 4, 1: 3}
    fmt.Println(m)

    // In Go 1.12+
    // Output: map[1:3 2:4 3:5]

    // Before Go 1.12 (the order was undefined)
    // map[3:5 2:4 1:3]
}

이제 맵이 테스트를 쉽게하기 위해 키 정렬 순서로 인쇄됩니다. 주문 규칙은 다음과 같습니다.

  • 적용 가능한 경우 nil은 낮음을 비교합니다.
  • int, float, strings 순서는 <
  • NaN은 비 NaN 수레보다 적은 수를 비교합니다.
  • bool은 true 전에 false를 비교합니다.
  • 복잡함은 실제와 가상의 비교
  • 컴퓨터 주소로 포인터 비교
  • 컴퓨터 주소로 채널 값 비교
  • 구조체는 각 필드를 차례로 비교합니다.
  • 배열은 각 요소를 차례로 비교합니다.
  • 인터페이스 값은 먼저 reflect.Type으로 구체적인 유형을 설명하는 방식으로 비교 한 다음 이전 규칙에서 설명한대로 구체적인 값으로 비교합니다.

지도를 인쇄 할 때 NaN과 같은 비 반사 키 값은 이전에로 표시되었습니다 <nil>. 이 릴리스부터 올바른 값이 인쇄됩니다.

여기에서 자세한 내용을 읽어보십시오 .


답변

James Craig Burley의 답변에 대한 답변 . 깨끗하고 재사용 가능한 디자인을 만들기 위해보다 객체 지향적 인 접근 방식을 선택할 수 있습니다. 이렇게하면 메서드를 지정된 맵의 유형에 안전하게 바인딩 할 수 있습니다. 나에게이 접근 방식은 깔끔하고 조직적으로 느껴집니다.

예:

package main

import (
    "fmt"
    "sort"
)

type myIntMap map[int]string

func (m myIntMap) sort() (index []int) {
    for k, _ := range m {
        index = append(index, k)
    }
    sort.Ints(index)
    return
}

func main() {
    m := myIntMap{
        1:  "one",
        11: "eleven",
        3:  "three",
    }
    for _, k := range m.sort() {
        fmt.Println(m[k])
    }
}

여러지도 유형이있는 확장 된 놀이터 예제 .

중요 사항

모든 경우에 맵과 정렬 된 슬라이스는 for맵에 대한 루프 range가 완료된 순간부터 분리됩니다 . 즉, 정렬 논리 이후에 맵이 수정되지만 사용하기 전에 문제가 발생할 수 있습니다. (스레드가 아님 / 루틴이 안전하지 않음). 병렬 맵 쓰기 액세스가 변경된 경우 쓰기 및 정렬 된 for루프 주위에 뮤텍스를 사용해야합니다 .

mutex.Lock()
for _, k := range m.sort() {
    fmt.Println(m[k])
}
mutex.Unlock()


답변

저처럼 기본적으로 동일한 정렬 코드를 여러 곳에서 원하거나 코드 복잡성을 낮추고 싶다면 정렬 자체를 별도의 함수로 추상화하여 다음과 같은 함수를 전달할 수 있습니다. 원하는 실제 작업 (물론 각 호출 사이트마다 다를 수 있음).

다음 과 같이 표시된 키 유형 K및 값 유형이 있는 맵 에서 일반적인 정렬 기능은 다음 Go 코드 템플릿과 유사 할 수 있습니다 (Go 버전 1은있는 그대로 지원하지 않음).V<K><V>

/* Go apparently doesn't support/allow 'interface{}' as the value (or
/* key) of a map such that any arbitrary type can be substituted at
/* run time, so several of these nearly-identical functions might be
/* needed for different key/value type combinations. */
func sortedMap<K><T>(m map[<K>]<V>, f func(k <K>, v <V>)) {
    var keys []<K>
    for k, _ := range m {
        keys = append(keys, k)
    }
    sort.Strings(keys)  # or sort.Ints(keys), sort.Sort(...), etc., per <K>
    for _, k := range keys {
        v := m[k]
        f(k, v)
    }
}

그런 다음 입력 맵과 (k <K>, v <V>)정렬 된 키 순서로 맵 요소에 대해 호출 되는 함수 ( 입력 인수로 사용)를 사용하여이를 호출합니다.

따라서 Mingu게시 한 답변 의 코드 버전은 다음과 같습니다.

package main

import (
    "fmt"
    "sort"
)

func sortedMapIntString(m map[int]string, f func(k int, v string)) {
    var keys []int
    for k, _ := range m {
        keys = append(keys, k)
    }
    sort.Ints(keys)
    for _, k := range keys {
        f(k, m[k])
    }
}

func main() {
    // Create a map for processing
    m := make(map[int]string)
    m[1] = "a"
    m[2] = "c"
    m[0] = "b"

    sortedMapIntString(m,
        func(k int, v string) { fmt.Println("Key:", k, "Value:", v) })
}

sortedMapIntString()함수는 map[int]string(동일한 정렬 순서가 필요하다고 가정 할 때) 모든 용도로 재사용 할 수 있으며 , 각 용도를 단 두 줄의 코드로 유지합니다.

단점은 다음과 같습니다.

  • 기능을 일류로 사용하는 데 익숙하지 않은 사람들은 읽기가 더 어렵습니다.
  • 느려질 수 있습니다 (성능 비교를하지 않았습니다).

다른 언어에는 다양한 솔루션이 있습니다.

  • 를 사용하는 경우 <K><V>(키와 값에 대한 나타낸다 유형이) 조금 익숙, 그 코드 템플릿은 C ++ 템플릿과 달리 몹시 없습니다.
  • Clojure 및 기타 언어는 기본 데이터 유형으로 정렬 된 맵을 지원합니다.
  • Go가 range사용자 정의 ordered-range( range원래 코드 대신) 로 대체 될 수있는 일급 유형을 만드는 방법을 모르겠지만 다른 일부 언어는 동일한 작업을 수행 할 수있을만큼 강력한 반복기를 제공한다고 생각합니다. 맡은 일.


답변

이것은 당신에게지도를 정렬에 대한 코드 예제를 제공합니다. 기본적으로 이것이 제공하는 것입니다.

var keys []int
for k := range myMap {
    keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark1-8      2863149           374 ns/op         152 B/op          5 allocs/op

그리고 이것은 내가 대신 사용하는 것이 좋습니다 .

keys := make([]int, 0, len(myMap))
for k := range myMap {
    keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark2-8      5320446           230 ns/op          80 B/op          2 allocs/op

전체 코드는 이 Go Playground 에서 찾을 수 있습니다 .


답변