Cambiar valores mientras itera

153

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

¿Hay una manera más simple o más rápida? ¿Es posible obtener directamente punteros range?

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

Denys Séguret
fuente
2
Entonces, ¿quieres algún tipo de Array.prototype.forEachJavaScript?
Florian Margaine
Esa es una idea interesante y podría haber sido una solución, pero llamar a una función que a su vez llamaría una función en cada iteración parece pesado e incorrecto en un lenguaje del lado del servidor. Y la falta de genéricos haría que esto se sintiera aún más pesado.
Denys Séguret
Honestamente, no creo que sea tan pesado. Llamar a una o dos funciones es muy barato, esto suele ser lo que más optimizan los compiladores. Lo probaría y lo compararía para ver si cumple con los requisitos.
Florian Margaine
Como Go carece de genéricos, me temo que la función pasada forEachnecesariamente comenzaría con una aserción de tipo. Eso no es realmente mejor que attr := &n.Attr[i].
Denys Séguret

Respuestas:

152

No, la abreviatura que desea 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 rango 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 utiliza a[i]como su segundo valor para matrices / sectores, lo que significa que el valor se copia, haciendo que el valor original sea intocable.

Este comportamiento se demuestra mediante 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 le 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 lo propusieron jnml y peterSO.

nemo
fuente
16
Una forma de pensar en esto es que asignar un valor causa una copia. Si vio val: = x [1], no sería completamente sorprendente que val fuera una copia de x [1]. En lugar de pensar que el rango hace algo especial, recuerde que cada iteración de un rango comienza asignando las variables de índice y valor, y que es esa asignación en lugar del rango lo que causa la copia.
Andy Davis
Lo siento, todavía estoy un poco confundido aquí. Si el segundo valor de for loop es un [i], entonces, ¿cuál es la diferencia entre a[i]el for loop y el a[i]mientras escribimos? Parece lo mismo pero no lo es, ¿verdad?
Tiến Nguyễn Hoàng
1
@ TiếnNguyễnHoàng rangeregresa a[i]como su segundo valor de retorno. Esta operación, val = a[i]como se hace mediante, rangecrea una copia del valor, de modo que cualquier operación de escritura valse aplica a una copia.
nemo
37

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

Salida:

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

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

También podrías 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 corte, es:

for i, attr := range n.Attr {
    if attr.Key == "href" {
        n.Attr[i].Val = "something"
    }
}
PeterSO
fuente
2
Es una pena que value := &someMap[key]no funcione si someMapes unmap
warvariuc
PeterSO en su primer fragmento de código, ¿no tiene que desferencia attr para asignarle algo? es decir*attr.Val = "something"
Homam Bahrani el
25

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 simple referirme n.Attr[i]explícitamente tanto en la línea que prueba Keycomo en la línea que establece Val, en lugar de usar attrpara una y n.Attr[i]para la otra.

Paul Hankin
fuente
15

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 recreo


Salida

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 recreo


Salida:

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
zzzz
fuente
Pensé que era obvio, pero no quiero cambiar las estructuras que obtengo (son del go.net/htmlpaquete)
Denys Séguret
1
@distroy: El segundo enfoque anterior no cambia los tipos (las "estructuras") wrt el OP.
zzzz
Sí, lo sé, pero en realidad no trae nada. Esperaba una idea que posiblemente podría haber perdido. Confío en que no hay una solución más simple, esa sería la respuesta.
Denys Séguret
1
@dystroy: Se hace traer algo, que no copie aquí y una copia de todo el Atributo. Y sí, estoy seguro de que tomar la dirección de un elemento de división para evitar una actualización de doble copia (r + w) del elemento es la solución óptima.
zzzz