Cambiar valores mientras se itera
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 attr
no 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.
No, la abreviatura que deseas no es posible.
La razón de esto es que range
copia 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.
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 Attribute
valores de tipo, a expensas de las comprobaciones de los límites de los sectores. En su ejemplo, el tipo Attribute
es relativamente pequeño, dos string
referencias 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 range
clá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"
}
}
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 Key
como en la línea que establece Val
, en lugar de usar attr
para uno y n.Attr[i]
para el otro.
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:""}