Sắp xếp các giá trị bản đồ Go theo các phím


94

Khi lặp lại bản đồ được trả về trong mã, được trả về bởi hàm chủ đề, các phím sẽ không xuất hiện theo thứ tự.

Làm cách nào để lấy các phím theo thứ tự / sắp xếp bản đồ để các phím có thứ tự và các giá trị tương ứng?

Đây là .


Câu trả lời:


157

Các Go blog: Go bản đồ trong hành động có một lời giải thích tuyệt vời.

Khi lặp qua bản đồ có vòng lặp phạm vi, thứ tự lặp không được chỉ định và không được đảm bảo là giống nhau từ lần lặp này sang lần lặp tiếp theo. Vì Lượt đi 1, thời gian chạy ngẫu nhiên hóa thứ tự lặp lại bản đồ, vì các lập trình viên dựa vào thứ tự lặp lại ổn định của lần triển khai trước đó. Nếu bạn yêu cầu một thứ tự lặp lại ổn định, bạn phải duy trì một cấu trúc dữ liệu riêng biệt xác định thứ tự đó.

Đây là phiên bản mã mẫu đã sửa đổi của tôi: 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])
    }
}

Đầu ra:

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

38
Điều này có thể được cải thiện với keys := make([]int, len(m))và sau đó chèn bằng chỉ mục keys[i] = kthay vìappend
jpillora

18

Theo thông số kỹ thuật của Go , thứ tự lặp lại trên bản đồ là không xác định và có thể khác nhau giữa các lần chạy chương trình. Trong thực tế, nó không chỉ là không xác định mà còn thực sự được ngẫu nhiên có chủ đích. Điều này là do nó từng có thể dự đoán được và các nhà phát triển ngôn ngữ Go không muốn mọi người dựa vào hành vi không xác định, vì vậy họ cố ý ngẫu nhiên hóa nó để không thể dựa vào hành vi này.

Sau đó, những gì bạn sẽ phải làm là kéo các phím thành một lát, sắp xếp chúng và sau đó phạm vi trên lát như thế này:

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
}

các lát đợi là các phần của một mảng. Làm cách nào để tạo một phần chỉ các phím trong bản đồ?
gramme.ninja

1
Trong Go, từ "slice" dùng để chỉ cấu trúc dữ liệu về cơ bản tương tự như mảng Java. Nó chỉ là một vấn đề thuật ngữ. Như để nhận được các phím, bạn phải nằm trong khoảng cách rõ ràng trên bản đồ và xây dựng một lát khi bạn đi như thế này: play.golang.org/p/ZTni0o19Lr
joshlf

À được rồi. Cảm ơn vì đã dạy tôi. Bây giờ, nó đang in tất cả các chẵn rồi tất cả các số lẻ. play.golang.org/p/K2y3m4Zzqd Làm cách nào để chuyển đổi tệp thay thế theo thứ tự?
gramme.ninja

1
Bạn sẽ cần sắp xếp lát bạn nhận lại (hoặc, cách khác, sắp xếp nó trong mapKeys trước khi quay lại). Bạn sẽ muốn kiểm tra gói sắp xếp .
joshlf

14

Tất cả các câu trả lời ở đây hiện đều chứa hành vi cũ của bản đồ. Trong Go 1.12+, bạn chỉ có thể in một giá trị bản đồ và nó sẽ được sắp xếp theo khóa tự động. Điều này đã được thêm vào vì nó cho phép kiểm tra các giá trị bản đồ một cách dễ dàng.

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

Bản đồ hiện được in theo thứ tự được sắp xếp theo khóa để dễ kiểm tra. Các quy tắc đặt hàng là:

  • Khi áp dụng, nil so sánh thấp
  • ints, float và string sắp xếp theo thứ tự <
  • NaN so sánh ít hơn NaN float
  • bool so sánh false trước true
  • Phức tạp so sánh giữa thực và ảo
  • Con trỏ so sánh theo địa chỉ máy
  • Giá trị kênh so sánh theo địa chỉ máy
  • Các cấu trúc lần lượt so sánh từng trường
  • Các mảng lần lượt so sánh từng phần tử
  • Các giá trị giao diện được so sánh trước tiên bằng phản xạ.

Khi in bản đồ, các giá trị khóa không phản xạ như NaN trước đây được hiển thị dưới dạng <nil>. Kể từ bản phát hành này, các giá trị chính xác được in.

Đọc thêm tại đây .


8
Điều này dường như chỉ áp dụng cho gói và in ấn fmt. Câu hỏi đặt ra là làm thế nào để sắp xếp một bản đồ chứ không phải làm thế nào để in một bản đồ đã được sắp xếp?
Tim

1
Anh ấy đã chia sẻ một liên kết sân chơi. Trong đó anh ta chỉ in một bản đồ.
Inanc Gumus

2

Nếu, giống như tôi, bạn thấy về cơ bản bạn muốn có cùng một mã sắp xếp ở nhiều nơi hoặc chỉ muốn giảm độ phức tạp của mã, bạn có thể trừu tượng hóa bản thân việc sắp xếp thành một hàm riêng biệt, mà bạn chuyển hàm đó công việc thực tế bạn muốn (tất nhiên sẽ khác nhau ở mỗi địa chỉ cuộc gọi).

Với một bản đồ có loại khóa Kvà loại giá trị V, được biểu diễn dưới dạng <K><V>bên dưới, hàm sắp xếp thông thường có thể trông giống như mẫu mã Go này (mà Go phiên bản 1 không hỗ trợ nguyên trạng):

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

Sau đó, gọi nó với bản đồ đầu vào và một hàm (lấy (k <K>, v <V>)làm đối số đầu vào của nó) được gọi trên các phần tử bản đồ theo thứ tự khóa được sắp xếp.

Vì vậy, một phiên bản của mã trong câu trả lời do Mingu đăng có thể trông giống như sau:

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

Các sortedMapIntString()chức năng có thể được tái sử dụng cho bất kỳ map[int]string(giả định thứ tự sắp xếp tương tự được mong muốn), giữ mỗi lần sử dụng để chỉ hai dòng mã.

Nhược điểm bao gồm:

  • Khó đọc hơn đối với những người không quen sử dụng các hàm như hạng nhất
  • Nó có thể chậm hơn (tôi chưa thực hiện so sánh hiệu suất)

Các ngôn ngữ khác có các giải pháp khác nhau:

  • Nếu việc sử dụng <K><V>(để biểu thị các kiểu cho khóa và giá trị) có vẻ hơi quen thuộc, thì mẫu mã đó không giống như các mẫu C ++.
  • Clojure và các ngôn ngữ khác hỗ trợ các bản đồ được sắp xếp dưới dạng các kiểu dữ liệu cơ bản.
  • Mặc dù tôi không biết bằng cách nào Go tạo ra rangemột loại hạng nhất để nó có thể được thay thế bằng một tùy chỉnh ordered-range(thay rangecho mã gốc), tôi nghĩ rằng một số ngôn ngữ khác cung cấp trình vòng lặp đủ mạnh để thực hiện điều tương tự Điều.

2
Đối với người mới bắt đầu, có thể cần chỉ ra rằng cú pháp <K>, <V> không được hỗ trợ trong Go.
justinhj

2

In reply to James Craig Burley của câu trả lời . Để tạo ra một thiết kế sạch sẽ và có thể tái sử dụng, người ta có thể chọn cách tiếp cận hướng đối tượng hơn. Bằng cách này, các phương thức có thể được ràng buộc một cách an toàn với các loại bản đồ được chỉ định. Đối với tôi, cách tiếp cận này cảm thấy sạch sẽ và có tổ chức.

Thí dụ:

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

Ví dụ về sân chơi mở rộng với nhiều loại bản đồ.

Lưu ý quan trọng

Trong mọi trường hợp, bản đồ và lát cắt đã sắp xếp được tách ra từ thời điểm kết thúc forvòng lặp trên bản đồ range. Có nghĩa là, nếu bản đồ được sửa đổi sau logic sắp xếp, nhưng trước khi bạn sử dụng nó, bạn có thể gặp rắc rối. (Không phải luồng / Đi theo quy trình an toàn). Nếu có sự thay đổi về quyền truy cập ghi Bản đồ song song, bạn sẽ cần sử dụng mutex xung quanh các lần ghi và forvòng lặp được sắp xếp .

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

0

Điều này cung cấp cho bạn ví dụ mã trên bản đồ sắp xếp. Về cơ bản đây là những gì họ cung cấp:

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

và đây là những gì tôi sẽ đề xuất sử dụng thay thế :

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

Bạn có thể tìm thấy mã đầy đủ trong Sân chơi cờ vây này .

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.