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

  

posted @ 2020-01-05 12:27  DrVonGoosewing  阅读(217)  评论(0编辑  收藏  举报