Is there autovivification for Go?
As #JimB correctly noticed, my definition is not that strict. About my goal: In Python we have a very elegant "emulation" for an autovivification:
class Path(dict):
def __missing__(self, key):
value = self[key] = type(self)()
return value
Is there a similar solution for Go?
Go maps will return a zero value for the type if the key doesn't exist, or the map is nil
https://play.golang.org/p/sBEiXGfC1c
var sliceMap map[string][]string
// slice is a nil []string
slice := sliceMap["does not exist"]
var stringMap map[string]string
// s is an empty string
s := stringMap["does not exist"]
Since a map with numeric values return will return 0 for missing entries, Go lets you use the increment and decrement operators on non-existent keys:
counters := map[string]int{}
counters["one"]++
Also extending JimB's answer, with the combination of map, interface{} and type assertion, you can dynamically create any complex structures:
type Obj map[interface{}]interface{}
func main() {
var o Obj
o = Obj{
"Name": "Bob",
"Age": 23,
3: 3.14,
}
fmt.Printf("%+v\n", o)
o["Address"] = Obj{"Country": "USA", "State": "Ohio"}
fmt.Printf("%+v\n", o)
o["Address"].(Obj)["City"] = "Columbus"
fmt.Printf("%+v\n", o)
fmt.Printf("City = %v\n", o["Address"].(Obj)["City"])
}
Output (try it on the Go Playground):
map[Name:Bob Age:23 3:3.14]
map[Age:23 3:3.14 Address:map[Country:USA State:Ohio] Name:Bob]
map[3:3.14 Address:map[Country:USA State:Ohio City:Columbus] Name:Bob Age:23]
City = Columbus
Related
Is it possible to use slices as keys?
There is my attempt:
h := map[[]string]string{
[]string{"a", "b"} : "ab",
}
the compiler gives me an error invalid map key type []string. So either it's not possible or I declared it incorrectly (if so, what would be a correct way?).
However, it is possible to use arrays as map keys:
package main
import "fmt"
func main() {
m := make(map[[2]int]bool)
m[[2]int{1, 2}] = false
fmt.Printf("%v", m)
}
No, slices cannot be used as map keys as they have no equality defined.
Volker already told that this is not possible and I will give a little bit more details of why is it so with examples from the spec.
Map spec tells you:
The comparison operators == and != must be fully defined for operands
of the key type; thus the key type must not be a function, map, or
slice.
It already tells you that the slice can't be a key, but you could have checked it also in the comparison spec:
Slice, map, and function values are not comparable.
This means that also slice can't be a key, an array can be a key. For example you can write:
h := map[[2]string]string{
[2]string{"a", "b"} : "ab",
}
Depending on your requirements and the complexity of your data, you could use a string as a map key and then use a hash of your slice as the map key.
The nice thing is you can use this technique with anything that can be converted to or from a slice of bytes.
Here's a quick way to convert your slice of strings into a slice of bytes:
[]byte(strings.Join([]string{},""))
Here's an example using SHA1:
type ByteSliceMap struct {
buf *bytes.Buffer
m map[string][]byte
}
func (b *ByteSliceMap) key(buf []byte) string {
h := sha1.New()
h.Write(buf)
sum := h.Sum(nil)
return fmt.Sprintf("%x", sum)
}
func (t *ByteSliceMap) value(key []byte) (value []byte, ok bool) {
value, ok = t.m[t.key(key)]
return
}
func (t *ByteSliceMap) add(key, value []byte) {
if t.m == nil {
t.m = make(map[string][]byte)
}
t.m[t.key(key)] = value
}
Working version
One way to get around this problem is to actually create a key from a slice which has well defined comparison operators:
func createKey(s []string) string { return fmt.Sprintf("%q", s) }
m := make(map[string]string)
s := []string{"a","b"}
m[createKey(s)] = "myValue"
In a similar fashion you would have to create functions for creating keys of slices with type different to string.
I know you can create a struct with a literal, listing the fields in order:
type Foo struct {
A string
B string
C string
}
foo := Foo{ "foo", "bar", "baz" }
Is there any way to do the same thing dynamically? I have an array of values (actually an array of arrays) and I want to assign them to an array of structs in field order, and there are rather more than three fields. Is there a way to say "assign this struct's fields from this array of values in order"? I really don't want to write a bunch of structArray[i].field1 = dataArray[i][0]; structArray[i].field2 = dataArray[i][1], etc.
My thoughts so far have been to use reflect, which seems overkillish, or maybe to create an array of field names and build json strings and unmarshal them. Any better ideas?
With reflection you can write a function like this:
func populate(dst any, src any) {
v := reflect.ValueOf(dst)
if v.Type().Kind() != reflect.Pointer {
panic("dst must be a pointer")
}
v = v.Elem()
if v.Type().Kind() != reflect.Struct {
panic("dst must be a pointer to struct")
}
w := reflect.ValueOf(src)
if w.Type().Kind() != reflect.Slice {
panic("src must be a slice")
}
for i := 0; i < v.NumField(); i++ {
// in case you need to support source slices of arbitrary types
value := w.Index(i)
if value.Type().Kind() == reflect.Interface {
value = value.Elem()
}
v.Field(i).Set(value)
}
}
You must make sure that dst is addressable, hence pass a pointer to Foo into populate; and that the i-th element in the source slice is actually assignable to the corresponding i-th field in the struct.
The code above is in a quite simplified form. You can add additional checks to it, e.g. with CanAddr or AssignableTo, if you think callers may misbehave.
Call it like:
func main() {
f := Foo{}
populate(&f, []string{"foo", "bar", "baz"})
fmt.Println(f) // {foo bar baz}
}
Here's a playground that also shows that you can pass a slice of []any as the source slice, in case the struct fields aren't all the same type: https://go.dev/play/p/G8qjDCt79C7
This question already has answers here:
Access struct property by name
(5 answers)
Golang dynamic access to a struct property
(2 answers)
How to access to a struct parameter value from a variable in Golang
(1 answer)
Closed 9 months ago.
Came from javascript background, and just started with Golang. I am learning all the new terms in Golang, and creating new question because I cannot find the answer I need (probably due to lack of knowledge of terms to search for)
I created a custom type, created an array of types, and I want to create a function where I can retrieve all the values of a specific key, and return an array of all the values (brands in this example)
type Car struct {
brand string
units int
}
....
var cars []Car
var singleCar Car
//So i have a loop here and inside the for-loop, i create many single cars
singleCar = Car {
brand: "Mercedes",
units: 20
}
//and i append the singleCar into cars
cars = append(cars, singleCar)
Now what I want to do is to create a function that I can retrieve all the brands, and I tried doing the following. I intend to have key as a dynamic value, so I can search by specific key, e.g. brand, model, capacity etc.
func getUniqueByKey(v []Car, key string) []string {
var combined []string
for i := range v {
combined = append(combined, v[i][key])
//this line returns error -
//invalid operation: cannot index v[i] (map index expression of type Car)compilerNonIndexableOperand
}
return combined
//This is suppose to return ["Mercedes", "Honda", "Ferrari"]
}
The above function is suppose to work if i use getUniqueByKey(cars, "brand") where in this example, brand is the key. But I do not know the syntaxes so it's returning error.
Seems like you're trying to get a property using a slice accessor, which doesn't work in Go. You'd need to write a function for each property. Here's an example with the brands:
func getUniqueBrands(v []Car) []string {
var combined []string
tempMap := make(map[string]bool)
for _, c := range v {
if _, p := tempMap[c.brand]; !p {
tempMap[c.brand] = true
combined = append(combined, c.brand)
}
}
return combined
}
Also, note the for loop being used to get the value of Car here. Go's range can be used to iterate over just indices or both indices and values. The index is discarded by assigning to _.
I would recommend re-using this code with an added switch-case block to get the result you want. If you need to return multiple types, use interface{} and type assertion.
Maybe you could marshal your struct into json data then convert it to a map. Example code:
package main
import (
"encoding/json"
"fmt"
)
type RandomStruct struct {
FieldA string
FieldB int
FieldC string
RandomFieldD bool
RandomFieldE interface{}
}
func main() {
fieldName := "FieldC"
randomStruct := RandomStruct{
FieldA: "a",
FieldB: 5,
FieldC: "c",
RandomFieldD: false,
RandomFieldE: map[string]string{"innerFieldA": "??"},
}
randomStructs := make([]RandomStruct, 0)
randomStructs = append(randomStructs, randomStruct, randomStruct, randomStruct)
res := FetchRandomFieldAndConcat(randomStructs, fieldName)
fmt.Println(res)
}
func FetchRandomFieldAndConcat(randomStructs []RandomStruct, fieldName string) []interface{} {
res := make([]interface{}, 0)
for _, randomStruct := range randomStructs {
jsonData, _ := json.Marshal(randomStruct)
jsonMap := make(map[string]interface{})
err := json.Unmarshal(jsonData, &jsonMap)
if err != nil {
fmt.Println(err)
// panic(err)
}
value, exists := jsonMap[fieldName]
if exists {
res = append(res, value)
}
}
return res
}
I have 2 go functions like following
func removeL2McEntry(a []api.L2McEntry, index int) []api.L2McEntry {
a = append(a[:index], a[index+1:]...)
element
return a[:len(a)]
}
func removeVlagBasedGroup(a []api.VlanPortBased, index int) []api.VlanPortBased {
a = append(a[:index], a[index+1:]...)
return a[:len(a)]
}
As you can see, both functions are doing the same work. But I need to separate them because the outputs and the inputs of the functions are different type.
I have tried:
func removeSlice(a interface{}, idx int) interface{} {
switch v := a.(type) {
case []string:
v = append(v[:idx], v[idx+1:]...)
fmt.Println("is ary", v)
return v[:len(v)]
case []int:
v = append(v[:idx], v[idx+1:]...)
fmt.Println("is ary", v)
return v[:len(v)]
default:
}
return nil
}
But there is too many repetitive code in this way.
Is there any way to make it just one function and reduce the repetitive code?
Thanks in advance.
As Adrian noted, removing an element from a slice is one line of code, in general:
a = append(a[:i], a[i+1]...)
// or
a = a[:i+copy(a[i:], a[i+1:])]
It's not really worth writing a function for it, just use this code snippet where needed.
If you do need to create a function that can handle any slice types, it can be created using reflection. But when using it, you will have to use a type assertion on the result, as the function can only return a static type of interface{}. It will also be slower than using the above snippet on your concrete slice value!
The above remove steps can be "reproduced" using the reflect package. Slicing is the Value.Slice() method, and the append operation is the reflect.AppendSlice() function.
This is how it could look like (types and bound checks omitted):
func remove(s interface{}, i int) interface{} {
v := reflect.ValueOf(s)
return reflect.AppendSlice(v.Slice(0, i), v.Slice(i+1, v.Len())).Interface()
}
Testing it:
is := []int{0, 1, 2, 3}
is = remove(is, 2).([]int)
fmt.Printf("%#v\n", is)
ss := []string{"0", "1", "2", "3"}
ss = remove(ss, 2).([]string)
fmt.Printf("%#v\n", ss)
Output (try it on the Go Playground):
[]int{0, 1, 3}
[]string{"0", "1", "3"}
But again: I don't advise anyone to use this (although working) code, just remove the element directly with the original snippet.
Is it possible to use slices as keys?
There is my attempt:
h := map[[]string]string{
[]string{"a", "b"} : "ab",
}
the compiler gives me an error invalid map key type []string. So either it's not possible or I declared it incorrectly (if so, what would be a correct way?).
However, it is possible to use arrays as map keys:
package main
import "fmt"
func main() {
m := make(map[[2]int]bool)
m[[2]int{1, 2}] = false
fmt.Printf("%v", m)
}
No, slices cannot be used as map keys as they have no equality defined.
Volker already told that this is not possible and I will give a little bit more details of why is it so with examples from the spec.
Map spec tells you:
The comparison operators == and != must be fully defined for operands
of the key type; thus the key type must not be a function, map, or
slice.
It already tells you that the slice can't be a key, but you could have checked it also in the comparison spec:
Slice, map, and function values are not comparable.
This means that also slice can't be a key, an array can be a key. For example you can write:
h := map[[2]string]string{
[2]string{"a", "b"} : "ab",
}
Depending on your requirements and the complexity of your data, you could use a string as a map key and then use a hash of your slice as the map key.
The nice thing is you can use this technique with anything that can be converted to or from a slice of bytes.
Here's a quick way to convert your slice of strings into a slice of bytes:
[]byte(strings.Join([]string{},""))
Here's an example using SHA1:
type ByteSliceMap struct {
buf *bytes.Buffer
m map[string][]byte
}
func (b *ByteSliceMap) key(buf []byte) string {
h := sha1.New()
h.Write(buf)
sum := h.Sum(nil)
return fmt.Sprintf("%x", sum)
}
func (t *ByteSliceMap) value(key []byte) (value []byte, ok bool) {
value, ok = t.m[t.key(key)]
return
}
func (t *ByteSliceMap) add(key, value []byte) {
if t.m == nil {
t.m = make(map[string][]byte)
}
t.m[t.key(key)] = value
}
Working version
One way to get around this problem is to actually create a key from a slice which has well defined comparison operators:
func createKey(s []string) string { return fmt.Sprintf("%q", s) }
m := make(map[string]string)
s := []string{"a","b"}
m[createKey(s)] = "myValue"
In a similar fashion you would have to create functions for creating keys of slices with type different to string.