Go Data Structures

As we have seen how control flow and functions work in Chapter – 4 of our Golang tutorial, let’s have a look at major Golang Data Structures.

Array –

Well, almost every programmer knows what is array. But let’s revisit the concept one more time –
Basically, array is number of elements of same type stored in sequential order. We use arrays to store fixed no. of more elements which is really a good data structure.
We need to follow below syntax to create an array :
var var_name [size]var_type
Example –
var arr [10]int //It will create an array with name”arr” of 10 integers
  • Once you declare an array with its size you are not allowed to change it.
  • If you try to insert more elements than array size, compiler will give you an error.
  • By default array size is 0 (zero)
  • Array index starts from 0th index
  • We can set value directly to array at particular index array_name[index]=value
  • The inbuilt len returns length of an array
Go provides a convenient way of storing elements.
func main(){
 var x [5]int
 x[3] = 1000
 fmt.Println(x)
b:=[5]int {54,76,34,2,3,} /*we have initialized this array and notice extra trailing , 
after last element of array which is required in Go. we no need to specify type because Go can figure it out */

}
main.go
package main
import (
 "fmt"
)
func main() {
 var name [50]string //array of 50 string elements
 fmt.Println(name)
 name[43] = "Welcome to Go Programming"
 fmt.Println(name)
 var x [100]string
 fmt.Println("Length of arr x:", len(x)) //calculate length of array
 z := [5]int{43, 78, 54, 23, 22}
 fmt.Println(z) 
 for i := 65; i <= 122; i++ {
 x[i-65] = string(i)

 }
 fmt.Println(x)
}

Data Structures : Slice

Slice is same like an array but it has variable length so we don’t need to specify the length to it. It will grow whenever it exceeds its size. Like an array, slice also has index and length but its length can be changed.
  • Slice also has continuous segments of memory locations
  • The default value of uninitialized slice is nil
  • Slices does not store the data. It just provides reference to an array
  • As we change the elements of slice, it will modify corresponding elements of that array
Syntax for slice creation is:
var var_name [ ]var_type
The only difference between slice and array is that there is no need to specify length while creating slice.
var x[ ] float64
Here, slice of type float64 elements will be created with length 0 & capacity 0
func main() {
nums:=[ ]int {5,7,4,8,9} //No need to specify length
fmt.Println(nums)
}
We can also create slice by using make() also which is available in builtin package of golang
func make(Type,size IntegerType)Type
 x:=make([ ]string,5) → Here the capacity of slice equal to length of it
This will create slice of underlying string array having size for 5 elements
Length→ No. of elements present
Capacity→ Total Size
The length and capacity of slice can be obtained by using len(slice), cap(slice)
Below is another example –
array:=[ ]float64 {1,2,3,4,5} //slice of float64 elements
 x:=array[0:5] 
 fmt.Println(x)
Normally we switch ON value of a variable but Golang allows you to switch ON type also.
var a [10]int → equivalent as a[0:10], a[:10], a[0:], a[:]
You can do slicing on string because string is slice of bytes.
Remember → String is made up of runes (Uni Code). A rune is Unicode UTF Code point (for ‘A’-65’, ‘a-97’). A Unicode code point is 1 to 4 bytes.
So, strings are made up of runes, runes are made up of bytes,so strings are made up of bytes. A string is bunch of bytes,slice of bytes.
You can access slice elements by using range in for loop.
There are built in functions to calculate length by using len(slice), capacity by using cap(slice), append one slice to another by using append(slice) and copy(slice) to copy one slice to other.
We can create slice in three ways:
  • Using var
  • Shorthand Notation
  • make( )
func main() {
 elements := []int{3, 5, 2, 6, 2}
 arrays := make([ ]int, 0, 3) //Creating slice with make( )
 for n := range elements {
 fmt.Println("Elements", n)
 }
 for i := 0; i < 80; i++ {
 arrays = append(arrays, i) // to append i elements to slice
fmt.Println("Len:", len(arrays), "Capacity:", cap(arrays),
 "Value: ", arrays[i])
 }}
Here in above program you can see how length and capacity increases when it exceeds size limits. The capacity will be doubled when size exceeds the capacity.
If we think that our slice might grow ,we can set capacity which is larger than length. This gives slice room to grow without creating new array every time our slice grows.
We also can create slice inside another slices like –
sliceOne:=make([ ][ ]int,0) //slice of slice of int
main.go
package main
import (
 "fmt"
)
func main() {
 primes := []int{1, 3, 5, 7, 9, 11}
 fmt.Printf("%T\n", primes) //type []int
 fmt.Println(primes) //[1 3 5 7 9 11]
 fmt.Println(primes[2:4]) // [5 7] 3rd 4th element

 var info []byte //Slice using var
 elements := []int{3, 5, 2, 6, 2} //Slice using shorthand notation
 arrays := make([]int, 0, 3) //Creating slice with make( )
 //String slice
 str := []string{"Amazing", "Brilliant", "Good One", "Excellent"}
 for n := range elements {
 fmt.Println("Elements", n)
 }
 for i := 0; i < 80; i++ {
arrays = append(arrays, i) //append i elements 
to arrays
 fmt.Println("Len:", len(arrays), "Capacity:", 
cap(arrays), "Value: ", arrays[i])
 }

 fmt.Println("Info elements:", info)
 for i, vals := range str {
 fmt.Println("Values", i, vals)
 }
 //Slice of Slice
 OutIn := make([][]int, 0, 3)
 for i := 0; i < 3; i++ {
 out := make([]int, 0, 4)
 for j := 0; j < 4; j++ {
 out = append(out, j)
 }
 OutIn = append(OutIn, out)
 }
 fmt.Println(OutIn)
}

Data Structures : Map

One of the most important and useful data structure in computer science is Hash Table. Golang provides map data structure which implements hashtable. An unique key value pair like dictionary is used to lookup values based on the key.
map [keyType] valueType
Map is represented by keyword map having key type in [ ]and value types.
The default value of map is nil as it is reference type like pointer or slice and for nil key and value pairs are 0.
This includes unordered pairs. Maps are like literals but unique keys are required.
var dictionary map[string]int //map using var
 dictionary[“Zero”]=1
 fmt.Println(dictionary[“Zero”]) //Accessing value using key
Here map name is dictionary having key value pairs of string & int type.
The values are accessed using key values. The maps can be created by using make( ) also which is more effective to use.
dictionary:=make(map[string]int) //map using make()
The make() creates a hash data structure and return map value pointed by key. Go provides familiar syntax to work with map data structure.
The functions provided are len(map) and delete(mapName,key)
func main(){
 var details = map[int]string{101: "Jordan", 102: "Roger", 103: "Rafel"} //using var 
fmt.Println(details)
 fmt.Println(details[102])
 countries := make(map[string]string) //Shorthand and make
 countries["US"] = "United States"
 countries["IND"] = "India"
fmt.Println(countries)
}
Comma OK idiom : If we are looking for value related to particular key then map returns two values: one value if found and value that indicates success or failure(not present). We call this comma OK idiom. 
func main(){
countries := make(map[string]string) 
 countries["US"] = "United States"
 countries["IND"] = "India"
if val,ok:=countries[“IND”] ; ok{ //comma ok idiom
 fmt.Println(“Value”,val,”is present”)
 fmt.Println(ok) //it will return true as ok is bool type
}
Range : Range is used to iterate over the slice and map along with for loop. When range is used with slice it returns two values- 1st one is index and 2nd one is copy of the element at that index.
func main(){
countries := make(map[string]string) 
 countries["US"] = "United States"
 countries["IND"] = "India"
 for country, capital := range countries {
 fmt.Println(country, "-->", capital)
 }
}
main.go
package main
import (
 "fmt"
)
func main() {
 var details = map[int]string{101: "Jordan", 102: "Roger", 103: "Rafel"}
 fmt.Println(details)
 fmt.Println(details[102]) //Accessing value based on keys
 countries := make(map[string]string) //Shorthand and make
 countries["US"] = "United States"
 countries["IND"] = "India"
 countries["UK"] = "United Kingdom"
 fmt.Println(countries)
 capitals := map[string]string{ //Shorthand composite literal
 "India": "Delhi",
 "US": "Washington"}
 capitals["UK"] = "Great Britian" //Adding an entry
 capitals["NZ"] = "Wellington"
 capitals["UK"] = "London" //Updating entry
 fmt.Println(capitals, " of length", len(capitals)) //len() for Length
 delete(capitals, "NZ") //deleting entry
 fmt.Println(capitals)
 //Comma OK
 if value, present := capitals["US"]; present {
 fmt.Println("Value", value, "is present")
 fmt.Println("Status:", present)
 }
 for country, capital := range countries {
 fmt.Println(country, "-->", capital)
 }
 //map inside map
 CountryInfo := map[string]map[string]int{
 "INDIA": {"IN": 91},
 "United States of America": {"US": 1}, //always give the,after last 
element
 }
 for key, val := range CountryInfo {
 fmt.Println(key, "--->", val)
 }
}

Data Structure – Struct

  • Encapsulation → state [“fields”] behaviour [“methods”] export / unexported
  • Reusability → Inheritance [“Embedded Types”]
  • Polymorphism → Interfaces
  • Overriding → Promotion
Golang doesn’t have keyword ‘Class’ but it has ‘struct’ which is contains named collection of fields/properties. Struct is also there in ‘C’ language.

Struct→

  • user defined type
  • we declare the type
  • the type has fields
  • the type can also have “tags”
  • the type has an underlying type
  • in this case, underlying type is struct
  • we declare variables of the type
  • we initialize those variables
  • initialize with specific value or with default values.
In some object oriented languages methods are composed within struct or class
Class Animal{
 public void eat() { } //Method defined in the class itself
 }
But in Golang, they are ‘associated’ with struct.
type Animal struct{ }
func (e Animal ) eat () { } //Methods defined outside but works on same struct
A struct is defined by using type and struct keyword along with meaningful name
type House struct { }
type Car struct { }
type #Mobile struct { } //invalid
Here type defines new type followed by name of struct and then struct keyword
Example –
type Mobile struct {
 brand string
 model string
 price int 
 }
We can create instance of our struct Mobile in many ways like
var mob Mobile
This will create local variable mob and sets default value to its fields (0 for int, “ ” for string, 0.0 for float)
mob:=new(Mobile)
It will allocate memory to all fields by setting default values and returning pointer to it (*Mobile). More often we need to give values to each of the fields. We can do this in two ways:
mob:=Mobile{brand:“Samsung”,model:”Galaxy”,price:24500}
OR
mob:=Mobile {“Samsung,Galaxy,24500”}

Fields →

We need to define meaningful name & type for fields or properties. We can initialize fields with its value or can keep blank so it will use default value.
We have to access the struct fields with “. (dot)” only.

Accessibility and Visibility of struct and variables

There is no concept like public, private, protected for the visibility purpose in Golang. As Golang is simple, if the first letter of the struct name or variable name is capital then it is visible outside that package.
type Mobile struct{ //this struct can be exported
 brand string //not exported
 Model string //exported
 }

Methods

Go supports methods to define struct.
func (receiverName receiverType) functionName(arguments) returnType
In between the keyword func and functionName, we’ve added the receiver. “Receiver” is like parameter-it has its name and type, and by creating this we have to call the function using “.” operator.
You cannot declare a method with a receiver whose type is defined in another package.

Sample Code

func (mob *Mobile) show () string { //func associated with Mobile
 mob.brand = "Xiomi"
 return mob.brand
}
 ……….
 var mob Mobile
 fmt.Println(mob.show())
Methods having pointer receivers can modify the value to which the receiver points.
We can define method on any type defined in struct as well as from same package but you cannot define method on type from another package.
main.go
package main
import (
 "fmt"
)
type width int //user defined type
type Mobile struct {
 brand string
 model string
 price int
}
func (mob Mobile) display() string { //func associated with Mobile
 mob.brand = "Xiomi"
 return mob.brand
}
func (mob *Mobile) show() string { //func associated with Mobile
 mob.brand = "Xiomi"
 return mob.brand
}
func main() {
 var height width
 fmt.Println(height)
 m := Mobile{}
 fmt.Println(m) //Default values inside struct{" " 0}
 var mob Mobile //Instance creation using var
 fmt.Println(mob)
 mobs := new(Mobile)
 fmt.Println(mobs)
 phone := Mobile{"Samsung", "Galaxy", 24500} //Struct initialization
 fmt.Println("Before Change:", phone)
 fmt.Println("Function Call", phone.display()) // Xiomi
 fmt.Println("After Change:", phone) //still old values are coming 
 {"Samsung","Galaxy",24500}
 fmt.Println("Function Call:", phone.show()) //calling show()
 fmt.Println("After Change:", phone) //Here changed values will 
 reflect
}

Type Aliasing –

We can create aliases for the type that you want to use.
main.go
 package main
 import (“fmt” “strings”)
 type s string
 func (myStr s) Upper() string{
 return strings.ToUpper(string(myStr))
 }
 func main(){
 fmt.Println(s(“hello”).Upper())
}
We can also attach methods to the types. See the “time” package having type Duration – https://golang.org/pkg/time/#Duration

Embedded Types –

A struct’s properties/fields usually represent has-a relationship like inheritance. Suppose we have a struct person.
type Person struct {
 Name string
 }
 func (per *Person) speak () {
 fmt.Println(“Hello, I’m”,per.Name)
}
And now if we want to create a new struct. We can do like this –
type Mobile struct{
 Person //Embedded type
 Model string
 }
So, Go supports relationship like this by using embedded type, also known as anonymous fields.
Here, we are using type Person by embedding into Mobile. Now to access the struct Person fields we can use type name.
mob:=new(Mobile)
 mob.Person.speak( )

Promotion –

Embedding is the composition not inheritance, but Go supports something called “promotion” where the fields and methods of the embedded type becomes available to the embedding type.
This is something like inheritance but actually not inheritance.
main.go
package main
import (
 "Fmt"
)
type Person struct {
 FirstName string
 LastName string
 Age int
}
func (per *Person) show() {
 fmt.Println(per.FirstName, "", per.LastName, "", per.Age)
}
func (cust *Customer) show() { //method overriding based on receiver
 fmt.Println(cust.ContactNo)
}
type Customer struct {
 Person //embedded type
 ContactNo int
}

func main() {
 cust := Customer{
 Person: Person{
 "John",
 "Matthew",
 25,
 },
 ContactNo: 9965,
 }
 cust.Person.show()
 cust.show()
}
Go automatically handles conversion between values and pointers for method calls. You may want to use a pointer receiver type to avoid copying method calls.

Struct Pointer

We can use struct pointer to access struct fields –
mob:=&Mobile {“Lenovo”,”A6”,”6700”}
 fmt.Println(mob.brand,mob.model,mob.price)
Well, we have covered some of the major Data Strucutres for Golang in this chapter. In the Part II of this chapter, we will dive deeper and explore some more Golang Data Structures. Make sure you check it out!
Related Posts

Leave a Comment

Golanggolang-control-and-flow