Изменить значения во время итерации

Предположим, у меня есть эти типы:

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

и что я хочу перебрать атрибуты моего узла, чтобы изменить их.

Я хотел бы иметь возможность сделать:

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

но так как attr не указатель, это не сработает, и я должен сделать:

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

Есть более простой или быстрый способ? Можно ли напрямую получить указатели из range?

Очевидно, что я не хочу менять структуры только для итерации, и более подробные решения не являются решениями.

Ответ 1

Нет, аббревиатура, которую вы хотите, не представляется возможным.

Причиной этого является то, что range копирует значения из фрагмента, который вы повторяете. Спецификация о диапазоне говорит:

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

Таким образом, диапазон использует a[i] как свое второе значение для массивов/срезов, что фактически означает, что значение копируется, что делает исходное значение неприкосновенным.

Это поведение демонстрируется следующим кодом:

x := make([]int, 3)

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

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

Код печатает полностью разные ячейки памяти для значения из диапазона и фактического значение в срезе:

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

Итак, единственное, что вы можете сделать, это либо использовать указатели, либо индекс, как уже было предложено jnml и peterSO.

Ответ 2

Кажется, вы просите что-то эквивалентное этому:

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

Вывод:

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

Это позволяет избежать создания, возможно, большой копии значений типа Attribute, за счет проверки границ срезов. В вашем примере тип Attribute относительно невелик, две ссылки на срез string: 2 * 3 * 8 = 48 байт на 64-битной архитектуре.

Вы также можете просто написать:

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

Но способ получения эквивалентного результата с предложением range, который создает копию, но минимизирует проверки границ срезов, заключается в следующем:

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

Ответ 3

Например:

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

Игровая площадка


Выход

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

Альтернативный подход:

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

Игровая площадка


Вывод:

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

Ответ 4

Я бы адаптировал ваше последнее предложение и использовал версию диапазона только для индекса.

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

Мне кажется более простым ссылаться на n.Attr[i] явно в обеих строках, которые проверяют Key и строку, которая устанавливает Val, вместо использования attr для одного и n.Attr[i] для другого.