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.