在迭代过程中更改值

huangapple go评论91阅读模式
英文:

Change values while iterating

问题

假设我有以下类型:

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获取指针?

显然,我不想为了迭代而更改结构,更冗长的解决方案也不是解决办法。

英文:

Let's suppose I have these types:

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

and that I want to iterate on my node's attributes to change them.

I would have loved to be able to do:

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

but as attr isn't a pointer, this wouldn't work and I have to do:

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

Is there a simpler or faster way? Is it possible to directly get pointers from range?

Obviously I don't want to change the structures just for the iteration and more verbose solutions are no solutions.

答案1

得分: 204

不,你想要的缩写是不可能的。

原因是range会复制你正在迭代的切片的值。
关于range规范中写道:

>
Range 表达式 第一个值 第二个值(如果第二个变量存在)
数组或切片 a [n]E, *[n]E, or []E 索引 i int a[i] E

所以,对于数组/切片,range使用a[i]作为它的第二个值,这实际上意味着该值被复制,使得原始值无法改变。

这个行为可以通过以下代码示例来演示:

x := make([]int, 3)

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

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

该代码会打印出来自range的值和切片中实际值完全不同的内存位置:

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

所以你唯一能做的就是使用指针或索引,就像jnml和peterSO已经提出的那样。

英文:

No, the abbreviation you want is not possible.

The reason for this is that range copies the values from the slice you're iterating over.
The specification about range says:

>
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

So, range uses a[i] as its second value for arrays/slices, which effectively means that the
value is copied, making the original value untouchable.

This behavior is demonstrated by the following code:

x := make([]int, 3)

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

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

The code prints you completely different memory locations for the value from range and the actual
value in the slice:

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

So the only thing you can do is to either use pointers or the index, as already proposed by jnml and peterSO.

答案2

得分: 49

你似乎在寻求与以下代码等效的内容:

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类型相对较小,是两个string切片引用:2 * 3 * 8 = 48字节(在64位架构的机器上)。

你也可以简单地写成:

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

You seem to be asking for something equivalent to this:

package main

import &quot;fmt&quot;

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

func main() {

	n := Node{
		[]Attribute{
			{&quot;key&quot;, &quot;value&quot;},
			{&quot;href&quot;, &quot;http://www.google.com&quot;},
		},
	}
	fmt.Println(n)

	for i := 0; i &lt; len(n.Attr); i++ {
		attr := &amp;n.Attr[i]
		if attr.Key == &quot;href&quot; {
			attr.Val = &quot;something&quot;
		}
	}

	fmt.Println(n)
}

Output:

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

This avoids creating a--possibly large--copy of type Attribute values, at the expense of slice bounds checks. In your example, type Attribute is relatively small, two string slice references: 2 * 3 * 8 = 48 bytes on a 64-bit architecture machine.

You could also simply write:

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

But, the way to get an equivalent result with a range clause, which creates a copy but minimizes slice bounds checks, is:

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

答案3

得分: 46

我会采纳你上次的建议,并使用只有索引的 range 版本。

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]

英文:

I'd adapt your last suggestion and use the index-only version of range.

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

It seems simpler to me to refer to n.Attr[i] explicitly in both the line that tests Key and the line that sets Val, rather than using attr for one and n.Attr[i] for the other.

答案4

得分: 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)
        }
}

Playground


输出

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

Playground


输出:

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

For example:

package main

import &quot;fmt&quot;

type Attribute struct {
        Key, Val string
}

type Node struct {
        Attr []*Attribute
}

func main() {
        n := Node{[]*Attribute{
                &amp;Attribute{&quot;foo&quot;, &quot;&quot;},
                &amp;Attribute{&quot;href&quot;, &quot;&quot;},
                &amp;Attribute{&quot;bar&quot;, &quot;&quot;},
        }}

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

        for _, v := range n.Attr {
                fmt.Printf(&quot;%#v\n&quot;, *v)
        }
}

Playground


Output

main.Attribute{Key:&quot;foo&quot;, Val:&quot;&quot;}
main.Attribute{Key:&quot;href&quot;, Val:&quot;something&quot;}
main.Attribute{Key:&quot;bar&quot;, Val:&quot;&quot;}

Alternative approach:

package main

import &quot;fmt&quot;

type Attribute struct {
        Key, Val string
}

type Node struct {
        Attr []Attribute
}

func main() {
        n := Node{[]Attribute{
            {&quot;foo&quot;, &quot;&quot;},
            {&quot;href&quot;, &quot;&quot;},
            {&quot;bar&quot;, &quot;&quot;},
        }}

        for i := range n.Attr {
                attr := &amp;n.Attr[i]
                if attr.Key == &quot;href&quot; {
                        attr.Val = &quot;something&quot;
                }
        }

        for _, v := range n.Attr {
                fmt.Printf(&quot;%#v\n&quot;, v)
        }
}

Playground


Output:

main.Attribute{Key:&quot;foo&quot;, Val:&quot;&quot;}
main.Attribute{Key:&quot;href&quot;, Val:&quot;something&quot;}
main.Attribute{Key:&quot;bar&quot;, Val:&quot;&quot;}

huangapple
  • 本文由 发表于 2013年4月11日 17:19:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/15945030.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定