Zmiana wartości podczas iteracji w golang
Załóżmy, że mam takie typy:
type Attribute struct {
Key, Val string
}
type Node struct {
Attr []Attribute
}
I że chcę iterować atrybuty mojego węzła, aby je zmienić.
Chciałbym móc to zrobić:
for _, attr := range n.Attr {
if attr.Key == "href" {
attr.Val = "something"
}
}
Ale ponieważ attr
nie jest wskaźnikiem, to nie zadziała i muszę to zrobić:
for i, attr := range n.Attr {
if attr.Key == "href" {
n.Attr[i].Val = "something"
}
}
Czy istnieje prostszy czy szybszy sposób? Czy możliwe jest bezpośrednie uzyskanie wskaźników z range
?
Oczywiście nie chcę zmieniać struktur tylko dla iteracji i bardziej gadatliwe rozwiązania nie są rozwiązaniami.
4 answers
Nie, żądany Skrót nie jest możliwy.
Powodem tego jest to, że range
kopiuje wartości z kawałka, nad którym się iterujesz.
Specyfikacja o zasięgu mówi:
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
Tak więc range używa a[i]
jako swojej drugiej wartości dla tablic/plasterków, co skutecznie oznacza, że
wartość jest kopiowana, co sprawia, że oryginalna wartość jest nietykalna.
To zachowanie jest demonstrowane przez następujący kod :
x := make([]int, 3)
x[0], x[1], x[2] = 1, 2, 3
for i, val := range x {
println(&x[i], "vs.", &val)
}
Kod Cię drukuje zupełnie inne miejsca pamięci dla wartości z zakresu i rzeczywistego wartość w plasterku:
0xf84000f010 vs. 0x7f095ed0bf68
0xf84000f014 vs. 0x7f095ed0bf68
0xf84000f018 vs. 0x7f095ed0bf68
Więc jedyne, co możesz zrobić, to użyć wskaźników lub indeksu, jak już zaproponowali jnml i peterSO.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-19 04:59:02
Wydaje się, że prosisz o coś równoważnego temu:
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)
}
Wyjście:
{[{key value} {href http://www.google.com}]}
{[{key value} {href something}]}
Pozwala to uniknąć tworzenia -- prawdopodobnie dużej -- kopii wartości type Attribute
, kosztem kontroli slice bounds. W twoim przykładzie Typ Attribute
jest stosunkowo mały, dwa odniesienia string
: 2 * 3 * 8 = 48 bajtów na 64-bitowej maszynie o architekturze.
for i := 0; i < len(n.Attr); i++ {
if n.Attr[i].Key == "href" {
n.Attr[i].Val = "something"
}
}
Ale, sposób na uzyskanie równoważnego wyniku z range
klauzulą, która tworzy kopię, ale minimalizuje kontrole granic, jest:
for i, attr := range n.Attr {
if attr.Key == "href" {
n.Attr[i].Val = "something"
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-11 11:33:54
Na przykład:
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)
}
}
Wyjście
main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
Podejście alternatywne:
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)
}
}
Wyjście:
main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-11 09:39:29
Dostosowałbym twoją ostatnią sugestię i użyłbym tylko indeksowej wersji range.
for i := range n.Attr {
if n.Attr[i].Key == "href" {
n.Attr[i].Val = "something"
}
}
Wydaje mi się prostsze odwoływanie się do n.Attr[i]
jawnie zarówno w linii testującej Key
, jak i linii ustawiającej Val
, zamiast używać attr
dla jednej i n.Attr[i]
dla drugiej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-15 11:24:28