Modifica i valori durante l'iterazione


153

Supponiamo che io abbia questi tipi:

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

e che voglio iterare sugli attributi del mio nodo per cambiarli.

Mi sarebbe piaciuto poter fare:

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

ma poiché attrnon è un puntatore, questo non funzionerebbe e devo fare:

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

C'è un modo più semplice o più veloce? È possibile ottenere direttamente i puntatori da range?

Ovviamente non voglio cambiare le strutture solo per l'iterazione e soluzioni più dettagliate non sono soluzioni.


2
Quindi vuoi un qualche tipo di Array.prototype.forEachJavaScript?
Florian Margaine,

È un'idea interessante e potrebbe essere stata una soluzione, ma chiamare una funzione che a sua volta chiamerebbe una funzione ad ogni iterazione sembra pesante e sbagliata in un linguaggio lato server. E la mancanza di generici renderebbe questo ancora più pesante.
Denys Séguret,

Onestamente, non penso sia così pesante. Chiamare una o due funzioni è molto economico, questo è ciò che i compilatori ottimizzano maggiormente. Lo proverei e lo confronterei per vedere se si adatta al conto.
Florian Margaine,

Dato che Go non ha generici, temo che la funzione passata debba forEachnecessariamente iniziare con un'asserzione di tipo. Non è molto meglio di attr := &n.Attr[i].
Denys Séguret,

Risposte:


152

No, l'abbreviazione desiderata non è possibile.

La ragione di ciò è che rangecopia i valori dalla porzione su cui stai ripetendo. Le specifiche sulla gamma dicono:

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

Pertanto, range utilizza a[i]come secondo valore per array / sezioni, il che significa che il valore viene copiato, rendendo intoccabile il valore originale.

Questo comportamento è dimostrato dal seguente codice :

x := make([]int, 3)

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

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

Il codice stampa posizioni di memoria completamente diverse per il valore compreso nell'intervallo e il valore effettivo nella sezione:

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

Quindi l'unica cosa che puoi fare è usare i puntatori o l'indice, come già proposto da jnml e peterSO.


16
Un modo di pensare a questo è che l'assegnazione di un valore provoca una copia. Se vedessi val: = x [1], non sorprenderebbe che val fosse una copia di x [1]. Piuttosto che pensare all'intervallo come a fare qualcosa di speciale, ricorda che ogni iterazione di un intervallo inizia assegnando l'indice e le variabili di valore e che è quell'assegnazione piuttosto che l'intervallo che causa la copia.
Andy Davis,

Scusa, sono ancora un po 'confuso qui. Se il secondo valore di for loop è un [i], allora qual è la differenza tra il a[i]ciclo for e il a[i]mentre scriviamo? Sembra la stessa cosa ma non lo è, giusto?
Tiến Nguyễn Hoàng,

1
@ TiếnNguyễnHoàng rangerestituisce a[i]come secondo valore restituito. Questa operazione, val = a[i]come fatto da, rangecrea una copia del valore in modo che qualsiasi operazione di scrittura valvenga applicata a una copia.
nemo,

37

Sembra che tu stia chiedendo qualcosa di equivalente a questo:

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

Produzione:

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

Ciò evita di creare una copia - possibilmente grande - di Attributevalori di tipo , a spese dei controlli dei limiti di sezione. Nel tuo esempio, type Attributeè relativamente piccolo, due stringriferimenti di slice: 2 * 3 * 8 = 48 byte su una macchina con architettura a 64 bit.

Puoi anche semplicemente scrivere:

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

Ma il modo per ottenere un risultato equivalente con una rangeclausola, che crea una copia ma riduce al minimo i controlli dei limiti di sezione, è:

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

2
È un peccato che value := &someMap[key]non funzionerà se someMapè unmap
warvariuc

peterSO nel tuo primo frammento di codice non devi deferire attr per assegnargli qualcosa? cioè*attr.Val = "something"
Homam Bahrani,

25

Adatterò il tuo ultimo suggerimento e utilizzerei la versione solo indice della gamma.

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

Mi sembra più semplice fare riferimento n.Attr[i]esplicitamente sia alla riga che verifica Keyche alla riga impostata Val, anziché utilizzare attrper l'una e n.Attr[i]per l'altra.


15

Per esempio:

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

Terreno di gioco


Produzione

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

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

Terreno di gioco


Produzione:

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

Ho pensato che fosse ovvio, ma non voglio cambiare le strutture che ottengo (sono dal go.net/htmlpacchetto)
Denys Séguret,

1
@dystroy: il secondo approccio sopra non cambia i tipi (le "strutture") rispetto all'OP.
zzzz,

Sì, lo so, ma in realtà non sta portando nulla. Mi aspettavo un'idea che avrei potuto perdere. Sono sicuro che non ci sia una soluzione più semplice che sarebbe la risposta.
Denys Séguret,

1
@dystroy: Si fa portare qualcosa, ma non si copia qui e indietro l'intero attributo. E sì, sono fiducioso che prendere la soluzione dell'indirizzo di un elemento slice per evitare un doppio aggiornamento (r + w) dell'elemento sia la soluzione ottimale.
zzzz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.