How to update slice of struct in recursive fashion

Viewed 120

For following code, I'm expecting output of {"NewName" [{"NewName" []}]} but it's not updating child structs. How can we make sure it updates every struct in hierarchy.

package main

import (
    "fmt"
)

type red struct {
    Name string
    Child []red
}

func (r *red) setName(nameString string){
    r.Name = nameString
    for _, child := range r.Child{
        child.setName(nameString)
    }
}

func main() {
    obj := red{Name:"NameA",Child:[]red{red{Name: "NameB"}}}
    fmt.Print(obj)
    fmt.Print("\n")

    obj.setName("NewName")
    //Expectation {"NewName" [{"NewName" []}]}
    fmt.Print(obj)
}
4 Answers

You need not to use pointers everywhere as suggested by others answers.

The problem in your code is that when you iterate the child, you get a value by copy for each item, you then set the name on the copy.

But as you do not copy back the modified value into the slice, the changes are not retained.

package main

import (
    "fmt"
)

type red struct {
    Name  string
    Child []red
}

func (r *red) setName(s string) {
    r.Name = s
    for i, ch := range r.Child {
        ch.setName(s) // ch is not a ptr to r.Child[i], it is a value copy
        r.Child[i] = ch // so you must re assign the copy into the slice!
    }
}

func main() {
    obj := red{Name: "A", Child: []red{red{Name: "B"}}}
    fmt.Print(obj)
    fmt.Print("\n")

    obj.setName("X")
    //Expectation {X [{X []}]}
    fmt.Print(obj)
}

You should make Child be []*red instead of []red to prevent copies of red being created.

See the following tweaks(the String method is just to make the output look pretty, else we'll get memory addresses printed):

package main

import (
    "fmt"
)

type red struct {
    Name  string
    Child []*red
}

func (r *red) String() string {
    ret := fmt.Sprintf("{%s [", r.Name)
    for _, c := range r.Child {
        ret = ret + c.Name + " "
    }
    ret += "]}"
    return ret
}

func (r *red) setName(s string) {
    r.Name = s
    for _, ch := range r.Child {
        ch.setName(s)
    }
}

func main() {
    obj := red{Name: "A", Child: []*red{&red{Name: "B"}}}
    fmt.Print(obj)
    fmt.Print("\n")

    obj.setName("X")
    //Expectation {X [{X []}]}
    fmt.Print(obj)
}

You can do that by changing Child []red to a list of pointers: Child []*red.

Here's the full code in Go playground

package main

import (
    "fmt"
)

type red struct {
    Name string
    Child []*red
}

func (r *red) setName(s string){
    r.Name = s
    for _, ch := range r.Child{
        ch.setName(s)
    }
}

func main() {
    obj := red{Name:"A",Child:[]*red{&red{Name: "B"}}}
    fmt.Println(obj.Name)
    for _, ch := range obj.Child{
       fmt.Println(ch.Name)
    }
    fmt.Print("\n")

    obj.setName("X")
    //Expectation X \n X
    fmt.Println(obj.Name)
    for _, ch := range obj.Child{
       fmt.Println(ch.Name)
    }
}

Actually the problem is here

for _, ch := range r.Child{
        ch.setName(s)
}

You are updating using ch which is not updating you r.Child slice actually. Your code is equivalent like this

var ch red
for i, _:= range r.Child{
    ch = r.Child[i]
    ch.setName(s)
}

To solve this you can update the slice this way.

for i, _ := range r.Child {
    r.Child[i].setName(s) // access the slice eliment by index and update
}

And it's a good pattern to use []*red for child.

Related