20180525

Changing values in a slice in a struct in a map...

Maps in Go are great as a out of the box simple key-value datastructure. However, maps can be a bit rigid from time to time. For instance, adding new instances into a slice in a struct in a map reference had me scratching my head quite severly.

So I made a little demo-project to work out the procedure. This is a bit clunky I must admit, but it do work.

---
package main

import (
    "crypto/sha1"
    "fmt"
    "hash"
    "io"
    "math/rand"
    "time"
)

type myStructT struct {
    name string
    pets []string
}

func main() {
    var myMap map[string]myStructT
    myMap = make(map[string]myStructT)

    rand.Seed(time.Now().UTC().UnixNano())
    mySlice := initSlice()
    myMap = initMap(mySlice)
    printMap(myMap)
    myMap = editMap(myMap, "Dewey")
    fmt.Println("----- After edit -----")
    printMap(myMap)
}

func editMap(in map[string]myStructT, index string) map[string]myStructT {
    var h hash.Hash
    var hash string
    var old myStructT

    h = sha1.New()
    io.WriteString(h, index)
    hash = fmt.Sprintf("%x", h.Sum(nil))

    old = in[hash]
    delete(in, hash)
    old.pets = append(old.pets, "Alligator")
    in[hash] = old
    return in
}

func printMap(in map[string]myStructT) {
    for i := range in {
        fmt.Printf("Name: %s\n", in[i].name)
        if len(in[i].pets) > 0 {
            fmt.Printf("Pets: %d\n", len(in[i].pets))
            for j := range in[i].pets {
                fmt.Printf("- %s\n", in[i].pets[j])
            }
        } else {
            fmt.Println("No Pets :(")
        }
        fmt.Println("----------")
    }
}

func initSlice() []myStructT {
    var s myStructT
    var out []myStructT

    name := [3]string{"Huey", "Luie", "Dewey"}
    pets := [6]string{"Cat", "Dog", "Ferret", "Bunny", "Hamster", "Parrot"}

    for i := range name {
        s.name = name[i]
        s.pets = nil
        x := randInt(0, 3)
        for j := 0; j < x; j++ {
            y := randInt(0, 6)
            s.pets = append(s.pets, pets[y])
        }
        out = append(out, s)
    }
    return out
}

func initMap(in []myStructT) map[string]myStructT {
    var h hash.Hash
    var hash string
    var s myStructT

    out := make(map[string]myStructT)

    for i := range in {
        h = sha1.New()
        io.WriteString(h, in[i].name)
        hash = fmt.Sprintf("%x", h.Sum(nil))
        s = in[i]
        out[hash] = s
    }
    return out
}

func randInt(min int, max int) int {
    return min + rand.Intn(max-min)
}

---

Essentially what happens is that I import the map, copy the given index I want to modify into a temp struct. Modify the temp struct as per usual procedure. Then kill the map instance of the given index, add a new map instance using the same index, and fill it with the modified content of the temp struct.

Finally, return to sender.

Hopefully I can find a better solution, I think I've read something about using pointers to operate directly on the map struct.

If I got it right, the map will then only contain a pointer to a given struct and it's instance. I'll have to read some more, ask around and lubricate my brain with beer to get there though...