Змінюйте значення під час ітерації


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.forEachJavaScript?
Флоріан Маргайн

Це цікава ідея, і це могло б бути рішенням, але виклик функції, яка в свою чергу викликає функцію при кожній ітерації, виглядає важкою і неправильною мовою на стороні сервера. І недолік дженериків зробив би це відчуття ще важчим.
Denys Séguret

Чесно кажучи, я не думаю, що це так важко. Виклик функції або двох дуже дешево, зазвичай це компілятори оптимізують найбільше. Я б спробував це і порівняв його, щоб побачити, чи відповідає його рахунку.
Флоріан Маргаїн

Оскільки в Go не вистачає дженерики, я побоююсь, що передана функція forEachобов'язково починається з твердження про тип. Це насправді не краще attr := &n.Attr[i].
Denys Séguret

Відповіді:


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]. Замість того, щоб думати про діапазон як про щось особливе, пам’ятайте, що кожна ітерація діапазону починається з присвоєння змінних індексу та значення, і що саме присвоєння, а не діапазону, викликає копію.
Енді Девіс

Вибачте, я все ще трохи плутаю тут. Якщо другим значенням для циклу є [i], то чим він відрізняється a[i]від циклу for для циклу, a[i]як ми пишемо? Це схоже на те саме, але це не так, правда?
Tiến Nguyễn Hoàng

1
@ TiếnNguyễnHoàng rangeповертається a[i]як друге повернене значення. Ця операція, val = a[i]як це робиться шляхом rangeстворення копії значення, так що будь-яка операція запису val, застосована до копії.
nemo

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

2
Шкода, що value := &someMap[key]нічого не вийде, якщо someMapцеmap
warvariuc

peterSO у вашому першому фрагменті коду, чи не потрібно attr 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:""}

Я подумав, що це очевидно, але я не хочу змінювати структури, які я отримую (вони з go.net/htmlпакету)
Denys Séguret

1
@dystroy: Другий вище підхід не змінює типи ("структури") ОП.
zzzz

Так, я знаю, але це насправді нічого не приносить. Я очікував ідеї, яку я, можливо, міг пропустити. Я відчуваю впевненість, що немає простішого рішення, то це було б відповіддю.
Denys Séguret

1
@dystroy: Це дійсно принести що - то, що НЕ копіювати тут і назад весь атрибут. І так, я впевнений, що прийняття адреси елемента фрагмента, щоб уникнути подвійного оновлення елемента (r + w), є оптимальним рішенням.
zzzz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.