Alterar valores enquanto itera

153

Vamos supor que eu tenho esses tipos:

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

e que eu quero iterar nos atributos do meu nó para alterá-los.

Eu adoraria poder fazer:

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

mas como attrnão é um ponteiro, isso não funcionaria e eu tenho que fazer:

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

Existe uma maneira mais simples ou mais rápida? É possível obter diretamente ponteiros range?

Obviamente, não quero alterar as estruturas apenas para a iteração, e soluções mais detalhadas não são soluções.

Denys Séguret
fonte
2
Então você quer algum tipo de Array.prototype.forEachJavaScript?
Florian Margaine
Essa é uma ideia interessante e que poderia ter sido uma solução, mas chamar uma função que por sua vez chamaria uma função a cada iteração parece pesada e errada em um idioma do servidor. E a falta de genéricos faria isso parecer ainda mais pesado.
Denys Séguret
Honestamente, eu não acho tão pesado assim. Chamar uma função ou duas é muito barato, geralmente é o que os compiladores otimizam mais. Eu tentaria e compararia para ver se isso se encaixa na conta.
Florian Margaine
Como Go não possui genéricos, receio que a função transmitida forEachnecessariamente comece com uma asserção de tipo. Isso não é realmente melhor do que attr := &n.Attr[i].
Denys Séguret

Respostas:

152

Não, a abreviação que você deseja não é possível.

A razão para isso é que rangecopia os valores da fatia na qual você está repetindo. A especificação sobre a faixa diz:

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

Portanto, o intervalo usa a[i]como seu segundo valor para matrizes / fatias, o que significa efetivamente que o valor é copiado, tornando o valor original intocável.

Esse comportamento é demonstrado pelo seguinte 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)
}

O código imprime locais de memória completamente diferentes para o valor do intervalo e o valor real na fatia:

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

Portanto, a única coisa que você pode fazer é usar ponteiros ou o índice, como já proposto por jnml e peterSO.

nemo
fonte
16
Uma maneira de pensar nisso é que atribuir um valor causa uma cópia. Se você visse val: = x [1], não seria de surpreender que val fosse uma cópia de x [1]. Em vez de pensar no intervalo como algo especial, lembre-se de que cada iteração de um intervalo começa atribuindo as variáveis ​​de índice e valor e que é essa atribuição e não o intervalo que causa a cópia.
Andy Davis
Desculpe, ainda estou um pouco confuso aqui. Se o 2º valor do loop for for a [i], qual é a diferença entre o a[i]loop for e o a[i]que escrevemos? Parece a mesma coisa, mas não é, certo?
Tiến Nguyễn Hoàng
1
@ TiếnNguyễnHoàng rangeretorna a[i]como seu segundo valor de retorno. Esta operação, val = a[i]conforme feita por, rangecria uma cópia do valor para que qualquer operação de gravação valseja aplicada a uma cópia.
nemo
37

Você parece estar pedindo algo equivalente a isso:

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

Resultado:

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

Isso evita a criação de uma cópia - possivelmente grande - dos Attributevalores de tipo , às custas das verificações de limites de fatia. No seu exemplo, o tipo Attributeé relativamente pequeno, com duas stringreferências de fatia: 2 * 3 * 8 = 48 bytes em uma máquina de arquitetura de 64 bits.

Você também pode simplesmente escrever:

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

Porém, a maneira de obter um resultado equivalente com uma rangecláusula, que cria uma cópia, mas minimiza as verificações de limites de fatia, é:

for i, attr := range n.Attr {
    if attr.Key == "href" {
        n.Attr[i].Val = "something"
    }
}
peterSO
fonte
2
É uma pena que value := &someMap[key]não vai funcionar, se someMapé ummap
warvariuc
peterSO em seu primeiro trecho de código, você não precisa deferir attr para atribuir algo a ele? ie*attr.Val = "something"
Homam Bahrani
25

Eu adaptaria sua última sugestão e usaria a versão apenas do índice do intervalo.

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

Parece-me mais simples me referir n.Attr[i]explicitamente na linha que testa Keye na linha que define Val, em vez de usar attrpara um e n.Attr[i]para o outro.

Paul Hankin
fonte
15

Por exemplo:

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

Parque infantil


Resultado

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

Abordagem alternativa:

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

Parque infantil


Resultado:

main.Attribute{Key:"foo", Val:""}
main.Attribute{Key:"href", Val:"something"}
main.Attribute{Key:"bar", Val:""}
zzzz
fonte
Eu pensei que era óbvio, mas eu não quero mudar as estruturas que recebo (eles são do go.net/htmlpacote)
Denys Séguret
1
@dystroy: A segunda abordagem acima não altera os tipos (as "estruturas") do OP.
Zzzz
Sim, eu sei, mas não está realmente trazendo nada. Eu estava esperando uma idéia que eu poderia ter perdido. Você está confiante de que não há solução mais simples do que essa seria a resposta.
Denys Séguret
1
@dystroy: Ele não trazer algo, ele não copiar aqui e voltar todo o Atributo. E sim, estou confiante de que usar o endereço de um elemento de fatia para evitar uma atualização de cópia dupla (r + w) do elemento é a solução ideal.
Zzzz