รับชิ้นกุญแจจากแผนที่


230

มีวิธีที่ง่ายกว่าหรือดีกว่าในการรับชิ้นส่วนของคีย์จากแผนที่ใน Go หรือไม่?

ขณะนี้ฉันกำลังวนซ้ำแผนที่และคัดลอกกุญแจไปยังส่วน:

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

19
คำตอบที่ถูกต้องคือไม่ไม่มีวิธีที่ง่ายกว่า / ดีกว่า
dldnh

คำตอบ:


202

ตัวอย่างเช่น,

package main

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

เพื่อให้มีประสิทธิภาพใน Go สิ่งสำคัญคือการลดการจัดสรรหน่วยความจำ


29
มันจะดีกว่าเล็กน้อยในการตั้งขนาดจริงแทนความจุและหลีกเลี่ยงการผนวกเข้าด้วยกัน ดูคำตอบของฉันสำหรับรายละเอียด
Vinay Pai

3
โปรดทราบว่าหากmymapไม่ได้เป็นตัวแปรในตัวเครื่อง (และขึ้นอยู่กับการเพิ่ม / ลดขนาด) นี่เป็นทางออกที่เหมาะสมเท่านั้น - จะช่วยให้มั่นใจได้ว่าหากขนาดของmymapการเปลี่ยนแปลงระหว่างการเริ่มต้นkeysและforลูปจะไม่มีการเปลี่ยนแปลงใด ๆ ปัญหาของขอบเขต
Melllvar

12
แผนที่ไม่ปลอดภัยภายใต้การเข้าถึงพร้อมกันไม่มีวิธีแก้ปัญหาที่ยอมรับได้หาก goroutine อื่นอาจเปลี่ยนแผนที่
Vinay Pai

@VinayPai ไม่เป็นไรที่จะอ่านจากแผนที่จาก goroutines หลายแห่ง แต่ไม่ใช่เขียน
darethas

@darethas ที่เข้าใจผิดทั่วไป เครื่องตรวจจับการแข่งขันจะตั้งค่าสถานะการใช้งานนี้ตั้งแต่ 1.6 จากบันทึกประจำรุ่น: "เช่นเคยหากมีหนึ่ง goroutine กำลังเขียนลงในแผนที่ไม่ควรมี goroutine อื่นอ่านหรือเขียนแผนที่พร้อมกันหากรันไทม์ตรวจพบเงื่อนไขนี้มันจะพิมพ์การวินิจฉัยและขัดข้องโปรแกรม" golang.org/doc/go1.6#runtime
Vinay Pai

375

นี่เป็นคำถามเก่า แต่นี่คือสองเซ็นต์ของฉัน คำตอบของ PeterSO นั้นกระชับกว่าเล็กน้อย แต่ก็มีประสิทธิภาพน้อยกว่าเล็กน้อย คุณรู้แล้วว่ามันจะมีขนาดใหญ่เท่าไรคุณจึงไม่จำเป็นต้องใช้ผนวก:

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

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

ในสถานการณ์ส่วนใหญ่มันอาจจะไม่สร้างความแตกต่างมากนัก แต่มันก็ไม่ได้ผลมากนักและในการทดสอบของฉัน (ใช้แผนที่ที่มี 1,000,000 สุ่ม int64ปุ่มและจากนั้นสร้างอาร์เรย์ของปุ่มสิบครั้งในแต่ละวิธี) เร็วขึ้น 20% ในการกำหนดสมาชิกของอาร์เรย์โดยตรงกว่าที่จะใช้ผนวก

แม้ว่าการตั้งค่าความจุจะลดการจัดสรรใหม่ แต่ยังคงต้องทำงานพิเศษเพิ่มเติมเพื่อตรวจสอบว่าคุณมีความจุครบตามแต่ละส่วนหรือไม่


46
ดูเหมือนว่าจะเหมือนกับรหัสของ OP ฉันยอมรับว่านี่เป็นวิธีที่ดีกว่า แต่ฉันอยากรู้ว่าฉันพลาดความแตกต่างระหว่างรหัสคำตอบนี้และรหัสของ OP หรือไม่
Emmaly Wilson

4
จุดดีฉันก็มองไปที่คำตอบอื่น ๆ และคิดถึงว่าคำตอบของฉันเหมือนกับ OP โอ้อย่างน้อยตอนนี้เราก็รู้แล้วว่าการลงโทษนั้นมีจุดประสงค์ในการใช้สิ่งใดโดยไม่จำเป็น :)
Vinay Pai

5
ทำไมคุณไม่ใช้ดัชนีกับช่วง, for i, k := range mymap{. ด้วยวิธีนี้คุณไม่จำเป็นต้องใช้ i ++?
mvndaai

30
บางทีฉันอาจจะพลาดบางสิ่งบางอย่างที่นี่ แต่ถ้าคุณทำi, k := range mymapแล้วiจะเป็นกุญแจและkจะเป็นค่าที่สอดคล้องกับกุญแจเหล่านั้นในแผนที่ ที่จริงแล้วจะไม่ช่วยให้คุณเติมชิ้นส่วนของคีย์
Vinay Pai

4
@ อลาสก้าหากคุณกังวลเกี่ยวกับค่าใช้จ่ายในการจัดสรรตัวแปรตัวนับชั่วคราวหนึ่งตัว แต่คิดว่าการเรียกใช้ฟังก์ชันจะใช้หน่วยความจำน้อยกว่าคุณควรให้ความรู้กับตัวเองเกี่ยวกับสิ่งที่เกิดขึ้นจริง คำแนะนำ: มันไม่ได้เป็นเวทย์มนตร์วิเศษที่ทำสิ่งต่าง ๆ ได้ฟรี หากคุณคิดว่าคำตอบที่ได้รับการยอมรับในขณะนี้มีความปลอดภัยภายใต้การเข้าถึงพร้อมกันคุณยังต้องกลับไปสู่พื้นฐาน: blog.golang.org/go-maps-in-action#TOC_6
Vinay Pai

79

นอกจากนี้คุณยังสามารถใช้อาร์เรย์ของคีย์พร้อมชนิด[]ValueตามวิธีMapKeysโครงสร้างValueจากแพคเกจ "reflect":

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
ฉันคิดว่านี่เป็นวิธีการที่ดีหากมีโอกาสเข้าถึงแผนที่พร้อมกัน: สิ่งนี้จะไม่ตกใจถ้าแผนที่เติบโตในระหว่างการวนซ้ำ เกี่ยวกับประสิทธิภาพฉันไม่แน่ใจจริงๆ แต่ฉันคิดว่ามันมีประสิทธิภาพสูงกว่าโซลูชันต่อท้าย
Atila Romero

@AtilaRomero ไม่แน่ใจว่าโซลูชันนี้มีข้อดีใด ๆ แต่เมื่อใช้การสะท้อนกลับเพื่อวัตถุประสงค์ใด ๆ สิ่งนี้จะมีประโยชน์มากกว่าเพราะช่วยให้สามารถรับคีย์ตามค่าที่พิมพ์ได้โดยตรง
เดนิส Kreshikhin

10
มีวิธีแปลงสิ่งนี้เป็น[]stringหรือไม่?
Doron Behar

14

วิธีที่ดีกว่าในการทำเช่นนี้คือการใช้append:

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

นอกจากนั้นคุณไม่มีโชค - โกไม่ใช่ภาษาที่แสดงออกมาก


10
มีประสิทธิภาพน้อยกว่าแบบเดิม - ต่อท้ายจะทำการจัดสรรหลายครั้งเพื่อขยายอาร์เรย์พื้นฐานและจะต้องอัปเดตความยาวชิ้นทุกการโทร การบอกว่าkeys = make([]int, 0, len(mymap))จะกำจัดการจัดสรร แต่ฉันคาดว่ามันจะช้าลง
Nick Craig-Wood

1
คำตอบนี้ปลอดภัยกว่าการใช้ len (mymap) ถ้ามีคนอื่นเปลี่ยนแผนที่ในขณะที่ทำสำเนาอยู่
Atila Romero

7

ฉันสร้างเกณฑ์มาตรฐานในสามวิธีที่อธิบายไว้ในคำตอบอื่น

เห็นได้ชัดว่าการจัดสรรชิ้นล่วงหน้าก่อนที่จะดึงกุญแจนั้นเร็วกว่าappendไอเอ็นจี แต่น่าแปลกใจที่reflect.ValueOf(m).MapKeys()วิธีนี้ช้ากว่าหลังอย่างมาก:

 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

นี่คือรหัส: https://play.golang.org/p/Z8O6a2jyfTH (เรียกใช้ในสนามเด็กเล่นยกเลิกการอ้างว่าใช้เวลานานเกินไปดังนั้นให้เรียกใช้ในเครื่อง)


2
ในkeysAppendฟังก์ชั่นของคุณคุณสามารถตั้งค่าความจุของkeysอาเรย์ด้วยmake([]uint64, 0, len(m))ซึ่งเปลี่ยนประสิทธิภาพของฟังก์ชั่นนั้นอย่างมากสำหรับฉัน
keithbhunter

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.