Maps and Structs in Go
Introduction
Today we will talk about Maps and Structs, the other two collection types. Maps, some language may call it Dictionary, a good name. But not matter what it is called, it works like a slice but with an additional dimention: keys. In Slices, index is all automatically generated numbers form 0 to it's (length-1). Maps, however, can have a more vivid index, normally we call it a "key".
Sturct, we will considier it as a collection type. Later in this article we will see the reason. By the way, in Python, an "list" can collects not only one type of infomation. For example, in Python we will have: my_list = ["a", 98, 23.1, "Jon Doe", False]. This may be a surprise to Go or other strong type language users. Because we all know for quicker memory access, Arrays or Slices can only contains one data type. In Struct we will see why Python has this magic.
In this article we will talk about:
Maps/ Structs
- Why do we need them?
- How to creat a map?
- How to manipulate a map?
Maps
1. Why do we need them?
In Arrays and Slices, index is nothing more than a series of automatically generated number from 0 to (length-1). In some situations, we want a more flexible index, not just numbers.
For example, if we want to collect population of some states. When we only have knowledge of Arrays/ Slices, we can make an array of populations. But we have to do extra note to remember which number belongs to which states.
Otherwise some days later nobody will know what is this array of numbers.
This can be a pain when the data is large, or when our data is changing. That is, we have to manually insure two books are coincident.
Maps can do this for us. It has a more vivid index call "key". Most of types in Go can be key of map. Except Slices/ Maps/ Functions.
Let's have a quick look of what a Map is:
package main import ( "fmt" ) func main() { statePops := map[string]int{ "California": 39250017, "Texas": 27862596, "Florida": 20612439, } fmt.Println(statePops) } // output map[California:39250017 Florida:20612439 Texas:27862596]
2. How to create a map?
a) Literal syntax
We can create a map by literal syntax(like above example). We just write out everything we need.
If we want to create an empty map, we can literal write: statePops := map[string]int{}
b) By make() function
We can create a Slice by make() function. We can also create a Map by make() function.
Especially when a map's key and value is later generated in a for-loop, and we don't want to/cannot initialize at it's creation time.
Not like in creating a Slice we have three arguments in make() function: type, length and capacity.
We only use one argument in creating a map. And the addinonal argument seems not working here.
package main import ( "fmt" ) func main() { statePops := make(map[string]int, 2) // integer 2 is given but no meaning fmt.Println(statePops, len(statePops)) statePops["California"] = 39250017 fmt.Println(statePops, len(statePops)) statePops["Texas"] = 27862596 fmt.Println(statePops, len(statePops)) statePops["Florida"] = 20612439 fmt.Println(statePops, len(statePops)) } // output map[] 0 map[California:39250017] 1 map[California:39250017 Texas:27862596] 2 map[California:39250017 Florida:20612439 Texas:27862596] 3
3. How to manipulate a map?
a) Pull out a value: by brackets and it's key
For example, in first map example, we can use: statePops["California"]
b) Add an element to a map: by brackets and it's key
in first map example, we can use: statePops["Ohio"] = 11614373
c) Element's order: not guarantee
Even if in difiniton we write one elemnt before another, their order is not guaranteed.
d) Delete an element: use build-in function delete()
This function has no return value. We will use it standalone.
package main import ( "fmt" ) func main() { statePops := map[string]int{ "California": 39250017, "Texas": 27862596, "Florida": 20612439, } fmt.Println(statePops) delete(statePops, "Texas") fmt.Println(statePops) } // output map[California:39250017 Florida:20612439 Texas:27862596] map[California:39250017 Florida:20612439]
e) If a key doesn't exist: will return zero
Map will return two values when we call it's key: value of the key you appointed and boolean of the key's existence.
package main import ( "fmt" ) func main() { statePops := map[string]int{ "California": 39250017, "Texas": 27862596, "Florida": 20612439, } fmt.Println(statePops["Ohio"]) val, ok := statePops["Ohio"] fmt.Println(val, ok) val, ok = statePops["Texas"] fmt.Println(val, ok) } // output 0 0 false 27862596 true
f) Equal operation between two maps: share same memory address
After = opertation, if we manipulate one map, the result will also reflect on other one.
package main import ( "fmt" ) func main() { statePops := map[string]int{ "California": 39250017, "Texas": 27862596, "Florida": 20612439, } sp := statePops delete(sp, "Texas") fmt.Println(sp) fmt.Println(statePops) } // output map[California:39250017 Florida:20612439] map[California:39250017 Florida:20612439]
Structs
Before everything, let's have a look, if we can create a "List" like we are in Python, collecting different type of infomation.
(In Python) my_list = ["a", 98, 23.1, "Jon Doe", False].
package main import ( "fmt" ) type List struct { myString1 string myInteger int myfloat32 float32 myString2 string myBoolean bool } func main() { aList := List{"a", 98, 23.1, "Jon Doe", false} fmt.Println(aList) } // output {a 98 23.1 Jon Doe false}
This output proves actually we can. And with a huge update compare to Array/Slice/Map, A Struct can collects different types of data! I believe nobody will doubt Structs is collection types anymore.
1. Why do we need them?
We consider structs as collection types. And it's more powerful than the other collection types we have talked before.
Because it can collects all different type of data together, which is Arrays/ Slices/ Maps cannot do.
2. How to create a struct?
a) Literal Syntax
We can create a struct by literal syntax. We write every thing out as we need.
When we initialize a struct, we don't have to write all field's name(like above). But for our code's robustness, for example later disign change, we shall write out all field's name clearly.
package main import ( "fmt" ) type Doctor struct { number int actorName string companions []string } func main() { aDoctor := Doctor{ number: 3, actorName: "Jon Pertwee", companions: []string{"Liz Shaw", "Jo Grant", "Sarah Jane Smith"}, } fmt.Println(aDoctor) } // output {3 Jon Pertwee [Liz Shaw Jo Grant Sarah Jane Smith]}
b) instant/ short-life/ anonymous struct
For a instant struct, we can have this short-hand creation.
Of course because of it is anonymous, we cannot refer it later at other place. So this struct cannot be used in any other place else.
package main import ( "fmt" ) func main() { aDoctor := struct{actorName string}{actorName: "Jon Pertwee"} fmt.Println(aDoctor) bDoctor := struct{actorName string; number int}{"Jon Pertwee", 3} //semi-colon fmt.Println(bDoctor) } //output {Jon Pertwee} {Jon Pertwee 3}
We don't know Python's underhood magic, but seemingly Python's create a List is more like Go's create a instant struct. Which can contains different types of infomation naturally.
3. How to manipulate a struct?
a) Pull out a value: by "." operator
If we want specific a field from the struct, we can use "." operator.
func main() { aDoctor := Doctor{ number: 3, actorName: "Jon Pertwee", companions: []string{"Liz Shaw", "Jo Grant", "Sarah Jane Smith"}, } fmt.Println(aDoctor.companions) } // output [Liz Shaw Jo Grant Sarah Jane Smith]
b) Equal operation between two struct: will not share same memory address
If two struct equal each other, manipulation on one sturct will not reflect on the other one. "=" operation will do a copy.
When our struct collection is very large, we may need to think twice before we pass it.
package main import ( "fmt" ) func main() { aDoctor := struct{actorName string}{actorName: "Jon Pertwee"} bDoctor := aDoctor bDoctor.actorName = "Tom Baker" fmt.Println(aDoctor, bDoctor) } // output {Jon Pertwee} {Tom Baker}
c) Inherit: No and Yes
In OOP language like Python or Java, there is a relationship call inherit. Or more simply, "is_a" relationship.
For example, in Python we can have an object called Animal, and have another object called Bird.
We can use this "is_a" relationship to combine these two object, say Bird "is_a" Animal. So Bird can use fields and methods from Animal, without any extra effort.
But in Go, we know it has no object/class system. How do we do?
The answer is, we simply write sturct's name Animal in struct Bird, then Bird will have fields and methods from Animal. (Someone will call it "embedding")
package main import ( "fmt" ) type Animal struct { Name string Origin string } func (this Animal) SelfIntroduce() { fmt.Println("I am", this.Name) } type Bird struct { Animal SpeedKPH float32 CanFly bool } func main() { b := Bird{} b.SpeedKPH = 48 b.Name = "Emu" // using field from Animal fmt.Println(b.Name, b.SpeedKPH) b.SelfIntroduce() // using method from Animal } // output Emu 48 I am Emu
d) tags: contains even more extra infomation
We can have tags behind struct's field. This tag infomation is nothing more than a string. It can be access with "reflect" library.
But how to use this infomation will be decided by each user.
For example, we can add a tag to describe the field's max length. Later processing we can use library "reflect" to read out this restrict infomation, and check if input is leagel.
Normally, some libraries will require we add tags for later processing. For example, "encoding/xml" library will require tags that describe how to unmarshal a xml file.
package main import ( "fmt" "reflect" ) type Animal struct { Name string `required max:100` Origin string } func main() { t := reflect.TypeOf(Animal{}) field, _ := t.FieldByName("Name") fmt.Println(field.Tag) } // output prog.go:9: struct field tag `required max:100` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair Go vet exited. required max:100