【问题标题】:Change values while iterating迭代时更改值
【发布时间】:2013-04-03 10:14:21
【问题描述】:

假设我有这些类型:

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

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

【问题讨论】:

  • 那么你想在 JavaScript 中使用某种 Array.prototype.forEach 吗?
  • 这是一个有趣的想法,这可能是一个解决方案,但调用一个函数,而该函数又会在每次迭代中调用一个函数,这在服务器端语言中看起来很重且错误。而缺乏泛型会让这种感觉更加沉重。
  • 老实说,我不认为它有那么重。调用一两个函数非常便宜,这通常是编译器最优化的。我会尝试并对其进行基准测试,看看它是否符合要求。
  • 由于 Go 缺乏泛型,我担心传递给 forEach 的函数必须以类型断言开头。这并不比attr := &n.Attr[i] 好。

标签: arrays for-loop go


【解决方案1】:

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

原因是range 复制了您正在迭代的切片中的值。 specification about 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

因此,range 使用 a[i] 作为数组/切片的第二个值,这实际上意味着 值被复制,使原始值无法触及。

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

代码为您打印出完全不同的内存位置,用于范围值和实际值 切片中的值:

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

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

【讨论】:

  • 一种思考方式是赋值导致复制。如果您看到 val := x[1],那么 val 是 x[1] 的副本也就不足为奇了。不要将范围视为特殊用途,请记住范围的每次迭代都从分配索引和值变量开始,并且是该分配而不是导致复制的范围。
  • 对不起,我还是有点困惑。如果for循环的第二个值是a[i],那么for循环中的a[i]和我们写的a[i]有什么区别?看起来是一样的,但实际上不是,对吧?
  • @TiếnNguyễnHoàng range 返回 a[i] 作为其第二个返回值。由range 完成的此操作val = a[i] 创建了该值的副本,因此对val 的任何写入操作都将应用于副本。
【解决方案2】:

您似乎在要求与此等价的东西:

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 = 64 位架构机器上的 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"
    }
}

【讨论】:

  • 很遗憾如果someMap 是map,value := &amp;someMap[key] 将不起作用
  • peterSO 在您的第一个代码 sn-p 中您不必尊重 attr 来为其分配一些东西吗?即*attr.Val = "something"
  • 为我工作,谢谢
【解决方案3】:

我会修改您的最后建议并使用仅索引版本的范围。

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] 来表示另一个。

【讨论】:

    【解决方案4】:

    例如:

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

    【讨论】:

    • 我认为这很明显,但我不想更改我得到的结构(它们来自 go.net/html 包)
    • @dystroy:上面的第二种方法不会更改 OP 的类型(“结构”)。
    • 是的,我知道,但它并没有真正带来任何东西。我期待一个我可能错过的想法。我相信没有比这更简单的解决方案了。
    • @dystroy:它确实带来了一些东西,它没有在这里复制并返回整个属性。是的,我相信获取切片元素的地址以避免元素的双重复制 (r+w) 更新是最佳解决方案。
    猜你喜欢
    • 1970-01-01
    • 2019-10-04
    • 1970-01-01
    • 2016-03-16
    • 2012-04-17
    • 2017-12-03
    • 1970-01-01
    • 2021-07-13
    相关资源
    最近更新 更多