เปลี่ยนค่าในขณะที่วนซ้ำ


153

สมมติว่าฉันมีประเภทเหล่านี้:

type Attribute struct {
    Key, Val string
}
type Node struct {
    Attr []Attribute
}

และฉันต้องการที่จะทำซ้ำในคุณลักษณะของโหนดเพื่อเปลี่ยนพวกเขา

ฉันอยากจะทำ:

for _, attr := range n.Attr {
    if attr.Key == "href" {
        attr.Val = "something"
    }
}

แต่เนื่องจากattrไม่ใช่ตัวชี้สิ่งนี้จะไม่ทำงานและฉันต้องทำ:

for i, attr := range n.Attr {
    if attr.Key == "href" {
        n.Attr[i].Val = "something"
    }
}

มีวิธีที่ง่ายขึ้นหรือเร็วขึ้น? เป็นไปได้rangeหรือไม่ที่จะได้รับพอยน์เตอร์โดยตรง

เห็นได้ชัดว่าฉันไม่ต้องการที่จะเปลี่ยนโครงสร้างเพียงสำหรับการทำซ้ำและการแก้ปัญหา verbose มากขึ้นไม่มีการแก้ปัญหา


2
ดังนั้นคุณต้องการArray.prototype.forEachJavaScript ในรูปแบบใด?
Florian Margaine

นั่นเป็นแนวคิดที่น่าสนใจและอาจเป็นวิธีแก้ปัญหา แต่การเรียกใช้ฟังก์ชันซึ่งจะเรียกใช้ฟังก์ชันที่การวนซ้ำแต่ละครั้งดูหนักและผิดในภาษาฝั่งเซิร์ฟเวอร์ และการขาดยาชื่อสามัญจะทำให้ความรู้สึกนี้หนักยิ่งขึ้น
Denys Séguret

สุจริตฉันไม่คิดว่ามันหนักขนาดนั้น การเรียกฟังก์ชั่นหนึ่งหรือสองฟังก์ชั่นนั้นมีราคาถูกมากนี่เป็นสิ่งที่คอมไพเลอร์ปรับให้เหมาะสม ฉันจะลองและเปรียบเทียบมันเพื่อดูว่าเหมาะกับใบเรียกเก็บเงินหรือไม่
Florian Margaine

ในขณะที่ Go ขาด generics ฉันกลัวว่าฟังก์ชันที่ส่งผ่านไปforEachนั้นจำเป็นต้องเริ่มต้นด้วยการยืนยันประเภท มันไม่ได้ดีไปกว่าattr := &n.Attr[i]นี้อีกแล้ว
Denys Séguret

คำตอบ:


152

ไม่คำย่อที่คุณต้องการนั้นเป็นไปไม่ได้

เหตุผลนี้คือการrangeคัดลอกค่าจากส่วนที่คุณวนซ้ำ ข้อกำหนดเกี่ยวกับช่วงพูดว่า:

Range expression                          1st value             2nd value (if 2nd variable is present)
array or slice  a   [n]E, *[n]E, or []E   index    i  int       a[i]       E

ดังนั้น range จึงใช้a[i]เป็นค่าที่สองสำหรับอาร์เรย์ / ชิ้นซึ่งหมายความว่าคัดลอกค่าอย่างมีประสิทธิภาพทำให้ค่าดั้งเดิมไม่สามารถแตะต้องได้

พฤติกรรมนี้แสดงให้เห็นโดยรหัสต่อไปนี้ :

x := make([]int, 3)

x[0], x[1], x[2] = 1, 2, 3

for i, val := range x {
    println(&x[i], "vs.", &val)
}

รหัสจะพิมพ์ตำแหน่งหน่วยความจำที่แตกต่างกันอย่างสิ้นเชิงสำหรับค่าจากช่วงและค่าจริงในส่วน:

0xf84000f010 vs. 0x7f095ed0bf68
0xf84000f014 vs. 0x7f095ed0bf68
0xf84000f018 vs. 0x7f095ed0bf68

ดังนั้นสิ่งเดียวที่คุณทำได้คือใช้ตัวชี้หรือดัชนีตามที่เสนอโดย jnml และ peterSO


16
วิธีหนึ่งในการคิดสิ่งนี้คือการกำหนดค่าจะทำให้เกิดการคัดลอก หากคุณเห็นวาล: = x [1] มันจะไม่แปลกใจเลยที่วาลเป็นสำเนาของ x [1] แทนที่จะคิดถึงช่วงที่ทำอะไรเป็นพิเศษจำไว้ว่าการวนซ้ำของแต่ละช่วงเริ่มต้นด้วยการกำหนดดัชนีและตัวแปรค่าและนั่นคือการมอบหมายนั้นแทนที่จะเป็นช่วงที่ทำให้เกิดการคัดลอก
Andy Davis

ขออภัยฉันยังคงสับสนเล็กน้อยที่นี่ หากค่าที่ 2 ของ for for loop เป็น [i] ดังนั้นอะไรคือความแตกต่างระหว่างa[i]จาก for for loop กับa ที่a[i]เราเขียน? ดูเหมือนกัน แต่มันไม่ใช่ใช่ไหม
Ti Nn NguyễnHoàng

1
@ TiếnNguyễnHoàng rangeส่งคืนa[i]เป็นค่าส่งคืนที่สอง การดำเนินการนี้val = a[i]ทำโดยrangeสร้างสำเนาของค่าเพื่อให้การดำเนินการเขียนใด ๆvalถูกนำไปใช้กับสำเนา
nemo

37

คุณดูเหมือนจะขอสิ่งที่เทียบเท่ากับสิ่งนี้:

package main

import "fmt"

type Attribute struct {
    Key, Val string
}
type Node struct {
    Attr []Attribute
}

func main() {

    n := Node{
        []Attribute{
            {"key", "value"},
            {"href", "http://www.google.com"},
        },
    }
    fmt.Println(n)

    for i := 0; i < len(n.Attr); i++ {
        attr := &n.Attr[i]
        if attr.Key == "href" {
            attr.Val = "something"
        }
    }

    fmt.Println(n)
}

เอาท์พุท:

{[{key value} {href http://www.google.com}]}
{[{key value} {href something}]}

วิธีนี้จะช่วยหลีกเลี่ยงการสร้างสำเนาของAttributeค่าชนิดที่อาจมีค่าใช้จ่ายในการตรวจสอบส่วนแบ่ง ในตัวอย่างของคุณพิมพ์Attributeมีขนาดค่อนข้างเล็กstringอ้างอิงสองชิ้น: 2 * 3 * 8 = 48 ไบต์บนเครื่องสถาปัตยกรรม 64 บิต

คุณสามารถเขียนได้ง่ายๆด้วย:

for i := 0; i < len(n.Attr); i++ {
    if n.Attr[i].Key == "href" {
        n.Attr[i].Val = "something"
    }
}

แต่วิธีที่จะได้ผลลัพธ์ที่เทียบเท่ากับrangeclause ซึ่งสร้างสำเนา แต่ลดการตรวจสอบขอบเขต slice ให้เหลือน้อยที่สุดคือ:

for i, attr := range n.Attr {
    if attr.Key == "href" {
        n.Attr[i].Val = "something"
    }
}

2
มันน่าเสียดายที่value := &someMap[key]จะไม่ทำงานถ้าsomeMapเป็นmap
warvariuc

peterSO ในข้อมูลโค้ดแรกของคุณคุณไม่ต้องสนใจ attr เพื่อกำหนดบางอย่างให้มัน ie*attr.Val = "something"
Homam Bahrani

25

ฉันจะปรับข้อเสนอแนะสุดท้ายของคุณและใช้ช่วงดัชนีเฉพาะรุ่น

for i := range n.Attr {
    if n.Attr[i].Key == "href" {
        n.Attr[i].Val = "something"
    }
}

ดูเหมือนว่าฉันจะอ้างถึงn.Attr[i]อย่างชัดเจนทั้งในบรรทัดที่ทดสอบKeyและบรรทัดที่กำหนดValแทนที่จะใช้attrสำหรับหนึ่งและn.Attr[i]อื่น ๆ


15

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

package main

import "fmt"

type Attribute struct {
        Key, Val string
}

type Node struct {
        Attr []*Attribute
}

func main() {
        n := Node{[]*Attribute{
                &Attribute{"foo", ""},
                &Attribute{"href", ""},
                &Attribute{"bar", ""},
        }}

        for _, attr := range n.Attr {
                if attr.Key == "href" {
                        attr.Val = "something"
                }
        }

        for _, v := range n.Attr {
                fmt.Printf("%#v\n", *v)
        }
}

สนามเด็กเล่น


เอาท์พุต

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}

ทางเลือกวิธีการ:

package main

import "fmt"

type Attribute struct {
        Key, Val string
}

type Node struct {
        Attr []Attribute
}

func main() {
        n := Node{[]Attribute{
            {"foo", ""},
            {"href", ""},
            {"bar", ""},
        }}

        for i := range n.Attr {
                attr := &n.Attr[i]
                if attr.Key == "href" {
                        attr.Val = "something"
                }
        }

        for _, v := range n.Attr {
                fmt.Printf("%#v\n", v)
        }
}

สนามเด็กเล่น


เอาท์พุท:

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}

ฉันคิดว่ามันชัดเจน แต่ฉันไม่ต้องการเปลี่ยนโครงสร้างที่ฉันได้รับ (พวกเขามาจากgo.net/htmlแพ็คเกจ)
Denys Séguret

1
@dystroy: วิธีที่สองข้างต้นไม่ได้เปลี่ยนประเภท ("โครงสร้าง") wrt the OP
zzzz

ใช่ฉันรู้ แต่มันไม่ได้นำอะไรเลย ฉันคาดหวังความคิดที่ฉันอาจจะพลาด ฉันคุณรู้สึกมั่นใจว่าไม่มีวิธีแก้ปัญหาที่ง่ายกว่านั้นจะเป็นคำตอบ
Denys Séguret

1
@dystroy: มันไม่นำสิ่งที่มันไม่ได้คัดลอกที่นี่และสำรองแอตทริบิวต์ทั้งหมด และใช่ฉันมั่นใจว่าการระบุที่อยู่ขององค์ประกอบส่วนเพื่อหลีกเลี่ยงการอัปเดตสำเนาสองครั้ง (r + w) ขององค์ประกอบนั้นเป็นทางออกที่ดีที่สุด
zzzz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.