Ubah nilai saat iterasi

153

Misalkan saya memiliki tipe-tipe ini:

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

dan bahwa saya ingin beralih pada atribut simpul saya untuk mengubahnya.

Saya ingin sekali dapat melakukan:

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

tetapi karena attrbukan sebuah pointer, ini tidak akan berhasil dan harus saya lakukan:

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

Apakah ada cara yang lebih sederhana atau lebih cepat? Apakah mungkin untuk mendapatkan pointer secara langsung range?

Jelas saya tidak ingin mengubah struktur hanya untuk iterasi dan solusi yang lebih verbal bukanlah solusi.

Denys Séguret
sumber
2
Jadi Anda ingin semacam Array.prototype.forEachJavaScript?
Florian Margaine
Itu ide yang menarik dan itu bisa menjadi solusi tetapi memanggil fungsi yang pada gilirannya akan memanggil fungsi di setiap iterasi terlihat berat dan salah dalam bahasa sisi server. Dan kurangnya obat generik akan membuat ini terasa lebih berat.
Denys Séguret
Jujur, saya pikir itu tidak seberat itu. Memanggil satu atau dua fungsi sangat murah, inilah yang biasanya mengoptimalkan kompiler. Saya akan mencoba dan membandingkannya untuk melihat apakah itu sesuai dengan tagihan.
Florian Margaine
Karena Go tidak memiliki obat generik, saya khawatir fungsi yang diteruskan forEachakan dimulai dengan pernyataan jenis. Itu tidak benar-benar lebih baik daripada attr := &n.Attr[i].
Denys Séguret

Jawaban:

152

Tidak, singkatan yang Anda inginkan tidak mungkin.

Alasannya adalah karena rangemenyalin nilai dari slice yang Anda iterasi. The spesifikasi tentang berbagai mengatakan:

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

Jadi, rentang menggunakan a[i]sebagai nilai kedua untuk array / irisan, yang secara efektif berarti bahwa nilai tersebut disalin, membuat nilai asli tidak tersentuh.

Perilaku ini ditunjukkan oleh kode berikut :

x := make([]int, 3)

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

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

Kode ini mencetak lokasi memori yang sama sekali berbeda untuk nilai dari rentang dan nilai aktual di slice:

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

Jadi satu-satunya hal yang dapat Anda lakukan adalah menggunakan pointer atau indeks, seperti yang telah diusulkan oleh jnml dan peterSO.

nemo
sumber
16
Salah satu cara untuk memikirkan ini adalah bahwa menetapkan nilai menyebabkan salinan. Jika Anda melihat val: = x [1], sama sekali tidak mengejutkan bahwa val adalah salinan x [1]. Daripada memikirkan rentang sebagai melakukan sesuatu yang istimewa, ingatlah bahwa setiap iterasi rentang dimulai dengan menugaskan variabel indeks dan nilai, dan bahwa itu adalah penugasan itu daripada rentang yang menyebabkan salinan.
Andy Davis
Maaf saya masih sedikit bingung di sini. Jika nilai ke-2 untuk loop adalah [i], lalu apa perbedaan antara a[i]dari untuk loop dan a[i]saat kita menulis? Sepertinya hal yang sama tetapi tidak, kan?
Tiến Nguyễn Hoàng
1
@ TiếnNguyễnHoàng rangekembali a[i]sebagai nilai pengembalian kedua. Operasi ini val = a[i],, seperti yang dilakukan oleh rangemembuat salinan nilai sehingga operasi penulisan apa pun valditerapkan ke salinan.
nemo
37

Anda tampaknya meminta sesuatu yang setara dengan ini:

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

Keluaran:

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

Ini menghindari pembuatan - kemungkinan besar - salinan Attributenilai tipe , dengan mengorbankan pengecekan slice bounds. Dalam contoh Anda, tipe Attributerelatif kecil, dua stringreferensi slice: 2 * 3 * 8 = 48 byte pada mesin arsitektur 64-bit.

Anda juga bisa menulis:

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

Tapi, cara untuk mendapatkan hasil yang setara dengan rangeklausa, yang membuat salinan tetapi meminimalkan cek batas slice, adalah:

for i, attr := range n.Attr {
    if attr.Key == "href" {
        n.Attr[i].Val = "something"
    }
}
peterSO
sumber
2
Ini disayangkan bahwa value := &someMap[key]tidak akan bekerja jika someMapadalahmap
warvariuc
peterSO dalam cuplikan kode pertama Anda, tidakkah Anda harus menghormati attr untuk menetapkan sesuatu padanya? yaitu*attr.Val = "something"
Homam Bahrani
25

Saya akan menyesuaikan saran terakhir Anda dan menggunakan versi rentang indeks saja.

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

Tampaknya lebih mudah bagi saya untuk merujuk n.Attr[i]secara eksplisit di kedua baris yang menguji Keydan baris yang mengatur Val, daripada menggunakan attruntuk satu dan n.Attr[i]untuk yang lain.

Paul Hankin
sumber
15

Sebagai contoh:

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

Tempat bermain


Keluaran

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

Pendekatan alternatif:

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

Tempat bermain


Keluaran:

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
zzzz
sumber
Saya pikir itu sudah jelas tetapi saya tidak ingin mengubah struktur yang saya dapatkan (itu dari go.net/htmlpaket)
Denys Séguret
1
@dystroy: Pendekatan kedua di atas tidak mengubah jenis ("struktur") wrt OP.
zzzz
Ya, saya tahu, tetapi itu tidak benar-benar membawa apa-apa. Saya mengharapkan ide yang mungkin saya lewatkan. Saya Anda merasa yakin bahwa tidak ada solusi yang lebih sederhana maka itu akan menjadi jawabannya.
Denys Séguret
1
@dystroy: Ini tidak membawa sesuatu, itu tidak menyalin di sini dan kembali seluruh Atribut. Dan ya, saya yakin bahwa mengambil alamat elemen slice untuk menghindari pembaruan salin ganda (r + w) elemen adalah solusi optimal.
zzzz