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.
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 decided that now that generics have been introduced into Go that something like map/reduce should be possible. So, I took a naive stab at it and I get the error:
./prog.go:18:36: cannot use thing (variable of type int) as type I in argument to mapper
Which doesn't explain if the problem is fundamental or I am simply doing something wrong syntactically. Can generic map/reduce be implemented in Go?
package main
import "fmt"
func main() {
things := []int{1, 2, 3, 4}
results := Map(things, func(t int) int {
return t + 1
})
fmt.Printf("%v", results)
}
func Map[I interface{}, O interface{}](things []I, mapper func(thing I) O) []O {
results := make([]O, 0, len(things))
for thing := range things {
results = append(results, mapper(thing))
}
return results
}
You have incorrect use of range. A single variable extracted from range will be the index (type int), not the value (type I, which is only coincidentally int in this case).
Try
for _, thing := range things{...}
This can be done quite easily. You have an error in your code, though right here:
for thing := range things {
You are iterating over the index values (int), not the values of type I. You're also specifying 2 constraints (types I and O) both set to be interface{}. You can just use any instead (it's shorthand for interface{})
So simply write:
func Map[T any, O any](things []T, mapper func(thing T) O) []O {
result := make([]O, 0, len(things))
for _, thing := range things {
result = append(result, mapper(thing))
}
return result
}
Demo
This is quite closely related to some code I reviewed on codereview exchange here. After going through the code, and writing snippets with a ton of suggestions, I decided to just create a package and throw it up on github instead. You can find the repo here.
In it, there's some examples that may come in handy, or help you work through some other quirks WRT generics in golang. I wsa specifically thinking about this bit, where you can filter a generic map type using callbacks like so:
// given the sMap type
type sMap[K comparable, V any] struct {
mu *sync.RWMutex
m map[K]V
}
// Filter returns a map containing the elements that matched the filter callback argument
func (s *sMap[K, V]) Filter(cb func(K, V) bool) map[K]V {
s.mu.RLock()
defer s.mu.RUnlock()
ret := make(map[K]V, len(s.m))
for k, v := range s.m {
if cb(k, v) {
ret[k] = v
}
}
return ret
}
I have two maps, both of them are keyed by strings, but the values are of two different custom types.
map[string]type1
map[string]type2
Now I want to write a function which can take an argument of either of these two types, because that function only looks at the keys and doesn't care about the values at all. So I think it should look like this:
func takeTheMap(argument map[string]interface{}) {
...
But that doesn't work due to:
cannot use myVariable (type map[string]customType) as type map[string]interface {} in argument to takeTheMap
https://play.golang.org/p/4Xkhi4HekO5
Can I make that work somehow?
The only polymorphism in Go is interfaces. The only alternatives to that are reflection, duplication, or rethinking the broader design so that you don't need to do what you're trying to do here.
If the last option isn't a possibility, personally I would recommend duplication, since it's a whole four lines of code.
keys := make([]string, 0, len(myMap))
for key,_ := range myMap {
keys = append(keys,key)
}
A big complicated generic helper seems kind of unnecessary.
A solution using an interface. This example may seem a bit overkill and it may be better to in your case (I'm not sure, not enough details in your example) to just use a couple of for loops.
package main
import (
"fmt"
)
type foo bool
type bar string
type mapOne map[string]foo
type mapTwo map[string]bar
func (m mapOne) Keys() []string {
s := []string{}
for k := range m {
s = append(s, k)
}
return s
}
func (m mapTwo) Keys() []string {
s := []string{}
for k := range m {
s = append(s, k)
}
return s
}
type ToKeys interface {
Keys() []string
}
func main() {
m1 := mapOne{"one": true, "two": false}
m2 := mapTwo{"three": "foo", "four": "bar"}
doSomething(m1)
doSomething(m2)
}
func doSomething(m ToKeys) {
fmt.Println(m.Keys())
}
Playground example
I wrote this example code (https://play.golang.org/p/u_oz5X4aU07):
func main() {
var obj interface{}
json.Unmarshal([]byte("[[1,2],[3,4]]"), &obj)
val := obj.([][]int)
fmt.Println(val)
}
Why I get the error:
interface conversion: interface {} is []interface {}, not [][]int
Is there a simple way to transform obj in a slice of slice?
This code works, but I'd like something more compact and efficient.
var val [][]float64
for r, v := range obj.([]interface{}) {
val = append(val,nil)
for _, w := range v.([]interface{}) {
val[r] = append(val[r], w.(float64))
}
}
No, ultimately you'll have to loop over the two slices!
You can read here why you can't just use one as the other:
https://research.swtch.com/interfaces
This answer might also be useful:
Why golang struct array cannot be assigned to an interface array
Essentially it's because the interface is stored as a 2 word pair, one defining the type and one the values.
You have to manually convert to the required type by visiting all the values in for-range loops.
I have run into this problem a few times when wanting to use keys of maps in a similar way but the values in the maps are different. I thought I could write a function that takes the key type I want with interface{} as the value type but it doesn't work.
func main() {
mapOne := map[string]int
mapTwo := map[string]double
mapThree := map[string]SomeStruct
useKeys(mapOne)
}
func useKeys(m map[string]interface{}) {
//something with keys here
}
Not sure if there is an elegant way to do this I just feel waist full rewriting simple things for different values.
Though maps and slices in go are generic themselves, they are not covariant (nor could they be, since interfaces aren't generics). It's part of working with a language that doesn't have generics, you will have to repeat some things.
If you really just need to get the keys of any old map, you can use reflection to do so:
func useKeys(m interface{}) {
v := reflect.ValueOf(m)
if v.Kind() != reflect.Map {
fmt.Println("not a map!")
return
}
keys := v.MapKeys()
fmt.Println(keys)
}
Go 1.18
You can write a function with type parameters (generic) for this:
func useKeys[V any](m map[string]V) V {
return m["foo"]
}
And use it as:
func main() {
m1 := map[string]int{"foo": 1}
m2 := map[string]float64{"foo": 4.5}
m3 := map[string]*SomeStruct{}
fmt.Println(useKeys(m1))
fmt.Println(useKeys(m2))
fmt.Println(useKeys(m3))
}
As you can see, the type parameter V unifies with the map value, so that you can explicitly force callers of useKeys to pass maps whose keys are string only.
You can see this on the GoTip Playground: https://gotipplay.golang.org/p/epFA2_9u5l5