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.

Author: abhi, 2013-04-11

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.

 103
Author: nemo,
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.

Możesz też po prostu napisać:
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"
    }
}
 21
Author: 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
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)
        }
}

Plac Zabaw


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

Plac Zabaw


Wyjście:

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
 13
Author: zzzz,
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.

 11
Author: Paul Hankin,
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