反復しながら値を変更する


153

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

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ですか?

明らかに、反復のためだけに構造を変更したくないので、より詳細なソリューションはソリューションではありません。


2
それでArray.prototype.forEach、JavaScriptで何かをしたいですか?
Florian Margaine、2013

これは興味深いアイデアであり、それが解決策である可能性もありますが、関数を呼び出すと、反復ごとに関数が呼び出され、サーバー側の言語では重くて間違っているように見えます。そしてジェネリックの欠如はこれをさらに重く感じるでしょう。
DenysSéguret2013

正直、そんなに重いとは思いません。1つまたは2つの関数の呼び出しは非常に安価です。通常、これがコンパイラーが最も最適化するものです。私はそれを試してみて、それが法案に合うかどうかを確認するためにそれをベンチマークします。
Florian Margaine、2013

Goにはジェネリックスがないため、渡される関数forEachは必ず型アサーションで始まると思います。それは本当により良いことではありませんattr := &n.Attr[i]
DenysSéguret2013

回答:


152

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

この理由は、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によってすでに提案されているように、ポインターまたはインデックスを使用することしかできません。


16
これを考える1つの方法は、値を割り当てるとコピーが発生するということです。val:= x [1]を見た場合、valがx [1]のコピーであることはまったく驚くに値しないでしょう。範囲を何か特別なことと考えるのではなく、範囲の各反復はインデックス変数と値変数を割り当てることから始まり、コピーを引き起こすのは範囲ではなくその割り当てであることを覚えておいてください。
アンディデイビス

ここではまだ少し混乱しています。forループの2番目の値がa [i]の場合a[i]、forループからのととの違いは何a[i]ですか?同じように見えますが、そうではありませんか?
–TiếnNguyễnHoàng2018

1
@TiếnNguyễnHoàng rangea[i]2番目の戻り値として戻ります。val = a[i]によって行われるこの操作rangeは、値のコピーを作成するため、書き込み操作valはコピーに適用されます。
nemo

37

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

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

2
それは残念だvalue := &someMap[key]場合は動作しませんsomeMapですmap
warvariuc

最初のコードスニペットのpeterSOに何かを割り当てるためにattrを参照する必要はありませんか?ie*attr.Val = "something"
Homam Bahrani

25

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

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

どちらか一方を使用するよりも、n.Attr[i]テストKeyする行とを設定する行の両方で明示的に参照する方が簡単なようです。Valattrn.Attr[i]


15

例えば:

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

go.net/html
当たり前の

1
@dystroy:上記の2番目のアプローチで、OPのタイプ(「構造」)変更されません
zzzz 2013

はい、わかりますが、実際には何ももたらしません。見逃していたのではないかと思っていました。それよりも簡単な解決策はないと確信していると思います。
DenysSéguret2013

1
@dystroy:それはない何かを持って、それはありませんここにコピーして、全体の属性をバックアップ。そして、はい、スライス要素のアドレスを取得して、要素の二重コピー(r + w)更新を回避することが最適なソリューションであると確信しています。
zzzz
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.