在循环中将big.Int附加到切片中出现了意外的结果。

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

Appending big.Int in loop to slice unexpected result

问题

以下是翻译好的内容:

以下的Go程序在输出1、2、3、4之后输出5、5、5、5。我原本期望两种情况下都是输出1、2、3、4。我做错了什么?

package main

import (
	"fmt"
	"math/big"
)

func primesLessThan(n *big.Int) (primes []big.Int) {
	var one big.Int
	one.SetInt64(1)
	var i big.Int
	i.SetInt64(1)
	for i.Cmp(n) < 0 {
		fmt.Println(i.String())
		primes = append(primes, i)
		i.Add(&i, &one)
	}
	return
}

func main() {
	primes := primesLessThan(big.NewInt(5))
	for _, p := range primes {
		fmt.Println(p.String())
	}
}

更新:以下代码片段说明了浅拷贝的意外副作用。以下代码的输出是3、3。

one := big.NewInt(1)
two := big.NewInt(2)

one = two // 浅拷贝。问题:如何进行深拷贝?

one.SetInt64(3) // 副作用:也会改变two

fmt.Println(one.String())
fmt.Println(two.String())
英文:

The following Go program produces 1,2,3,4 in followed by 5,5,5,5. I was expecting 1,2,3,4 in both cases. What am I doing wrong?

package main

import (
	&quot;fmt&quot;
	&quot;math/big&quot;
)

func primesLessThan(n *big.Int) (primes []big.Int) {
	var one big.Int
	one.SetInt64(1)
	var i big.Int
	i.SetInt64(1)
	for i.Cmp(n) &lt; 0 {
		fmt.Println(i.String())
		primes = append(primes, i)
		i.Add(&amp;i, &amp;one)
	}
	return
}

func main() {
	primes := primesLessThan(big.NewInt(5))
	for _, p := range primes {
		fmt.Println(p.String())
	}
}

Update: the following code snippet illustrates the unexpected side effects of the shallow copy described in the responses. The output of the following snippet is 3, 3

one := big.NewInt(1)
two := big.NewInt(2)

one = two // Shallow copy. Question: how do I do a deep copy?

one.SetInt64(3) // Side-effect: also changes two

fmt.Println(one.String())
fmt.Println(two.String())

答案1

得分: 3

这是因为对象存储其内部数据的方式导致的。

以这个例子为例:

package main

import "fmt"

type foo struct {
    value int
}

func bar() (r []foo) {
    var f foo
    for i := 0; i < 5; i++ {
        f.value = i
        r = append(r, f)
    }
    return
}

func main() {
    for _, v := range bar() {
        fmt.Println(v)
    }
}

输出将会是预期的结果:

{0}
{1}
{2}
{3}
{4}

你的例子中的问题在于big.Int将其值存储在一个切片中,而切片是指针。因此,当创建big.Int的副本时,新的副本包含了指向内存中相同切片的新指针。这样就创建了浅拷贝而不是深拷贝。

请参考https://golang.org/src/math/big/int.go?s=388:468#L8中bit.Int的声明,然后参考https://golang.org/src/math/big/nat.go#L25中nat的声明。

以下是使用big.Int的解决方案:

package main

import (
    "fmt"
    "math/big"
)

func primesLessThan(n *big.Int) (primes []big.Int) {
    var one big.Int
    one.SetInt64(1)
    var i big.Int
    i.SetInt64(1)
    for i.Cmp(n) < 0 {
        var result big.Int
        result.Set(&i)
        fmt.Println(result.String())
        primes = append(primes, result)
        i.Add(&i, &one)
    }
    return
}

func main() {
    primes := primesLessThan(big.NewInt(5))
    for _, p := range primes {
        fmt.Println(p.String())
    }
}
英文:

It's because of the way the object stores its internal data.

Take this example:

package main

import &quot;fmt&quot;

type foo struct {
	value int
}

func bar() (r []foo) {
	var f foo
	for i := 0; i &lt; 5; i++ {
		f.value = i
		r = append(r, f)
	}
	return
}

func main() {
	for _, v := range bar() {
		fmt.Println(v)
	}
}

The output will be the expected

{0}
{1}
{2}
{3}
{4}

The issue in your example is that big.Int stores its value in a slice, and slices are pointers. So when a copy of a big.Int is created, the new copy contains a new pointer to the same slice in memory. A shallow copies is created rather than a deep copy.

See https://golang.org/src/math/big/int.go?s=388:468#L8 for how bit.Int is declared, then see https://golang.org/src/math/big/nat.go#L25 for how nat is declared.

Here is a solution that uses big.Int

package main

import (
	&quot;fmt&quot;
	&quot;math/big&quot;
)

func primesLessThan(n *big.Int) (primes []big.Int) {
	var one big.Int
	one.SetInt64(1)
	var i big.Int
	i.SetInt64(1)
	for i.Cmp(n) &lt; 0 {
		var result big.Int
		result.Set(&amp;i)
		fmt.Println(result.String())
		primes = append(primes, result)
		i.Add(&amp;i, &amp;one)
	}
	return
}

func main() {
	primes := primesLessThan(big.NewInt(5))
	for _, p := range primes {
		fmt.Println(p.String())
	}
}

答案2

得分: 0

@GarMan的解决方案很好,但你可以更简单地在append()级别上进行字符串转换,对于primes []string来说:

primes = append(primes, i.String())
英文:

The @GarMan solution is fine but you can do simpler doing the string conversion at the append() level for primes []string:

primes = append(primes, i.String())

答案3

得分: -2

我们看到...你的big.Int对象的行为类似于指针数组。通过在primes的每个槽位中存储i,实际上是在每个位置存储了完全相同的指针。这就是为什么它打印了四次的5。

我们看到这个的原因是...由于big.Int的内部实现,每个big.Int对象都有一个内部切片。切片被实现为指针,因为它们实际上只是一种高级数组。因此,虽然你可能有一个big.Int对象,但该对象有一个指向切片的指针。

当复制一个big.Int对象时,你实际上是复制了这个内部切片的指针。

@GarMan是正确的,我也同意这与浅拷贝和深拷贝有关。

英文:

What we see... Your primes []big.Int is acting like an array of pointers. By storing i in each slot of primes you are essentially storing the exact same pointer in each spot. That's why it's printing 5 four times.

Why we see this... Due to the internals of big.Int, each strict object of big.Int has an internal slice. Slices are implemented as pointers because they are really just fancy arrays. So, while you may have a big.Int object, the object has a pointer to a slice.

When copying a big.Int object you are copying this internal slice pointer.

@GarMan is right and I also agree it has to do with shallow vs. deep copies.

huangapple
  • 本文由 发表于 2016年2月23日 11:34:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/35568334.html
匿名

发表评论

匿名网友

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

确定