ChatGPT解决这个技术问题 Extra ChatGPT

迭代时更改值

假设我有这些类型:

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] 好。

n
nemo

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

这样做的原因是 range 从您正在迭代的切片中复制值。 specification about range 说:

范围表达式 第一个值 第二个值(如果存在第二个变量) 数组或切片 a [n]E、*[n]E 或 []E 索引 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 的任何写入操作都将应用于副本。
p
peterSO

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

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

遗憾的是,如果 someMapmapvalue := &someMap[key] 将不起作用
peterSO 在您的第一个代码片段中,您不必尊重 attr 来为其分配一些东西吗?即*attr.Val = "something"
为我工作,谢谢
P
Paul Hankin

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

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


z
zzzz

例如:

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)更新是最佳解决方案。