web-dev-qa-db-ja.com

繰り返しながら値を変更する

これらのタイプがあるとしましょう:

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から直接ポインターを取得することは可能ですか?

明らかに、反復のためだけに構造を変更したくはありません。より冗長な解決策は解決策ではありません。

134
Denys Séguret

いいえ、必要な略語は使用できません。

この理由は、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]を配列/スライスの2番目の値として使用します。これは、値がコピーされ、元の値が変更できないことを効果的に意味します。

この動作は 次のコード で示されています:

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で既に提案されているように、ポインターまたはインデックスを使用することだけです。

131
nemo

あなたはこれと同等のものを求めているようです:

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は比較的小さく、2つのstringスライス参照:64ビットアーキテクチャマシンでは2 * 3 * 8 = 48バイトです。

次のように書くこともできます:

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

私はあなたの最後の提案を採用し、範囲のインデックスのみのバージョンを使用します。

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

Keyをテストする行とValを設定する行の両方でn.Attr[i]を明示的に参照する方が、一方にattrを使用し、もう一方にn.Attr[i]を使用するよりも簡単に思えます。

17
Paul Hankin

例えば:

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:""}
14
zzzz