Lấy một lát chìa khóa từ bản đồ


230

Có cách nào đơn giản / đẹp hơn để lấy một lát chìa khóa từ bản đồ trong Go không?

Hiện tại tôi đang lặp lại trên bản đồ và sao chép các phím vào một lát:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}

19
Câu trả lời đúng là không, không có cách nào đơn giản / đẹp hơn.
dldnh 16/03/18

Câu trả lời:


202

Ví dụ,

package main

func main() {
    mymap := make(map[int]string)
    keys := make([]int, 0, len(mymap))
    for k := range mymap {
        keys = append(keys, k)
    }
}

Để có hiệu quả trong Go, điều quan trọng là giảm thiểu phân bổ bộ nhớ.


29
Tốt hơn một chút là đặt kích thước thực tế thay vì dung lượng và tránh nối thêm hoàn toàn. Xem câu trả lời của tôi để biết chi tiết.
Vinay Pai

3
Lưu ý rằng nếu mymapkhông phải là biến cục bộ (và do đó có thể tăng / thu hẹp), thì đây là giải pháp phù hợp duy nhất - nó đảm bảo rằng nếu kích thước mymapthay đổi giữa khởi tạo keysforvòng lặp, sẽ không có bất kỳ biến đổi nào vấn đề giới hạn.
Melllvar

12
bản đồ không an toàn dưới quyền truy cập đồng thời, không giải pháp nào được chấp nhận nếu một con goroutine khác có thể thay đổi bản đồ.
Vinay Pai

@VinayPai bạn có thể đọc từ bản đồ từ nhiều
con khỉ đột

@darethas đó là một quan niệm sai lầm phổ biến. Trình phát hiện cuộc đua sẽ gắn cờ việc sử dụng này kể từ 1.6. Từ ghi chú phát hành: "Như mọi khi, nếu một con goroutine đang viết lên bản đồ, thì không có con goroutine nào nên đọc hoặc viết bản đồ đồng thời. Nếu bộ thực thi phát hiện tình trạng này, nó sẽ in ra một chẩn đoán và làm hỏng chương trình." golang.org/doc/go1.6#r nb
Vinay Pai

375

Đây là một câu hỏi cũ, nhưng đây là hai xu của tôi. Câu trả lời của PeterSO ngắn gọn hơn một chút, nhưng hơi kém hiệu quả. Bạn đã biết nó sẽ lớn đến mức nào nên bạn thậm chí không cần sử dụng phụ lục:

keys := make([]int, len(mymap))

i := 0
for k := range mymap {
    keys[i] = k
    i++
}

Trong hầu hết các tình huống, nó có thể sẽ không tạo ra nhiều sự khác biệt, nhưng nó không hoạt động nhiều hơn và trong các thử nghiệm của tôi (sử dụng bản đồ với 1.000.000 int64khóa ngẫu nhiên và sau đó tạo ra các mảng khóa mười lần với mỗi phương thức), đó là về Nhanh hơn 20% để gán các thành viên của mảng trực tiếp hơn là sử dụng nối thêm.

Mặc dù cài đặt công suất sẽ loại bỏ việc phân bổ lại, việc nối thêm vẫn phải thực hiện thêm công việc để kiểm tra xem bạn đã đạt công suất trên mỗi lần nối hay chưa.


46
Điều này trông giống hệt như mã của OP. Tôi đồng ý rằng đây là cách tốt hơn, nhưng tôi tò mò nếu tôi bỏ lỡ sự khác biệt giữa mã câu trả lời này và mã của OP.
Emmaly Wilson

4
Điểm hay, tôi bằng cách nào đó đã xem xét các câu trả lời khác và bỏ lỡ rằng câu trả lời của tôi hoàn toàn giống với OP. Ồ, ít nhất bây giờ chúng tôi biết khoảng hình phạt là gì khi sử dụng nối thêm một cách không cần thiết :)
Vinay Pai

5
Tại sao bạn không sử dụng một chỉ mục với phạm vi , for i, k := range mymap{. Theo cách đó bạn không cần i ++?
mvndaai

30
Có thể tôi đang thiếu một cái gì đó ở đây, nhưng nếu bạn đã làm i, k := range mymap, thì đó isẽ là các khóa và ksẽ là các giá trị tương ứng với các khóa đó trên bản đồ. Điều đó sẽ không thực sự giúp bạn điền vào một lát chìa khóa.
Vinay Pai

4
@Alaska nếu bạn quan tâm đến chi phí phân bổ một biến đếm tạm thời, nhưng nghĩ rằng một lệnh gọi hàm sẽ mất ít bộ nhớ hơn, bạn nên tự tìm hiểu về những gì thực sự xảy ra khi một hàm được gọi. Gợi ý: Nó không phải là một câu thần chú làm mọi thứ miễn phí. Nếu bạn nghĩ rằng câu trả lời hiện được chấp nhận là an toàn khi truy cập đồng thời, bạn cũng cần quay lại những điều cơ bản: blog.golang.org/go-maps-in-action#TOC_6 .
Vinay Pai

79

Bạn cũng có thể lấy một mảng các khóa với loại []Valuetheo phương thức MapKeysstruct Valuetừ gói "phản ánh":

package main

import (
    "fmt"
    "reflect"
)

func main() {
    abc := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
    }

    keys := reflect.ValueOf(abc).MapKeys()

    fmt.Println(keys) // [a b c]
}

1
Tôi nghĩ rằng đây là một cách tiếp cận tốt nếu có cơ hội truy cập bản đồ đồng thời: điều này sẽ không hoảng loạn nếu bản đồ phát triển trong vòng lặp. Về hiệu suất, tôi không thực sự chắc chắn, nhưng tôi nghi ngờ nó vượt trội hơn giải pháp chắp thêm.
Atila Romero

@AtilaRomero Không chắc chắn rằng giải pháp này có bất kỳ lợi thế nào, nhưng khi sử dụng sự phản chiếu cho bất kỳ mục đích nào thì điều này hữu ích hơn, vì nó cho phép lấy một khóa làm Giá trị được nhập trực tiếp.
Denis Kreshikhin

10
Có cách nào để chuyển đổi này thành []string?
Doron Behar

14

Một cách tốt hơn để làm điều này sẽ là sử dụng append:

keys = []int{}
for k := range mymap {
    keys = append(keys, k)
}

Ngoài ra, bạn không gặp may mắn, Go Go không phải là một ngôn ngữ rất biểu cảm.


10
Mặc dù nó kém hiệu quả hơn so với bản gốc - append sẽ thực hiện nhiều phân bổ để phát triển mảng bên dưới và phải cập nhật độ dài lát cắt mỗi cuộc gọi. Nói keys = make([]int, 0, len(mymap))sẽ thoát khỏi phân bổ nhưng tôi hy vọng nó sẽ vẫn chậm hơn.
Nick Craig-Wood

1
Câu trả lời này an toàn hơn so với sử dụng len (mymap), nếu người khác thay đổi bản đồ trong khi bản sao được tạo ra.
Atila Romero

7

Tôi đã thực hiện một tiêu chuẩn sơ sài về ba phương pháp được mô tả trong các phản ứng khác.

Rõ ràng việc phân bổ trước lát cắt trước khi kéo các phím nhanh hơn appending, nhưng đáng ngạc nhiên, reflect.ValueOf(m).MapKeys()phương pháp này chậm hơn đáng kể so với phương pháp sau:

 go run scratch.go
populating
filling 100000000 slots
done in 56.630774791s
running prealloc
took: 9.989049786s
running append
took: 18.948676741s
running reflect
took: 25.50070649s

Đây là mã: https://play.golang.org/p/Z8O6a2jyfTH (chạy nó trong sân chơi hủy bỏ tuyên bố rằng nó mất quá nhiều thời gian, vì vậy, tốt, chạy nó cục bộ.)


2
Trong keysAppendhàm của bạn , bạn có thể đặt dung lượng của keysmảng với make([]uint64, 0, len(m)), điều này đã thay đổi đáng kể hiệu năng của hàm đó đối với tôi.
keithbhunter

1

Truy cập https://play.golang.org/p/dx6PTtuBXQW

package main

import (
    "fmt"
    "sort"
)

func main() {
    mapEg := map[string]string{"c":"a","a":"c","b":"b"}
    keys := make([]string, 0, len(mapEg))
    for k := range mapEg {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    fmt.Println(keys)
}
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.