Ändern Sie die Werte während der Iteration


153

Nehmen wir an, ich habe folgende Typen:

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

und dass ich die Attribute meines Knotens durchlaufen möchte, um sie zu ändern.

Am liebsten hätte ich Folgendes tun können:

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

aber da attres kein Zeiger ist, würde dies nicht funktionieren und ich muss tun:

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

Gibt es einen einfacheren oder schnelleren Weg? Ist es möglich, Zeiger direkt abzurufen range?

Natürlich möchte ich die Strukturen nicht nur für die Iteration ändern, und ausführlichere Lösungen sind keine Lösungen.


2
Sie möchten also eine Art Array.prototype.forEachJavaScript?
Florian Margaine

Das ist eine interessante Idee, und das hätte eine Lösung sein können, aber das Aufrufen einer Funktion, die wiederum bei jeder Iteration eine Funktion aufruft, sieht in einer serverseitigen Sprache schwer und falsch aus. Und der Mangel an Generika würde dieses Gefühl noch schwerer machen.
Denys Séguret

Ehrlich gesagt denke ich nicht, dass es so schwer ist. Das Aufrufen einer oder zweier Funktionen ist sehr billig. Dies ist normalerweise das, was Compiler am meisten optimieren. Ich würde es versuchen und es vergleichen, um zu sehen, ob es der Rechnung entspricht.
Florian Margaine

Da Go keine Generika besitzt, befürchte ich, dass die übergebene Funktion forEachnotwendigerweise mit einer Typzusicherung beginnen würde. Das ist nicht wirklich besser als attr := &n.Attr[i].
Denys Séguret

Antworten:


152

Nein, die gewünschte Abkürzung ist nicht möglich.

Der Grund dafür ist, dass rangedie Werte aus dem Slice kopiert werden, über das Sie iterieren. Die Spezifikation über die Reichweite lautet:

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

Daher wird range a[i]als zweiter Wert für Arrays / Slices verwendet. Dies bedeutet effektiv, dass der Wert kopiert wird, wodurch der ursprüngliche Wert unberührbar wird.

Dieses Verhalten wird durch den folgenden Code demonstriert :

x := make([]int, 3)

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

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

Der Code druckt Ihnen völlig unterschiedliche Speicherorte für den Wert aus dem Bereich und den tatsächlichen Wert im Slice:

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

Sie können also entweder Zeiger oder den Index verwenden, wie bereits von jnml und peterSO vorgeschlagen.


16
Eine Möglichkeit, sich das vorzustellen, besteht darin, dass das Zuweisen eines Werts eine Kopie verursacht. Wenn Sie val: = x [1] sehen würden, wäre es völlig nicht überraschend, dass val eine Kopie von x [1] ist. Anstatt den Bereich als etwas Besonderes zu betrachten, denken Sie daran, dass jede Iteration eines Bereichs mit der Zuweisung der Index- und Wertvariablen beginnt und dass diese Zuweisung und nicht der Bereich die Kopie verursacht.
Andy Davis

Entschuldigung, ich bin hier immer noch ein bisschen verwirrt. Wenn der 2. Wert der for-Schleife ein [i] ist, was ist dann der Unterschied zwischen a[i]der for-Schleife und der a[i]beim Schreiben? Es sieht genauso aus, ist es aber nicht, oder?
Tiến Nguyễn Hoàng

1
@ TiếnNguyễnHoàng rangegibt a[i]als zweiten Rückgabewert zurück. Dieser Vorgang, val = a[i]wie getan , indem rangeerstellt eine Kopie des Wertes so dass jeder Schreibvorgang valauf eine Kopie angelegt wird.
Nemo

37

Sie scheinen nach etwas Äquivalentem zu fragen:

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

Ausgabe:

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

Dadurch wird vermieden Attribute, dass auf Kosten von Slice-Bound-Checks eine - möglicherweise große - Kopie von Typwerten erstellt wird. In Ihrem Beispiel ist der Typ Attributerelativ klein, zwei stringSlice-Referenzen: 2 * 3 * 8 = 48 Byte auf einem 64-Bit-Architekturcomputer.

Sie könnten auch einfach schreiben:

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

Der Weg, um ein gleichwertiges Ergebnis mit einer rangeKlausel zu erhalten, die eine Kopie erstellt, aber die Überprüfung der Slice-Grenzen minimiert, ist:

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

2
Es ist schade, dass value := &someMap[key]nicht funktionieren wird, wenn someMapamap
warvariuc

peterSO in deinem ersten Code-Snippet musst du nicht attr respektieren, um ihm etwas zuzuweisen? dh*attr.Val = "something"
Homam Bahrani

25

Ich würde Ihren letzten Vorschlag anpassen und die Nur-Index-Version des Bereichs verwenden.

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

Es scheint mir einfacher zu verweisen n.Attr[i]ausdrücklich sowohl in der Linie , dass die Tests Keyund die Linie , dass Sätze Val, anstatt attrfür ein und n.Attr[i]für den anderen.


15

Beispielsweise:

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

Spielplatz


Ausgabe

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

Alternativer Ansatz:

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

Spielplatz


Ausgabe:

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

Ich dachte, es wäre offensichtlich, aber ich möchte die Strukturen, die ich bekomme, nicht ändern (sie sind aus dem go.net/htmlPaket)
Denys Séguret

1
@dystroy: Der obige zweite Ansatz ändert nicht die Typen (die "Strukturen") des OP.
zzzz

Ja, ich weiß, aber es bringt nicht wirklich etwas. Ich hatte eine Idee erwartet, die ich möglicherweise übersehen hätte. Ich bin zuversichtlich, dass es keine einfachere Lösung gibt, als dies die Antwort wäre.
Denys Séguret

1
@dystroy: Es hat etwas bringen, ist es nicht kopiert hier und das gesamte Attribut zurück. Und ja, ich bin zuversichtlich, dass es die optimale Lösung ist, die Adresse eines Slice-Elements zu verwenden, um eine Doppelkopie-Aktualisierung (r + w) des Elements zu vermeiden.
zzzz
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.