Slice getting changed after copying it and working with the copy

I wrote this code for making slice copy, and it is working fine, and the main slice that is passed as parameter to the function is not impacted:

package main

import "fmt"

type Team []Person
type Person struct {
	Name string
	Age  int
}

func main() {
	team := Team{
		Person{"Hasan", 34}, Person{"Karam", 32},
	}
	fmt.Printf("original before clonning: %v\n", team)
	team_cloned := team.Clone()
	fmt.Printf("original after clonning: %v\n", team)
	fmt.Printf("clones slice: %v\n", team_cloned)
}

func (c *Team) Clone() Team {
	var s = make(Team, len(*c))
	copy(s, *c)
	for index, _ := range s {
		s[index].Name = "change name"
	}
	return s
}

But in my other code, the original slice is getting change even I’m passing a clone from it to the function:

type Inventories []Inventory
type Inventory struct { //instead of: map[string]map[string]Pairs
	Warehouse string
	Item      string
	Batches   Lots
}
type Lots []Lot
type Lot struct {
	Date  time.Time
	Key   string
	Value float64
}

func (c *Inventories) Clone() Inventories {
	var s = make(Inventories, len(*c))
	copy(s, *c)
	return s
}

func (outs Inventories) BuildBatchesFrom(ins Inventories) (batchesBalance Inventories, outgoing Inventories) {
	batchesOut := Inventories{}

	for _, in := range batchesBalance {
		for _, out := range outgoing {
			if out.Warehouse == in.Warehouse && out.Item == in.Item {
				batches := Lots{}
			OUTER:
				for {
					oldestBatch := in.Batches.First()
					batchQty := math.Min(in.Batches.First().Value, math.Abs(out.Batches.First().Value))

					batches = append(batches, Lot{out.Batches.First().Date, oldestBatch.Key, batchQty})

					out.Batches[0].Value = out.Batches.First().Value + batchQty
					in.Batches[0].Value = oldestBatch.Value - batchQty

					if in.Batches.First().Value == 0 {
						in.Batches.PopFirst()
					}
					if out.Batches.First().Value == 0 {
						out.Batches.PopFirst()
						if len(out.Batches) == 0 {
							break
						} else {
							continue OUTER
						}
					} else {
						continue OUTER
					}
				}
				batchesOut = append(batchesOut, Inventory{
					Warehouse: out.Warehouse,
					Item:      out.Item,
					Batches:   batches,
				})
			}
		}
		//os.Exit(3)
	}
	return batchesOut, batchesBalance
} 

func main() {
ins := Inventory{
		Warehouse: "DMM",
		Item:      "Gloves",
		Batches: Lots{
			Lot{mustTime(time.Parse(custom, "1/7/2020")), "Jan", 50},
			Lot{mustTime(time.Parse(custom, "2/1/2020")), "Feb", 70},
		},
	}

outs := Inventory{
		Warehouse: "DMM",
		Item:      "Gloves",
		Batches: Lots{
			Lot{mustTime(time.Parse(custom, "1/5/2020")), "", -10},
			Lot{mustTime(time.Parse(custom, "2/9/2020")), "", -30},
		},
	}

        fmt.Printf("\n\n[1] Ins: \n%v", ins)     // This output is different after running outs_clone.BuildBatchesFrom(ins_clone)
	fmt.Printf("\n\n[2] Outs: \n%v", outs)   //  // This output is different after running outs_clone.BuildBatchesFrom(ins_clone)
	
        ins_clone := ins.Clone()
	outs_clone := outs.Clone()

	batchesOut, batchesBalance := outs_clone.BuildBatchesFrom(ins_clone)

	fmt.Printf("\n\n[1] Ins: \n%v", ins)    // This output is different before running outs_clone.BuildBatchesFrom(ins_clone)
	fmt.Printf("\n\n[2] Outs: \n%v", outs)   // This output is different after running outs_clone.BuildBatchesFrom(ins_clone)

	fmt.Printf("\n\n[4] Batches outs: \n%v", batchesOut)
	fmt.Printf("\n\n[5] Batches Balances: \n%v", batchesBalance)
}

In the above, ins is changing after running the function, though I’m not passing it there, and ins_clone() is changing after the function though I’m cloning it in the first line of the function code, same for outs and outs_clone()

After reviewing the codes (although having some errors due to missing functions), it is working as expected?

I do not expect BuildBatchesFrom to behave the same as Clone because there are some logical operations in it (e.g. min/max and sort by date). As for Clone, it is a clear linear duplication for the array slice.

Can you clarify the requirements again please?

Playground: https://play.golang.org/p/OmpXKlvnwsg

2 Likes

Thanks for your reply, actually the code I provided is not complete, it is just file of the file in order not to make it complicated, but it contains the related info.

I got it, the reason is that my slice contains a sub slice so the copy worked for the top one but did not work for the sub one, which is sharing same data, I fixed it by re-writing the code by creating a new slice, pushing the data to it, then use it to replace the original one, as below:

func (i *Inventories) CloneFrom(c Inventories) {
	inv := new(Inventories)
	for _, v := range c {
		batches := Lots{}
		for _, b := range v.Batches {
			batches = append(batches, Lot{
				Date:  b.Date,
				Key:   b.Key,
				Value: b.Value,
			})
		}

		*inv = append(*inv, Inventory{
			Warehouse: v.Warehouse,
			Item:      v.Item,
			Batches:   batches,
		})
	}
	(*i).ReplaceBy(inv)
}

func (i *Inventories) ReplaceBy(x *Inventories) {
	*i = *x
}

My full code is:

package main

import (
	"fmt"
	"log"
	"math"
	"sort"
	"time"
)

const (
	layoutISO = "2006-01-02"
	layoutUS  = "January 2, 2006"
	custom    = "1/2/2006"
)

type Inventories []Inventory
type Inventory struct { //instead of: map[string]map[string]Pairs
	Warehouse string
	Item      string
	Batches   Lots
}
type Lots []Lot
type Lot struct {
	Date  time.Time
	Key   string
	Value float64
}

func main() {
	fmt.Println("Hello, 世界")
	date := "12/31/2019" // "1999-12-31"
	t, _ := time.Parse(custom, date)
	var inventories = new(Inventories)
	var inventory = Inventory{} // new(Inventory) // Or = Inventory{} both are working //warehouse[item[batch, qty]]
	inventory.Warehouse = "DMM"
	inventory.Item = "Helmet"
	inventory.Batches = append(inventory.Batches, Lot{t, "Jan", 20})
	inventory.InsertBatch(Lot{time.Now(), "", -30})
	inventory.Batches.Insert(Lot{time.Now(), "Feb", 60})

	x := Inventory{
		Warehouse: "DMM",
		Item:      "Gloves",
		Batches: Lots{
			Lot{mustTime(time.Parse(custom, "1/7/2020")), "Jan", 50},
			Lot{mustTime(time.Parse(custom, "2/1/2020")), "Feb", 70},
			Lot{mustTime(time.Parse(custom, "1/5/2020")), "", -10},
			Lot{mustTime(time.Parse(custom, "2/9/2020")), "", -30},
		},
	}

	// Below can be used for grouping batches in each warehouse seperatly, this can be combined in the lines under
	//	inventory.Batches.Group()
	//	x.GroupBatches()

	// Above can be replaced by below
	inventories.Insert(inventory)
	inventories.Insert(x)

	outs := inventories.Clone()
	outs.Reductions()
	ins := inventories.Clone()
	ins.Additions()

	fmt.Printf("\n\n[1] Ins: \n%v", ins)
	fmt.Printf("\n\n[2] Outs: \n%v", outs)

	//ins_clone := ins.Clone()
	batchesOut, batchesBalance := outs.BuildBatchesFrom(ins)

	fmt.Printf("\n\n[1] Ins: \n%v", ins)
	fmt.Printf("\n\n[2] Outs: \n%v", outs)

	fmt.Printf("\n\n[4] Batches outs: \n%v", batchesOut)
	fmt.Printf("\n\n[5] Batches Balances: \n%v", batchesBalance)
}

func (i *Inventories) Insert(x Inventory) {
	*i = append(*i, x)
}

func (i *Inventories) GroupBatches() {
	inv := new(Inventories)
	for _, el := range *i {
		el.GroupBatches()
		inv.Insert(el)
	}
	(*i).ReplaceBy(inv)
}

func (i *Inventories) SortBatches() {
	inv := new(Inventories)
	for _, el := range *i {
		sort.Sort(Lots(el.Batches))
		inv.Insert(el)
	}
	(*i).ReplaceBy(inv)
}

func (i *Inventories) Additions() {
	inv := new(Inventories)
	for _, el := range *i {
		for _, b := range el.Batches {
			if b.Value > 0 {
				if index := inv.Find(el.Warehouse, el.Item); index == len(*inv) {
					*inv = append(*inv, Inventory{
						Warehouse: el.Warehouse,
						Item:      el.Item,
						Batches:   Lots{b},
					})
				} else {
					inv.InsertBatchAt(index, b)
				}
			}
		}
	}
	(*i).ReplaceBy(inv)
}

func (i *Inventories) Reductions() {
	inv := new(Inventories)
	for _, el := range *i {
		for _, b := range el.Batches {
			if b.Value < 0 {
				if index := inv.Find(el.Warehouse, el.Item); index == len(*inv) {
					*inv = append(*inv, Inventory{
						Warehouse: el.Warehouse,
						Item:      el.Item,
						Batches:   Lots{b},
					})
				} else {
					inv.InsertBatchAt(index, b)
				}
			}
		}
	}
	(*i).ReplaceBy(inv)
}

// Do NOT USE IT, replace it by CloneFrom()
func (c *Inventories) Clone() Inventories {
	var s = make(Inventories, len(*c))
	copy(s, *c)
	return s
}

func (i *Inventories) CloneFrom(c Inventories) {
	inv := new(Inventories)
	for _, v := range c {
		batches := Lots{}
		for _, b := range v.Batches {
			batches = append(batches, Lot{
				Date:  b.Date,
				Key:   b.Key,
				Value: b.Value,
			})
		}

		*inv = append(*inv, Inventory{
			Warehouse: v.Warehouse,
			Item:      v.Item,
			Batches:   batches,
		})
	}
	(*i).ReplaceBy(inv)
}

// Find returns the smallest index i at which x == a[i],
// or len(a) if there is no such index.
func (inv *Inventories) Find(whs string, itm string) int {
	for i, n := range *inv {
		if whs == n.Warehouse && itm == n.Item {
			return i
		}
	}
	return len(*inv)
}

func (outs Inventories) BuildBatchesFrom(ins Inventories) (batchesBalance Inventories, outgoing Inventories) {
	batchesBalance.CloneFrom(ins) // := ins.Clone()
	outgoing.CloneFrom(outs)      // := outs.Clone()
	batchesOut := Inventories{}

	for _, in := range batchesBalance {
		for _, out := range outgoing {
			if out.Warehouse == in.Warehouse && out.Item == in.Item {
				batches := Lots{}
			OUTER:
				for {
					oldestBatch := in.Batches.First()
					batchQty := math.Min(in.Batches.First().Value, math.Abs(out.Batches.First().Value))

					batches = append(batches, Lot{out.Batches.First().Date, oldestBatch.Key, batchQty})

					out.Batches[0].Value = out.Batches.First().Value + batchQty
					in.Batches[0].Value = oldestBatch.Value - batchQty

					if in.Batches.First().Value == 0 {
						in.Batches.PopFirst()
					}
					if out.Batches.First().Value == 0 {
						out.Batches.PopFirst()
						if len(out.Batches) == 0 {
							break
						} else {
							continue OUTER
						}
					} else {
						continue OUTER
					}
				}
				batchesOut = append(batchesOut, Inventory{
					Warehouse: out.Warehouse,
					Item:      out.Item,
					Batches:   batches,
				})
			}
		}
		//os.Exit(3)
	}
	return batchesOut, batchesBalance
}

func (i *Inventories) ReplaceBy(x *Inventories) {
	*i = *x
}

func (i *Inventories) InsertBatchAt(index int, b Lot) {
	(*i)[index].Batches.Insert(b)
	//a = append(a[:index+1], a[index:]...)
	//*i = append(*i, x)
}

func (i *Inventory) InsertBatch(x Lot) {
	(*i).Batches = append((*i).Batches, x)
}

func (i *Inventory) GroupBatches() {
	(*i).Batches.Group()
}

func (p *Lots) Group() {
	lots := new(Lots)
	lots.FromMap(p.Map())
	p.ReplaceBy(lots)
}

func (p *Lots) Pop(i int) {
	*p = append((*p)[:i], (*p)[i+1:]...) // append(a, b...)  // append(a[:i], a[i+1:]...)
}

func (p *Lots) PopLast() {
	*p = append((*p)[:len(*p)-1])
}

func (p *Lots) First() Lot {
	return (*p)[0]
}

func (p *Lots) Last() Lot {
	return (*p)[len(*p)]
}

func (p *Lots) PopFirst() {
	*p = append((*p)[1:len(*p)])
}

func (p *Lots) FromMap(m map[string]float64) {
	for k, v := range m {
		(*p).Insert(Lot{time.Now(), k, v})
	}
}

// Below to enable sorting: sort.Sort(Lots(lots))
func (l Lots) Len() int           { return len(l) }
func (l Lots) Less(i, j int) bool { return (l[i].Date).Before(l[j].Date) } // { return l[i].Key < l[j].Key }
func (l Lots) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }

func (p *Lots) Insert(x Lot) {
	*p = append(*p, x)
}

func (p *Lots) ReplaceBy(x *Lots) {
	*p = *x
}

func (p *Lots) Map() map[string]float64 {
	sum := make(map[string]float64)
	for _, el := range *p {
		sum[el.Key] = sum[el.Key] + el.Value
	}
	return sum
}

type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less reports whether the element with
	// index i should sort before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}

func mustTime(t time.Time, err error) time.Time {
	failOnError(err)
	return t
}

func failOnError(err error) {
	if err != nil {
		log.Fatal("Error:", err)
		panic(err)
	}
}

// Contains tells whether a contains x.
func Contains(a []string, x string) bool {
	for _, n := range a {
		if x == n {
			return true
		}
	}
	return false
}
1 Like