出现奇怪的行为 – 变量没有正确递增。

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

Go weird behaviour - variable not incrementing correctly

问题

我有以下的代码,如果一个切片中不存在某个元素,它会添加一个新元素。如果元素已经存在,则应该增加现有元素的qty属性,而不是添加一个新元素:

package main

import (
    "fmt"
)

type BoxItem struct {
    Id  int
    Qty int
}

type Box struct {
    BoxItems []BoxItem
}

func (box *Box) AddBoxItem(boxItem BoxItem) BoxItem {

    // 如果该项已经存在,则增加其qty
    for i, item := range box.BoxItems {
        if item.Id == boxItem.Id {
            box.BoxItems[i].Qty++
            return box.BoxItems[i]
        }
    }

    // 新项,追加到切片中
    box.BoxItems = append(box.BoxItems, boxItem)
    return boxItem
}

func main() {

    boxItems := []BoxItem{}
    box := Box{boxItems}

    boxItem := BoxItem{Id: 1, Qty: 1}

    // 将该项添加3次,其qty应该增加到3
    box.AddBoxItem(boxItem)
    box.AddBoxItem(boxItem)
    box.AddBoxItem(boxItem)

    fmt.Println(len(box.BoxItems)) // 输出1,正确

    for _, item := range box.BoxItems {
        fmt.Println(item.Qty) // 输出1,应该输出3
    }
}

问题在于qty属性没有被正确地增加。在提供的示例中,它总是以1结束,而不是3。

我已经调试了代码,似乎已经到达了增加部分,但该值没有被保存到item中。

这里有什么问题?

英文:

I have the following code that adds a new element to a slice if it doesnt exist already. If it does exist then the qty property should be incremented of the existing element instead of a new element being added:

package main

import (
    "fmt"
)

type BoxItem struct {
    Id int
    Qty int
}

type Box struct {
    BoxItems []BoxItem
}

func (box *Box) AddBoxItem(boxItem BoxItem) BoxItem {

    // If the item exists already then increment its qty
    for _, item := range box.BoxItems {
        if item.Id == boxItem.Id {
             item.Qty++
             return item
        }
    }
    
    // New item so append
    box.BoxItems = append(box.BoxItems, boxItem)
    return boxItem
}


func main() {

    boxItems := []BoxItem{}
    box := Box{boxItems}

    boxItem := BoxItem{Id: 1, Qty: 1}

    // Add this item 3 times its qty should be increased to 3 afterwards
    box.AddBoxItem(boxItem)
    box.AddBoxItem(boxItem)
    box.AddBoxItem(boxItem)


    fmt.Println(len(box.BoxItems))  // Prints 1 which is correct

    for _, item := range box.BoxItems {
        fmt.Println(item.Qty)  // Prints 1 when it should print 3
    }
}

The problem is that the qty is never incremented correctly. It always ends in 1 when it should be 3 in the example provided.

I have debugged the code and it does appear that the increment section is reached but the value just isnt persisted to the item.

What is wrong here?

答案1

得分: 5

你正在对box.BoxItems的副本中的Qty进行递增操作,因为range会产生切片中元素的副本。请参考这个示例

所以,在for _, item := range box.BoxItems中,itembox.BoxItems中元素的副本。

将你的循环改为:

for i := 0; i < len(box.BoxItems); i++ {
    if box.boxItems[i].Id == boxItem.Id {
         box.boxItems[i].Qty++
         return box.BoxItems[i]
    }
}

Playground

英文:

You are incrementing Qty in the copy of the box.BoxItems because range will yield the copy of the elements in the slice. See this example.

So, in for _, item := range box.BoxItems, item is a copy of of the elements in box.BoxItems.

Change your loop to

for i := 0; i &lt; len(box.BoxItems); i++ {
    if box.boxItems[i].Id == boxItem.Id {
         box.boxItems[i].Qty++
         return box.BoxItems[i]
    }
}

Playground

答案2

得分: 3

我会像其他人一样回答你的问题。然而,要注意的是,你尝试解决的问题并不适合通过循环遍历一系列值来解决。请继续阅读:

问题的解决方案

就像其他人所说的那样,for-range提供了对一系列值的不可变迭代。这意味着你对迭代中提供的值所做的任何更改都将丢失。它基本上给你的是实际值的副本,而不是实际值本身。

for _, item := range box.BoxItems {
//     ^-不是真正的`item`,而是一个副本!

解决这个问题的一种方法是在for idx, val := range中跟踪索引值,并使用这个idx直接访问你要查找的值。

如果你改变你的for循环以保留索引值:

for i, item := range box.BoxItems {
//  ^-保留这个

你将能够引用你循环的数组中的实际项:

for i, item := range box.BoxItems {
    // 在这里,item是box.BoxItems[i]位置上的值的副本
    if item.Id == boxItem.Id {
         // 直接引用切片中的项
         box.BoxItems[i].Qty++
         return box.BoxItems[i] // 需要返回实际的项,而不是副本
    }
}

Playground

我更喜欢这种方法,而不是for i; i<Len; i++的方法,因为我觉得这种方法更易读。但这只是个人喜好的问题,而且for i的形式会更高效(注意不要过早优化!)。

你真正的问题是

你试图避免在Id已经存在时复制BoxItems。为了做到这一点,你遍历整个box.BoxItems切片的范围。如果你的box.BoxItems切片中有N个项,那么在找到你要查找的项不存在之前,你可能会遍历所有的N个项!基本上,这意味着你的算法是O(N)的。

如果你按自然顺序递增Id

也就是说,0, 1, 2, 3, ..., n - 1, n,你可以继续使用切片来索引你的盒子项。你可以这样做:

func (box *Box) AddBoxItem(boxItem BoxItem) BoxItem {
    // 通过Id查找你的项
    if boxItem.Id < len(box.BoxItems) {
        // 它存在,不要创建它,只需递增
        item := box.BoxItems[boxItem.Id]
        item.Qty++
        box.BoxItems[boxItem.Id] = item
        return item
    }
    // 新项,所以追加
    box.BoxItems = append(box.BoxItems, boxItem)
    return boxItem
}

Playground

如果你按任意顺序递增Id

你应该使用一个提供快速查找的数据结构,比如内置的map,它提供了O(1)的查找(这意味着你只需要进行一次操作就能找到你的项,而不是n次操作)。

type Box struct {
    BoxItems map[int]BoxItem
}

func (box *Box) AddBoxItem(boxItem BoxItem) BoxItem {

    // 通过Id查找map
    item, ok := box.BoxItems[boxItem.Id]
    if ok {
        // 它存在,不要创建它,只需递增
        item.Qty++
    } else {
        item = boxItem
    }

    // 新项,所以将其添加到map中
    box.BoxItems[boxItem.Id] = item
    return item
}

Playground

这是解决你的问题的更正确的方法。

英文:

I will answer your question pretty much like others have done. However, not that the problem you try to solve is not best served by looping over a range of values. Read on:

Solution to your question

Like others have said, for-range provide an immutable iteration over the range of values. That means any change you make to the value provided in the iteration will be lost. It's basically giving you a copy of the real value, not the actual value.

for _, item := range box.BoxItems {
//     ^-not the real `item`, it&#39;s a copy!

A way around this is to keep track of the indexing value in the for idx, val := range, and use this idx to address the value you look for directly.

If you change your for-loop to keep the index value:

for i, item := range box.BoxItems {
//  ^-keep this

You will be able to reference the actual item in the array you loop on:

for i, item := range box.BoxItems {
    // Here, item is a copy of the value at box.BoxItems[i]
    if item.Id == boxItem.Id {
         // Refer directly to an item inside the slice
         box.BoxItems[i].Qty++
         return box.BoxItems[i] // Need to return the actual one, not the copy
    }
}

Playground

I would favor this approach over the for i; i&lt;Len; i++ one as I find it more readable. But this is simply a matter of taste and the for i form will be more efficient (beware of premature-optimization!).

Your real problem is

What you're trying to do is to avoid duplicating BoxItems if their Id already exists. To do this, you iterate over the whole range of the box.BoxItems slice. If you have N items in your box.BoxItems slice, you will potentially iterate over all N items before finding out that the item you're looking for doesn't exist! Basically, this means your algorithm is O(N).

If you increment Id in natural order

That is, 0, 1, 2, 3, ..., n - 1, n, you can keep using a slice to index your box items. You would do like this:

func (box *Box) AddBoxItem(boxItem BoxItem) BoxItem {
    // Lookup your item by Id
    if boxItem.Id &lt; len(box.BoxItems) {
        // It exists, do don&#39;t create it, just increment
        item := box.BoxItems[boxItem.Id]
        item.Qty++
        box.BoxItems[boxItem.Id] = item
        return item
    }
    // New item so append
    box.BoxItems = append(box.BoxItems, boxItem)
    return boxItem
}

Playground

If you increment Id in any order

You should use a datastructure that offers fast lookups, such as the built-in map, which offers O(1) lookups (that means, you need to do a single operation to find your item, not n operations).

type Box struct {
    BoxItems map[int]BoxItem
}

func (box *Box) AddBoxItem(boxItem BoxItem) BoxItem {

    // Lookup the map by Id
    item, ok := box.BoxItems[boxItem.Id]
    if ok {
        // It exists, do don&#39;t create it, just increment
        item.Qty++
    } else {
        item = boxItem
    }

    // New item so add it to the map
    box.BoxItems[boxItem.Id] = item
    return item
}

Playground

This is a more correct way to solve your problem.

答案3

得分: 1

index, value := range someSlice中,valuesomeSlice[index]的一个全新副本。

package main

import (
	"fmt"
)

type BoxItem struct {
	Id  int
	Qty int
}

type Box struct {
	BoxItems []BoxItem
}

func (box *Box) AddBoxItem(boxItem BoxItem) BoxItem {

	// 如果该项已存在,则增加其数量
	for i := range box.BoxItems {
		item := &box.BoxItems[i]
		if item.Id == boxItem.Id {
			item.Qty++
			return *item
		}
	}

	// 新项,追加到列表中
	box.BoxItems = append(box.BoxItems, boxItem)
	return boxItem
}

func main() {

	boxItems := []BoxItem{}
	box := Box{boxItems}

	boxItem := BoxItem{Id: 1, Qty: 1}

	// 将该项添加3次,其数量应增加到3
	box.AddBoxItem(boxItem)
	box.AddBoxItem(boxItem)
	box.AddBoxItem(boxItem)

	fmt.Println(len(box.BoxItems)) // 输出1,正确

	for _, item := range box.BoxItems {
		fmt.Println(item.Qty) // 输出1,应该输出3
	}
}

输出结果:

1
3

Playground

英文:

In the index, value := range someSlice, the value is a fresh new copy of someSlice[index].

package main

import (
        &quot;fmt&quot;
)

type BoxItem struct {
        Id  int
        Qty int
}

type Box struct {
        BoxItems []BoxItem
}

func (box *Box) AddBoxItem(boxItem BoxItem) BoxItem {

        // If the item exists already then increment its qty
        for i := range box.BoxItems {
                item := &amp;box.BoxItems[i]
                if item.Id == boxItem.Id {
                        item.Qty++
                        return *item
                }
        }

        // New item so append
        box.BoxItems = append(box.BoxItems, boxItem)
        return boxItem
}

func main() {

        boxItems := []BoxItem{}
        box := Box{boxItems}

        boxItem := BoxItem{Id: 1, Qty: 1}

        // Add this item 3 times its qty should be increased to 3 afterwards
        box.AddBoxItem(boxItem)
        box.AddBoxItem(boxItem)
        box.AddBoxItem(boxItem)

        fmt.Println(len(box.BoxItems)) // Prints 1 which is correct

        for _, item := range box.BoxItems {
                fmt.Println(item.Qty) // Prints 1 when it should print 3
        }
}

Playground


Output:

1
3

huangapple
  • 本文由 发表于 2013年8月4日 22:22:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/18043965.html
匿名

发表评论

匿名网友

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

确定