英文:
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 (
"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())
}
}
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 "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)
}
}
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 (
"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())
}
}
答案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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论