Counting occurences of campaigns in products in Golang - go

I have the code below
teasers := []*models.TeaserCount{}
var teaser models.TeaserCount
for _, product := range ProductResponse.Products {
added := false
if len(product.Campaign.Id) > 0 {
if len(teasers) > 0 {
for _, teaserCount := range teasers {
if teaserCount.Id == product.Campaign.Id {
fmt.Println(teaserCount.Id, teaserCount.Count+1)
teaserCount.Count++
added = true
break
}
}
if added == false {
teaser = models.TeaserCount{
Id: product.Campaign.Id,
Count: 0,
}
teasers = append(teasers, &teaser)
}
} else {
teaser = models.TeaserCount{
Id: product.Campaign.Id,
Count: 0,
}
teasers = append(teasers, &teaser)
}
}
}
What I want to do, is to count how many times each campaign occured in product
I want to have an array of objects including campaign id and occurences
Results that I get is that each and every single object in array is the same(the last one added by append)
How is that so, the behavior seems quite strange to me, maybe that has to do with pointers?

You're appending a pointer to the local loop variable, which changes on each iteration:
// This pointer will always point to the current/last loop iteration value
teasers = append(teasers, &teaser)
You should instead either append a pointer to a copy:
temp := teaser
teasers = append(teasers, &temp)
Or a pointer to the element of the slice:
for i, product := range ProductResponse.Products {
// ...
teasers = append(teasers, &ProductResponse.Products[i])
If you choose the former, the pointer will be to a copy dedicated to teasers, whereas if you do the latter, it will be a pointer to the element of the original slice (meaning if the value in the slice changes, that will be reflected in teasers).

Related

Failed to update rows in "jinzhu/gorm" pkg

I need to update value of fields in multiple rows.
I'm querying to get some of the database rows, but it doesn't work.
DB.Where("is_send = ?", "0").Find(&artists)
for _, artist := range artists {
if condition {
artist.IsSend = 1
... (more updatee)
DB.Save(&artist)
}
}
Change how you range it, by referring the below example:
for _, elem := range elems {
elem = new_val // Won't work, because elem is a copy of
// the value from elems
}
for i := range elems {
elems[i] = new_val // Works, because elems[i] deferences
// the pointer to the actual value in elems
}
Read: Gotchas
Also, if you're not modifying all fields, rather than using Save you can use Update as well. Refer: GORM CRUD's Interface UPDATE

How to change value in go array?

Here is what I am trying todo
for _,p := range *players {
for _,tp := range *tournamentPlayers{
if p.Id==tp.PlayerId {
p.Points += tp.Prize
}
}
}
after for nothing is saved
When you range over an array, the second variable will be a copy of the value. So when you're modifying it, you don't actually modify the value stored in the array.
You need to use the index:
for i := range *players {
for _,tp := range *tournamentPlayers{
if players[i].Id==tp.PlayerId {
players[i].Points += tp.Prize
}
}
}
You'll find more informations in the spec.

Delete first item in slice if length > 100

When the RSS feeds updates (it doesn't right now, just dummy data) the new items are appended to the "feed" slice. Over time this could mean that it contains millions of items, I don't want that.
So when there are more than 100 items in the slice it should delete items starting from the top (item 0). In this example I'm using an RSS file with ust 100 items so the sample code below should delete from the top after 50 items:
package main
import (
"fmt"
"github.com/SlyMarbo/rss"
"time"
)
var feed *rss.Feed
var directory = "./dump"
func main() {
for {
checkRSS()
// Check every minute if feed.Refresh has passed so it run's update()
time.Sleep(1 * time.Minute)
}
}
func checkRSS() (*rss.Feed, error) {
var err error
// If feed is still empty fetch it first so we can run update()
if feed == nil {
feed, err = rss.Fetch("http://cloud.dgier.nl/api.xml")
} else {
err = feed.Update()
}
length := len(feed.Items)
for key, value := range feed.Items {
fmt.Println(key, value.Title, value.Read)
if key >= 50 {
fmt.Println("Item key is > 50")
}
}
fmt.Printf("Current length: %d\n", length)
fmt.Printf("Refreshing at %s\n", feed.Refresh)
return feed, err
}
If the number of items in the feed grows over the limit, slice it:
length := len(feed.Items)
if length > limit {
feed.Items = feed.Items[length - limit:]
}
When the length is over the limit, the new length will be exactly limit.
You don't need a for loop there.
To achieve this you probably want to use subslicing. Say you want to remove x items from feed, you can simply do feed = feed[x:] which will yield all items after index x-1 and assign it back to the feed slice. If in your actual code you just want to remove the first item then it would be feed = feed[1:]

Golang, appending leaves only last element

Here is example code:
package main
import (
"fmt"
)
type Product struct {
Id int64
Title string
AttrVals []string
}
type ProductAttrValView struct {
Product
Attr string
}
type ProductAttrVal struct {
Attr string
Product int64
Value string
}
func main() {
p := Product{Id: 1, Title: "test", AttrVals: []string{}}
var prod *Product
prodViews := []ProductAttrValView{
ProductAttrValView{ Product: p, Attr: "text1" },
ProductAttrValView{ Product: p, Attr: "text2" },
ProductAttrValView{ Product: p, Attr: "text3" },
ProductAttrValView{ Product: p, Attr: "text4" },
}
// collapse join View to Product with Attrs
for _, pview := range prodViews {
if prod == nil {
prod = &pview.Product
prod.AttrVals = make([]string, 0, len(prodViews))
}
if pview.Attr != "" {
fmt.Printf("appending '%s' to %p\n", pview.Attr, prod) // output for debug
prod.AttrVals = append(prod.AttrVals, pview.Attr)
}
}
fmt.Printf("%+v\n", prod) // output for debug
}
http://play.golang.org/p/949w5tYjcH
Here i have some returned data from DB in ProductAttrValView struct and want
put it into Product struct and also fill Product.AttrVals
It prints:
&{Id:1 Title:test AttrVals:[text4]}
While i expect this:
&{Id:1 Title:test AttrVals:[text1 text2 text3 text4]}
So, all text should be appended, but for some reason only the last element stays in Attrs slice.
You are re-using variables in your for-range loop, and each iteration modifies the value of that same variable. You can create a new value each iteration with the trick:
pview := pview
http://play.golang.org/p/qtJXxdtuq2
You also initialize the slice with a length of 4, but you append another value (ignoring the first 4). You likely meant to set the capacity of the slice as opposed to the length:
prod.AttrVals = make([]string, 0, len(prodViews))
Because the value of prod is changing each iteration, the code would be a lot less confusing if you specifically initialized the prod value, instead of assigning the address of &pview.Product
prod = &Product{AttrVals: make([]string, 0, len(prodViews))}
[time line]
You create a single product p, containing an initialized []string
That same p is assigned to all prodViews that we will iterate over.
On the first iteration through the loop, you assign *prod to that initial p value, then change AttrVals to a new []string of length 4. This doesn't alter the original p.
pview.Attr is appended to prod.AttrVals, making a it length 5, and creating a new underlying array. This isn't reflected in the values of p.
On subsequent iterations, pview is overwritten with the next value in prodViews. This overwrites the value of prod too, since it points to the pview.Product value. This means that prod.AttrVals is now the same one from the original p.
pview.Attr is appended to a slice of length 0, and the underlying array is replaced with more capacity, so pview.Attr still isn't contained in the original p.
pview is again overwritten with the next value, which contains original p values, setting your AttrVals length back to 0 with an empty array.
The cycle continues until the final single value is printed.

Go weird behaviour - variable not incrementing correctly

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?
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 < len(box.BoxItems); i++ {
if box.boxItems[i].Id == boxItem.Id {
box.boxItems[i].Qty++
return box.BoxItems[i]
}
}
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'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<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 < 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
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'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.
In the index, value := range someSlice, the value is a fresh new copy of someSlice[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
Output:
1
3

Resources