I know I can iterate over a map m with
for k, v := range m { ... }
and look for a key, but is there a more efficient way of testing for a key's existence in a map?
Here's how you check if a map contains a key.
val, ok := myMap["foo"]
// If the key exists
if ok {
// Do something
}
This initializes two variables. val is the value of "foo" from the map if it exists, or a "zero value" if it doesn't (in this case the empty string). ok is a bool that will be set to true if the key existed.
If you want, you can shorten this to a one-liner.
if val, ok := myMap["foo"]; ok {
//do something here
}
Go allows you to put an initializing statement before the condition (notice the semicolon) in the if statement. The consequence of this is that the scope ofval and ok will be limited to the body of the if statement, which is helpful if you only need to access them there.
In addition to The Go Programming Language Specification, you should read Effective Go. In the section on maps, they say, amongst other things:
An attempt to fetch a map value with a key that is not present in the
map will return the zero value for the type of the entries in the map.
For instance, if the map contains integers, looking up a non-existent
key will return 0. A set can be implemented as a map with value type
bool. Set the map entry to true to put the value in the set, and then
test it by simple indexing.
attended := map[string]bool{
"Ann": true,
"Joe": true,
...
}
if attended[person] { // will be false if person is not in the map
fmt.Println(person, "was at the meeting")
}
Sometimes you need to distinguish a missing entry from a zero value.
Is there an entry for "UTC" or is that 0 because it's not in the map
at all? You can discriminate with a form of multiple assignment.
var seconds int
var ok bool
seconds, ok = timeZone[tz]
For obvious reasons this is called the “comma ok” idiom. In this
example, if tz is present, seconds will be set appropriately and ok
will be true; if not, seconds will be set to zero and ok will be
false. Here's a function that puts it together with a nice error
report:
func offset(tz string) int {
if seconds, ok := timeZone[tz]; ok {
return seconds
}
log.Println("unknown time zone:", tz)
return 0
}
To test for presence in the map without worrying about the actual
value, you can use the blank identifier (_) in place of the usual
variable for the value.
_, present := timeZone[tz]
Searched on the go-nuts email list and found a solution posted by Peter Froehlich on 11/15/2009.
package main
import "fmt"
func main() {
dict := map[string]int {"foo" : 1, "bar" : 2}
value, ok := dict["baz"]
if ok {
fmt.Println("value: ", value)
} else {
fmt.Println("key not found")
}
}
Or, more compactly,
if value, ok := dict["baz"]; ok {
fmt.Println("value: ", value)
} else {
fmt.Println("key not found")
}
Note, using this form of the if statement, the value and ok variables are only visible inside the if conditions.
Short Answer
_, exists := timeZone[tz] // Just checks for key existence
val, exists := timeZone[tz] // Checks for key existence and retrieves the value
Example
Here's an example at the Go Playground.
Longer Answer
Per the Maps section of Effective Go:
An attempt to fetch a map value with a key that is not present in the map will return the zero value for the type of the entries in the map. For instance, if the map contains integers, looking up a non-existent key will return 0.
Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for "UTC" or is that the empty string because it's not in the map at all? You can discriminate with a form of multiple assignment.
var seconds int
var ok bool
seconds, ok = timeZone[tz]
For obvious reasons this is called the “comma ok” idiom. In this example, if tz is present, seconds will be set appropriately and ok will be true; if not, seconds will be set to zero and ok will be false. Here's a function that puts it together with a nice error report:
func offset(tz string) int {
if seconds, ok := timeZone[tz]; ok {
return seconds
}
log.Println("unknown time zone:", tz)
return 0
}
To test for presence in the map without worrying about the actual value, you can use the blank identifier (_) in place of the usual variable for the value.
_, present := timeZone[tz]
Have a look at this snippet of code
nameMap := make(map[string]int)
nameMap["river"] = 33
v ,exist := nameMap["river"]
if exist {
fmt.Println("exist ",v)
}
As noted by other answers, the general solution is to use an index expression in an assignment of the special form:
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
var v, ok T = a[x]
This is nice and clean. It has some restrictions though: it must be an assignment of special form. Right-hand side expression must be the map index expression only, and the left-hand expression list must contain exactly 2 operands, first to which the value type is assignable, and a second to which a bool value is assignable. The first value of the result of this special form will be the value associated with the key, and the second value will tell if there is actually an entry in the map with the given key (if the key exists in the map). The left-hand side expression list may also contain the blank identifier if one of the results is not needed.
It's important to know that if the indexed map value is nil or does not contain the key, the index expression evaluates to the zero value of the value type of the map. So for example:
m := map[int]string{}
s := m[1] // s will be the empty string ""
var m2 map[int]float64 // m2 is nil!
f := m2[2] // f will be 0.0
fmt.Printf("%q %f", s, f) // Prints: "" 0.000000
Try it on the Go Playground.
So if we know that we don't use the zero value in our map, we can take advantage of this.
For example if the value type is string, and we know we never store entries in the map where the value is the empty string (zero value for the string type), we can also test if the key is in the map by comparing the non-special form of the (result of the) index expression to the zero value:
m := map[int]string{
0: "zero",
1: "one",
}
fmt.Printf("Key 0 exists: %t\nKey 1 exists: %t\nKey 2 exists: %t",
m[0] != "", m[1] != "", m[2] != "")
Output (try it on the Go Playground):
Key 0 exists: true
Key 1 exists: true
Key 2 exists: false
In practice there are many cases where we don't store the zero-value value in the map, so this can be used quite often. For example interfaces and function types have a zero value nil, which we often don't store in maps. So testing if a key is in the map can be achieved by comparing it to nil.
Using this "technique" has another advantage too: you can check existence of multiple keys in a compact way (you can't do that with the special "comma ok" form). More about this: Check if key exists in multiple maps in one condition
Getting the zero value of the value type when indexing with a non-existing key also allows us to use maps with bool values conveniently as sets. For example:
set := map[string]bool{
"one": true,
"two": true,
}
fmt.Println("Contains 'one':", set["one"])
if set["two"] {
fmt.Println("'two' is in the set")
}
if !set["three"] {
fmt.Println("'three' is not in the set")
}
It outputs (try it on the Go Playground):
Contains 'one': true
'two' is in the set
'three' is not in the set
See related: How can I create an array that contains unique strings?
var d map[string]string
value, ok := d["key"]
if ok {
fmt.Println("Key Present ", value)
} else {
fmt.Println(" Key Not Present ")
}
var empty struct{}
var ok bool
var m map[string]struct{}
m = make(map[string]struct{})
m["somestring"] = empty
_, ok = m["somestring"]
fmt.Println("somestring exists?", ok)
_, ok = m["not"]
fmt.Println("not exists?", ok)
Then, go run maps.go
somestring exists? true
not exists? false
It is mentioned under "Index expressions".
An index expression on a map a of type map[K]V used in an assignment
or initialization of the special form
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
yields an additional untyped boolean value. The value of ok is true if
the key x is present in the map, and false otherwise.
A two value assignment can be used for this purpose. Please check my sample program below
package main
import (
"fmt"
)
func main() {
//creating a map with 3 key-value pairs
sampleMap := map[string]int{"key1": 100, "key2": 500, "key3": 999}
//A two value assignment can be used to check existence of a key.
value, isKeyPresent := sampleMap["key2"]
//isKeyPresent will be true if key present in sampleMap
if isKeyPresent {
//key exist
fmt.Println("key present, value = ", value)
} else {
//key does not exist
fmt.Println("key does not exist")
}
}
Example usage: Looping through a slice, for pairMap checking if key exists.
It an algorithm to find all pairs that adds to a specific sum.
func findPairs(slice1 []int, sum int) {
pairMap := make(map[int]int)
for i, v := range slice1 {
if valuei, ok := pairMap[v]; ok {
fmt.Println("Pair Found", i, valuei)
} else {
pairMap[sum-v] = i
}
}
}
Related
Say I have two structs that define a linked list:
....
....
type node struct {
item interface{}
next *node
}
type LinkedList struct {
first *node
N int
}
...
...
and I want to compare the value of the type of the underlying node, say, in a find function where we check if k == node.item such that:
func (l *LinkedList) find (key interface{}) bool {
result := false
if !l.isEmpty() {
for x:= l.first; x != nil; x = x.next {
if x.item == key {
result = true
break
}
}
return result
}
this will not work for the expected find function because the underlying types are different, hence the func will always return false. We can confirm this upon reflecting the type:
fmt.Println(reflect.TypeOf(key), reflect.TypeOf(x.item))
>>> string, *main.node
Tried workarounds?
I've tried asserting the type but alas this does not work and panics
tmp := x.item.(string)
>>>panic: interface conversion: interface {} is *main.node, not string
This case is the same for using fmt.Sprintf(x.item)
I'm a bit stumped as to where to go from here. Is there a way to do this?
Inserting item to linked list
The following snippet should clarify how insertion is handled
func (l *LinkedList) insertFirst(item interface{}) {
var first *node = new(node)
oldfirst := l.first
first.item = item
first.next = oldfirst
l.first = first
l.N++
}
.....
//which gets called somewhere like
var n *node = new(node)
n.item = item
l.insertFirst(n)
.....wait no theres the error!
----------
burak-serdar you are 100% correct that I am inserting the node in the node!
The interface comparison in find() is a valid comparison and it will work if the type of the key and the type of the value stored in the node are the same. However, evidence points to you adding a node in place of a value.
We have a map[string]string, I assume that means the zero value of a string retrieved from the map is ""
So doesn't that mean that this:
var userId, ok = params["user_id"];
if !ok || userId == "" {
return 422, "Missing user_id in request"
}
is the same logic as this:
var userId = params["user_id"];
if userId == "" {
return 422, "Missing user_id in request"
}
just making sure my understanding is correct.
It's not the same if you intend to store the zero value of the value type.
See this example:
m := map[string]string{
"empty": "",
}
if v, ok := m["empty"]; ok {
fmt.Printf("'empty' is present: %q\n", v)
} else {
fmt.Println("'empty' is not present")
}
if v, ok := m["missing"]; ok {
fmt.Printf("'missing' is present: %q\n", v)
} else {
fmt.Printf("'missing' is not present")
}
It outputs (try it on the Go Playground):
'empty' is present: ""
'missing' is not present
It's true that if you never store the zero value in the map, you may simply use if m[value] == zeroValue {}. This is detailed here: How to check if a map contains a key in Go?
This "property" of maps can be exploited to create sets elegantly. See How can I create an array that contains unique strings?
And using this "technique" has another advantage too: you can check existence of multiple keys in a compact way (you can't do that with the special "comma ok" form). More about this: Check if key exists in multiple maps in one condition
This code is functioning but I don't understand how.
In the code below hostProxy[host] may or may not contain a function. I don't understand how the variable "ok" is defined or how it gets its value. It is not defined before this line.
if fn, ok := hostProxy[host]; ok {
fn.ServeHTTP(w, r)
return
}
if target, ok := hostTarget[host]; ok {
....
}
This is covered in Spec: Index expressions:
An index expression on a map a of type map[K]V used in an assignment or initialization of the special form
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
var v, ok T = a[x]
yields an additional untyped boolean value. The value of ok is true if the key x is present in the map, and false otherwise.
So for example this code:
if fn, ok := hostProxy[host]; ok {
fn.ServeHTTP(w, r)
return
}
Means to get the value associated with host key from the hostProxy map, create and store the value in the fn variable, and the result (whether the key was found in the map) in the ok variable. And this ok variable (which will be of type bool) is used as the condition of the if statement. So if the host key is in the hostProxy map, it goes ahead and uses it.
Yes ok isn't defined before, but in your example you have := which will define variables for you under the hood and will assign values obtained from map lookup.
I have written the following type and Get function for the Web Crawler exercise of the go tour.
type UrlCache struct {
urls map[string]string
mux sync.Mutex
}
func (c *UrlCache) Get(key string) (value string, ok bool) {
c.mux.Lock()
defer c.mux.Unlock()
value, ok = c.urls[key]
return
}
Everything works but I wonder if there is a way to improve the Get function, I have tried the following:
func (c *UrlCache) Get(key string) (string, bool) {
c.mux.Lock()
defer c.mux.Unlock()
return c.urls[key]
}
But that throws
prog.go:24:2: not enough arguments to return
have (string)
want (string, bool)
Is there a way to pass both return values of the get on the map as return?
It is not possible in a single return statement.
And the reason for this is in the Spec: Index expressions:
An index expression on a map a of type map[K]V used in an assignment or initialization of the special form
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
var v, ok T = a[x]
yields an additional untyped boolean value. The value of ok is true if the key x is present in the map, and false otherwise.
The stress is that the special comma-ok form may only be used in an assignment or initialization. You try to use it in a return statement, so the index expression yields only a single result, hence the compile-time error you get.
And since assignments in Go are not expressions but statements, you can't even do something like:
return (value, ok = c.urls[key]) // COMPILE-TIME ERROR!
My apologies, misread things:
When you return the map value, you are accessing the map in a 'single-value' context. You can access the map with one value returned, or with two. Unless you signal go that you want two values, you will only get one. So you can't really (if you do want to return two things) avoid getting the map value with the value, ok := signature. The first version of the function names the return parameters, allowing for a bare return statement at the end to 'just work', the second version would require
value, ok := c.urls[key]
return value, ok
I have a variable of type value that stores a map, but I can not access the values by providing the keys:
rascal>a
value: ("s":"s")
rascal>a["s"]
|stdin:///|(2,3,<1,2>,<1,5>): subscript not supported on value at |stdin:///|(2,3,<1,2>,<1,5>)
☞ Advice
How can I parse the value to map in order to be able to retrieve my value ?
if (map[str,str] myMap := a) {
// do stuff with myMap
}
else {
throw "<a> is not a map?";
}
Another way of "narrowing types" is using pattern matching in function parameters:
rascal>value x = 1;
int: 1
rascal>int myFunc(int i) = 2 * i;
ok
rascal>myFunc(x);
int: 2
And yet another way is using visit or switch:
visit(bigValue) {
case Expression e => ...work with e...
}
The general idea is:
pattern matching means narrowing (downcasting)
pattern matching may fail and so is always in a conditional context
there are many places in Rascal where you can use pattern matching: function dispatch, switch, visit, :=, <-