Byte Ebi's Logo

Byte Ebi 🍀

A Bit everyday A Byte every week

[A Tour of Go Study Notes] 06 Range, Map, and Function

Understanding Go Range, Map, and Function using official tutorials

Ray

Entering the world of Golang, we first acquaint ourselves with basic Golang usage using the official tutorial: A Tour of Go .
This article introduces Range, Map, and Function.

Range

The range form of a for loop can iterate over a slice or map, similar to the foreach usage in other languages.
When iterating over a slice with a for loop, each iteration returns two values.
The first value is the element’s index, and the second value is a copy of the element’s content.

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}
/*
>>> 2**0 = 1
>>> 2**1 = 2
>>> 2**2 = 4
>>> 2**3 = 8
>>> 2**4 = 16
>>> 2**5 = 32
>>> 2**6 = 64
>>> 2**7 = 128
*/

If a value isn’t needed (usually the index), it can be ignored using _.
Golang is sensitive to defining variables that aren’t used, which causes an error: key declared but not used.

for i, _ := range pow
for _, value := range pow

If only the index is needed, the second variable can be ignored directly.

for i := range pow

Here’s an example that only retrieves the index:

package main

import "fmt"

func main() {
	pow := make([]int, 10)
	for i := range pow {
		pow[i] = 1 << uint(i) // Sets the value to left shift the index, equivalent to 2 to the power of i
	}
	for _, value := range pow {
		fmt.Printf("%d\n", value)
	}
}
/*
>>> 1
>>> 2
>>> 4
>>> 8
>>> 16
>>> 32
>>> 64
>>> 128
>>> 256
>>> 512
*/

Map

In previous examples, whether an array or slice, numeric values were used as indexes, while maps can use strings as indexes. They are used in a Key-Value combination.

When a map is uninitialized or lacks assignments, the initial value of the map (zero value) is nil.
A nil map lacks keys and cannot have keys added.
Using the make function initializes a map of a specified type and returns it.

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex

func main() {
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m["Bell Labs"])
}
/*
>>> {40.68433 -74.39967}
*/

Maps are similar to structs, but keys are mandatory.

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

func main() {
	fmt.Println(m)
}
/*
>>> map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
*/

If the top-level type is just a type name, it can be omitted.

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

func main() {
	fmt.Println(m)
}
/*
>>> map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
*/

Modifying Values in Maps

Inserting or modifying elements in a map:

m[key] = elem

Retrieving an element:

elem = m[key]

Deleting an element:

delete(m, key)

Checking if a certain key exists using two-value assignment:

elem, ok = m[key]

If the key exists in m, ok = true; otherwise, ok = false.
When reading a non-existent key, you get the default value of the map’s element type.
In cases where elem or ok aren’t defined during usage, : can be used for short variable declaration.

elem, ok := m[key]

In practice:

package main

import "fmt"

func main() {
	m := make(map[string]int)

	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"])

	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"])

	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok)
}
/*
>>> The value: 42
>>> The value: 48
>>> The value: 0
>>> The value: 0 Present? false
*/

Function

Function values

Functions are also values and can be passed around. They can be used as parameters or return values for functions.

package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}
/*
>>> 13
>>> 5
>>> 81
*/

Function Closures

A Go function can also be a closure. A closure is a function value that references variables outside its body.
The closure function is assigned to a variable and can access the variables.
In other words, closure functions are “bound” to variables.

In the example, the adder function returns a closure, so each sum variable has its own closure function.

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
	    return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}
/*
>>> 0 0
>>> 1 -2
>>> 3 -6
>>> 6 -12
>>> 10 -20
>>> 15 -30
>>> 21 -42
>>> 28 -56
>>> 36 -72
>>> 45 -90
*/

This example demonstrates that the adder function returns a closure, allowing each sum variable to possess its own enclosed function. Within the main function, pos and neg leverage two distinct closure functions for operations.

Recent Posts

Categories

Tags