web-dev-qa-db-fra.com

Changer les valeurs en itérant

Supposons que j'ai ces types:

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

et que je veux itérer sur les attributs de mon noeud pour les changer.

J'aurais aimé pouvoir faire:

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

mais comme attr n'est pas un pointeur, cela ne fonctionnerait pas et je dois le faire:

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

Y a-t-il un moyen plus simple ou plus rapide? Est-il possible d'obtenir directement des pointeurs de range?

Évidemment, je ne veux pas changer les structures juste pour l'itération et des solutions plus verbeuses ne sont pas des solutions.

134
Denys Séguret

Non, l'abréviation que vous voulez n'est pas possible.

La raison en est que range copie les valeurs de la tranche sur laquelle vous effectuez une itération. Le spécification sur la plage dit:

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

La plage utilise donc a[i] en tant que deuxième valeur pour les tableaux/tranches, ce qui signifie en fait que la valeur est copiée, ce qui rend la valeur d'origine intouchable.

Ce comportement est démontré par le code suivant :

x := make([]int, 3)

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

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

Le code vous imprime des emplacements de mémoire complètement différents pour la valeur de la plage et la valeur réelle dans la tranche:

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

Donc, la seule chose que vous pouvez faire est d’utiliser des pointeurs ou l’index, comme déjà proposé par jnml et peterSO.

131
nemo

Vous semblez demander quelque chose d'équivalent à ceci:

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

Sortie:

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

Cela évite de créer une copie - éventuellement volumineuse - de type Attribute de valeur, aux dépens des contrôles de limite de tranche. Dans votre exemple, taper Attribute est relativement petit, deux références de slices string: 2 * 3 * 8 = 48 octets sur une machine d'architecture 64 bits.

Vous pouvez aussi simplement écrire:

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

Cependant, la manière d'obtenir un résultat équivalent avec une clause range, qui crée une copie mais minimise les contrôles de limite de tranche, est la suivante:

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

J'adapterais votre dernière suggestion et utiliserais la version d'index uniquement de range.

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

Il me semble plus simple de parler de n.Attr[i] explicitement dans la ligne qui teste Key et la ligne qui définit Val, plutôt que d'utiliser attr pour one et n.Attr[i] pour l'autre.

17
Paul Hankin

Par exemple:

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

Terrain de je


Sortie

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

Approche alternative:

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

Terrain de je


Sortie:

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