Changer les valeurs lors de l'itération

153

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 nœud pour les changer.

J'aurais aimé pouvoir faire:

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

mais ce attrn'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"
    }
}

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

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

Denys Séguret
la source
2
Alors, vous voulez une sorte de Array.prototype.forEachJavaScript?
Florian Margaine
C'est une idée intéressante et cela aurait pu être une solution, mais appeler une fonction qui à son tour appellerait une fonction à chaque itération semble lourd et incorrect dans un langage côté serveur. Et le manque de génériques rendrait cette sensation encore plus lourde.
Denys Séguret
Honnêtement, je ne pense pas que ce soit si lourd. L'appel d'une fonction ou deux est très bon marché, c'est généralement ce que les compilateurs optimisent le plus. Je l'essayerais et le comparerais pour voir si cela correspond à la facture.
Florian Margaine
Comme Go manque de génériques, j'ai bien peur que la fonction transmise forEachcommence nécessairement par une assertion de type. Ce n'est pas vraiment mieux que attr := &n.Attr[i].
Denys Séguret

Réponses:

152

Non, l'abréviation souhaitée n'est pas possible.

La raison en est que rangecopie les valeurs de la tranche sur laquelle vous effectuez une itération. La spécification sur la gamme 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

Ainsi, range utilise a[i]comme 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 illustré 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.

nemo
la source
16
Une façon de penser à cela est que l'attribution d'une valeur entraîne une copie. Si vous voyiez val: = x [1], il ne serait pas surprenant que val soit une copie de x [1]. Plutôt que de considérer la plage comme quelque chose de spécial, rappelez-vous que chaque itération d'une plage commence par attribuer les variables d'index et de valeur, et que c'est cette affectation plutôt que la plage qui provoque la copie.
Andy Davis
Désolé, je suis encore un peu confus ici. Si la deuxième valeur de la boucle for est un [i], alors quelle est la différence entre la a[i]boucle for et le au moment a[i]où nous écrivons? Cela ressemble à la même chose mais ce n'est pas le cas, non?
Tiến Nguyễn Hoàng
1
@ TiếnNguyễnHoàng rangerenvoie a[i]sa deuxième valeur de retour. Cette opération, val = a[i]comme effectuée par rangecrée une copie de la valeur afin que toute opération d'écriture valsoit appliquée à une copie.
nemo
37

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

Production:

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

Cela évite de créer une copie - éventuellement volumineuse - des Attributevaleurs de type , au détriment des vérifications des limites de tranche. Dans votre exemple, le type Attributeest relativement petit, deux stringréférences de tranche: 2 * 3 * 8 = 48 octets sur une machine à 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"
    }
}

Mais, le moyen d'obtenir un résultat équivalent avec une rangeclause, qui crée une copie mais minimise les vérifications des limites de tranche, est:

for i, attr := range n.Attr {
    if attr.Key == "href" {
        n.Attr[i].Val = "something"
    }
}
peterSO
la source
2
C'est dommage que value := &someMap[key]cela ne fonctionnera pas si someMapc'est unmap
warvariuc
peterSO dans votre premier extrait de code, n'avez-vous pas à déférer à attr pour lui attribuer quelque chose? ie*attr.Val = "something"
Homam Bahrani
25

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

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

Il me semble plus simple de se référer n.Attr[i]explicitement à la fois à la ligne qui teste Keyet à la ligne qui définit Val, plutôt que d'utiliser attrpour l'un et n.Attr[i]pour l'autre.

Paul Hankin
la source
15

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 jeux


Production

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 jeux


Production:

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
zzzz
la source
Je pensais que c'était évident mais je ne veux pas changer les structures que j'obtiens (elles proviennent du go.net/htmlpaquet)
Denys Séguret
1
@dystroy: La deuxième approche ci - dessus ne change pas les types (les "structures") de l'OP.
zzzz
Oui, je sais, mais ça n'apporte vraiment rien. Je m'attendais à une idée que j'aurais pu manquer. Je suis convaincu qu’il n’existe pas de solution plus simple que ce serait la réponse.
Denys Séguret
1
@dystroy: Il n'apporte quelque chose, il ne copie ici et arrière l'ensemble d' attributs. Et oui, je suis convaincu que prendre l'adresse d'un élément slice pour éviter une mise à jour en double copie (r + w) de l'élément est la solution optimale.
zzzz