去奇怪的行为 - 变量没有正确递增

时间:2013-08-04 14:22:33

标签: go

我有以下代码,如果它已经不存在,则会向切片添加新元素。如果它确实存在,那么qty属性应该增加现有元素而不是添加新元素:

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

问题是qty永远不会正确递增。它总是以1结尾,在提供的例子中它应该是3。

我已经调试了代码,看起来已经达到了增量部分,但是值并没有持久保存到项目中。

这里有什么问题?

3 个答案:

答案 0 :(得分:5)

您正在Qty的副本中递增box.BoxItems,因为range将生成切片中元素的副本。请参阅this example

因此,在for _, item := range box.BoxItems中,item是box.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

答案 1 :(得分:3)

我会像其他人一样回答你的问题。但是,并不是通过循环一系列值来解决您尝试解决的问题。请继续阅读:

您问题的解决方案

正如其他人所说,for-range在值范围内提供了不可变的迭代。这意味着您对迭代中提供的值所做的任何更改都将丢失。它基本上是给你一个真实值的副本,而不是实际值。

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

解决此问题的方法是跟踪for idx, val := range中的索引值,并使用此idx来解决您直接查找的值。

如果更改for循环以保持索引值:

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

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

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

我赞成这种方法而不是for i; i<Len; i++,因为我发现它更具可读性。但这只是一个品味问题,for i形式会更有效率(谨防过早优化!)。

你真正的问题是

您要做的是,如果BoxItems已经存在,请避免重复Id。为此,您将遍历box.BoxItems切片的整个范围。如果您的N切片中有box.BoxItems项,则可能会在找到您要查找的项目不存在之前迭代所有N个项目!基本上,这意味着您的算法是O(N)。

如果按自然顺序递增Id

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

func (box *Box) AddBoxItem(boxItem BoxItem) BoxItem {
    // Lookup your item by Id
    if boxItem.Id < len(box.BoxItems) {
        // It exists, do don'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

如果以任何顺序递增Id

您应该使用提供快速查找的数据结构,例如内置map,它提供O(1)查找(这意味着,您需要执行单个操作才能找到您的项目,而不是{ {1}}操作)。

n

Playground

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

答案 2 :(得分: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 {

        // If the item exists already then increment its qty
        for i := range box.BoxItems {
                item := &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


输出:

1
3