Cambiar valores mientras se itera

Resuelto Denys Séguret asked hace 11 años • 4 respuestas

Supongamos que tengo estos tipos:

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

y que quiero iterar sobre los atributos de mi nodo para cambiarlos.

Me hubiera encantado poder hacer:

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

pero como attrno es un puntero, esto no funcionaría y tengo que hacer:

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

¿Existe una forma más sencilla o rápida? ¿Es posible obtener sugerencias directamente desderange ?

Obviamente no quiero cambiar las estructuras solo para la iteración y las soluciones más detalladas no son soluciones.

Denys Séguret avatar Apr 11 '13 16:04 Denys Séguret
Aceptado

No, la abreviatura que deseas no es posible.

La razón de esto es que rangecopia los valores del segmento sobre el que está iterando. La especificación sobre el alcance dice:

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

Por lo tanto, el rango se utiliza a[i]como segundo valor para matrices/porciones, lo que efectivamente significa que el valor se copia, haciendo que el valor original sea intocable.

Este comportamiento se demuestra con el siguiente código :

x := make([]int, 3)

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

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

El código imprime ubicaciones de memoria completamente diferentes para el valor del rango y el valor real en el segmento:

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

Entonces, lo único que puede hacer es usar punteros o el índice, como ya propusieron jnml y peterSO.

nemo avatar Apr 11 '2013 15:04 nemo

Parece que estás pidiendo algo equivalente a esto:

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

Producción:

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

Esto evita la creación de una copia (posiblemente grande) de Attributevalores de tipo, a expensas de las comprobaciones de los límites de los sectores. En su ejemplo, el tipo Attributees relativamente pequeño, dos stringreferencias de segmento: 2 * 3 * 8 = 48 bytes en una máquina con arquitectura de 64 bits.

También puedes simplemente escribir:

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

Pero la forma de obtener un resultado equivalente con una rangecláusula, que crea una copia pero minimiza las comprobaciones de los límites de los sectores, es:

for i, attr := range n.Attr {
    if attr.Key == "href" {
        n.Attr[i].Val = "something"
    }
}
peterSO avatar Apr 11 '2013 11:04 peterSO

Adaptaría su última sugerencia y usaría la versión de rango de solo índice.

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

Me parece más sencillo hacer referencia n.Attr[i]explícita tanto en la línea que prueba Keycomo en la línea que establece Val, en lugar de usar attrpara uno y n.Attr[i]para el otro.

Paul Hankin avatar Apr 15 '2013 11:04 Paul Hankin

Por ejemplo:

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

Patio de juegos


Producción

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

Enfoque alternativo:

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

Patio de juegos


Producción:

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
zzzz avatar Apr 11 '2013 09:04 zzzz