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

153

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

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?

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

Денис Сегюре
источник
2
Так вы хотите что-то Array.prototype.forEachв JavaScript?
Флориан Маргейн
Это интересная идея, и это могло бы быть решением, но вызов функции, которая, в свою очередь, вызывала бы функцию на каждой итерации, выглядит тяжелым и неправильным на языке сервера. И отсутствие дженериков заставило бы это чувствовать себя еще тяжелее.
Денис Сегюре
Честно говоря, я не думаю, что это так тяжело. Вызов одной или двух функций очень дешев, обычно именно это оптимизирует компилятор. Я бы попробовал это и проверил это, чтобы видеть, соответствует ли это счету.
Florian Margaine
Поскольку в Go отсутствуют шаблоны, я боюсь, что переданная функция обязательно forEachдолжна начинаться с утверждения типа. Это не совсем лучше, чем attr := &n.Attr[i].
Денис Сегюре

Ответы:

152

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

Причиной этого является то, что 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.

Немо
источник
16
Один из способов думать об этом состоит в том, что присвоение значения вызывает копию. Если бы вы увидели val: = x [1], было бы совершенно неудивительно, что val был копией x [1]. Вместо того, чтобы думать о диапазоне как о чем-то особенном, помните, что каждая итерация диапазона начинается с назначения переменных индекса и значения и что именно это назначение, а не диапазон, вызывает копирование.
Энди Дэвис
Извините, я все еще немного запутался здесь. Если 2-е значение цикла for - это [i], то чем отличается a[i]цикл from от цикла for, a[i]как мы пишем? Похоже, то же самое, но это не так, верно?
Tiến Nguyễn Hoàng
1
@ TiếnNguyễnHoàng rangeвозвращается в a[i]качестве второго возвращаемого значения. Эта операция, val = a[i]как и при выполнении, rangeсоздает копию значения, поэтому любая операция записи в valнее применяется к копии.
Немо
37

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

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"
    }
}
peterSO
источник
2
Жаль, что value := &someMap[key]не получится, если someMapэтоmap
warvariuc
peterSO в вашем первом фрагменте кода разве вы не должны уважать attr, чтобы назначить что-то ему? т.е.*attr.Val = "something"
Хомам Бахрани
25

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

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]для другого.

Пол Ханкин
источник
15

Например:

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:""}
ZZZZ
источник
Я думал, что это было очевидно, но я не хочу менять структуры, которые я получаю (они из go.net/htmlпакета)
Денис Сегюре
1
@dystroy: второй подход выше не меняет типы («структуры») по сравнению с OP.
zzzz
Да, я знаю, но на самом деле это ничего не приносит. Я ожидал идею, которую мог бы пропустить. Я уверен, что нет более простого решения, чем ответ.
Денис Сегюре
1
@dystroy: это действительно что-то приносит, оно не копирует сюда и не возвращает весь атрибут. И да, я уверен, что взятие адреса элемента слайса, чтобы избежать двойного копирования (r + w) обновления элемента, является оптимальным решением.
zzzz