Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Golang

Introduction to Golang

Features

I don't plan to give you an "elevator pitch" as to why you would want to use Go, but here are a few items that might help you if you need to justify the time you spend on it.

In general there are many attributes to every programming language and each language is located somewhere else in relation to those different attributes.

I think there are two central areas where Go has advantages over other languages. One of them is the built-in concurrency that makes it easier to use all the cores that you can find in a modern computer. The other is the cross compilation. Meaning it is easy to write your code on one computer and compile your code on that computer targeting other operating systems.

If we are already talking about compilation, it is also much faster than that of C or other compiled languages while its run-time is as fast as those other compiled languages. The fast compile time might not sound as a big deal, but if your compilation takes 10 seconds instead of 5 minutes or two hours, your feedback loop is much faster and thus you can develop faster.

Another advantage that Go has is that it manages the memory for you so unlike in C you don't have to deal with memory management. On the other hand by giving up on some of the (usually unnecessary) flexibility of Python it is also not as wasteful with memory allocation.

It provides you control over the types of the variables, but in many cases it does not force you to be precise.

  • Built-in concurrency

  • Compile to standalone binaries (cross compilation available!)

  • Strong and statically typed

  • Simplicity

  • Fast compile times

  • Garbage collected

Why Golang?

The following 2 articles predate the development of Go, but they can illustrate well one of the big challenges of the software development world to which Go provides a good solution.

Comments from companies using it: Golang is faster and allows easy parallel implementation. Besides, services like swan, bee, rabbit-MQ, etc.

Go Designed by

Open Souce

To some people, yours truly included, it is important to know that the go compiler is open source. You can find its source code on GitHub.

Platforms

Go can be compiled to the 3 major desktop operating systems and to few other, smaller ones. It can be used in mobile environments, but as far as I know it does not have a good solution for the GUI part.

Major Open Source Projects

It might be important for you to know that there are a number of very important and high-profile Open Source projects written in Go. There are also thousands of other, much smaller Open Source projects that you can find primarily on GitHub.

One of my recommendations for when you are learning Go (or any other programming language) is to find some Open Source projects in areas where you are interested and start reading their code. Try to make some changes and send them back as contributions. That can be a really nice way to get involved in something real with the pressure you might have when you work on a project in a company.

Install Golang

Show Installed Version of Golang

  • version

Once you have installed the Go compiler, let's check that you can run it on the command line. In MS Windows open a CMD window, or if you use Linux/Mac open a terminal and type:

$ go version

the output should look something like this:

go version go1.12.9 linux/amd64

Editor/IDE for Golang

  • editor
  • IDE

In order to write some Go program you will need a text editor. You can use Notepad++ on Windows or vim or emacs anywhere, but the recommended editor is the Visual Studio Code of Microsoft using the Go plugins. You can download it from the link bellow.

When you open your first file with a .go extension it will offer you to install the plugins. Let it install everything it wants to. At the end you might need to restarted the IDE.

Hello World

  • package
  • main
  • import
  • func
  • run
  • build
  • fmt
package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello World")
}
go run hello_world.go
Hello World
  • main function is the entry point of every program
  • fmt.Print

Every Go program has a main file that must start with "package main" and it must have a function called main.

In order to print something to the screen we first need to import the fmt class and then inside the "main" function we can write fmt.Println("Hello World").

We save this with a .go extension. Then we can run it from the command line by typing go run and the name of your file. This will compile the code into a temporary directory and then run it.

Build Hello World exe

If you'd like to crete a distributable version of your code you can type go build and the name of your file. That will create an executable with the same name (just without the extension). This is specific to the Operating System and platform you currently use. Later we'll see how

go build hello_world.go
./hello_world

Source of examples

Separate directories! - main redeclared in this block

  • Put each example / exercise in a separate directory
  • As each package must have only one main() function.
package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello Same One")
}
package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello Same Two")
}
go run .
# _/home/gabor/work/slides/golang/examples/same
./same_two.go:7:6: main redeclared in this block
	previous declaration at ./same_one.go:7:6

Exercise: Hello World

This exercise is "easy" it is here to make sure you managed to set up your development environment that can be very frustrating if your computer has some special configurations. Which is quite common.

  • Install go if you don't have it yet.
  • Install an editor/IDE with the appropriate plugin for go.
  • Check if go version is running.
  • Write the Hello World program and run it both from the IDE and from the command line.

Basics

Hello Foo - Println

  • Println
  • :=

In go you can use the := operator to declare a variable and assign a value to it at the same time. Then you can use that variable in a Println. As you might notice, (I did not when I first tried this), the ``Printlnfunction inserts a space between the values it prints. For better control about this you can usePrintf`.

package main

import (
	"fmt"
)
func main() {
	name := "Foo"
	fmt.Println("Hello", name)
}
Hello Foo

Hello Bar - Printf

  • Printf
  • %s

When printing values of variables it might be better to use the Printf function than the Println as the former allows us to use placeholders such as %s and be more precise where we would want to put a space and where don't. These formatting placeholders are similar to what we have in other programming languages, but Go has a few additional placeholders. Specifically %s isn't special. It stands for strings.

package main

import (
	"fmt"
)

func main() {
	name := "Bar"
	fmt.Printf("Hello %s\n", name)
}
Hello Bar

Hello Bar - Printf %v

  • Printf
  • %v

In some case it is better to use %v as it is type-agnostic. We're going to use it more often during the rest of these pages.

package main

import (
	"fmt"
)

func main() {
	name := "Bar"
	fmt.Printf("Hello %v\n", name)
}
Hello Bar

Enforce variables types

Each variable in Go has a type and Go will not let you mix values and variables of different types. When we created the variable with the := operator Go automatically deducted the type from the assigned value. In this case it was string.

package main

import (
	"fmt"
)

func main() {
	name := "Foo"
	fmt.Println(name)
	name = 42
}

# command-line-arguments
./variable.go:8:7: cannot use 42 (type int) as type string in assignment
  • Compile-time error

Basic Types

  • string
  • int
  • uint
  • float
  • bool
  • byte
  • real
  • complex
  • imag

This is just a list of the basic types in Go. You only need to remember that there are different types. For now you can use the defaults offered by Golang. Later, when you get deeper in the language these types allow you to improve the speed and the memory usage of your application by specifying the size of each variable.

The numbers indicate the number of bits each variable takes. In languages such Python and Perl you would not need to care about this at all, but you would not have control over these aspects either. (In the Numpy library of Python you do have these distinctions.)

As I wrote, don't worry about them for now.

string          (any utf-8 character)

uint            (unsigned integer of 32 or 64 bits - depends on implementation)
uint8           (unsigned integer (0, 255)
uint16          (unsigned integer)
uint32          (unsigned integer)
uint64          (unsigned integer)


int             (signed integer, the same bit-size as uint)
int8            (signed integer (-128, 127))
int16
int32
int64

float32
float64

bool

byte             (alias for uint8)
rune             (alias for int32)

complex64       1 + 2i    or just 3i
complex128

Show inferred variable type - Printf %T

  • Printf
  • %T

When we create a variable with the := assignment, Go automatically decides the type of the variable based on the initial value assigned to it. Using Printf and the %T placeholder you can print out this type.

package main

import (
	"fmt"
)

func main() {
	name := "Foo"
	age := 42.5
	married := true
	children := 2
	really := 2i

	fmt.Printf("%T\n", name)     // string
	fmt.Printf("%T\n", age)      // float64
	fmt.Printf("%T\n", married)  // bool
	fmt.Printf("%T\n", children) // int
	fmt.Printf("%T\n", really)   // complex128
}
string
float64
bool
int
complex128

Show type of variable - reflect.TypeOf

  • reflect
  • TypeOf

You can also use the TypeOf function of the reflect package.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	a := 42
	fmt.Println(reflect.TypeOf(a)) // int

	b := 3.14
	fmt.Println(reflect.TypeOf(b)) // float64

	c := "hello world"
	fmt.Println(reflect.TypeOf(c)) // string

	d := []string{}
	fmt.Println(reflect.TypeOf(d)) // []string
}
int
float64
string
[]string

get variable type - %T or reflect.TypeOf

package main

import (
	"fmt"
	"reflect"
)

func main() {
	a := "name"
	b := 42
	c := 19.23
	d := true

	fmt.Println(fmt.Sprintf("%T", a)) // string
	fmt.Println(fmt.Sprintf("%T", b)) // int
	fmt.Println(fmt.Sprintf("%T", c)) // float64
	fmt.Println(fmt.Sprintf("%T", d)) // bool

	fmt.Println(reflect.TypeOf(a)) // string
	fmt.Println(reflect.TypeOf(b)) // int
	fmt.Println(reflect.TypeOf(c)) // float64
	fmt.Println(reflect.TypeOf(d)) // bool
}

Variable declaration (var)

  • var

  • :=

  • There are 3 ways to declare variables in Go

The first one, the one that we have already seen uses the := operator. It declares a variable and immediately assigns a value to it. The type of the variable is deducted from the value assigned to it.

The second in our example uses the var keyword and explicitely sets the type. var b int32 = 2 This is used when we would like to fine-tune the type of the variable.

In the third example var a int16 we declare the variable but we don't assign any value to it yet. This is used when need don't know the initial value when we declare the variable. This can happen, for example, when we are looking for some value in a loop, and we would like the result to be available outside of the loop. This is related to the scoping of variables that we'll discuss later.

package main

import (
	"fmt"
)

func main() {
	c := 3 // type inferred

	var b int32 = 2

	var a int16
	a = 1

	fmt.Println(a) // 1
	fmt.Println(b) // 2
	fmt.Println(c) // 3

	fmt.Printf("%T\n", a) // int16
	fmt.Printf("%T\n", b) // int32
	fmt.Printf("%T\n", c) // int
}
1
2
3
int16
int32
int

Default values of variables

  • false

Variables declared without an explicit initial value are given their zero value as default.

  • 0 for numeric types.
  • "" (the empty string) for strings.
  • false for the boolean type.
package main

import "fmt"

func main() {
	var n int
	var f float64
	var s string
	var b bool

	fmt.Println(n) // 0
	fmt.Println(f) // 0
	fmt.Println(s) //    (empty string)
	fmt.Println(b) // false
}
0
0

false

Scan input strings from STDIN

  • STDIN
  • Scan

We'll have a lot more to say about getting input from the user, but in order to be able to write some useful code, let's introduce a simple way to get input from the user. For this we need to declare a variable first to be a string. Then we call the Scan function and give the variable as a parameter. More precisely we pass a pointer to the variable to the function by prefixing it with &. Later we'll discuss pointers as well, for now all you need to know is that Scan will wait till the user types in something and presses ENTER. Then all the content will be in the variable.

package main

import (
	"fmt"
)

func main() {
	var name string // declare variable as a string without assignment
	fmt.Print("Your name: ")
	fmt.Scan(&name)
	fmt.Printf("Hello %s, how are you?\n", name)
	fmt.Printf("Type %T\n", name) // string
}

if else statement

  • if
  • else

We'll have a longer discussion about conditinal statements later, but it is quite hard to do anything useful without them so let's have a quick look at an if - else construct. Here we check if the value in the name variable equals to the string Go and depending on the success of this comparision we print something.

package main

import (
	"fmt"
)

func main() {
	var name string
	fmt.Print("What is the name of this langauage? ")
	fmt.Scan(&name)

	if name == "Go" {
		fmt.Println("Yes, that's the answer!")
	} else {
		fmt.Println("Well..")
	}
}

Converting string to integer - strconv, Atoi

  • strconv
  • Atoi
  • err
  • nil
package main

import (
	"fmt"
	"strconv"
)

func main() {
	var i int
	i, _ = strconv.Atoi("42")
	fmt.Printf("%v %T\n", i, i) // 42 int

	i, _ = strconv.Atoi("0")
	fmt.Printf("%v %T\n", i, i) // 0 int

	i, _ = strconv.Atoi("23\n")
	fmt.Printf("%v %T\n", i, i) // 0 int

	i, _ = strconv.Atoi("17x")
	fmt.Printf("%v %T\n", i, i) // 0 int
}
42 int
0 int
0 int
0 int
  • In the first two examples the conversion was successful.
  • In the 3rd and 4th examples it failed.
  • How can we know?

While internally Go can represent numbers, the communication with the outside world is always using strings. So when we read from a file we always get strings. When we ask the user to type in a number and the user types in a number, we still receive it as a string. For example as the string "42". So we need a way to convert a string that looks like a number to the numeric representation of Go.

Error Handling

  • Functions that can fail will return the error value as the last value.
  • We can sweep under the carper by assigning it to _.

Converting string to integer with error handling - strconv, Itoa

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var i int
	var err error

	i, err = strconv.Atoi("42")
	if err == nil {
		fmt.Printf("%v %T\n", i, i) // 42 int
	} else {
		fmt.Println(err)
	}

	i, err = strconv.Atoi("23\n")
	if err == nil {
		fmt.Printf("%v %T\n", i, i)
	} else {
		fmt.Println(err) // strconv.Atoi: parsing "23\n": invalid syntax
	}

	i, err = strconv.Atoi("17x")
	if err == nil {
		fmt.Printf("%v %T\n", i, i)
	} else {
		fmt.Println(err) // strconv.Atoi: parsing "17x": invalid syntax
	}
}
42 int
strconv.Atoi: parsing "23\n": invalid syntax
strconv.Atoi: parsing "17x": invalid syntax

This can, of course go wrong. If we ask for an integer and the user types in "42x" or even "FooBar". So the conversion might fail. The way Go usually handles errors is by returning a second value which is the special value nil if everything went fine, or the error object is something broke. It is the responsibility of the caller to check the error. So in the follwing examples you can see that from each function we accept two values, the actual value we are interested in and another value that we assign to a variable called err. It is not a special name, but it is quite common in Go to use the variable name err for this purpose.

Then in each one of the example we check if the value of err is equal to nil or if there was an error in the conversion.

Converting string to float - strconv, ParseFloat

  • strconv
  • ParseFloat
  • err
  • nil
package main

import (
	"fmt"
	"strconv"
)

func main() {
	var f float64
	var err error

	f, err = strconv.ParseFloat("4.2", 64)
	if err == nil {
		fmt.Printf("%v %T\n", f, f) // 4.2 float64
	} else {
		fmt.Println(err)
	}
}
4.2 float64

Converting integer to string - strconv, Itoa

  • strconv
  • Itoa
package main

import (
	"fmt"
	"strconv"
)

func main() {
	text := strconv.Itoa(42)
	fmt.Printf("%s\n", text) // 42
}
42

Scan STDIN convert to number

  • Scan
  • Atoi
  • ParseFloat
  • Exit
package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	var inputStr string
	fmt.Print("A numer: ")
	fmt.Scan(&inputStr)
	fmt.Printf("Input: %v Type %T\n", inputStr, inputStr)
	//number, err := strconv.Atoi(inputStr)
	number, err := strconv.ParseFloat(inputStr, 64)
	if err != nil {
		fmt.Printf("There was an error: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("Input: %v Type %T\n", number, number)
}

Comments

  • //
package main

import "fmt"

// Singe line comment

/*

Multi
line
comment

*/

func main() {
	//    fmt.Println("Golang is bad")
	fmt.Println("Hello World") // comment after statement
}
Hello World

Exercise: circle STDIN

Write a program that asks the user for a number, the radius of a circle, and prints out the area and the circumference of the circle.

go run circle.go
radius: 2
Area: 
Circumference:

Exercise: rectangular STDIN

Write a program that asks the user for two numbers on STDIN (the width and the length of a rectangular) and prints the area and the circumference.

For example:

$ go run rectangular.go
width: 3
length: 4
Area: 12
Circumference: 14

Exercise: calculator STDIN

Write a command-line calculator that works with the 4 basic operators +-*/ like this:

$ go run cacl.go
a: 3
op: +
b: 4
7

$ go run calc.go
a: 8
op: /
b: 2
4
  • What happens if we try to divide by 0?

Solution: circle STDIN

package main

import (
	"fmt"
	"strconv"
)

func main() {
	pi := 3.14

	var radiusStr string
	fmt.Print("radius: ")
	fmt.Scan(&radiusStr)

	radius, err := strconv.ParseFloat(radiusStr, 64)
	if err == nil {
		fmt.Println(radius)
		area := pi * radius * radius
		circumference := 2 * pi * radius
		fmt.Printf("Area: %v\n", area)
		fmt.Printf("Cirumference: %v\n", circumference)
	}
}

Solution: circle STDIN with math

  • math

Of course you don't need to type in the value if PI yourself. There is a module called math that provides you the value at a much better precision. There is also a function called Pow that can rasie any number to any power.

package main

import (
	"fmt"
	"math"
	"strconv"
)

func main() {
	fmt.Println(math.Pi)

	var radiusStr string
	fmt.Print("radius: ")
	fmt.Scan(&radiusStr)

	radius, err := strconv.ParseFloat(radiusStr, 64)
	if err == nil {
		fmt.Println(radius)
		area := math.Pi * math.Pow(radius, 2)
		circumference := 2 * math.Pi * radius
		fmt.Printf("Area: %v\n", area)
		fmt.Printf("Cirumference: %v\n", circumference)
	}
}

Solution: rectangular STDIN

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var widthStr string
	fmt.Print("width: ")
	fmt.Scan(&widthStr)

	var lengthStr string
	fmt.Print("length: ")
	fmt.Scan(&lengthStr)

	// convert to integer

	width, errw := strconv.Atoi(widthStr)
	length, errl := strconv.Atoi(lengthStr)
	if errw == nil && errl == nil {
		fmt.Println(width)
		fmt.Println(length)
		area := width * length
		circumference := 2 * (width + length)
		fmt.Printf("Area: %v\n", area)
		fmt.Printf("Cirumference: %v\n", circumference)
	}
}

Solution: calculator STDIN

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	var aStr string
	var bStr string
	var operator string
	var a float64
	var b float64
	var err error
	var result float64

	fmt.Print("a: ")
	fmt.Scan(&aStr)
	a, err = strconv.ParseFloat(aStr, 64)
	if err != nil {
		fmt.Printf("The value '%v' could not be converted to a floating point number. %v\n", aStr, err)
		os.Exit(1)
	}

	fmt.Print("op: ")
	fmt.Scan(&operator)

	fmt.Print("b: ")
	fmt.Scan(&bStr)
	b, err = strconv.ParseFloat(bStr, 64)
	if err != nil {
		fmt.Printf("The value '%v' could not be converted to a floating point number. %v\n", bStr, err)
		os.Exit(1)

	}

	if operator == "+" {
		result = a + b
	} else if operator == "-" {
		result = a - b
	} else if operator == "*" {
		result = a * b
	} else if operator == "/" {
		result = a / b
	} else {
		fmt.Printf("Unhandled operator: '%v'\n", operator)
		os.Exit(1)
	}

	fmt.Printf("%v %v %v = %v\n", a, operator, b, result)

}

Solution: calculator STDIN switch

  • swicth
  • case
package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	var aStr string
	var bStr string
	var operator string
	var a float64
	var b float64
	var err error
	var result float64

	fmt.Print("a: ")
	fmt.Scan(&aStr)
	a, err = strconv.ParseFloat(aStr, 64)
	if err != nil {
		fmt.Printf("The value '%v' could not be converted to a floating point number. %v\n", aStr, err)
		os.Exit(1)
	}

	fmt.Print("op: ")
	fmt.Scan(&operator)

	fmt.Print("b: ")
	fmt.Scan(&bStr)
	b, err = strconv.ParseFloat(bStr, 64)
	if err != nil {
		fmt.Printf("The value '%v' could not be converted to a floating point number. %v\n", bStr, err)
		os.Exit(1)

	}

	switch operator {
	case "+":
		result = a + b
	case "-":
		result = a - b
	case "*":
		result = a * b
	case "/":
		result = a / b
	default:
		fmt.Printf("Unhandled operator: '%v'\n", operator)
		os.Exit(1)
	}
	fmt.Printf("%v %v %v = %v\n", a, operator, b, result)

}

CLI

Args - (argv) command line arguments

  • Args
  • os.Args

os.Args is a slice of strings (we'll talk about slices in later in detail) that contains the values on the command line.

The first element of this slice (index 0) is the path to the executable. If we run this using go run then it will be some strange-looking temporary location.

len gives us the number of elements on the command line that will be 1 if there are no parameters on the command line.

Using a square-bracket after the name os.Args allows us to access the specific elements in the slice.

Later we'll learn higher level abstractions to read command line parameters, but for now this will be enough.

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println(len(os.Args))
	fmt.Println(os.Args)
	fmt.Println(os.Args[0]) // the path to the compiled executable

	fmt.Printf("%T\n", os.Args) // []string   (slice)
}
go run examples/cli/cli.go  hello world
3
[/tmp/go-build781021115/b001/exe/cli hello world]
/tmp/go-build781021115/b001/exe/cli
[]string

Exit early with exit code

  • Exit
  • os.Exit
  • %ERRORLEVEL%
  • $?

In the earlier examples our code stopped running when the last statement in the main function was executed. This is usually the normal way for your code to end, but there can be various circumstances when you want your program to stop running even before it reaches the last statement. You can do this by calling the os.Exit() function.

You will also have to pass a number to this function which is going to be the exit-code of your program. If you did not explicitly call os.Exit() then go will automatically set the exit-code to be 0. That means success.

In general exit-code 0 means success. Any other number indicates failure. Which number indicates which failure is totally up to you as a programmer.

The exit code is not printed anywhere on the screen, it is sort-of invisible, but the user of your program can check it by looking at the value of the $? variable on Unix/Linux/Mac systems, or the %ERRORLEVEL% variable on MS Windows systems.

package main

import (
	"fmt"
	"os"
)

func main() {
	if len(os.Args) < 3 {
		fmt.Printf("Usage: %s PARAM PARAM\n", os.Args[0])
		os.Exit(2)
	}
	fmt.Println(os.Args)
}
echo $0
echo %ERRORLEVEL%

Exercise: rectangular

Write a program that accepts two numbers on the command line (the width and the length of a rectangular) and prints the area.

For example:

$ go run rectangular.go 3 4
12

Exercise: calculator

Write a command-line calculator that works with the 4 basic operators +-*/ like this:

$ go run cacl.go 3 + 4
7

$ go run calc.go 8 / 2
4
  • Does multiplication also work?
  • What happens if we try to divide by 0?

Solution: rectangular CLI

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	var err error
	var width int
	var height int

	if len(os.Args) != 3 {
		fmt.Printf("Usage: %v WIDTH HEIGHT\n", os.Args[0])
		os.Exit(1)
	}

	width, err = strconv.Atoi(os.Args[1])
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	height, err = strconv.Atoi(os.Args[2])
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	fmt.Println(width)
	fmt.Println(height)

	area := width * height
	fmt.Printf("The area of the rectangular is: %v\n", area)
}

TODO: Solution: calculator CLI

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	//fmt.Println(os.Args)
	if len(os.Args) != 4 {
		fmt.Println("Usage: calc.go NUMBER OPERATOR NUMBER")
		os.Exit(0)
	}

	var a, _ = strconv.Atoi(os.Args[1])
	var op = os.Args[2]
	var b, _ = strconv.Atoi(os.Args[3])
	var result int

	if op == "+" {
		result = a + b
	} else if op == "*" {
		result = a * b
	} else if op == "/" {
		if b == 0 {
			fmt.Println("Cannot devide by 0")
			os.Exit(1)
		}
		result = a / b
	} else if op == "-" {
		result = a - b
	} else {
		fmt.Printf("Operator '%s' is not handled.\n", op)
		os.Exit(1)
	}

	fmt.Println(result)
}

TODO: Solution: calculator (switch)

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	//fmt.Println(os.Args)
	if len(os.Args) != 4 {
		fmt.Println("Usage: calc.go NUMBER OPERATOR NUMBER")
		os.Exit(0)
	}

	var a, _ = strconv.Atoi(os.Args[1])
	var op = os.Args[2]
	var b, _ = strconv.Atoi(os.Args[3])
	var result int

	switch op {
	case "+":
		result = a + b
	case "*":
		result = a * b
	case "/":
		if b == 0 {
			fmt.Println("Cannot devide by 0")
			os.Exit(1)
		}
		result = a / b
	case "-":
		result = a - b
	default:
		fmt.Printf("Operator '%s' is not handled.\n", op)
		os.Exit(1)
	}

	fmt.Println(result)
}
  • implicit break! (no fall-through)

Skeleton

empty file


go run empty.go

package main: empty.go:1:2: expected 'package', found 'EOF'

Only package main

package main


# command-line-arguments
runtime.package-main_main·f: function main is undeclared in the main package

Other package name

package qqrq


go run: cannot run non-main package

Skeleton file

package main

func main() {
}
go run skeleton.go

Numbers

Integer-based operations

  • %
  • ++
  • --
  • +=
  • /=
  • pow
package main

import (
	"fmt"
	"math"
)

func main() {
	a := 3
	b := 7

	sum := a + b
	diff := a - b
	div := b / a
	mul := a * b
	mod := b % a
	pow := math.Pow(2, 3)
	sqr := math.Pow(9, 0.5)

	fmt.Printf("sum %v\n", sum)
	fmt.Printf("diff %v\n", diff)
	fmt.Printf("mul %v\n", mul)
	fmt.Printf("div %v\n", div) // integer divided by integer is integer
	fmt.Printf("modulus %v\n", mod)
	fmt.Printf("power %v\n", pow)
	fmt.Printf("square %v\n", sqr)

	divFloat := float64(b) / float64(a)
	fmt.Printf("div float: %v\n", divFloat)

	x := 1
	fmt.Printf("x: %v\n", x)
	x += 2 // x = x + 2
	fmt.Printf("x: %v\n", x)
	x++ // x = x + 1
	fmt.Printf("x: %v\n", x)
	x-- // x = x - 1
	fmt.Printf("x: %v\n", x)

	// no prefix autoincrement and autodecrement
	// ++x
	// --x
}
sum 10
diff -4
mul 21
div 2
modulus 1
power 8
square 3
div float: 2.3333333333333335
x: 1
x: 3
x: 4
x: 3

Floating-point based operations

  • No modulo operator
  • No autoincrement and autodecrement
package main

import (
	"fmt"
)

func main() {
	a := 3.0
	b := 7.0

	sum := a + b
	diff := a - b
	div := b / a
	mul := a * b

	fmt.Printf("sum %v\n", sum)
	fmt.Printf("diff %v\n", diff)
	fmt.Printf("mul %v\n", mul)
	fmt.Printf("div %v\n", div)
}
sum 10
diff -4
mul 21
div 2.3333333333333335

Precision

package main

import (
	"fmt"
	"strconv"
)

func main() {
	aStr := "2.1"
	bStr := "7.3"
	a, _ := strconv.ParseFloat(aStr, 64)
	b, _ := strconv.ParseFloat(bStr, 64)
	fmt.Println(a + b)
	fmt.Println(a - b)
}
9.4
-5.199999999999999

Mixed operations

  • Cannot have operation between differen types
package main

import (
	"fmt"
)

func main() {
	a := 3
	b := 7.0

	sum := a + b
	fmt.Printf("sum %v\n", sum)

	div := a / b
	fmt.Printf("div %v\n", div)

}
# command-line-arguments
./numbers_mix.go:11:11: invalid operation: a + b (mismatched types int and float64)
./numbers_mix.go:14:11: invalid operation: a / b (mismatched types int and float64)

int8

  • int8
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var z int8 = 126

	fmt.Println(z)
	z++
	fmt.Println(z)
	z++
	fmt.Println(z)
	z--
	fmt.Println(z)

	fmt.Println(unsafe.Sizeof(z))
}
126
127
-128
127
1

uint8

  • uint8
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var z uint8 = 254
	fmt.Println(z)
	z++
	fmt.Println(z)
	z++
	fmt.Println(z)
	z--
	fmt.Println(z)

	fmt.Println(unsafe.Sizeof(z))
}
254
255
0
255
1

Bytes

  • byte
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var z byte = 254
	fmt.Println(z)
	z++
	fmt.Println(z)
	z++
	fmt.Println(z)
	z--
	fmt.Println(z)

	fmt.Println(unsafe.Sizeof(z))
}
254
255
0
255
1

Byte is uint8

  • byte

  • byte is just an alias for uint8

package main

import (
	"fmt"
)

func main() {
	var b byte = 152
	var u uint8 = 152

	fmt.Println(b == u)
}
true

uint16

  • uint16
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var z uint16
	fmt.Println(z)
	z--
	fmt.Println(z)

	fmt.Println(unsafe.Sizeof(z))
}
0
65535
2

uint32

  • uint32
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var z uint32
	fmt.Println(z)
	z--
	fmt.Println(z)

	fmt.Println(unsafe.Sizeof(z))
}
0
4294967295
4

Converting values to other types - float32, int, string

  • float32()

  • int()

  • string()

  • Sprintf

  • %f

  • %d

  • integers to float32()

  • floats to int()

  • integers to string() but that converts the number to the value it represents in ASCII or Unicode table.

  • In order to get the same "look" but as a string we need to use the Sprintf function from fmt.

package main

import (
	"fmt"
)

func main() {
	n := 65
	q := float32(n)
	fmt.Printf("%v %T\n", n, n) //  65 int
	fmt.Printf("%v %T\n", q, q) // 65 float32

	f := 42.23
	p := int(f)
	fmt.Printf("%v %T\n", f, f) // 42.23 float64
	fmt.Printf("%v %T\n", p, p) // 42 int

	ns := string(n)
	fmt.Printf("%v %T\n", ns, ns) // A, string

	ns2 := fmt.Sprintf("%d", n)
	fmt.Printf("%v %T\n", ns2, ns2) // 65, string

	fs := fmt.Sprintf("%f", f)
	fmt.Printf("%v %T\n", fs, fs) // 42.230000, string

	fmt.Println()
	num := 258
	fmt.Printf("%v %T\n", num, num) // 258 int
	num16 := int16(num)
	fmt.Printf("%v %T\n", num16, num16) // 258 int16
	num8 := int8(num)
	fmt.Printf("%v %T\n", num8, num8) // 2 int8 - loss of the high bytes!
}

Complex numbers

package main

import "fmt"

func main() {
	n := 2i + 3
	fmt.Printf("%v %T\n", n, n)
	r := real(n)
	fmt.Printf("%v %T\n", r, r)
	i := imag(n)
	fmt.Printf("%v %T\n", i, i)
	fmt.Println()

	z := 1i
	fmt.Printf("%v %T\n", z, z)
	v := z * z
	fmt.Printf("%v %T\n", v, v)
	fmt.Println(v == -1)
	fmt.Println()

	c := complex(4, 5)
	fmt.Printf("%v %T\n", c, c)
}
(3+2i) complex128
3 float64
2 float64

(0+1i) complex128
(-1+0i) complex128
true

(4+5i) complex128

binary - octal - hex

This representation exists only from 1.13

package main

import "fmt"

func main() {
	decimal := 42
	binary  := 0b101010
	octal   := 0o52
	hexa    := 0x2A 
	fmt.Println(decimal)
	fmt.Println(binary)
	fmt.Println(octal)
	fmt.Println(hexa)
}
42
42
42
42

Random

  • rand

  • Float64

  • Int

  • Intn

  • Pseudo Random

  • rand

  • Go will always start from the same random seed of 1 meaning that this script will always generate the same "random" numbers.

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	x := rand.Float64()
	fmt.Println(x)

	y := rand.Int()
	fmt.Println(y)

	a := rand.Intn(10) // between [0, n)
	fmt.Println(a)
	b := rand.Intn(10) // between [0, n)
	fmt.Println(b)

}
0.6046602879796196
8674665223082153551
7
9

Random with seed

  • Seed

  • time.Now().Unix

  • In order to generate different random numbers one needs to set the starting point by calling Seed.

  • A common way to do that is to provide the number of seconds since the epoch.

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	fmt.Println(time.Now().UnixNano())

	rand.Seed(time.Now().UnixNano())

	a := rand.Float64()

	fmt.Println(a)
}
1586851169266458373
0.2296012220177759

Exercise: One-dimensional spacefight - level 1

  • This is the first level of a game we are going to build.

  • You are in the one-dimensional space where you only have distance, but no direction. There is an enemy spaceship you need to shoot down. The distance of the spaceship is represented as an integer number. You shoot by typing in an integer number.

  • The computer "thinks" of a number between 1-20 and the user can guess it. (The computer asks the user for a guess).

  • Then the computer tells the user if the guess was correct or if it was smaller or larger than the chosen number.

  • In the higher levels of this game the user will be able to guess several times, but for now let's start with this simple version.

Solution: One-dimensiona spacefight - level 1

package main

import (
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"time"
)

func main() {
	fmt.Println("Welcome to the game!")
	max := 20

	rand.Seed(time.Now().UnixNano())
	hidden := rand.Intn(max) + 1

	fmt.Printf("The hidden number is %v\n", hidden)
	fmt.Printf("Your guess [1-%v]: ", max)

	var input string
	fmt.Scan(&input)

	guess, err := strconv.Atoi(input)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if guess < hidden {
		fmt.Println("Too small")
	} else if guess > hidden {
		fmt.Println("Too big")
	} else {
		fmt.Println("Direct hit!")
	}
}

Boolean - Logical operators

Boolean values - bool, true, false

  • true
  • false
  • !
package main

import "fmt"

func main() {
	var flag bool = true
	fmt.Println(flag)        // true
	fmt.Printf("%T\n", flag) // bool

	flag = !flag
	fmt.Println(flag) // false

	other := false
	fmt.Printf("%v %T\n", other, other) // false bool
}

Boolean truth table

  • &&
  • ||
  • !
package main

import "fmt"

func main() {
	fmt.Printf("%v\n", true && true)   // true
	fmt.Printf("%v\n", true && false)  // false
	fmt.Printf("%v\n", false && true)  // false
	fmt.Printf("%v\n", false && false) // false

	fmt.Println()
	fmt.Printf("%v\n", true || true)   // true
	fmt.Printf("%v\n", true || false)  // true
	fmt.Printf("%v\n", false || true)  // true
	fmt.Printf("%v\n", false || false) // false

	fmt.Println()
	fmt.Printf("%v\n", !true)  // false
	fmt.Printf("%v\n", !false) // true
}

Loops

3-part for loop

  • for
package main

import (
	"fmt"
)

func main() {
	for i := 0; i < 5; i++ {
		fmt.Println(i)
	}
}
0
1
2
3
4

while-like for loop

  • while
package main

import (
	"fmt"
)

func main() {
	i := 0
	for i < 5 {
		fmt.Println(i)
		i++
	}
}
0
1
2
3
4

infinite loop

  • You don't have to put a condition in the for loop, but that will create an infinite loop.
  • If you happen to run this on the command line, you can press Ctrl-C to stop this program.
  • In this case you will need to use break or some other way to exit the loop.
package main

import (
	"fmt"
)

func main() {
	i := 0
	for {
		fmt.Println(i)
		i++
	}
}
...
285583
285584
285585
285586
285587
285588
^Csignal: interrupt

break out from loop

  • break
package main

import (
	"fmt"
)

func main() {
	i := 0
	for {
		fmt.Println(i)
		i++
		if i > 5 {
			break
		}
		fmt.Println("tail")
	}
	fmt.Println("after loop")
}
0
tail
1
tail
2
tail
3
tail
4
tail
5
after loop

continue

  • continue
package main

import (
	"fmt"
)

func main() {
	i := 0
	for {
		fmt.Println("")
		i++
		fmt.Printf("A: %v\n", i)
		if i > 4 {
			break
		}
		fmt.Printf("B: %v\n", i)
		if i == 3 {
			continue
		}
		fmt.Printf("C: %v\n", i)
	}
	fmt.Println("after loop")
}

A: 1
B: 1
C: 1

A: 2
B: 2
C: 2

A: 3
B: 3

A: 4
B: 4
C: 4

A: 5
after loop

loop on two variables

package main

import (
	"fmt"
)

func main() {
	for i, j := 1, 5; i+j < 20; i, j = i+1, j+2 {
		fmt.Printf("%v  %v\n", i, j)
	}
}
1  5
2  7
3  9
4  11
5  13

break out from internal loop (labels)

package main

import (
	"fmt"
)

func main() {
OUT:
	for i := 1; i < 5; i++ {
		for j := i; j < 5; j++ {
			k := i * j
			if k > 10 {
				break OUT
			}
			fmt.Println(k)
		}
	}
}
1
2
3
4
4
6
8
9

Exercise: One-dimensional spacefight

The game have several levels. Solve them one after the other.

  • The computer "thinks" about a random integer between 1 and 20 then asks the player. The player types in an integer. The computer says if it is too small, to big, or direct hit.
  • The user can guess multiple times. Exit only when there was a direct hit.
  • The user can enter x any time and quite the game.
  • The user can enter p any time and the hidden value is printed (cheating)
  • Allow the user to change the game to "move" mode by typing "m". In this mode after every guess after we compared the values change the hidden number by -2, -1, 0, 1, or 2.
  • If the user enters "d" we switch to the "debug-mode" of the game. In this mode, before every turn we print the current hidden value.

Exercise: FizzBuzz

Write a program that prints the numbers from 1 to 100. For multiples of 3 print "Fizz" instead of the number. For multiples of 5 print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".

Expected output:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz

Solution: One-dimensional spacefight - multiple guesses till hit

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	fmt.Println("Welcome to the game!")

	reader := bufio.NewReader(os.Stdin)

	rand.Seed(time.Now().UnixNano())
	var hidden = rand.Intn(20)
	//fmt.Println(hidden)
	for {
		fmt.Print("Your guess: ")
		var input string
		var err error
		input, err = reader.ReadString('\n')
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Println(input)
		input = strings.TrimSuffix(input, "\n")
		var guess int
		guess, err = strconv.Atoi(input)
		if err != nil {
			fmt.Println(err)
			fmt.Print("Try again!")
			continue
		}
		fmt.Println(guess)
		if guess < hidden {
			fmt.Println("Too small")
		} else if guess > hidden {
			fmt.Println("Too big")
		} else {
			fmt.Println("Direct hit!")
			break
		}
	}
}

Solution: One-dimensional spacefight - allow x

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	fmt.Println("Welcome to the game!")

	reader := bufio.NewReader(os.Stdin)

	rand.Seed(time.Now().UnixNano())
	var hidden = rand.Intn(20)
	//fmt.Println(hidden)
	for {
		fmt.Print("Your guess: ")
		var input string
		var err error
		input, err = reader.ReadString('\n')
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Println(input)
		input = strings.TrimSuffix(input, "\n")
		if input == "x" {
			fmt.Println("Good bye!")
			break
		}

		var guess int
		guess, err = strconv.Atoi(input)
		if err != nil {
			fmt.Println(err)
			fmt.Print("Try again!")
			continue
		}
		fmt.Println(guess)
		if guess < hidden {
			fmt.Println("Too small")
		} else if guess > hidden {
			fmt.Println("Too big")
		} else {
			fmt.Println("Direct hit!")
			break
		}
	}
}
package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	fmt.Println("Welcome to the game!")

	reader := bufio.NewReader(os.Stdin)

	rand.Seed(time.Now().UnixNano())
	var hidden = rand.Intn(20)
	//fmt.Println(hidden)
	for {
		fmt.Print("Your guess: ")
		var input string
		var err error
		input, err = reader.ReadString('\n')
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Println(input)
		input = strings.TrimSuffix(input, "\n")
		if input == "x" {
			fmt.Println("Good bye!")
			break
		}
		if input == "p" {
			fmt.Printf("The hidden number is %d\n", hidden)
			continue
		}

		var guess int
		guess, err = strconv.Atoi(input)
		if err != nil {
			fmt.Println(err)
			fmt.Print("Try again!")
			continue
		}
		fmt.Println(guess)
		if guess < hidden {
			fmt.Println("Too small")
		} else if guess > hidden {
			fmt.Println("Too big")
		} else {
			fmt.Println("Direct hit!")
			break
		}
	}
}

Solution: One-dimensional spacefight - allow m

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	fmt.Println("Welcome to the game!")

	reader := bufio.NewReader(os.Stdin)

	rand.Seed(time.Now().UnixNano())
	var hidden = rand.Intn(20)
	//fmt.Println(hidden)
	var move = false
	for {
		fmt.Print("Your guess: ")
		var input string
		var err error
		input, err = reader.ReadString('\n')
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Println(input)
		input = strings.TrimSuffix(input, "\n")
		if input == "x" {
			fmt.Println("Good bye!")
			break
		}
		if input == "p" {
			fmt.Printf("The hidden number is %d\n", hidden)
			continue
		}
		if input == "m" {
			move = !move
			continue
		}

		var guess int
		guess, err = strconv.Atoi(input)
		if err != nil {
			fmt.Println(err)
			fmt.Print("Try again!")
			continue
		}
		fmt.Println(guess)
		if guess < hidden {
			fmt.Println("Too small")
		} else if guess > hidden {
			fmt.Println("Too big")
		} else {
			fmt.Println("Direct hit!")
			break
		}
		hidden += rand.Intn(4) - 2
	}
}

Solution: FizzBuzz

package main

import (
	"fmt"
	"strconv"
)

func main() {
	for n := 1; n < 100; n++ {
		resp := ""

		if n%3 == 0 {
			resp += "Fizz"
		}
		if n%5 == 0 {
			resp += "Buzz"
		}
		if resp == "" {
			resp = strconv.Itoa(n)
		}

		fmt.Println(resp)
	}
}

Strings

Single and double quotes

  • Double quote is for strings - a series of bytes (uint8)
  • Single quote is for runes (alias for int32 signed integer) - a character value

Runes

  • rune

  • int32

  • alias for int32

package main

import (
	"fmt"
)

func main() {
	rn := 'a'
	fmt.Println(rn)

	fmt.Printf("%T\n", rn)
	fmt.Printf("%c\n", rn)

	fmt.Println(rn == rune(97))
	fmt.Println(97 == int32(rn))

	fmt.Printf("char: %c\n", 97) // to display
	fmt.Printf("code: %d\n", rn) // to display
}
97
int32
a
true
true
char: a
code: 97

Strings

package main

import (
	"fmt"
)

func main() {
	text := "Hello World!"
	fmt.Println(text)
	fmt.Println(text[1])
	fmt.Printf("%c\n", text[1])
}
Hello World!
101
e

Change string

package main

import (
	"fmt"
)

func main() {
	text := "Hello World!"
	fmt.Println(text)

	// text[5] = "Y" // cannot assign to text[5]

	text = text[:5] + "-" + text[6:]
	fmt.Println(text)
}
Hello World!
Hello-World!

Strings and Runes

package main

import (
	"fmt"
)

func main() {
	a := "שלום"
	fmt.Println(a)
	fmt.Println(len(a))
	fmt.Printf("%T %T\n", a, a[0])
	fmt.Printf("%c %v\n", a[0], a[0])
	//fmt.Printf("%v\n", a[0:1])
	fmt.Println()
	txt := "H"
	fmt.Printf("%-2v %T\n", txt, txt)
	rn := 'H'
	fmt.Printf("%2v %T\n", rn, rn)
	fmt.Printf("%c\n", rn)
	fmt.Printf("%v %T\n", txt[0], txt[0])

	text := "Hello World!"
	fmt.Println(text)
	fmt.Printf("%v %T\n", text[0], text[0])
	if text[0] == 'H' {
		fmt.Println("match even thought one of them is uint8 and the other one is int32")
	}
}
H  string
72 int32
H
72 uint8
Hello World!
72 uint8
match even thought one of them is uint8 and the other one is int32

Length of string

  • len
package main

import "fmt"

func main() {
	a := "Hello"
	la := len(a)
	fmt.Printf("%v  %v\n", a, la)

	b := ""
	lb := len(b)
	fmt.Printf("%v  %v\n", b, lb)

	c := "Hello World!"
	lc := len(c)
	fmt.Printf("%v  %v\n", c, lc)

}

iterate over characters in a string

  • range
package main

import (
	"fmt"
)

func main() {
	text := "Hello World!"
	text = "שלום"
	text = "Gábor Szabó"
	text = "lò mañana 😈 mas"
	text = "¿Còmo estas?"
	fmt.Println(text)

	for i, chr := range text {
		fmt.Printf("index: %2v  chr: %c\n", i, chr)
	}
}

String Contains, HasPrefix, HasSuffix

  • Contains
  • HasPrefix
  • HasSuffix
package main

import (
	"fmt"
	"strings"
)

func main() {
	text := "Some text here"
	fmt.Println(text)
	fmt.Println(strings.HasSuffix(text, "here"))
	fmt.Println(strings.HasPrefix(text, "here"))
	fmt.Println(strings.HasPrefix(text, "So"))

	fmt.Println(strings.Contains(text, "text"))

}

Split

  • Split
package main

import (
	"fmt"
	"strings"
)

func main() {
	text := "This text,has,comma separated,vales"
	fmt.Println(text)

	shortStrings := strings.Split(text, ",")
	fmt.Println(shortStrings)
	fmt.Println(len(shortStrings))

	veryShortStrings := strings.Split(text, "")
	fmt.Println(veryShortStrings)
	fmt.Println(len(veryShortStrings))
}
This text,has,comma separated,vales
[This text has comma separated vales]
4
[T h i s   t e x t , h a s , c o m m a   s e p a r a t e d , v a l e s]
35

Join

  • join
package main

import (
	"fmt"
	"strings"
)

func main() {
	shortStrings := []string{"abc", "def", "hello", "world"}
	text := strings.Join(shortStrings, "-")
	fmt.Println(text)
}
abc-def-hello-world

Split on whitespaces

  • Fields
  • Split
package main

import (
	"fmt"
	"strings"
)

func main() {
	expression := "  hello    space   world 42 "
	fmt.Printf("'%s'\n", expression)
	parts := strings.Fields(expression)
	fmt.Println(parts)
	fmt.Println(len(parts))
	fmt.Println()

	for _, part := range parts {
		fmt.Printf("%v\n", part)
	}
}
'  hello    space   world 42 '
[hello space world 42]
4

hello
space
world
42
'  hello    space   world 42 '
[hello space world 42]
4

Newlines

package main

import (
	"fmt"
)

func main() {
	a := "\n"
	b := '\n'
	fmt.Printf("string: '%v'\n", a)
	fmt.Printf("rune: '%v'\n", b) // 10

	c := "\r"
	d := '\r'
	fmt.Printf("string: '%v'\n", c)
	fmt.Printf("rune: '%v'\n", d) // 13
}
string: '
'
rune: '10'
string: '
'
rune: '13'

Read line from stdin (keyboard)

Read from the stdin (standard input) Get input from the keyboard in golang

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Enter Your name: ")
	name, _ := reader.ReadString('\n')
	fmt.Println("Hello", name)
}

Read line from stdin (keyboard) with error handling

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Enter Your name: ")
	name, err := reader.ReadString('\n')
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Hello", name)
	}
}

Trim line - remove trailing newline from string

  • trim
  • TrimSuffix
package main

import (
	"fmt"
	"strings"
)

func main() {
	line := "hello\n"
	line = strings.TrimSuffix(line, "\n") // remove newline
	fmt.Printf("'%s'\n", line)

	line = strings.TrimSuffix(line, "\n") // not bothered if there was no newline
	fmt.Printf("'%s'\n", line)

	line = "hello\n\n"
	line = strings.TrimSuffix(line, "\n") // removing only one newline
	fmt.Printf("'%s'\n", line)
}
'hello'
'hello'
'hello
'

Strings and bytes

  • strings are just aliases for bytes
  • strings are (generally?) immutable
package main

import "fmt"

func main() {
	s := "some string"
	fmt.Printf("%v %T\n", s, s)
	b := []byte{65, 66, 66, 65}
	fmt.Printf("%s %T\n", b, b)
}

Scan in a loop

package main

import (
	"fmt"
)

func main() {
	var word string
	for {
		fmt.Scan(&word)
		if word == "bye" {
			fmt.Println("Bye bye")
			break
		}
		fmt.Println(word)
	}
}
hello world
some more bye ls
hello
world
some
more
Bye bye

Compare strings

package main

import (
	"fmt"
)

func main() {
	a := "cat"
	b := "dog"
	fmt.Println(a == b)
	fmt.Println(a < b)
	fmt.Println()

	smile := "😄"
	angry := "😠"

	fmt.Println(smile == angry)
	fmt.Println(smile < angry)
	fmt.Println(smile > angry)

}
false
true

false
true
false

Raw strings

package main

import "fmt"

func main() {
	x := "a\nb"
	y := `a\nb`
	fmt.Println(x)
	fmt.Println("-------")
	fmt.Println(y)
}
a
b
-------
a\nb

Multiline strings

package main

import "fmt"

func main() {
	text := `This
is a long
string
with multiple lines
`
	fmt.Printf("Type: %T\n\n", text)
	fmt.Println(text)

}
Type: string

This
is a long
string
with multiple lines

Exercise: how long is this string?

  • The code will ask the user to type in a string and will print out the length of the string.

Exercise: which string is longer?

  • The code will prompt the user for two strings and then print out which string is longer.

Exercise: which string is ahead in ABC?

  • The code will prompt the user for two strings and then print out which string is ahead in ABC order.

Exercise: which string is ahead - menu

  • The code will first show two options like the following and ask the user to type in either 1 or 2.
1) by ABC
2) by length
  • Then the code will prompt the user for two strings and then print out which ahead comparing them either by their location in the ABC or by their length, depending on the first selection.

Exercise: Hide substring

Given a string:

  • show 4 * characters and then the last 4 characters
  • show the first 4 characters and then 4 stars
  • replace every character, except the last 4 by a *
  • replace every character, except the first 4 by a *

Here the first line is the input, the rest are the 4 output strings:

0123456789
****6789
0123****
******6789
0123******

Solution: how long is this string?

package main

import "fmt"

func main() {
	var str string
	fmt.Print("The string: ")
	fmt.Scan(&str)

	fmt.Printf("%v \n", len(str))
}

Solution: which string is longer?

  • len
package main

import "fmt"

func main() {
	var first string
	fmt.Print("First string: ")
	fmt.Scan(&first)

	var second string
	fmt.Print("Second string: ")
	fmt.Scan(&second)

	if len(first) > len(second) {
		fmt.Printf("'%v' is longer than '%v'\n", first, second)
		return
	}

	if len(first) < len(second) {
		fmt.Printf("'%v' is longer than '%v'\n", second, first)
		return
	}

	fmt.Printf("'%v' and '%v' are the same length\n", second, first)
}

Solution: which string is ahead in ABC?

package main

import "fmt"

func main() {
	var str1 string
	var str2 string
	fmt.Print("First string: ")
	fmt.Scan(&str1)
	fmt.Print("Second string: ")
	fmt.Scan(&str2)

	if str1 < str2 {
		fmt.Printf("The first string '%s' is ahead of '%s'\n", str1, str2)
	} else if str1 > str2 {
		fmt.Printf("The second string '%s' is ahead of '%s'\n", str2, str1)
	} else {
		fmt.Printf("The two string '%s' and '%s' are equal.\n", str2, str1)
	}
}

Solution: which string is ahead - menu

package main

import "fmt"

func main() {
	var str1 string
	var str2 string
	fmt.Print("First string: ")
	fmt.Scan(&str1)
	fmt.Print("Second string: ")
	fmt.Scan(&str2)

	fmt.Println("1) by length")
	fmt.Println("2) by abc")
	fmt.Print("How shall we compare them? ")
	var choice string
	fmt.Scan(&choice)

	result := 0
	switch choice {
	case "1":
		if len(str1) < len(str2) {
			result = 1
		}
		if len(str1) > len(str2) {
			result = 2
		}
	case "2":
		if str1 < str2 {
			result = 1
		}
		if str1 > str2 {
			result = 2
		}
	}
	fmt.Println(result)
	switch result {
	case 1:
		fmt.Printf("The first string '%s' is ahead of '%s'\n", str1, str2)
	case 2:
		fmt.Printf("The second string '%s' is ahead of '%s'\n", str2, str1)
	default:
		fmt.Printf("The two string '%s' and '%s' are equal.\n", str2, str1)
	}
}

Solution: Hide substring

  • Repeat
package main

import (
	"fmt"
	"strings"
)

func main() {
	input := "0123456789"
	fmt.Println(input)

	// show 4 * characters and then the last 4 characters
	out := "****" + input[len(input)-4:]
	fmt.Println(out)

	// show the first 4 characters and then 4 stars
	out = input[0:4] + "****"
	fmt.Println(out)

	// replace every character, except the last 4 by a *
	out = strings.Repeat("*", len(input)-4) + input[len(input)-4:]
	fmt.Println(out)

	// replace every character, except the first 4 by a *
	out = input[0:4] + strings.Repeat("*", len(input)-4)
	fmt.Println(out)
}

Arrays

Arrays

  • len
  • []
  • len

An array can hold a series of values. Both the length and the type of the values of an array is fixed at the time we create it. Unlike in some other languages, you cannot mix different types of values in an array. The content can be changed. We can access the individual elements of an array with a post-fix square-bracket indexing.

  • Length is part of the type!
  • Two arrays with different length are also different types.
package main

import (
	"fmt"
)

func main() {
	var res = [3]int{7, 5, 9}

	fmt.Println(res)
	fmt.Println(res[1])
	fmt.Println(len(res))

	fmt.Printf("%T\n", res)
}
[7 5 9]
5
3
[3]int

Array index out of range - compile time

package main

import (
	"fmt"
)

func main() {
	var res = [3]int{7, 5, 9}

	fmt.Println(res)
	fmt.Println(res[4])
	fmt.Println(len(res))

	fmt.Printf("%T\n", res)
}
# command-line-arguments
./array_out_of_range.go:11:17: invalid array index 4 (out of bounds for 3-element array)

Array index out of range - run time

package main

import (
	"fmt"
)

func main() {
	var res = [3]int{7, 5, 9}

	fmt.Println(res)
	i := 1
	j := 4
	fmt.Println(res[i])
	fmt.Println(res[j])
}
[7 5 9]
5
panic: runtime error: index out of range

goroutine 1 [running]:
main.main()
	/home/gabor/work/slides/golang/examples/array-index/array_index.go:14 +0x15b
exit status 2

Array change value

package main

import (
	"fmt"
)

func main() {
	var res = [3]int{1, 2, 3}

	fmt.Println(res)
	res[1] = 42
	fmt.Println(res)
}
[1 2 3]
[1 42 3]

Arrays automatic length

  • [...]

There was a slight duplication of information in the above example as we could have deducted the size from the list of initial values. This happens with the 3 dots.

package main

import (
	"fmt"
)

func main() {
	var res = [...]int{7, 5, 9}

	fmt.Println(res)
	fmt.Println(res[1])
	fmt.Println(len(res))

	fmt.Printf("%T\n", res)
}
[7 5 9]
5
3
[3]int

Array: empty and fill

On the other hand we could also initialize an array with only the size, without initial values. In this case the default values in the array will be the 0 values of the appropriate type.

package main

import (
	"fmt"
)

func main() {
	var res [3]int

	fmt.Println(res)      // [0 0 0]
	fmt.Println(res[1])   // 0
	fmt.Println(len(res)) // 3

	fmt.Printf("%T\n", res) // [3]int

	res[1] = 85
	res[0] = 97
	res[2] = 93

	fmt.Println(res)      // [97 85 93]
	fmt.Println(res[1])   // 85
	fmt.Println(len(res)) // 3

	fmt.Printf("%T\n", res) // [3]int
}

Empty array of strings

You can also use an array of strings.

package main

import (
	"fmt"
)

func main() {
	var names [2]string
	if names[0] == "" {
		fmt.Println("empty") // empty
	}

	fmt.Println(names)      // [  ]
	fmt.Println(names[1])   //
	fmt.Println(len(names)) // 2

	fmt.Printf("%T\n", names) // [2]string

	names[1] = "Jane Doe"
	names[0] = "Joe"

	fmt.Println(names)    // [Joe Jane Doe]
	fmt.Println(names[1]) // Jane Doe
}

Array assignment (copy)

package main

import (
	"fmt"
)

func main() {
	var ar = [...]int{1, 2, 3}
	br := ar

	fmt.Println(ar)
	fmt.Println(br)
	ar[1] = 42
	fmt.Println(ar)
	fmt.Println(br)
}
[1 2 3]
[1 2 3]
[1 42 3]
[1 2 3]

Assigning an array is by default creates a copy. Some cases this is what we want, in other cases we'd prefer to have reference / pointer to the same data in memmory.

Array assignment (pointer)

  • &
package main

import (
	"fmt"
)

func main() {
	var ar = [3]int{1, 2, 3}
	br := &ar

	fmt.Println(ar)
	fmt.Println(br)
	ar[1] = 42
	fmt.Println(ar)
	fmt.Println(br)

	fmt.Println(ar[1])
	fmt.Println(br[1])

}
[1 2 3]
&[1 2 3]
[1 42 3]
&[1 42 3]
42
42

This way br is a poiner to ar so they refer to the same data in memory. Even though br is a pointer we can still access elements in the same way as in the original array. We'll discuss pointers in depth later on.

Matrix (two dimensional array)

package main

import (
	"fmt"
)

func main() {
	matrix := [3][3]int{
		[3]int{1, 2, 3},
		[3]int{4, 1, 0},
		[3]int{0, 0, 1},
	}
	fmt.Println(matrix)
	fmt.Println(matrix[0])
	fmt.Println(matrix[1][0])
}

// 	matrix := [...][3]int{    Would also work
//  var matrix [3][3]int      Is to define without giving values
[[1 2 3] [4 1 0] [0 0 1]]
[1 2 3]
4

For loop on array - iterate over array

  • range
  • for
package main

import (
	"fmt"
)

func main() {
	dwarfs := [...]string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}

	fmt.Printf("%T\n%v\n", dwarfs, dwarfs)

	for i, name := range dwarfs {
		fmt.Printf("location: %d  name: %s\n", i, name)
	}
}
[7]string
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
location: 0  name: Doc
location: 1  name: Grumpy
location: 2  name: Happy
location: 3  name: Sleepy
location: 4  name: Bashful
location: 5  name: Sneezy
location: 6  name: Dopey

for loop on values of array (no index)

package main

import "fmt"

func main() {
	dwarfs := [...]string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}
	for _, name := range dwarfs {
		fmt.Println(name)
	}
}
Doc
Grumpy
Happy
Sleepy
Bashful
Sneezy
Dopey

Arrays conclusion

x := [3]int{}               // 3-long arrays with default 0 values
x := [...]int{2, 7, 4}      // 3-long array with values
x := [3]int{2, 7, 4}        // same, but with unnecessary duplication of information
z := x[2]                    // access element 
x[2] = 42                   // assign to element
x[4]                        // out-of-range index: compile-time or run-time error
for i, v := range array {}  // iterator over index and value 

Exercise: Language Selector

  • Create a menu where people can pick a language by selecting the number next to the language.
  • You have a list of languages in an array as we have in the skeleton.
package main

import (
	"fmt"
)

func main() {
	languages := [...]string{"English", "French", "Urdu", "Farsi", "Hungarian", "Hebrew"}
	fmt.Println(languages)

}
  • It displays a "menu" that associates each language with a number.
  • The user types in one of the numbers.
  • The code checks if it is a number and if it is in the correct range.
  • If it is, then we display the selected language.
  • The interaction will look like this:
1) English
2) French
3) Urdu
4) Farsi
5) Hungarian
6) Hebrew
Select a number: 5
5
Selection: Hungarian

Exercise: count digits

  • Given a list of digits, count how many time each digit appears.

Skeleton:

package main

import "fmt"

func main() {
	digits := [...]int{3, 7, 6, 7, 9, 1, 3, 7, 8, 3, 1, 7, 0, 1, 2, 3}
	fmt.Println(digits)

	// ...
}

Expected output:

[3 7 6 7 9 1 3 7 8 3 1 7 0 1 2 3]
0: 1
1: 3
2: 1
3: 4
4: 0
5: 0
6: 1
7: 4
8: 1
9: 1

Exercise: count digits from string

  • Given a string of digits, count how many times each digit appears?

Skeleton:

package main

import "fmt"

func main() {
	dgts := "3767913713127648173"
	fmt.Println(dgts)
}

Expected output:

3767913713127648173
[3 7 6 7 9 1 3 7 1 3 1 2 7 6 4 8 1 7 3]
0 0
1 4
2 1
3 4
4 1
5 0
6 2
7 5
8 1
9 1

Exercise: Report statistics

Given some numbers in an array, print out the total, the average, the minimum and the maximum values.

package main

import "fmt"

func main() {
	numbers := [...]int{2, 3, 5, -1, 1, 8}
	fmt.Println(numbers)

}

Expected output:

[2 3 5 -1 1 8]
Total: 18
Average: 3
Min: -1
Max: 8

Solution: Language Selector

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	languages := [...]string{"English", "French", "Urdu", "Farsi", "Hungarian", "Hebrew"}
	for i, lang := range languages {
		fmt.Printf("%v) %v\n", i+1, lang)
	}
	var choiceStr string
	fmt.Print("Select a number: ")
	fmt.Scan(&choiceStr)
	fmt.Println(choiceStr)

	choice, err := strconv.Atoi(choiceStr)
	if err != nil {
		fmt.Printf("The selection '%v' was not an integer\n", choiceStr)
		fmt.Println(err)
		os.Exit(1)
	}

	if choice <= 0 || choice > len(languages) {
		fmt.Printf("The selection '%v' was out of range\n", choiceStr)
		os.Exit(1)
	}
	fmt.Printf("Selection: %v\n", languages[choice-1])

}

Solution: count digits

package main

import "fmt"

func main() {
	count := [10]int{}

	digits := [...]int{3, 7, 6, 7, 9, 1, 3, 7, 8, 3, 1, 7, 0, 1, 2, 3}
	fmt.Println(digits)

	for i := 0; i < len(digits); i++ {
		//fmt.Println(digits[i])
		count[digits[i]]++
	}
	for i := 0; i < 10; i++ {
		fmt.Printf("%d: %d\n", i, count[i])
	}
}

Solution: count digits from string

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	count := [10]int{}

	dgts := "3767913713127648173"
	fmt.Println(dgts)
	digits := strings.Split(dgts, "")
	fmt.Println(digits)

	for _, digitStr := range digits {
		digit, _ := strconv.Atoi(digitStr)
		count[digit]++
	}

	for i, cnt := range count {
		fmt.Printf("%v %v\n", i, cnt)
	}
}

Solution: Report statistics

package main

import "fmt"

func main() {
	numbers := [...]int{2, 3, 5, -1, 1, 8}
	fmt.Println(numbers)
	if len(numbers) == 0 {
		fmt.Println("We cannot provide stats on empty set of numbers")
		return
	}

	total := 0
	min := numbers[0]
	max := numbers[0]
	for _, num := range numbers {
		if num < min {
			min = num
		}
		if num > max {
			max = num
		}
		total += num
	}
	//fmt.Println(min(numbers))
	fmt.Printf("Total: %v\n", total)
	average := float32(total) / float32(len(numbers))
	fmt.Printf("Average: %v\n", average)
	fmt.Printf("Min: %v\n", min)
	fmt.Printf("Max: %v\n", max)
}

Variables

Variable Declarations

  • Every variable must be declared.
  • Not using a declared variable is a compile-time error.

Variables - Naming

  • Lower-case variables are scoped to the current package.

  • Upper-case variables are exported from the package and globally visible.

  • Block-scoped variables (e.g. in a function) are only visible in the block.

  • camelCase for private name

  • PascalCase for public names

  • Special case when part of the name is an acronym:

  • theURL

  • theHTML

Declare multiple variables in one line

package main

import (
	"fmt"
)

func main() {
	a, b := 23, 12
	fmt.Println(a)
	fmt.Println(b)
}
23
12

Variables cannot be redefined (no new variables on left side of :=)

package main

import (
	"fmt"
)

func main() {
	a := 42
	fmt.Println(a)

	a := 13
	fmt.Println(a)

}
# command-line-arguments
./redefine_variable_fail.go:11:4: no new variables on left side of :=

At least one new variable on the left side of :=

  • The existing variable must receive a value of the same type it had earlier.
package main

import "fmt"

func main() {
	a, b := 1, 2
	fmt.Println(a, b)
	a, c := 3, 4
	fmt.Println(a, b, c)

}
1 2
3 2 4

Use the same err on the left hand side

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	a, err := strconv.Atoi("23")
	fmt.Println(a)
	if err != nil {
		os.Exit(1)
	}
	b, err := strconv.ParseFloat("3.4", 64)
	if err != nil {
		os.Exit(1)
	}
	fmt.Println(b)
}
23
3.4

Package variable

package main

import (
	"fmt"
)

var y int

func main() {
	showY()
	setY()
	showY()
}

func setY() {
	y = 42
}
func showY() {
	fmt.Println(y)
}
0
42

Shadowing package variable

package main

import (
	"fmt"
)

var x float32 = 3.14

func main() {
	fmt.Printf("%v %T\n", x, x)
	x := 23
	fmt.Printf("%v %T\n", x, x)
}
3.14 float32
23 int

Variable scope

package main

import (
	"fmt"
)

var x float32 = 3.14

func main() {
	fmt.Printf("%v %T\n", x, x)
	x := true
	fmt.Printf("%v %T\n", x, x)
	if true {
		x := "hello"
		fmt.Printf("%v %T\n", x, x)
	}

	for x := 1; x < 2; x++ {
		fmt.Printf("%v %T\n", x, x)
	}

	showX()

	{
		x := 77
		fmt.Println(x)
	}

	fmt.Printf("%v %T\n", x, x)

}

func showX() {
	fmt.Printf("showX: %v %T\n", x, x)
}
3.14 float32
[Foo Bar] []string
hello string
1
showX: 3.14 3.14[Foo Bar] []string

Constants

Constants

  • const

  • const - constants

package main

import (
	"fmt"
)

const pi float32 = 3.14

func main() {
	fmt.Println(pi)
}
3.14

Constants cannot be changed

package main

import (
	"fmt"
)

const pi float32 = 3.14

func main() {
	fmt.Println(pi)
	pi = 23.14
}
# command-line-arguments
./const_protection.go:11:5: cannot assign to pi

Shadowing constants

  • constants can be shadowed as well, but it is not a good idea to do so.
package main

import (
	"fmt"
)

const pi float32 = 1.1

func main() {
	fmt.Println(pi)

	const pi float32 = 2.2
	fmt.Println(pi)

	{
		pi := 3.3
		fmt.Println(pi)
		pi = 4.4
		fmt.Println(pi)

	}
	fmt.Println(pi)
}
1.1
2.2
3.3
4.4
2.2

It is declared both outside the main function and inside of it.

Constant blocks

package main

import (
	"fmt"
)

const (
	a = 1
	b = 3
	c = 5
)

func main() {
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
}
1
3
5

iota

  • iota

  • iota (increment by 1 on every use in every constant block of var) enumeration

package main

import (
	"fmt"
)

const (
	a = iota
	b = iota
	c = iota
)

const (
	d = iota
	e
	f
)

func main() {
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	fmt.Println()

	fmt.Println(d)
	fmt.Println(e)
	fmt.Println(f)
}
0
1
2

0
1
2

iota skipping 0

  • iota
package main

import (
	"fmt"
)

const (
	_ = iota
	one
	two
	three
)

const (
	six = iota + 6
	seven
	eight
)

func main() {
	fmt.Println(one)
	fmt.Println(two)
	fmt.Println(three)
	fmt.Println()
	fmt.Println(six)
	fmt.Println(seven)
	fmt.Println(eight)
}
1
2
3

6
7
8

const powers of 2

  • iota
package main

import (
	"fmt"
)

const (
	one = 1 << iota
	two
	four
	eight
	sixteen
)

func main() {
	fmt.Println(one)
	fmt.Println(two)
	fmt.Println(four)
	fmt.Println(eight)
	fmt.Println(sixteen)

	fmt.Println()
	fmt.Println(two | eight)
}
1
2
4
8
16

10

Functions

Hello World function

  • func
package main

import (
	"fmt"
)

func main() {
	sayHello()
}

func sayHello() {
	fmt.Printf("Hello World!\n")
}
Hello World!

Hello You function (passing parameter)

  • func
package main

import (
	"fmt"
)

func main() {
	hello("Foo")
}

func hello(text string) {
	fmt.Printf("Hello %s\n", text)
}
Hello Foo

Function with multiple parameters

package main

import (
	"fmt"
)

func main() {
	printSubstring("The black cat climbed the green tree", 10, 13)
}

func printSubstring(text string, start, end int) {
	fmt.Println(text[start:end])
}
cat

Function with return value

  • return
package main

import (
	"fmt"
)

func main() {
	res := add(2, 3)
	fmt.Println(res)
}

func add(a, b int) int {
	return a + b
}
5

Multiple return values

package main

import "fmt"

func main() {
	fmt.Println("Hello World")
	a, b := calc(3, 2)
	fmt.Printf("%v and %v\n", a, b)
}

func calc(a, b int) (int, int) {
	return a + b, a - b
}
Hello World
5 and 1

Returning an error from a functions

package main

import "fmt"

func main() {
	a, err := div(6, 2)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("6/2 = %v\n", a)

	b, err := div(6, 0)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("6/0 = %v\n", b)

}

func div(a, b float32) (float32, error) {
	if b == 0 {
		return 0.0, fmt.Errorf("Can't divide by 0")
	}

	result := a / b
	return result, nil
}
6/2 = 3
Can't divide by 0

Named return parameters

package main

import "fmt"

func main() {
	a, err := div(6, 2)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("6/2 = %v\n", a)

	b, err := div(6, 0)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("6/0 = %v\n", b)
}

func div(a, b float32) (result float32, err error) {
	if b == 0 {
		err = fmt.Errorf("Can't divide by 0")
		return
	}

	result = a / b
	return
}
6/2 = 3
Can't divide by 0

Passing a function as a parameter - Callback function

As it is right now, the run function can only accept callback functions without any parameter

package main

import (
	"fmt"
)

func main() {
	run(hello)
	run(world)
}

func hello() {
	fmt.Println("Hello")
}

func world() {
	fmt.Println("World!")
}

func run(f func()) {
	f()
}

Callback function with one parameter

package main

import (
	"fmt"
)

func main() {
	// fmt.Printf("%T\n", hello)  // func()
	run(hello)
	run(world)
}

func hello(fullName string) {
	fmt.Printf("Hello %s\n", fullName)
}

func world(name string) {
	fmt.Printf("World! %s\n", name)
}

func run(f func(text string)) {
	f("Foo Bar")
}

Anonymous functions

package main

import "fmt"

func main() {
	fmt.Println("Start")
	run(func() {
		fmt.Println("Hello")
	})
	fmt.Println("End")
}

func run(f func()) {
	fmt.Printf("%T\n", f)
	f()
}
Start
func()
Hello
End

Anonymous functions assigned to name

package main

import "fmt"

func main() {
	f := func() {
		fmt.Println("Hello Anonymous!")
	}
	fmt.Println("Start")
	f()
	fmt.Println("End")
	fmt.Printf("%T", f)
}
Start
Hello Anonymous!
End
func()

Numbers are passed by value

package main

import "fmt"

func main() {
	a := 1
	fmt.Printf("before %v\n", a)
	inc(a)
	fmt.Printf("after %v\n", a)
}

func inc(val int) {
	fmt.Printf("val in inc: %v\n", val)
	val++
	fmt.Printf("val in inc: %v\n", val)
}
before 1
val in inc: 1
val in inc: 2
after 1

Function Overloading - Multiple function with the same name

  • There is no function overloading in Golang
package main

import "fmt"

func main() {
}

func say() {
	fmt.Println("Hello World")
}

func say(name string) {
	fmt.Printf("Hello %v\n", name)
}
# command-line-arguments
./redeclare_function.go:12:6: say redeclared in this block
	previous declaration at ./redeclare_function.go:8:6

Defer

  • defer

Every defer statement is executed after the enclosing function ends. In reverse order. (Similar to END block in Perl, similar to with context in python)

package main

import "fmt"

func main() {
	fmt.Println("first")
	defer fmt.Println("one")
	fmt.Println("second")
	defer fmt.Println("two")
	fmt.Println("third")
}
$ go run defer.go

first
second
third
two
one

Defer early returns

package main

import "fmt"

func main() {
	run(false)
	fmt.Println("--------")
	run(true)
}

func run(early bool) {
	fmt.Println("first")
	defer fmt.Println("do at the end")
	fmt.Println("second")
	if early {
		return
	}

	fmt.Println("last")
}
first
second
last
do at the end
--------
first
second
do at the end

Deferred cd in a function

package main

import (
	"fmt"
	"log"
	"os"
)

func main() {
	var dir string
	dir, _ = os.Getwd()
	fmt.Printf("Before  %v\n", dir)
	cd("..", showDir)
	dir, _ = os.Getwd()
	fmt.Printf("After %v\n", dir)
}

func showDir() {
	fmt.Println("Hello")
	dir, _ := os.Getwd()
	fmt.Printf("Inside:  %v\n", dir)
}

func cd(path string, f func()) {
	//fmt.Println(path)
	cwd, err := os.Getwd()
	if err != nil {
		log.Panic(err)
	}
	defer os.Chdir(cwd)
	err = os.Chdir(path)
	f()
}

Defer in if-statements

  • Even if we put the defer call inside an if-statement, the deferred function will only execute at the end of the enclosing function.
package main

import (
	"fmt"
)

func main() {
	if true {
		fmt.Println("Before")
		defer fmt.Println("Middle")
		fmt.Println("After")	
	}

	fmt.Println("Outside")
}

Defer and parameters

package main

import "fmt"

func main() {
	text := "start"
	defer fmt.Println(text)
	defer say(text)
	text = "end"
	fmt.Printf("end: %v\n", text)
}

func say(text string) {
	fmt.Println(text)
}
  • The deffered function will see the its parameter when we defer the function not when it is executed

Exercise: Fibonacci

Implement a function that accepts a positive integer (n) and returns the n-th numbers of the Fibonacci series.

Exercise: Defer remove temporary directory

Write a function that will create a temporary directory and then it will remove it once the function is done. Make sure the directory is removed no matter how you exit from the function.

package main

import "fmt"

func main() {
	doSomething()

}

func doSomething() {
	dir := createTempDir()

	//defer removeTempDir(dir)

	if true {
		removeTempDir(dir)
		return
	}

	removeTempDir(dir)
}

func createTempDir() string {
	return "some/path"
}

func removeTempDir(dir string) {
	fmt.Printf("")
}

Exercise: FizzBuzz in function

Write a function that given a number will print the number itself or something else according to these rules:

  • For multiples of 3 print "Fizz" instead of the number.
  • For multiples of 5 print "Buzz".
  • For numbers which are multiples of both three and five print "FizzBuzz".

that prints the numbers from 1 to 100.

Expected output:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz

Exercise: ROT13

Implement a function that given a string will return its ROT13 encryption and given the encrypted string will return the original string. Characters that are not letters will remain the same.

e.g. "hello World!" is converted to "Uryyb Jbeyq!"

Solution: FizzBuzz in function

package main

import (
	"fmt"
	"strconv"
)

func main() {
	for i:=1; i < 16; i++ {
		fmt.Println(fb(i))
	}
}

func fb(n int) string {
	resp := ""

	if n % 3 == 0 {
		resp += "Fizz"
	}
	if n % 5 == 0 {
		resp += "Buzz"
	}
	if resp == "" {
		resp = strconv.Itoa(n)
	}
	return resp
}
package main

import (
	"fmt"
	"strconv"
	"testing"
)

func TestRegular(t *testing.T) {
	for _, n := range [...]int{1, 2, 4} {
		expected := strconv.Itoa(n)
		actual := fb(n)
		if actual != expected {
			t.Error(fmt.Sprintf("Expected '%v', Actual '%v'", expected, actual))
		}

	}
}

func TestFizz(t *testing.T) {
	expected := "Fizz"
	for _, n := range [...]int{3, 6, 9, 12, 18} {
		actual := fb(n)
		if actual != expected {
			t.Error(fmt.Sprintf("Expected '%v', Actual '%v'", expected, actual))
		}

	}
}

func TestBuzz(t *testing.T) {
	expected := "Buzz"
	for _, n := range [...]int{5, 10, 20, 25} {
		actual := fb(n)
		if actual != expected {
			t.Error(fmt.Sprintf("Expected '%v', Actual '%v'", expected, actual))
		}

	}
}

func TestFizzBuzz(t *testing.T) {
	expected := "FizzBuzz"
	for _, n := range [...]int{15, 30, 60} {
		actual := fb(n)
		if actual != expected {
			t.Error(fmt.Sprintf("Expected '%v', Actual '%v'", expected, actual))
		}

	}
}

Solution: ROT13

package main

import (
	"fmt"
	"strings"
)

func main() {
	original := "abcdefghijklmnopqrstyvwxyz ABCQRS !?¡ñ"
	//original = "Hello World!"
	fmt.Println(len(original))
	encrypted := rot13(original)
	decrypted := rot13(encrypted)

	fmt.Println(original)
	fmt.Println(encrypted)
	fmt.Println(decrypted)
	fmt.Println(original == decrypted)
}

func rot13(input string) string {
	result := make([]string, 0, len(input))
	for _, chr := range input {
		if 'a' <= chr && chr <= 'z' {
			chr = ((chr - 'a' + 13) % 26) + 'a'
		}
		if 'A' <= chr && chr <= 'Z' {
			chr = ((chr - 'A' + 13) % 26) + 'A'
		}
		result = append(result, string(chr))
	}
	output := strings.Join(result, "")
	return output
}

Slices

Slice of an array

  • len
  • cap

We can use the array[start:end] syntax to get a slice of the array. The returned object is called a slice and it is a window onto the underlying array. It has a length defined by the start and end of the slice. It also has a capacity, which is all the places in the array from the start of the sliced to the end of the array.

package main

import (
	"fmt"
)

func main() {
	planets := [...]string{"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn"}

	fmt.Println(planets)
	fmt.Println(len(planets))
	fmt.Println(cap(planets))
	fmt.Printf("%T\n", planets)

	part := planets[1:4]
	fmt.Println(part)
	fmt.Println(len(part))
	fmt.Println(cap(part))
	fmt.Printf("%T\n", part)
}
[Mercury Venus Earth Mars Jupiter Saturn]
6
6
[6]string
[Venus Earth Mars]
3
5
[]string

Slice

  • slice

  • len

  • cap

  • You can view a slice to be just a very flexible array.

  • Actually it is a slice of an array. A view on a section of the array.

  • len - length

  • cal - capacity

The only difference you can see when we create a slice is that we don't explicitely say its size and we also don't put the 3 dots ... in the square bracket.

There is also a cap function that returns the size of the underlying array.

We can access the elements of a slice using the postfix square-bracket notation. Just as with arrays.

package main

import (
	"fmt"
)

func main() {
	var dwarfs = []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}

	fmt.Println(dwarfs)
	fmt.Println(dwarfs[0])
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))
}
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
Doc
7
7

Change value in slice

We can access values in a slice and change them exactly as we do with an array. After all the slice is juts a window onto some array behind the scenes.

package main

import (
	"fmt"
)

func main() {
	var dwarfs = []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}

	fmt.Println(dwarfs)
	fmt.Println(dwarfs[1])
	dwarfs[1] = "Snowwhite"

	fmt.Println(dwarfs)
	fmt.Println(dwarfs[1])

	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))
}
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
Grumpy
[Doc Snowwhite Happy Sleepy Bashful Sneezy Dopey]
Snowwhite
7
7

Slice Assign

  • It is an alias to the other slice, same place in memory

Unlike with arrays, when we assign a slice, we only assign the location of the slice in the memory. So if we change the content of one of the slices then the other one also sees the change.

package main

import (
	"fmt"
)

func main() {
	var dwarfs = []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}
	theSeven := dwarfs

	fmt.Println(dwarfs)
	fmt.Println(theSeven)

	dwarfs[1] = "Snowwhite"

	fmt.Println(dwarfs)
	fmt.Println(theSeven)
}
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
[Doc Snowwhite Happy Sleepy Bashful Sneezy Dopey]
[Doc Snowwhite Happy Sleepy Bashful Sneezy Dopey]

Slice of a slice

We can take a slice of a slice. It is just another, most likely smaller, window on the same array in the background.

package main

import (
	"fmt"
)

func main() {
	var dwarfs = []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}

	a := dwarfs[:]
	b := dwarfs[2:4]

	fmt.Println(dwarfs)
	fmt.Println(a)
	fmt.Println(b)

	dwarfs[2] = "Snowwhite"
	fmt.Println()
	fmt.Println(dwarfs)
	fmt.Println(a)
	fmt.Println(b)
}
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
[Happy Sleepy]

[Doc Grumpy Snowwhite Sleepy Bashful Sneezy Dopey]
[Doc Grumpy Snowwhite Sleepy Bashful Sneezy Dopey]
[Snowwhite Sleepy]
  • This would work on arrays as well: Slices of an array

Append to a slice

package main

import "fmt"

func main() {
	base := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}
	fmt.Println(base)
	fmt.Println(len(base))
	part := base[3:7]
	fmt.Println(part)
	fmt.Println(len(part))
	fmt.Println(cap(part))
	fmt.Println()

	part = append(part, "X") // the slice was extended in the same array
	fmt.Println(part)
	fmt.Println(base)
	fmt.Println(len(part))
	fmt.Println(cap(part))
	fmt.Println()

	part = append(part, "Y") // creates a new, larger array and copyes the data.
	fmt.Println(part)
	fmt.Println(base)
	fmt.Println(len(part))
	fmt.Println(cap(part))
}
[a b c d e f g h]
8
[d e f g]
4
5

[d e f g X]
[a b c d e f g X]
5
5

[d e f g X Y]
[a b c d e f g X]
6
10

Slice append

  • append
package main

import (
	"fmt"
)

func main() {
	dwarfs := []string{}
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))

	dwarfs = append(dwarfs, "Happy")
	d := dwarfs
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))
	fmt.Println()

	dwarfs = append(dwarfs, "Grumpy", "Sleepy", "Doc", "Bashful", "Sneezy", "Dopey")
	fmt.Println(d)
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))
	fmt.Println()

	dwarfs = append(dwarfs, "Snow white")
	fmt.Println(d)
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))
}
[]
0
0
[Happy]
1
1

[Happy]
[Happy Grumpy Sleepy Doc Bashful Sneezy Dopey]
7
7

[Happy]
[Happy Grumpy Sleepy Doc Bashful Sneezy Dopey Snow white]
8
14

Remove last element of slice (pop)

  • pop
package main

import (
	"fmt"
)

func main() {
	dwarfs := []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}
	x := dwarfs
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))

	// remove last element
	dwarfs = dwarfs[:len(dwarfs)-1]
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))

	// shorten the slice (remove from the end)
	dwarfs = dwarfs[0:3]
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))

	d := append(dwarfs, "qqrq")
	fmt.Println(d)
	fmt.Println(len(d))
	fmt.Println(cap(d))
	fmt.Println(x)
	fmt.Println(dwarfs)

}
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
7
7
[Doc Grumpy Happy Sleepy Bashful Sneezy]
6
7
[Doc Grumpy Happy]
3
7
  • The capacity remaind the same

Remove first element of slice (shift, pop(0))

  • shift
package main

import (
	"fmt"
)

func main() {
	dwarfs := []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))

	// remove first element
	first := dwarfs[0]
	fmt.Println(first)

	dwarfs = dwarfs[1:]
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))

}
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
7
7
Doc
[Grumpy Happy Sleepy Bashful Sneezy Dopey]
6
6

Pre-allocate capacity for slice with make

  • make
package main

import (
	"fmt"
)

func main() {
	dwarfs := make([]string, 0, 10)
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))

	dwarfs = append(dwarfs, "Happy")
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))

	dwarfs = append(dwarfs, "Grumpy", "Sleepy")
	fmt.Println(dwarfs)
	fmt.Println(len(dwarfs))
	fmt.Println(cap(dwarfs))
}
[]
0
10
[Happy]
1
10
[Happy Grumpy Sleepy]
3
10

For loop in slice - iterate over slice

  • range
  • for
package main

import (
	"fmt"
)

func main() {
	dwarfs := []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}

	fmt.Printf("%T\n%v\n", dwarfs, dwarfs)

	for i, name := range dwarfs {
		fmt.Printf("location: %d  name: %s\n", i, name)
	}
}
[]string
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
location: 0  name: Doc
location: 1  name: Grumpy
location: 2  name: Happy
location: 3  name: Sleepy
location: 4  name: Bashful
location: 5  name: Sneezy
location: 6  name: Dopey

for loop on values of slice (no index)

package main

import "fmt"

func main() {
	dwarfs := []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}
	for _, name := range dwarfs {
		fmt.Println(name)
	}
}

Merge two slices

  • ...
package main

import (
	"fmt"
)

func main() {
	a := []int{1, 2, 3}
	b := []int{4, 5, 6}
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println()

	// c := append(a, b)  // cannot use b (type []int) as type int in append
	c := append(a, b...)
	fmt.Println(c)
	fmt.Printf("%T\n", c)
	fmt.Println(len(c))
	fmt.Println(cap(c))
}
[1 2 3]
[4 5 6]

[1 2 3 4 5 6]
[]int
6
6

Find element in array or slice

package main

import (
	"fmt"
)

func main() {
	celestialObjects := []string{"Moon", "Gas", "Asteroid", "Dwarf", "Asteroid", "Moon", "Asteroid"}
	fmt.Println(celestialObjects)
	var location int
	var found bool
	var str string

	str = "Asteroid"
	location, found = findElement(str, celestialObjects)
	if found {
		fmt.Printf("Found %v in %v\n", str, location)
	} else {
		fmt.Printf("Not found %v in %v\n", str, location)
	}

	str = "Star"
	location, found = findElement(str, celestialObjects)
	if found {
		fmt.Printf("Found %v in %v\n", str, location)
	} else {
		fmt.Printf("Not found %v in %v\n", str, location)
	}

}

func findElement(elem string, elements []string) (int, bool) {
	for i, value := range elements {
		if value == elem {
			return i, true
		}
	}
	return -1, false
}
[Moon Gas Asteroid Dwarf Asteroid Moon Asteroid]
Found Asteroid in 2
Not found Star in -1

Remove element of slice

package main

import (
	"fmt"
)

func main() {
	celestialObjects := []string{"Moon", "Gas", "Asteroid", "Dwarf", "Star", "Commet"}
	fmt.Println(celestialObjects)

	celestialObjects = append(celestialObjects[:3], celestialObjects[4:]...)
	fmt.Println(celestialObjects)

}
[Moon Gas Asteroid Dwarf Star Commet]
[Moon Gas Asteroid Star Commet]

Weird merge slices

  • When we try to remove an element in the middle but assign to a new name.
  • Avoid this mess!
package main

import (
	"fmt"
)

func main() {
	celestialObjects := []string{"Moon", "Gas", "Asteroid", "Dwarf", "Star", "Commet"}
	fmt.Println(celestialObjects)

	fmt.Println(celestialObjects[:3])
	fmt.Println(celestialObjects[4:])
	fmt.Println()

	part := append(celestialObjects[:3], celestialObjects[4:]...)
	fmt.Println(part)
	fmt.Println(celestialObjects)
	fmt.Printf("part - len: %v cap: %v\n", len(part), cap(part))
	fmt.Printf("orig - len: %v cap: %v\n", len(celestialObjects), cap(celestialObjects))
}
[Moon Gas Asteroid Dwarf Star Commet]
[Moon Gas Asteroid]
[Star Commet]

[Moon Gas Asteroid Star Commet]
[Moon Gas Asteroid Star Commet Commet]

Sort slice

  • sort

  • sort.Strings

  • sort.Ints

  • sort

package main

import (
	"fmt"
	"sort"
)

func main() {
	dwarfs := []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}
	fmt.Println(dwarfs)

	sort.Strings(dwarfs)
	fmt.Println(dwarfs)

	scores := []int{17, 3, 42, 28}
	fmt.Println(scores)

	sort.Ints(scores)
	fmt.Println(scores)
}
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
[Bashful Doc Dopey Grumpy Happy Sleepy Sneezy]
[17 3 42 28]
[3 17 28 42]

Are values sorted?

  • StringsAreSorted
  • IntsAreSorted
  • Float64sAreSorted
package main

import (
	"fmt"
	"sort"
)

func main() {
	dwarfs := []string{"Doc", "Happy", "Grumpy"}
	fmt.Println(dwarfs)
	fmt.Println(sort.StringsAreSorted(dwarfs))

	sort.Strings(dwarfs)
	fmt.Println(dwarfs)
	fmt.Println(sort.StringsAreSorted(dwarfs))
	fmt.Println()

	greekNames := []string{"Alpha", "Beta"}
	fmt.Println(sort.StringsAreSorted(greekNames))
	fmt.Println()

	scores := []int{17, 3, 42, 28}
	fmt.Println(scores)
	fmt.Println(sort.IntsAreSorted(scores))
}
[Doc Happy Grumpy]
false
[Doc Grumpy Happy]
true

true

[17 3 42 28]
false

Sort strings by length

package main

import (
	"fmt"
	"sort"
)

func main() {
	animals := []string{"snail", "dog", "cow", "elephant", "chicken", "mouse"}
	fmt.Println(animals)

	sort.Strings(animals)
	fmt.Println(animals)

	sort.Slice(animals, func(i, j int) bool {
		return len(animals[i]) < len(animals[j])
	})
	fmt.Println(animals)
}
[snail dog cow elephant chicken mouse]
[chicken cow dog elephant mouse snail]
[cow dog mouse snail chicken elephant]

Sort strings by length and then abc order

package main

import (
	"fmt"
	"sort"
)

func main() {
	animals := []string{"snail", "dog", "cow", "elephant", "chicken", "mouse"}
	fmt.Println(animals)

	sort.Slice(animals, func(i, j int) bool {
		if len(animals[i]) != len(animals[j]) {
			return len(animals[i]) < len(animals[j])
		}
		return animals[i] < animals[j]
	})
	fmt.Println(animals)

}
[snail dog cow elephant chicken mouse]
[cow dog mouse snail chicken elephant]

Search in slice

package main

import (
	"fmt"
	"sort"
)

func main() {
	dwarfs := []string{"Doc", "Grumpy", "Happy", "Sleepy", "Bashful", "Sneezy", "Dopey"}
	fmt.Println(dwarfs)
	fmt.Printf("Length: %v\n", len(dwarfs))
	fmt.Printf("StringsAreSorted: %v\n", sort.StringsAreSorted(dwarfs))
	res := sort.SearchStrings(dwarfs, "Sleepy")
	fmt.Printf("Index of Sleepy %v\n", res)

	name := "Snow White"
	res = sort.SearchStrings(dwarfs, name)
	fmt.Printf("Index of '%v': %v\n", name, res)

}
[Doc Grumpy Happy Sleepy Bashful Sneezy Dopey]
Length: 7
StringsAreSorted: false
Index of Sleepy 3
Index of 'Snow White': 7

Variadic function (arbitrary number of parameters)

  • Unknown number of parameters
  • The function receives a slice containinig the values.
  • variadic functions
package main

import (
	"fmt"
)

func main() {
	res := sum(2, 3, 7, 11)
	fmt.Println(res)
}

func sum(num ...int) int {
	fmt.Printf("%T  %v\n", num, num) // slice
	mySum := 0
	for _, v := range num {
		mySum += v
	}
	return mySum
}
[]int  [2 3 7 11]
23

Defer and slice

package main

import "fmt"

func main() {
	words := []string{"zero", "one", "two"}
	defer say(words)
	words[0] = "END"
}

func say(text []string) {
	fmt.Println(text)
}

Exercise: count words

  • Count how many times each word appears in a given slice
  • You can use this skeleton:
package main

import (
	"fmt"
)

func main() {
	celestialObjects := []string{"Moon", "Gas", "Asteroid", "Dwarf", "Asteroid", "Moon", "Asteroid"}
	fmt.Println(celestialObjects)

}
  • Expected output:
[Moon Gas Asteroid Dwarf Asteroid Moon Asteroid]
Moon       2
Gas        1
Asteroid   3
Dwarf      1

Exercise: Create a list of words from sentences

  • Given a list of strings with words separated by spaces, create a single list of all the words.
package main

import (
	"fmt"
)

func main() {
	// source:
	lines := []string{
		"grape banana mango",
		"nut orange peach",
		"apple nut banana apple mango",
	}
	fmt.Println(lines)

	// result:
	fruits := []string{"grape", "banana", "mango", "nut", "orange", "peach", "apple", "nut", "banana", "apple", "mango"}
	fmt.Println(fruits)
	for _, fruit := range fruits {
		fmt.Println(fruit)
	}
}

Exercise: Create a unique list of values

Given a list of strings, create a list of unique values sorted by abc.

package main

import "fmt"

func main() {
	// input
	fruits := []string{"grape", "banana", "mango", "nut", "orange", "peach", "apple", "nut", "banana", "apple", "mango"}
	fmt.Println(fruits)

	// expected output:
	uniqueFruites := []string{"apple", "banana", "grape", "mango", "nut", "orange", "peach"}
	fmt.Println(uniqueFruites)
}

Exercise: Reverse Polish Calculator

  • Implement a reverse polish calculator.

Examples:

go run rpc.go "2 7 + =" 
9
go run rpc.go "2.1 7.2 - =" 
-5.1
go run rpc.go "7 2 / =" 
3.5
go run rpc.go "2 3 5 + * ="
16

Exercise: DNA Sequencing

  • A, C, T, G are called bases or nucleotides
  • Given a sequence like "ACCGXXCXXGTTACTGGGCXTTGT" (nucleoids mixed up with other elements) return the sequences containing only ACTG orderd by length.
  • The above string can be split up to ["ACCG", "C", "GTTACTGGGC", "TTGT"] and then it can be sorted to get the following:
  • Expected result: ["GTTACTGGGC", "ACCG", "TTGT", "C"]

Solution: count words

package main

import (
	"fmt"
)

func main() {
	celestialObjects := []string{"Moon", "Gas", "Asteroid", "Dwarf", "Asteroid", "Moon", "Asteroid"}
	fmt.Println(celestialObjects)

	count := []int{}
	words := []string{}

OBJECTS:
	for _, word := range celestialObjects {
		for i, value := range words {
			if value == word {
				count[i]++
				continue OBJECTS
			}
		}
		words = append(words, word)
		count = append(count, 1)
	}

	for i, word := range words {
		fmt.Printf("%-10v %v\n", word, count[i])
	}
}

Solution: Create a list of words from sentences

package main

import (
	"fmt"
	"strings"
)

func main() {
	// source:
	lines := []string{
		"grape banana mango",
		"nut orange peach",
		"apple nut banana apple mango",
	}
	fmt.Println(lines)

	var fruits []string
	// Append words from each line
	for _, line := range lines {
		words := strings.Split(line, " ")
		fmt.Printf("fruits: %v, appending words: %v\n", fruits, words)
		fruits = append(fruits, words...)
	}

	// Print final result
	fmt.Println(fruits)
	for _, fruit := range fruits {
		fmt.Println(fruit)
	}
}
[grape banana mango nut orange peach apple nut banana apple mango]
fruits: [], appending words: [grape banana mango]
fruits: [grape banana mango], appending words: [nut orange peach]
fruits: [grape banana mango nut orange peach], appending words: [apple nut banana apple mango]
[grape banana mango nut orange peach apple nut banana apple mango]
grape
banana
mango
nut
orange
peach
apple
nut
banana
apple
mango

Solution: Create a unique list of values

package main

import (
	"fmt"
	"sort"
)

func main() {
	// input
	fruits := []string{"grape", "banana", "mango", "nut", "orange", "peach", "apple", "nut", "banana", "apple", "mango"}
	fmt.Println(fruits)
	sort.Strings(fruits)

	var uniqueFruits []string
	for i, word := range fruits {
		if i == 0 || fruits[i-1] != word {
			uniqueFruits = append(uniqueFruits, word)
		}
	}
	fmt.Println(uniqueFruits)
}
[grape banana mango nut orange peach apple nut banana apple mango]
[apple banana grape mango nut orange peach]

Solution: Reverse Polish Calculator

package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Printf("Usage: %s EXPRESSION (put the whole thing in quotes)\n", os.Args[0])
		os.Exit(1)
	}
	expression := os.Args[1]
	//fmt.Println(expression)
	parts := strings.Fields(expression)
	//fmt.Println(parts)
	//fmt.Println(len(parts))
	result := rpc(parts)
	fmt.Println(result)

}

func rpc(parts []string) float64 {
	stack := []float64{}
	for _, value := range parts {
		//fmt.Println(value)
		if value == "=" {
			log.Println("=")
			if len(stack) == 0 {
				panic("Operator = received and we have no value to print")
			}
			a := stack[len(stack)-1]
			stack = stack[:len(stack)-1]
			return a
		}
		if value == "+" || value == "*" || value == "-" || value == "/" {
			log.Println(value)
			if len(stack) < 2 {
				panic("Operator + received too early")
			}
			b := stack[len(stack)-1]
			a := stack[len(stack)-2]
			var c float64
			switch value {
			case "+":
				c = a + b
			case "-":
				c = a - b
			case "*":
				c = a * b
			case "/":
				c = a / b
			default:
				panic("How could this happen?")
			}

			stack = stack[:len(stack)-2]
			stack = append(stack, c)
			continue
		}
		number, err := strconv.ParseFloat(value, 64)
		if err != nil {
			panic(fmt.Sprintf("Unhandled value %v", value))
		}
		log.Println(number)
		stack = append(stack, number)
	}
	return 0
}

Solution: DNA Sequencing

package main

import (
	"fmt"
	"sort"
	"strings"
)

func main() {
	dna := "ACCGXXCXXGTTACTGGGCXTTGT"
	sequences := strings.Split(dna, "X")
	fmt.Println(sequences)
	fmt.Println(len(sequences)) // 6, this contains 2 empty strings as well

	shortSequences := make([]string, 0, len(sequences))
	fmt.Printf("%v, %v\n", len(shortSequences), cap(shortSequences)) // 0, 6
	for _, val := range sequences {
		if val != "" {
			shortSequences = append(shortSequences, val)
		}
	}
	fmt.Println(shortSequences)
	fmt.Printf("%v, %v\n", len(shortSequences), cap(shortSequences)) // 4, 6

	sort.Slice(shortSequences, func(i, j int) bool {
		return len(shortSequences[i]) > len(shortSequences[j])
	})
	fmt.Println(shortSequences)
	fmt.Printf("%v, %v\n", len(shortSequences), cap(shortSequences)) // 4, 6
}
[ACCG  C  GTTACTGGGC TTGT]
6
0, 6
[ACCG C GTTACTGGGC TTGT]
4, 6
[GTTACTGGGC ACCG TTGT C]
4, 6

Solution: DNA Sequencing with in place filter

package main

import (
	"fmt"
	"sort"
	"strings"
)

func main() {
	dna := "ACCGXXCXXGTTACTGGGCXTTGT"
	sequences := strings.Split(dna, "X")
	fmt.Println(sequences)
	fmt.Println(len(sequences)) // 6, this contains 2 empty strings as well

	i := 0
	for j := 0; j < len(sequences); j++ {
		if sequences[j] != "" {
			sequences[i] = sequences[j]
			i++
		}
	}
	sequences = sequences[:i]
	fmt.Println(sequences)
	fmt.Printf("%v, %v\n", len(sequences), cap(sequences)) // 4, 6

	sort.Slice(sequences, func(i, j int) bool {
		return len(sequences[i]) > len(sequences[j])
	})
	fmt.Println(sequences)
	fmt.Printf("%v, %v\n", len(sequences), cap(sequences)) // 4, 6
}
[ACCG  C  GTTACTGGGC TTGT]
6
[ACCG C GTTACTGGGC TTGT]
4, 6
[GTTACTGGGC ACCG TTGT C]
4, 6

Files

Read file line-by-line with Scanner

  • Open

  • os.Open

  • bufio

  • NewScanner

  • Scan

  • Removes the newlines

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	filename := "random.txt"
	fh, err := os.Open(filename)
	if err != nil {
		fmt.Printf("Could not open file '%v': %v", filename, err)
		os.Exit(1)
	}
	scanner := bufio.NewScanner(fh)
	for scanner.Scan() {
		line := scanner.Text()
		fmt.Printf("Line: %v\n", line)
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading:", err)
	}
}

{% embed include file="src/examples/read-file-with-scanner/random.txt)

Read file line by line with Reader

  • Open

  • NewReader

  • ReadString

  • readline

  • Leaves the newlines at the end of the line

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	filename := "random1.txt"
	fh, err := os.Open(filename)
	if err != nil {
		fmt.Printf("Could not open file '%v': %v", filename, err)
		os.Exit(1)
	}
	reader := bufio.NewReader(fh)
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
			}
			break
		}
		fmt.Printf("Line: %v", line)
	}
}
This is just some random
text with more than one lines.
This is already the 3rd line.

Read file as one string (slurp)

  • ReadFile
  • slurp
package main

import (
	"fmt"
	"io/ioutil"
	"os"
)

func main() {
	filename := "slurp_file.go"
	dat, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}
	fmt.Println(string(dat))
}

Write to file

  • Create
  • WriteString
  • write
package main

import (
	"fmt"
	"os"
)

func main() {
	var filename = "z/data.txt"

	text := "Some text"
	var fh, err = os.Create(filename)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fh.WriteString(text + "\n")
	fh.Close()

}

Write number to file

package main

import (
	"fmt"
	"os"
)

func main() {
	var filename = "data.txt"

	answer := 42

	fh, err := os.Create(filename)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	text := fmt.Sprintf("%d\n", answer)
	fh.WriteString(text)
	fh.Close()

}

Append to file

  • append
package main

import (
	"fmt"
	"io/ioutil"
	"os"
)

const filename = "my.log"

func main() {
	debug("Hello")
	show()
	debug("World")
	show()
}

func show() {
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Printf("Could not open file '%v' for reading: %v", filename, err)
		return
	}
	fmt.Println(string(content))
	fmt.Println()
}

func debug(text string) {
	fh, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Println(err)
		return
	}
	fh.WriteString(text + "\n")
	fh.Close()
}

Reading CSV file

{% embed include file="src/examples/read-csv/process_csv_file.csv)

  • Sum the numbers in the 3rd column
package main

import (
	"bufio"
	"encoding/csv"
	"fmt"
	"log"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Printf("Usage: %s FILENAME\n", os.Args[0])
		os.Exit(1)
	}
	var filename = os.Args[1]
	//fmt.Println(filename)
	fh, err := os.Open(filename)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	reader := bufio.NewReader(fh)
	r := csv.NewReader(reader)
	r.Comma = ';'

	records, err := r.ReadAll()
	if err != nil {
		log.Fatal(err)
	}

	//fmt.Printf("%T\n", records)
	var sum = 0
	for _, row := range records {
		val, err := strconv.Atoi(row[2])
		if err != nil {
			fmt.Printf("Error %v converting to int: '%v'", err, row[2])
			continue
		}
		sum += val
	}
	fmt.Println(sum)
}
go run examples/read-csv/read_csv.go examples/csv/process_csv_file.csv

Result:

126

Exercise: Sum of numbers in a file

  • Given a file where each row contains a number, print the sum of the numbers

{% embed include file="src/examples/sum/sum.txt)

Exercise: Count number of digitis

Given a file like the following, write a program that will count how many times each digit appears:

23  4565676 14343
14423 563  353

Exercise: ROT13 on file

  • write a program that will accept the name of a file and replace it with the ROT13 encoded version of the text.
  • Encode each letter a-z and A-Z, but leave everything else intact.
  • If written correctly, running the program again on the same file should convert it back to the original version.

Exercise: Selector with list of items from a file

  • Write a program that will get a file on the command line.
  • It will display a menu allowing the user to select item by selecting the number next to it.
  • e.g. the file looks like this:
blue
yellow
green
black

The menu should look like this:

1) blue
2) yellow
3) green
4) black
  • The user then can select a number (e.g. 3) and the program prints "green".
  • Make sure the program handles well if the input is invalid.

TODO: Solution: Sum of numbers in a file

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	var err error
	if len(os.Args) != 2 {
		fmt.Printf("Missing filename.\nUsage: %v FILENAME\n", os.Args[0])
		os.Exit(1)
	}

	filename := os.Args[1]
	//fmt.Println(filename)
	var fh *os.File
	fh, err = os.Open(filename)
	//fmt.Printf("%T", fh)
	if err != nil {
		fmt.Printf("Error opening file '%v'\n%v\n", filename, err)
		os.Exit(1)
	}
	reader := bufio.NewReader(fh)
	var line string
	for true {
		line, _ = reader.ReadString('\n')
		//fmt.Print(line)
		for _, c := range line {
			if c == ' ' || c == '\n' {
				continue
			}
			fmt.Printf("%q\n", c)
		}
		if line == "" {
			break
		}
	}

}

Maps

Map (hash, dictionary)

  • map

A map is an unordered datastructur of key-value pairs.

Empty Map

  • map
package main

import "fmt"

func main() {
	grades := map[string]int{}
	fmt.Printf("%T\n", grades)

	grades["Mary"] = 99
	grades["Jane"] = 88
	grades["Bob"] = 93

	fmt.Println(grades)

	grade := grades["Jane"]
	fmt.Println(grade)
}

Empty Map with make

  • map
package main

import "fmt"

func main() {
	grades := make(map[string]int)
	fmt.Printf("%T\n", grades)

	grades["Mary"] = 99
	grades["Jane"] = 88
	grades["Bob"] = 93

	fmt.Println(grades)
}
map[string]int
map[Bob:93 Jane:88 Mary:99]

Map type defintion without container

package main

import "fmt"

func main() {
	var grades map[string]int  // defined type but does not create the container for it
	fmt.Printf("%T\n", grades) // map[string]int

	grades["Mary"] = 99
}
map[string]int
panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
	/home/gabor/work/slides/golang/examples/map-no-container/map_no_container.go:9 +0xba
exit status 2

Create map with data in it already

package main

import "fmt"

func main() {
	grades := map[string]int{
		"Mary": 99,
		"Jane": 88,
		"Bob":  93,
	}
	fmt.Printf("%T\n", grades) // map[string]int
	fmt.Println(grades)        // map[Bob:93 Jane:88 Mary:99]
}

Delete Map element

package main

import "fmt"

func main() {
	grades := map[string]int{
		"Mary": 99,
		"Jane": 88,
		"Bob":  93,
	}
	fmt.Printf("%T\n", grades) // map[string]int

	fmt.Println(grades) // map[Bob:93 Jane:88 Mary:99]

	delete(grades, "Jane")

	fmt.Println(grades) // map[Bob:93 Mary:99]
}

Size of map (len)

package main

import "fmt"

func main() {
	scores := map[string]int{"Alma": 23, "Cecilia": 12, "Berta": 78}
	fmt.Println(len(scores))
	scores["David"] = 37
	fmt.Println(len(scores))
	delete(scores, "Alma")
	fmt.Println(len(scores))
}
3
4
3

Access map element (that does not exist)

  • What happens when we access an element that does not exist?
  • Go returns 0.
  • So we can't know if the field exists and its value is 0 or if it does not exist at all.
package main

import "fmt"

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

	grades["Mary"] = 99
	grades["Joe"] = 0
	fmt.Println(grades["Mary"])
	fmt.Println(grades["Joe"])
	fmt.Println(grades["Jane"])

}
99
0
0

Map element exists

  • The variable name ok is not speacial. It is just a convention.
package main

import "fmt"

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

	grades["Mary"] = 99
	grades["Joe"] = 0

	value, ok := grades["Mary"]
	fmt.Println(ok)
	fmt.Println(value)
	fmt.Println()

	value, ok = grades["Joe"]
	fmt.Println(ok)
	fmt.Println(value)
	fmt.Println()

	value, ok = grades["Jane"]
	fmt.Println(ok)
	fmt.Println(value)
}
true
99

true
0

false
0

Increment map elements

package main

import "fmt"

func main() {
	counter := map[string]int{}
	fmt.Println(counter)
	counter["Foo"]++
	fmt.Println(counter)

}
map[]
map[Foo:1]

Iterate over elements of map

package main

import "fmt"

func main() {
	grades := make(map[string]int)
	grades["Mary"] = 99
	grades["Jane"] = 88
	grades["Bob"] = 93

	for key, value := range grades {
		fmt.Printf("%-4s  %d\n", key, value)
	}
}
Mary  99
Jane  88
Bob   93

Keys of a map

  • keys

  • There is no function to fetch the list of keys.

  • We can iterate over the keys and put them in an slice with pre-allocated size.

package main

import (
	"fmt"
)

func main() {
	scores := map[string]int{"Alma": 23, "Cecilia": 12, "Berta": 78, "David": 37}
	fmt.Println(len(scores))
	fmt.Println(scores)

	names := make([]string, 0, len(scores))
	for name := range scores {
		names = append(names, name)
	}
	fmt.Println(names)
	for _, name := range names {
		fmt.Println(name)
	}
}
4
map[Alma:23 Berta:78 Cecilia:12 David:37]
[Alma Cecilia Berta David]
Alma
Cecilia
Berta
David

Sort map

  • You can't really sort a map, but you can iterate over the keys in some sorted way.
  • You just need to fetch the keys, sort them, and then iterate over that slice.
package main

import (
	"fmt"
	"sort"
)

func main() {
	scores := map[string]int{"Alma": 23, "Cecilia": 12, "David": 37, "Berta": 78}
	fmt.Println(len(scores))
	fmt.Println(scores)
	fmt.Println()

	for name, score := range scores {
		fmt.Printf("%-7v %v\n", name, score)
	}
	fmt.Println()

	names := make([]string, 0, len(scores))
	for name := range scores {
		names = append(names, name)
	}
	fmt.Println(names)
	sort.Strings(names)
	fmt.Println(names)
	fmt.Println()

	for _, name := range names {
		fmt.Printf("%-7v %v\n", name, scores[name])
	}
}
4
map[Alma:23 Berta:78 Cecilia:12 David:37]

Cecilia 12
David   37
Berta   78
Alma    23

[Alma Cecilia David Berta]
[Alma Berta Cecilia David]

Alma    23
Berta   78
Cecilia 12
David   37

Sort map by value

package main

import (
	"fmt"
	"sort"
)

func main() {
	scores := map[string]int{"Alma": 23, "Cecilia": 12, "David": 37, "Berta": 78}
	fmt.Println(len(scores))
	fmt.Println(scores)
	fmt.Println()

	for name, score := range scores {
		fmt.Printf("%-7v %v\n", name, score)
	}
	fmt.Println()

	names := make([]string, 0, len(scores))
	for name := range scores {
		names = append(names, name)
	}
	fmt.Println(names)
	fmt.Println()

	sort.Slice(names, func(i, j int) bool {
		return scores[names[i]] > scores[names[j]]
	})
	fmt.Println(names)
	fmt.Println()

	for _, name := range names {
		fmt.Printf("%-7v %v\n", name, scores[name])
	}
}
4
map[Alma:23 Berta:78 Cecilia:12 David:37]

Berta   78
Alma    23
Cecilia 12
David   37

[Alma Cecilia David Berta]

[Berta David Alma Cecilia]

Berta   78
David   37
Alma    23
Cecilia 12

map of slices

package main

import (
	"fmt"
)

func main() {
	joeGrades := []int{2, 3, 4}
	fmt.Println(joeGrades)
	janeGrades := []int{7, 9, 5, 10}
	fmt.Println(janeGrades)

	people := make(map[string][]int)
	fmt.Println(people)
	people["joe"] = joeGrades
	people["jane"] = janeGrades
	fmt.Println(people)
}
[2 3 4]
[7 9 5 10]
map[]
map[jane:[7 9 5 10] joe:[2 3 4]]

add entry delete(map, name) delete entry

Mixed map

package main

import "fmt"

func main() {
	person := make(map[string]interface{})
	person["name"] = "Foo Bar"
	person["year"] = 1998
	person["children"] = []string{"Joe", "Jane", "Jannet"}
	fmt.Println(person)

	for key, value := range person {
		fmt.Printf("%v %T\n", key, value)
	}

	// to iterate over interface one needs to use the .(T) modifyer
	for index, name := range person["children"].([]string) {
		fmt.Printf("  %v %v\n", index, name)
	}
}
map[children:[Joe Jane Jannet] name:Foo Bar year:1998]
name string
year int
children []string
  0 Joe
  1 Jane
  2 Jannet

Exercise: count characters

Given a string count how many time each character appears

package main

import "fmt"

func main() {
	text := "This is a very long text. OK, maybe it is not that long after all."
	fmt.Println(text)

}

Expected output:

This is a very long text. OK, maybe it is not that long after all.
r: 2
n: 3
m: 1
T: 1
s: 3
y: 2
g: 2
t: 7
.: 2
,: 1
f: 1
h: 2
v: 1
i: 4
e: 4
l: 4
o: 3
x: 1
O: 1
K: 1
b: 1
a: 5

Exercise: count characters - sort by frequency

  • Given a string count how many time each character appears and list them by frequency.
  • Make the count case-insensitive (so "T" and "t" will count as the same letter.)
package main

import (
	"fmt"
)

func main() {
	text := "This is a very long text. OK, maybe it is not that long after all."
	fmt.Println(text)

}

Expected output:

This is a very long text. OK, maybe it is not that long after all.
s: 3
r: 2
l: 4
n: 3
x: 1
.: 2
m: 1
h: 2
b: 1
v: 1
t: 7
T: 1
e: 4
o: 3
O: 1
K: 1
,: 1
a: 5
y: 2
g: 2
f: 1
i: 4

Exercise: count words

package main

import "fmt"

func main() {
	var text = "hello world how are you world and how are you"
	fmt.Println(text)

}

Expected output:

hello: 1
world: 2
how: 2
are: 2
you: 2
and: 1

Exercise: count words from file

Given a text file like this one:

{% embed include file="src/examples/count-words-from-file/words_and_spaces.txt)

  • Print out a report how many times each word appears.
  • Disregard case of the letters.
  • Show them in order of frequency.
words_and_spaces.txt
ad 13
labor 10
dolor 6
in 6
ut 5
lorem 5
qui 3
sint 3
dolore 2
commodo 2
eu 1
incididunt 1
tempor 1
reprehenderit 1
proident 1
ipsum 1
occaecat 1

Solution: count characters

package main

import "fmt"

func main() {
	text := "This is a very long text. OK, maybe it is not that long after all."
	counter := make(map[string]int)
	for _, c := range text {
		//fmt.Printf("%v", string(c))
		if string(c) != " " {
			counter[string(c)]++
		}
	}

	fmt.Println(text)
	for char, count := range counter {
		fmt.Printf("%v: %v\n", char, count)
	}

}

Solution: count characters - sort by frequency

package main

import (
	"fmt"
	"sort"
)

func main() {
	text := "This is a very long text. OK, maybe it is not that long after all."
	counter := make(map[string]int)
	for _, c := range text {
		//fmt.Printf("%v", string(c))
		if string(c) != " " {
			counter[string(c)]++
		}
	}

	fmt.Println(text)
	chars := make([]string, 0, len(counter))
	for chr := range counter {
		chars = append(chars, chr)
	}

	sort.Slice(chars, func(i, j int) bool {
		return counter[chars[i]] > counter[chars[j]]
	})
	for _, chr := range chars {
		fmt.Printf("%v: %v\n", chr, counter[chr])
	}

}

Solution: count words

package main

import (
	"fmt"
	"strings"
)

func main() {
	var count = make(map[string]int)
	var text = "hello world how are you world and how are you"
	//fmt.Println(text)

	var words = strings.Split(text, " ")
	//fmt.Println(words)
	for _, word := range words {
		count[word]++
	}
	//fmt.Println(count)

	for k, v := range count {
		fmt.Printf("%s: %d\n", k, v)
	}

}

Solution: count words from file

package main

import (
	"bufio"
	"fmt"
	"os"
	"sort"
	"strings"
)

func main() {
	filename := "words_and_spaces.txt"
	fmt.Println(filename)

	fh, err := os.Open(filename)
	if err != nil {
		fmt.Printf("Could not open file '%v': %v", filename, err)
		os.Exit(1)
	}
	reader := bufio.NewReader(fh)
	counter := make(map[string]int)
	for {
		line, _ := reader.ReadString('\n')
		//fmt.Print(line)
		fields := strings.Fields(line)
		//fmt.Println(fields)
		for _, word := range fields {
			word = strings.ToLower(word)
			counter[word]++
		}
		if line == "" {
			break
		}
	}

	// for word, cnt := range counter {
	// 	fmt.Printf("%v %v\n", word, cnt)
	// }

	words := make([]string, 0, len(counter))
	for word := range counter {
		words = append(words, word)
	}
	sort.Slice(words, func(i, j int) bool {
		return counter[words[i]] > counter[words[j]]
	})

	for _, word := range words {
		fmt.Printf("%v %v\n", word, counter[word])
	}

}

Testing

Testing in Go

There are a number of convetions on how to write tests in Go.

  • Tests files have a suffix _test like app_test.go.
  • They have functions where the name starts with Test like TestSometing.
  • You can run the tests by typing go test in the directory of the project.

Testing modules

There are a number of packages that will help you write tests. In this chapter we'll look at the standard module testing that comes with every installation of Go. Then later we are going to take a look at a number of other packages that are listed here.

Simple example with testing

  • testing
  • test

In this example we have a single-file Go "application" called comp.go that has a main function and an additional function called add that is supposed to add two integers together and return the result. If you look carefully you might notice that we have a typo in the code, but that's there on purpose. Our goal now is to see how can we verify this code.

package main

import (
	"fmt"
)

func main() {
	res := add(2, 2)
	fmt.Println(res)
}

func add(a, b int) int {
	return a * b
}

So we have an additional file called comp_test.go. This belongs to the same main package and it imports the testing package. It has a function called TestAdd that received a pointer to a testing.T instance. Remember, not we are going to call this function, but the testing system of Go. Inside the function we have a call to t.Log which is just some logging information. Then we call the add function that resides in the same package but in the main file. We pass two numbers an accept the results. This is how we would normally use the functions.

Then we have the interesting part. We compare the received value with the expected value which is 4. If they are not the same, we call the t.Error function to report the problem. We can put any text there.

That's it.

package main

import "testing"

func TestAdd(t *testing.T) {
	t.Log("Hello from the test")
	total := add(2, 2)
	if total != 4 {
		t.Error("Sum was incorrect")
	}
}

We can now run the tests:

go test

to get the following output:

PASS
ok  	_/home/gabor/work/slides/golang/examples/simple-test	0.001s

We could also pass the -v flag to get a bit more verbose output.

go test -v
=== RUN   TestSum
    TestSum: comp_test.go:6: Hello from the test
--- PASS: TestSum (0.00s)
PASS
ok  	_/home/gabor/work/slides/golang/examples/simple-test	0.003s

Test with failure

  • testing
  • test

The previous test was great, but soon people will report that the add function does not always work. In fact they will report it never works properly. If you are lucky they will also supply you with a case for which it did not work properly so you can try to reproduce the problem. You do that by writing another test case:

The code is still the same:

package main

import (
	"fmt"
)

func main() {
	res := add(2, 2)
	fmt.Println(res)
}

func add(a, b int) int {
	return a * b
}

But now we have two separate test cases:

package main

import "testing"

func TestAdd1(t *testing.T) {
	t.Log("Hello from the test")
	total := add(2, 2)
	if total != 4 {
		t.Error("Sum was incorrect")
	}
}

func TestAdd2(t *testing.T) {
	t.Log("Hello from the test")
	total := add(3, 3)
	if total != 6 {
		t.Errorf("expected 6 received %v", total)
	}
}

If we run the tests now:

go test

We get a failure report in which we can see the line number of the failure and the message. If we prepare our message well, then wen can immediately see the actual value and the expected value that might be able to help us locate the problem.

--- FAIL: TestAdd2 (0.00s)
    comp2_test.go:14: Hello from the test
    comp2_test.go:17: expected 6 received 9
FAIL
exit status 1
FAIL	_/home/gabor/work/slides/golang/examples/test-fail	0.002s

If this is not enough we can ask for more verbose output:

go test -v
=== RUN   TestAdd1
    TestAdd1: comp2_test.go:6: Hello from the test
--- PASS: TestAdd1 (0.00s)
=== RUN   TestAdd2
    TestAdd2: comp2_test.go:14: Hello from the test
    TestAdd2: comp2_test.go:17: expected 6 received 9
--- FAIL: TestAdd2 (0.00s)
FAIL
exit status 1
FAIL	_/home/gabor/work/slides/golang/examples/test-fail	0.002s

Run selected test functions

At this point you have basically two choices. Assuming you are sure the test is correct, either you can fix the code now, or you can decide that you delay fixing this code as you have more urgent things to do.

If and when you start fixing the code you will have to run the tests again and again. If you have many tests this can be time consuming. You might want to run the specific test that is currently failing. You can do this using the -run flag.

Only the TestAdd1:

go test -run TestAdd1

Only the TestAdd2:

go test -run TestAdd2

Both:

go test -run TestAdd

In general you can use regexes to match the names of the test functions you would like to run:

go test -run "^(func_test_name)$"

Exercise: Test Anagram

  • Given the following code, that checks if two strings are anagrams, write tests that verify the function.
package main

import (
	"fmt"
	"sort"
	"strings"
)

func main() {
	fmt.Println(is_anagram("ab", "ba"))
	fmt.Println(is_anagram("hello", "hello"))
	fmt.Println(is_anagram("hell", "ello"))
}

func is_anagram(a, b string) bool {
	aa := strings.Split(a, "")
	sort.Strings(aa)
	aaa := strings.Join(aa, "")

	bb := strings.Split(b, "")
	sort.Strings(bb)
	bbb := strings.Join(bb, "")

	return aaa == bbb
}

Exercise: Test Calculator

Given the following program with the calc function, write some tests verifying the function.

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	//fmt.Println(os.Args)
	if len(os.Args) != 4 {
		fmt.Println("Usage: calc.go NUMBER OPERATOR NUMBER")
		os.Exit(0)
	}

	var a, _ = strconv.Atoi(os.Args[1])
	var op = os.Args[2]
	var b, _ = strconv.Atoi(os.Args[3])
	result, err := calc(a, op, b)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Println(result)
}

func calc(a int, op string, b int) (int, error) {
	var result int

	if op == "+" {
		result = a + b
	} else if op == "*" {
		result = a * b
	} else if op == "/" {
		if b == 0 {
			return 0, fmt.Errorf("Cannot devide by 0")
		}
		result = a / b
	} else if op == "-" {
		result = a - b
	} else {
		return 0, fmt.Errorf("operator '%s' is not handled", op)
	}
	return result, nil
}

Exercise: statistics

  • Take the following program, factor out a function called stats that will get a slice of numbers and return the min, max, total, and average.

  • Write test that will check the results

package main

import "fmt"

func main() {
	numbers := [...]int{2, 3, 5, -1, 1, 8}
	fmt.Println(numbers)
	if len(numbers) == 0 {
		fmt.Println("We cannot provide stats on empty set of numbers")
		return
	}

	total := 0
	min := numbers[0]
	max := numbers[0]
	for _, num := range numbers {
		if num < min {
			min = num
		}
		if num > max {
			max = num
		}
		total += num
	}
	//fmt.Println(min(numbers))
	fmt.Printf("Total: %v\n", total)
	average := float32(total) / float32(len(numbers))
	fmt.Printf("Average: %v\n", average)
	fmt.Printf("Min: %v\n", min)
	fmt.Printf("Max: %v\n", max)
}

Solution: Test Anagram

package main

import "testing"

func TestAnagram(t *testing.T) {
	res := is_anagram("abc", "abd")
	if res != false {
		t.Errorf("No anagram")
	}

	res = is_anagram("abc", "acb")
	if res != true {
		t.Errorf("Is anagram")
	}
}

Solution: Test Calculator

package main

import "testing"

func TestCalc(t *testing.T) {
	result, err := calc(2, "+", 3)

	if err != nil {
		t.Errorf("Error found where not expected %v", err)
	}
	if result != 5 {
		t.Errorf("Expected 5 received %v", result)
	}
}

Solution: statistics

package main

import "fmt"

func main() {
	numbers := []int{2, 3, 5, -1, 1, 8}
	fmt.Println(numbers)
	if len(numbers) == 0 {
		fmt.Println("We cannot provide stats on empty set of numbers")
		return
	}

	min, max, total, average := stats(numbers)

	//fmt.Println(min(numbers))
	fmt.Printf("Total: %v\n", total)
	fmt.Printf("Average: %v\n", average)
	fmt.Printf("Min: %v\n", min)
	fmt.Printf("Max: %v\n", max)
}

func stats(numbers []int) (int, int, int, float32) {
	total := 0
	min := numbers[0]
	max := numbers[0]
	for _, num := range numbers {
		if num < min {
			min = num
		}
		if num > max {
			max = num
		}
		total += num
	}
	average := float32(total) / float32(len(numbers))

	return min, max, total, average
}
package main

import "testing"

func TestStats(t *testing.T) {
	numbers := []int{2, 3, 5, -1, 1, 8}
	min, max, total, average := stats(numbers)
	if min != -1 {
		t.Errorf("min is expected to be -1 actual: %v", min)
	}
	if max != 8 {
		t.Errorf("max is expected to be 8 actual: %v", max)
	}
	if total != 18 {
		t.Errorf("total is expected to be 18 actual: %v", total)
	}
	if average != 3 {
		t.Errorf("average is expected to be 3 actual: %v", average)
	}
}

Struct

Struct and type

  • type
  • struct
package main

import (
	"fmt"
)

type aPerson struct {
	id       int
	name     string
	shoeSize float32
	children []string
}

func main() {
	joe := aPerson{
		id:       1,
		name:     "Joe",
		shoeSize: 42.5,
		children: []string{"Alpha", "Beta"},
	}
	fmt.Println(joe)
	fmt.Println()

	fmt.Println(joe.id)
	fmt.Println(joe.name)
	fmt.Println(joe.shoeSize)
	fmt.Println(joe.children)
	fmt.Println(joe.children[0])
}
{1 Joe 42.5 [Alpha Beta]}

1
Joe
42.5
[Alpha Beta]
Alpha

Struct with partial information (default values)

package main

import (
	"fmt"
)

type aPerson struct {
	id       int
	name     string
	email    string
	shoeSize float32
}

func main() {
	joe := aPerson{
		id:       1,
		name:     "Joe",
		email:    "joe@joehome.com",
		shoeSize: 42.5,
	}
	fmt.Println(joe)
	fmt.Println()

	jane := aPerson{
		id:   2,
		name: "Jane",
	}
	fmt.Println(jane)
}
{1 Joe joe@joehome.com 42.5}

{2 Jane  0}

Slice of structs

package main

import (
	"fmt"
)

type aPerson struct {
	name  string
	email string
}

func main() {
	joe := aPerson{
		name:  "Joe",
		email: "joe@joehome.com",
	}

	jane := aPerson{
		name:  "Jane",
		email: "jane@janehome.com",
	}

	people := []aPerson{}
	fmt.Println(people)

	people = append(people, joe, jane)
	fmt.Println(people)
}
[]
[{Joe joe@joehome.com} {Jane jane@janehome.com}]

Anonymous struct

package main

import (
	"fmt"
)

func main() {
	ip := struct {
		address string
		name    string
	}{
		address: "127.0.0.1",
		name:    "home"}

	fmt.Println(ip)
	fmt.Println(ip.address)
	fmt.Println(ip.name)
}
{127.0.0.1 home}
127.0.0.1
home

Struct in a struct

package main

import "fmt"

type myPoint struct {
	x float64
	y float64
}

type myLine struct {
	a myPoint
	b myPoint
}

func main() {
	p1 := myPoint{
		x: 2.1,
		y: 3.1,
	}
	fmt.Println(p1)

	line := myLine{
		a: p1,
		b: myPoint{
			x: -0.1,
			y: 1.1,
		},
	}
	fmt.Println(line)
	fmt.Println(line.a.x)
	fmt.Println(line.b.x)
}
{2.1 3.1}
{-0.1 1.1}
{{2.1 3.1} {-0.1 1.1}}
2.1

composition via embedding instead of inheritance

package main

import "fmt"

type myPoint struct {
	x int
	y int
}

type myOther struct {
	x float32
	z int
}

type myCircle struct {
	myPoint
	myOther
	r int
}

func main() {
	p1 := myPoint{
		x: 1,
		y: 2,
	}
	c1 := myCircle{}
	c1.myPoint.x = 3
	c1.myOther.x = 6
	c1.y = 4
	c1.r = 5

	fmt.Println(p1)
	fmt.Println(c1)

	// c2 := myCircle{
	// 	myPoint: myPoint{x: 6, y: 7},
	// 	r:       8,
	// }
	// fmt.Println(c2)

}
{1 2}
{{3 4} 5}
{{6 7} 8}

Tags and introspection (reflect)

  • reflect

  • TypeOf

  • We can add "free-text" tags to the elements of the struct, but it is better to use key-value pairs in there.

  • We can use introspection to look at the content of the tags.

package main

import (
	"fmt"
	"reflect"
)

type aPerson struct {
	id       int    `unique:"true"`
	name     string `required:"true" max:"100"`
	children []string
}

func main() {
	t := reflect.TypeOf(aPerson{})
	field, ok := t.FieldByName("id")
	if ok {
		fmt.Printf("id: %v\n", field.Tag)
		fmt.Println(field.Tag.Get("unique"))
		fmt.Println(field.Tag.Get("required"))

		value, ok := field.Tag.Lookup("unique")
		if ok {
			fmt.Printf("unique value: %v\n", value)
		}
	}
	field, ok = t.FieldByName("children")
	if ok {
		fmt.Printf("children: %v\n", field.Tag)
	}

	a := aPerson{
		id: 1,
	}
	fmt.Println(a)
}
id: unique:"true"
true

unique value: true
children: 
{1  []}

use cleanenv

package main

// go get -u github.com/ilyakaznacheev/cleanenv

import (
	"fmt"
	"os"

	"github.com/ilyakaznacheev/cleanenv"
)

type configDatabase struct {
	Port string `yaml:"port" env:"PORT" env-default:"5432"`
	Host string `yaml:"host" env:"HOST" env-default:"localhost"`
	// Name     string `yaml:"name" env:"NAME" env-default:"postgres"`
	// User     string `yaml:"user" env:"USER" env-default:"user"`
	// Password string `yaml:"password" env:"PASSWORD"`
}

var cfg configDatabase

func main() {
	err := cleanenv.ReadConfig("config.yml", &cfg)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Printf("port: %v\n", cfg.Port)
	fmt.Printf("host: %v\n", cfg.Host)
}
port: 23

methods

  • A method is a function execution in a known context.
package main

import (
	"fmt"
	"math"
)

type myCircle struct {
	x float64
	y float64
	r float64
}

func (c myCircle) area() float64 {
	return c.r * c.r * math.Pi
}

func main() {
	a := myCircle{
		x: 2,
		y: 3,
		r: 4,
	}
	fmt.Println(a)

	theArea := a.area()
	fmt.Println(theArea)
}
{2 3 4}
50.26548245743669

method of int

package main

import (
	"fmt"
	"math"
)

type myint int

func (i myint) abs() float64 {
	return math.Abs(float64(i))
}

func main() {
	var n myint = -3
	fmt.Println(n.abs())
}
3

map keys method

package main

import (
	"fmt"
)

type hash map[string]int

func (h hash) keys() []string {
	keys := make([]string, 0, len(h))
	for k := range h {
		keys = append(keys, k)
	}
	return keys
}

func main() {
	data := hash{
		"foo": 3,
		"bar": 17,
		"adi": 10,
	}
	fmt.Println(data)
	ks := data.keys()
	fmt.Println(ks)
}
map[adi:10 bar:17 foo:3]
[foo bar adi]

method gets copy of struct

package main

import "fmt"

type aPerson struct {
	name string
}

func (p aPerson) changeName(newName string) {
	fmt.Printf("Old name: %v\n", p.name)
	p.name = newName
	fmt.Printf("New name: %v\n", p.name)
}

func main() {
	joe := aPerson{name: "Joe"}
	fmt.Println(joe)
	joe.changeName("Jane")
	fmt.Println(joe)
}
{Joe}
Old name: Joe
New name: Jane
{Joe}

method pass pointer of struct

package main

import "fmt"

type aPerson struct {
	name string
}

func (p *aPerson) changeName(newName string) {
	fmt.Printf("Old name: %v\n", p.name)
	p.name = newName
	fmt.Printf("New name: %v\n", p.name)
}

func main() {
	joe := aPerson{name: "Joe"}
	fmt.Println(joe)
	joe.changeName("Jane")
	fmt.Println(joe)
}
{Joe}
Old name: Joe
New name: Jane
{Jane}

Exercise: read-csv-struct

  • Take the following CSV file, read in the content into structs so that the latitude and longitude values are stored in float64.
city,latitude,longitude
Budapest,47.497913,19.040236
Prague,50.075539,14.437800
Vienna,48.208176,16.373819
Bratislava,48.148598,17.107748

Exercise: implement 2D point and move

  • Implement a function using a struct that can store x,y values representing a point.
  • Then add a function called move that accepts dx and dy the distances the point needs to move and update the location of the point.

Exercise: implement 3D point and move

  • Improve the previous code by creating another struct that can handle points in 3D.

Exercise: implement wc

Implement the wc command of Unix/Linux: Given a name of file print out the number of lines, number of words, and number of characters in the file.

Given multiple file, print out the values for each file and then print out the totals for all the files.

{% embed include file="src/examples/wc/one.txt)

{% embed include file="src/examples/wc/two.txt)

 2   5  24 one.txt
 3  11 100 two.txt
 5  16 124 total

Solution: implement wc

TODO: finish this

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"
)

func main() {
	fmt.Println(os.Args)
	wc(os.Args[1:])
}

func wc(filenames []string) (int, int, int) {
	rows := 0
	words := 0
	chars := 0
	filename := filenames[0]
	fh, err := os.Open(filename)
	if err != nil {
		os.Exit(1)
	}
	reader := bufio.NewReader(fh)
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
			}
			break
		}
		rows++
		words += len(strings.Fields(line))
		chars += len(line)
	}
	return rows, words, chars
}
package main

import (
	"testing"
)

// var expected map[string]

func TestWCa(t *testing.T) {
	files := []string{"one.txt"}
	rows, words, chars := wc(files)
	if rows != 2 {
		t.Errorf("Expected rows: 2, actual rows %v\n", rows)
	}
	if words != 5 {
		t.Errorf("Expected words: 5, actual words %v\n", words)
	}
	if chars != 24 {
		t.Errorf("Expected chars: 24, actual chars %v\n", chars)
	}
}

func TestWCb(t *testing.T) {
	files := []string{"two.txt"}
	rows, words, chars := wc(files)
	if rows != 3 {
		t.Errorf("Expected rows: 3, actual rows %v\n", rows)
	}
	if words != 11 {
		t.Errorf("Expected words: 11, actual words %v\n", words)
	}
	if chars != 100 {
		t.Errorf("Expected chars: 100, actual chars %v\n", chars)
	}
}

Logging

Simple Logging

package main

import (
	"log"
)

func main() {
	log.Print("First log")
	log.Print("Each log line is on its own")
	log.Println("Despite its name it does not add any extra newline")
	log.Printf("Value: %v seen", 42)
}
2020/04/10 08:49:05 First log
2020/04/10 08:49:05 Each log line is on its own
2020/04/10 08:49:05 Despite its name it does not add any extra newline
2020/04/10 08:49:05 Value: 42 seen
  • By default log prints to STDERR (Standard Error)

Logging Fatal errors

  • Fatal, Fatalf, and Fataln print the message and call os.Exit(1) for the program to exit with code 1.
  • Does not execute the lines after calling Fatal
package main

import (
	"log"
)

func main() {
	log.Print("First")
	log.Fatal("Oups")
	log.Print("Last")
}
2020/04/10 08:49:41 First
2020/04/10 08:49:41 Oups
exit status 1

Logging to a file - rewrite

  • Set the output filehandle using SetOutput
package main

import (
	"log"
	"os"
)

func main() {
	var filename = "logging_to_file.log"
	var fh, err = os.Create(filename)
	if err != nil {
		log.Fatalf("Could not open file '%v': %v", filename, err)
	}
	log.SetOutput(fh)

	log.Print("Hello logfile")
	log.Fatal("This is bad")
}
2020/04/10 08:55:04 Hello logfile
2020/04/10 08:55:04 This is bad

Logging to a file - append

package main

import (
	"log"
	"os"
)

func main() {
	var filename = "logging_to_file_append.log"
	var fh, err = os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalf("Could not open file '%v': %v", filename, err)
	}
	log.SetOutput(fh)

	log.Print("Hello logfile")
	log.Fatal("This is bad")
}

{% embed include file="src/examples/logging-to-file-append/logging_to_file_append.log)

Logging the filename

package main

import (
	"log"
)

func main() {
	log.Print("standard (default)")

	log.SetFlags(log.LstdFlags | log.Lshortfile)
	log.Print("standard + shortfile")

	log.SetFlags(log.LstdFlags | log.Llongfile)
	log.Print("standard + shortfile")

	log.SetFlags(log.Lshortfile)
	log.Print("shortfile")
}
2020/04/10 09:16:07 standard (default)
2020/04/10 09:16:07 logging_filename.go:11: standard + shortfile
2020/04/10 09:16:07 /home/gabor/work/slides/golang/examples/logging-filename/logging_filename.go:14: standard + shortfile
logging_filename.go:17: shortfile

Logging flags

package main

import (
	"log"
)

func main() {
	log.Print(log.Ldate)         // 1
	log.Print(log.Ltime)         // 2
	log.Print(log.LstdFlags)     // 3 Ldate | Ltime
	log.Print(log.Lmicroseconds) // 4
	log.Print(log.Llongfile)     // 8
	log.Print(log.Lshortfile)    // 16
	log.Print(log.LUTC)          // 32

	log.Print("")
	log.Print(log.Flags()) // 3
}

Logging: Set Prefix

  • SetPrefix

  • Prefix

  • SetPrefix can set the prefix for each log message

  • Prefix returns the current prefix

package main

import (
	"log"
)

func main() {
	log.SetPrefix("Foo ")
	log.Print("First")
	log.Print("Second")
	log.SetPrefix("Bar ")
	log.Print("Third")
}
Foo 2020/04/10 09:33:40 First
Foo 2020/04/10 09:33:40 Second
Bar 2020/04/10 09:33:40 Third

Turn logging on/off

  • Output
  • Discard
  • NullWriter
  • /dev/null
  • Stderr

By default the log module writes to the standard error (STDERR). We can turn off the logging by setting the Output channel to ioutil.Discard. We can turn on the logging again by setting the Output channel to os.Stderr.

package main

import (
	"io/ioutil"
	"log"
	"os"
)

func main() {
	log.Print("One")
	log.SetOutput(ioutil.Discard)
	log.Print("Two")
	log.SetOutput(os.Stderr)
	log.Print("Three")
}

TODO: log levels?

TODO: log function names

TODO: logrotation

Time

Monolitic vs Wallclock time

  • Wallclock - the real time, might suddently change even in negative way if the clock is updated.

  • Monolitic - always growth.

  • They are both stored

Time example

  • time
  • Now
  • Unix

The Now function of the time package will return a representation of the current time. When printed it will show well formatted datetime string, but we can also use the Unix and UnixNano functions to the the time elapsed since the epoch in seconds and nanoseconds respectively.

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()
	fmt.Printf("%T\n", t)
	fmt.Println(t)
	fmt.Println(t.Unix())
	fmt.Println(t.UnixNano())
}
time.Time
2020-04-24 21:54:38.111505742 +0300 IDT m=+0.000032815
1587754478
1587754478111505742

Nanoseconds

  • UnixNano
package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	fmt.Println(now)
	fmt.Println(now.Unix())
	fmt.Println(now.UnixNano())
}
2020-04-24 21:43:01.206928432 +0300 IDT m=+0.000030861
1587753781
1587753781206928432

sleep and elapsed time

  • Sleep
  • Now
  • Sub
package main

import (
	"fmt"
	"time"
)

func main() {
	before := time.Now()
	fmt.Printf("%T\n", before)
	fmt.Println(before.UnixNano())

	time.Sleep(1000000) // 1 ms

	after := time.Now()
	fmt.Println(after.UnixNano())

	elapsed := after.Sub(before)
	fmt.Printf("Elapsed: %T  %v\n", elapsed, elapsed)
}
time.Time
1587754553086396917
1587754553087857409
Elapsed: time.Duration  1.460494ms

Time format

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()
	fmt.Println("Default format:", t)
	fmt.Println("Unix format:   ", t.Format(time.UnixDate))
	fmt.Println("RFC3339 format:", t.Format(time.RFC3339))
	fmt.Println("My format:     ", t.Format("Mon Jan 2 15:04:05 MST 2006"))
	fmt.Println("My format:     ", t.Format("2006-01-02 15:04:05"))

}
Default format: 2020-04-24 22:12:50.510675771 +0300 IDT m=+0.000032091
Unix format:    Fri Apr 24 22:12:50 IDT 2020
RFC3339 format: 2020-04-24T22:12:50+03:00
My format:      Fri Apr 24 22:12:50 IDT 2020
My format:      2020-04-24 22:12:50

Date Arithmetic

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()
	fmt.Printf("%T\n", t)
	fmt.Printf("Now:              %v\n", t)

	d2 := t.AddDate(0, 0, 2)
	fmt.Printf("2 days from now:  %v\n", d2)

	y1 := t.AddDate(1, 0, 0)
	fmt.Printf("A year from now:  %v\n", y1)

	m1 := t.AddDate(0, -1, 0)
	fmt.Printf("Last month:       %v\n", m1)

	fmt.Println()
	fmt.Printf("Now:              %v\n", t)

}
time.Time
Now:              2020-04-24 22:20:50.306276218 +0300 IDT m=+0.000028191
2 days from now:  2020-04-26 22:20:50.306276218 +0300 IDT
A year from now:  2021-04-24 22:20:50.306276218 +0300 IDT
Last month:       2020-03-24 22:20:50.306276218 +0200 IST

Now:              2020-04-24 22:20:50.306276218 +0300 IDT m=+0.000028191

Pointers

Integer assignment is copying (not pointer)

Normally, that is without the use of pointers if we assign a variable that contains a number, we create a copy of the value in a new place in the memory. Then if we change the value of the original variable (in this case if we increment it by one), then only the content of that variable changes.

The same happens if we change the new variable (in this case assigning 3 to it), only the content of that variable changes.

Normally this is what you want in regular assignment.

package main

import (
	"fmt"
)

func main() {
	a := 1
	b := a
	fmt.Printf("%v %v %T %T\n", a, b, a, b)
	a++
	fmt.Printf("%v %v %T %T\n", a, b, a, b)
	b = 3
	fmt.Printf("%v %v %T %T\n", a, b, a, b)
}
1 1 int int
2 1 int int
2 3 int int

Pointer to integer

  • &

We can use the & prefix during the assignment that means: take the pointer to this variable and copy the pointer. This means that the new variable (b in our case) will point to the same place in memory where the value of the original variable is. The content was not copied.

If at this point you print out the content of the new variable, you'll see the hexadecimal value of the pointer. In order to print out the real value you need to prefix the variable by a *.

If you now change the original variable, you'll see both are incremented. If you change the new variable (you'll have to use the * prefix for this) both are incremented.

Maybe it is better to say that there is only one value jut two ways to access it.

I think you would rarely do this in such code, but it is important to understand the concept for when we will want to pass a variable by reference to a function.

  • &amp; get pointer to
  • * get value behind pointer
package main

import (
	"fmt"
)

func main() {
	a := 1
	b := &a
	fmt.Println(b)
	fmt.Println(*b)
	fmt.Printf("%v %v %T %T\n", a, *b, a, b)
	a++
	fmt.Printf("%v %v %T %T\n", a, *b, a, b)
	*b = 3
	fmt.Printf("%v %v %T %T\n", a, *b, a, b)
}
0xc0000140e0
1
1 1 int *int
2 2 int *int
3 3 int *int

Array Pointer

Just as with variables holding integers, variables holding arrays are also copied dirng assignment. Here too you can use a pointer to have two ways to access the same array.

  • Assigning an array creates a copy of the data.
  • One can assign a pointer and then both variables access the same place in memory.
package main

import (
	"fmt"
)

func main() {
	a := [...]string{"Foo", "Bar"}
	b := a
	c := &a
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	fmt.Println()

	a[0] = "Zorg"
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
}
[Foo Bar]
[Foo Bar]
&[Foo Bar]

[Zorg Bar]
[Foo Bar]
&[Zorg Bar]

Slice Pointer and copy slice

  • copy

  • Assigning a slices assigns the reference to the slice, to the same place in memory.

  • However if we then change one of the variables (e.g. enlarge it), it might be moved to another array and then the two get separated.

  • If we assign a pointer to the slices, that pointer goes to the "head of the slice" which is moved when we move the slice.

package main

import (
	"fmt"
)

func main() {
	a := []string{"Foo", "Bar"}
	b := a
	c := &a
	d := make([]string, len(a))
	copy(d, a)
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	fmt.Println(d)
	fmt.Println()

	a[0] = "Zorg"
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	fmt.Println(d)
	fmt.Println()

	a = append(a, "Other")
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	fmt.Println(d)

}
[Foo Bar]
[Foo Bar]
&[Foo Bar]
[Foo Bar]

[Zorg Bar]
[Zorg Bar]
&[Zorg Bar]
[Foo Bar]

[Zorg Bar Other]
[Zorg Bar]
&[Zorg Bar Other]
[Foo Bar]

Panic (Exception handling)

Panic

In many other programming languages (e.g Python, Java) if the code encounters a problem it raises an exception that if not treated properly will stop the execution. In some other programming languages (e.g. C, Perl) functions return an error code when something does not work.

Go leans towards the languages that return error, though it usually returns it as a separate value. In addition Go has a concept similar to exceptions, but is rarely used and to reflect the different usage it also has a different name. It is called panic.

In Go if you try to open a file and you fail, this is considered as a normal situation - not an exceptional one, hence Go will return a representation of the error but won't try to stop the program. On the other hand if you try to divide by 0, Go will freak out and panic.

Let's see how does that work.

package main

import (
	"fmt"
)

func main() {
	fmt.Println("before")

	x := div(6, 2)
	fmt.Println(x)

	fmt.Println("middle")

	y := div(6, 0)
	fmt.Println(y)

	fmt.Println("after")
}

func div(a, b int) int {
	c := a / b
	return c
}
before
3
middle
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.div(...)
	/home/gabor/work/slides/golang/examples/go-panic/go_panic.go:22
main.main()
	/home/gabor/work/slides/golang/examples/go-panic/go_panic.go:15 +0x13a
exit status 2

We Panic

  • panic
  • raise

We can also initiate our own "panic" by calling the panic function.

package main

import (
	"fmt"
)

func main() {
	fmt.Println("before")

	x := div(6, 2)
	fmt.Println(x)

	fmt.Println("middle")

	y := div(6, 0)
	fmt.Println(y)

	fmt.Println("after")
}

func div(a, b int) int {
	if b == 0 {
		panic("Do you expect us do divide by 0?")
	}

	c := a / b
	return c
}
before
3
middle
panic: Do you expect us do divide by 0?

goroutine 1 [running]:
main.div(...)
	/home/gabor/work/slides/golang/examples/we-panic/we_panic.go:23
main.main()
	/home/gabor/work/slides/golang/examples/we-panic/we_panic.go:15 +0x151
exit status 2

Turn error into panic when port is used

package main

import (
	"fmt"
	"net/http"
)

func mainPage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World")
}

func main() {
	http.HandleFunc("/", mainPage)
	fmt.Println("Going to listen on http://localhost:5000  Ctr-c to stop the server.")
	err := http.ListenAndServe("127.0.0.1:5000", nil)
	if err != nil {
		//log.Fatal(err)
		panic(err)
	}
}

Panic after defer

  • defer
package main

import "fmt"

func main() {
	doIt(false)
	fmt.Println()
	doIt(true)
}

func doIt(earlyStop bool) {
	fmt.Println("Start")
	defer fmt.Println("Deferred")
	if earlyStop {
		panic("This is bad")
	}

	fmt.Println("Finish")
}
Start
Finish
Deferred

Start
Deferred
panic: This is bad

goroutine 1 [running]:
main.doIt(0x4db401)
	/home/gabor/work/slides/golang/examples/panic-after-defer/panic_after_defer.go:15 +0x1a5
main.main()
	/home/gabor/work/slides/golang/examples/panic-after-defer/panic_after_defer.go:8 +0x5d
exit status 2

Recover (and re-panic)

  • recover
  • try
  • catch
package main

import (
	"fmt"
)

func main() {
	fmt.Println("Before")
	fmt.Println()

	div(4, 2)
	fmt.Println()
	div(4, 0)

	fmt.Println()
	fmt.Println("After")
}

func div(a, b int) {
	fmt.Println("Start")

	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("Error: %v\n", err)
			//log.Print("Error: ", err)
			//panic(err)
		}
	}()
	res := a / b
	fmt.Println(res)

	fmt.Println("End")
}
Before

Start
2
End

Start
Error: runtime error: integer divide by zero

After

Recover from deep panic

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Before")
	fmt.Println()

	dividend := 16
	divisors := []int{8, 4, 0, 2}
	for _, divisor := range divisors {
		mydiv(dividend, divisor)
	}

	fmt.Println()
	fmt.Println("After")
}

func mydiv(a, b int) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("Error: %v\n", err)
			//log.Print("Error: ", err)
			//panic(err)
		}
	}()
	externalDiv(a, b)
}

func externalDiv(a, b int) {
	//fmt.Println("Start")

	res := a / b
	fmt.Printf("%v / %v = %v\n", a, b, res)

	//fmt.Println("End")
}
Before

16 / 8 = 2
16 / 4 = 4
Error: runtime error: integer divide by zero
16 / 2 = 8

After

Convert panic to returned error

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Before")
	fmt.Println()

	dividend := 16
	divisors := []int{8, 4, 0, 2}
	for _, divisor := range divisors {
		res, err := mydiv(dividend, divisor)
		if err != nil {
			fmt.Printf("Error: %v\n", err)
			continue
		}
		fmt.Println(res)
	}

	fmt.Println()
	fmt.Println("After")
}

func mydiv(a, b int) (res int, myerr error) {
	// var res int
	// var err error

	defer func() {
		if err := recover(); err != nil {
			//fmt.Printf("Error: %v\n", err)
			myerr = fmt.Errorf("%v", err)
			//log.Print("Error: ", err)
			//panic(err)
		}
	}()
	res = externalDiv(a, b)
	return
}

func externalDiv(a, b int) int {
	res := a / b
	return res
}
Before

2
4
Error: runtime error: integer divide by zero
8

After

Exercise: read several files

Given several files where each file contains two numbers separated by a comma, print out the result of dividing the first number by the second number. e.g.

go run read_several_files.go a.txt b.txt c.txt d.txt

should work.

4,2
4,0
21,3

Solution: read several files

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
)

func div(a, b int) (res int, err error) {
	defer func() {
		myerr := recover()
		if myerr != nil {
			// fmt.Printf("%T %v\n", myerr, myerr)
			err = fmt.Errorf("bad")
		}
	}()

	res = a / b
	return
}

func main() {
	files := os.Args[1:]
	if len(files) == 0 {
		fmt.Println("We need at least one file")
		os.Exit(1)
	}
	for _, filename := range files {
		dat, err := ioutil.ReadFile(filename)
		if err != nil {
			fmt.Printf("Could not open file '%v' because: %v\n", filename, err)
			continue
		}

		text := strings.TrimSuffix(string(dat), "\n")
		parts := strings.Split(text, ",")
		a, err := strconv.Atoi(parts[0])
		if err != nil {
			fmt.Printf("Could not convert '%v' to integer: %v", parts[0], err)
			continue
		}
		b, err := strconv.Atoi(parts[1])
		if err != nil {
			fmt.Printf("Could not convert '%v' to integer: %v", parts[1], err)
			continue
		}

		c, err := div(a, b)
		if err != nil {
			fmt.Printf("Could not divide %v / %v Error: %v", a, b, err)
		}
		fmt.Println(c)
	}
}
2
Could not open file 'b.txt' because: open b.txt: no such file or directory
Could not divide 4 / 0 Error: bad0
7

Goroutine

Without goroutine

Regular functions calls are executed sequentially. Each call can only start after all the previous calls have finished.

package main

import (
	"fmt"
	"time"
)

func count(n int, name string) {
	for i := 0; i < n; i++ {
		fmt.Printf("%s %d\n", name, i)
		time.Sleep(1000)
	}
}

func main() {
	fmt.Println("Welcome")
	count(3, "first")
	count(3, "second")
	count(3, "third")
	count(3, "fourth")
	fmt.Println("Done")
}
Welcome
first 0
first 1
first 2
second 0
second 1
second 2
third 0
third 1
third 2
fourth 0
fourth 1
fourth 2
Done

goroutine example

  • go

If we call them with the go keyword they become go-routines and they start to work in parallel. So in this example you can see the output is mixed up.

package main

import (
	"fmt"
	"time"
)

func count(n int, name string) {
	for i := 0; i < n; i++ {
		fmt.Printf("%s %d\n", name, i)
		time.Sleep(1000)
	}
}

func main() {
	fmt.Println("Welcome")
	count(3, "first")
	go count(3, "second")
	go count(3, "third")
	count(3, "fourth")
	fmt.Println("Done")
}
Welcome
first 0
first 1
first 2
fourth 0
second 0
second 1
third 0
third 1
second 2
fourth 1
third 2
fourth 2
Done

goroutine not finished yet

  • go

In this example we told the first call to run 30 times, but it could only run 6 times before the main part of the code finished that killed the whole process. So we need to be able to wait till the go-routine finishes.

package main

import (
	"fmt"
	"time"
)

func count(n int, name string) {
	for i := 0; i < n; i++ {
		fmt.Printf("%s %d\n", name, i)
		time.Sleep(1000)
	}
}

func main() {
	fmt.Println("Welcome")
	go count(30, "first")
	count(3, "second")
	count(3, "third")
	fmt.Println("Done")
}
Welcome
second 0
second 1
first 0
second 2
first 1
third 0
first 2
first 3
third 1
first 4
third 2
first 5
Done

goroutine no wait

A simpler and maybe clearer example for not waiting.

package main

import (
	"fmt"
	"time"
)

func count(n int, name string) {
	for i := 0; i < n; i++ {
		fmt.Printf("%s %d\n", name, i)
		time.Sleep(1000)
	}
}

func main() {
	go count(3, "first")
	fmt.Println("Done")
}
Done

Global waitgroup for goroutines

  • sync
  • WaitGroup
  • Add
  • Wait
  • Done
package main

import (
	"fmt"
	"sync"
)

func count(n int, name string) {
	for i := 1; i <= n; i++ {
		fmt.Printf("%v %v\n", name, i)
	}
	wg.Done()

}

var wg sync.WaitGroup

func main() {
	fmt.Println("Start")

	wg.Add(1)

	go count(1000, "Apple")
	go count(5, "Banana")

	wg.Wait()
	fmt.Println("End")
}
Start
Banana 1
Banana 2
Banana 3
Banana 4
Banana 5
End

Wait for goroutines

  • sync
  • WaitGroup
  • Add
  • Wait
  • Done
package main

import (
	"fmt"
	"sync"
)

func count(n int, name string) {
	for i := 1; i <= n; i++ {
		fmt.Printf("%v %v\n", name, i)
	}
}

func main() {
	fmt.Println("Start")
	var wg sync.WaitGroup

	wg.Add(1)

	go func() {
		count(5, "Apple")
		wg.Done()
	}()

	wg.Wait()
	fmt.Println("End")
}
Start
Apple 1
Apple 2
Apple 3
Apple 4
Apple 5
End

Return data from goroutines

package main

import (
	"fmt"
	"sync"
)

func main() {
	fmt.Println("Start")
	var wg sync.WaitGroup

	wg.Add(1)

	go func() {
		res := count(5, "Apple")
		fmt.Printf("Apple: %v\n", res)
		wg.Done()
	}()

	wg.Wait()
	fmt.Println("End")
}

func count(n int, name string) int {
	sum := 0
	for i := 1; i <= n; i++ {
		fmt.Printf("%v %v\n", name, i)
		sum += i
	}
	return sum
}
Start
Apple 1
Apple 2
Apple 3
Apple 4
Apple 5
Apple: 15
End

Counter - shared variable

package main

import (
	"fmt"
	"sync"
)

var counter int
var wg sync.WaitGroup

func count(n int) {
	for i := 0; i < n; i++ {
		counter++
	}
	wg.Done()
}

func main() {
	for j := 0; j < 3; j++ {
		wg.Add(1)
		go count(10000)
	}
	wg.Wait()

	fmt.Println(counter)
}
26959

Mutex

sync.RWMutex
RLock()
Lock()
RUnlock

Channels

Either the sender or the receiver will be blocked till the other side is also ready. Only when they are aligned the message will be sent and then both will continue running.

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Start")
	c := make(chan string)

	go sendMessage(c)

	msg := <-c
	fmt.Println(msg)

	fmt.Println("End")
}

func sendMessage(c chan string) {
	c <- "Hello World"
}
Start
Hello World
End

Channels are blocking

package main

import "fmt"

func main() {
	ch := make(chan string)
	ch <- "Hello"

	text := <-ch
	fmt.Println(text)
}
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/home/gabor/work/slides/golang/examples/blocking-channels/blocking_channels.go:7 +0x59
exit status 2

Channel capacity - buffered channel

package main

import (
	"fmt"
	"time"
)

func sleep() {
	time.Sleep(1000000000)
	fmt.Println("woke up")
}

func main() {
	ch := make(chan string, 2)
	// go sleep()
	ch <- "One"
	ch <- "Two"
	//  ch <- "Three"

	text := <-ch
	fmt.Println(text)

	fmt.Println(<-ch)

	//	fmt.Println(<-ch)
}

Channel with loop

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Start")
	c := make(chan string)

	go count(5, "Apple", c)

	for i := 0; i < 8; i++ {
		msg, open := <-c
		if !open {
			break
		}
		fmt.Println(msg)
	}
	// for msg := range c {
	// 	//fmt.Println(time.Now().UnixNano())
	// 	fmt.Print(msg)
	// 	time.Sleep(1000000000)
	// }
	fmt.Println("End")
}

func count(n int, name string, c chan string) {
	for i := 1; i <= n; i++ {
		c <- fmt.Sprintf("%v %v\n", name, i)
	}
	close(c)
}
Start
Apple 1
Apple 2
Apple 3
Apple 4
Apple 5
End

Pipeline map

package main

import (
	"fmt"
	"time"
)

func double(in <-chan int, out chan<- int) {
	for {
		number := <-in
		dbl := 2 * number
		//secs := 100000 //rand.Intn(10)
		time.Sleep(1000000000)
		out <- dbl
	}
}

func main() {
	jobs := make(chan int, 6)
	results := make(chan int)

	go double(jobs, results)
	// go double(ch1, ch2)
	// go double(ch1, ch2)
	numbers := []int{3, 7, 11, 8, 12, 4}
	start := time.Now()
	for _, n := range numbers {
		jobs <- n
	}

	for i := 0; i < len(numbers); i++ {
		res := <-results
		fmt.Println(res)
	}

	end := time.Now()
	fmt.Printf("Elapsed time: %v\n", end.Sub(start))
	close(jobs)
	fmt.Println("done")
}

Pipeline filter

package main

import (
	"fmt"
)

func big(in <-chan int, out chan<- int) {
	for {
		number, open := <-in
		if !open {
			close(out)
			break
		}
		if number > 100 {
			out <- number
		}
	}
}

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go big(ch1, ch2)
	numbers := []int{3, 7, 11, 8, 12, 4}
	for _, n := range numbers {
		ch1 <- n
		res, open := <-ch2
		if !open {
			break
		}
		fmt.Println(res)
	}
	close(ch1)
	fmt.Println("done")
}

TODO: Pipelines

package main

import (
	"fmt"
	"time"
)

func double(in <-chan int, out chan<- int) {
	for {
		number := <-in
		dbl := 2 * number
		//secs := 100000 //rand.Intn(10)
		time.Sleep(10000)
		out <- dbl
	}
}

func big(in <-chan int, out chan<- int) {
	for {
		number, open := <-in
		if !open {
			close(out)
			break
		}
		if number > 100 {
			out <- number
		}
	}
}

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	//go double(ch1, ch2)
	go big(ch1, ch2)
	numbers := []int{3, 7, 11, 8, 12, 4}
	for _, n := range numbers {
		ch1 <- n
		res, open := <-ch2
		if !open {
			break
		}
		fmt.Println(res)
	}
	close(ch1)
	fmt.Println("done")
}

Fibonacci goroutine

package main

import (
	"fmt"
)

func fibo(n int, out chan<- int) {
	out <- 1

	if n == 1 {
		close(out)
		return
	}

	out <- 1
	if n == 2 {
		close(out)
		return
	}
	a := 1
	b := 1
	for i := 3; i <= n; i++ {
		a, b = b, a+b
		out <- b
	}
	close(out)
}

func main() {
	ch := make(chan int)

	n := 10
	go fibo(n, ch)

	for res := range ch {
		fmt.Println(res)
	}
	fmt.Println("done")
}

Loop from a channel

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan string)

	go count("one", 1000, ch1)

	for {
		text := <-ch1
		fmt.Println(text)
	}
}

func count(name string, ms int, out chan<- string) {
	i := 0
	for {
		i++
		out <- fmt.Sprintf("%v %v", name, i)
		time.Sleep(time.Duration(1000000 * ms))
	}
}

Select channel

  • select
package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)

	go count("one", 0, ch1)
	go count("two", 2000, ch2)

	var text string
	// for {
	// 	text = <-ch1
	// 	fmt.Println(text)
	// 	text = <-ch2
	// 	fmt.Println(text)
	// }

	for {
		select {
		case text = <-ch1:
			fmt.Println(text)
		case text = <-ch2:
			fmt.Println(text)
			os.Exit(1)
		}
	}

}

func count(name string, ms int, out chan<- string) {
	i := 0
	for {
		i++
		out <- fmt.Sprintf("%v %v", name, i)
		if ms == 0 {
			continue
		}
		time.Sleep(time.Duration(1000000 * ms))
	}
}

Delayed start

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	go start(ch)
	fmt.Println("now")
	ch <- 23
	time.Sleep(1000)
	ch <- 19
	time.Sleep(1000)
	ch <- 42

	time.Sleep(10000000)
	fmt.Println("end")
}

func start(in <-chan int) {
	fmt.Println("start")

	var port int
	received := time.Now()
LOOP:
	for {
		select {
		case port = <-in:
			received = time.Now()
			fmt.Printf("received port %v at %v\n", port, received)
		default:
			now := time.Now()
			elapsed := now.Sub(received)
			if elapsed > 1000000 {
				if port != 0 {
					fmt.Printf("delayed start on port %v\n", port)

				} else {
					fmt.Println("No port received")
				}
				break LOOP
			}
		}
		time.Sleep(1000)
	}
	fmt.Println("start done")
}

Job pool

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func worker(in <-chan int, out chan<- int) {
	for number := range in {
		fmt.Printf("work on %v\n", number)
		delay := rand.Intn(3)
		time.Sleep(time.Duration(1000000 * delay))
		out <- number
	}
	//	close(out)
}

func main() {
	to := make(chan int, 10)
	from := make(chan int, 10)

	max := 10
	go worker(to, from)
	go worker(to, from)
	go worker(to, from)

	for i := 1; i <= max; i++ {
		to <- i
	}
	close(to)

	for i := 1; i <= max; i++ {
		res := <-from
		fmt.Printf("Result: %v\n", res)
	}

	// for res := range from {
	// 	fmt.Printf("Result: %v\n", res)
	// }

	time.Sleep(100000000)
}

Check for race conditions

go run -race app.go

Stand alone web application

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"time"
)

func mainPage(w http.ResponseWriter, r *http.Request) {
	aStr := r.PostFormValue("a")
	bStr := r.PostFormValue("b")
	a, _ := strconv.Atoi(aStr)
	b, _ := strconv.Atoi(bStr)

	result := a + b

	html := fmt.Sprintf(`<h1>Calculator</h1>
	<form method="POST">
	<input name="a">
	<input name="b">
	<input type="submit" value="Add">
	</form>
	Result: %v
	<hr>
	<a href="/exit">exit</a>`, result)

	fmt.Fprintf(w, "%v", html)
}

func exitApp(w http.ResponseWriter, r *http.Request) {
	go func() {
		time.Sleep(1000)
		os.Exit(0)
	}()
	fmt.Fprintf(w, "goodbye")
}

func openBrowser(targetURL string) {
	var err error

	switch runtime.GOOS {
	case "linux":
		err = exec.Command("xdg-open", targetURL).Start()
		// TODO: "Windows Subsytem for Linux" is also recognized as "linux", but then we need
		// err = exec.Command("rundll32.exe", "url.dll,FileProtocolHandler", targetURL).Start()
	case "windows":
		err = exec.Command("rundll32.exe", "url.dll,FileProtocolHandler", targetURL).Start()
	case "darwin":
		err = exec.Command("open", targetURL).Start()
	default:
		err = fmt.Errorf("unsupported platform %v", runtime.GOOS)
	}
	if err != nil {
		log.Fatal(err)
	}

}

// TODO: Find usable port, but open the browser only to that
// TODO: is there a better way to stop the application than just exit?

func main() {
	http.HandleFunc("/", mainPage)
	http.HandleFunc("/exit", exitApp)

	port := 5000
	connection := fmt.Sprintf("127.0.0.1:%v", port)
	url := fmt.Sprintf("http://%v", connection)
	fmt.Printf("Going to listen on %v  Ctr-c to stop the server.\n", url)
	// TODO: only open once we know the server was started.
	go func() {
		time.Sleep(1000)
		openBrowser(url)
	}()
	err := http.ListenAndServe(connection, nil)
	if err != nil {
		//log.Fatal(err)
		panic(err)
	}
	fmt.Println("OK")
}

Maximum processes

runtime.GOMAXPROCS(1) // set max to be used.
runtime.GOMAXPROCS(-1) // ask how many are there?

Exercise: Collect data from urls

Given a file with a list of URLs, fetch each one of the pages and print out the size of each page. (For a bit more complex exercise, print out their title.)

https://google.com/
https://youtube.com/
https://facebook.com/
https://baidu.com/
https://twitter.com/
https://instagram.com/
https://wikipedia.com/
https://www.amazon.com/
https://yahoo.com/
https://yandex.ru/
https://vk.com/
https://live.com/
https://naver.com/
https://yahoo.co.jp/
https://google.com.br/
https://netflix.com/
https://reddit.com/
https://ok.ru/
https://mail.ru/
https://ebay.com/
https://linkedin.com/
https://qq.com/
https://pinterest.com/
https://bing.com/
https://whatsapp.com/
https://office.com/
https://amazon.de/
https://aliexpress.com/
https://amazon.co.jp/
https://msn.com/
https://google.de/
https://paypal.com/
https://rakuten.co.jp/
https://amazon.co.uk/
https://daum.net/
https://google.co.jp/
https://taobao.com/
https://bilbili.com/
https://imdb.com/
https://booking.com/
https://roblox.com/
https://9apps.com/
https://globo.com/
https://duckduckgo.com/
https://www.nttdocomo.co.jp/

Exercise: Process multiple CSV files

Given a bunch of csv files, read each file and tell us how many cells were in each file.

Exercise: counter with lock

Take the example we saw earlier where we counted in several goroutines and apply the Mutex locks to ensure it does not miss a count.

Exercise: Fibonacci in parallel

Take the code from the Fibonacci example and check if you could run it in parallel. Observe how does the CPU behave as the number of concurrent jobs increas from 1 to the number of cores you have and beyond.

Solution: Collect data from urls

package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Printf("Usage: %s FILENAME\n", os.Args[0])
		os.Exit(1)
	}

	urls := readFile(os.Args[1])

	for _, url := range urls[0:3] {
		text, err := get(url)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("%-40s %6v\n", url, len(text))
	}
}

func readFile(filename string) []string {
	urls := make([]string, 0, 50)

	log.Println(filename)
	fh, err := os.Open(filename)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	scanner := bufio.NewScanner(fh)
	for scanner.Scan() {
		line := scanner.Text()
		urls = append(urls, line)
	}
	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading:", err)
		os.Exit(1)
	}
	return urls
}

func get(url string) (string, error) {
	text := ""
	resp, err := http.Get(url)
	if err != nil {
		return text, err
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return text, err
	}

	return string(body), nil
}

Functions 2

Numbers passed by reference

package main

import "fmt"

func main() {
	a := 1
	fmt.Printf("before %v\n", a)
	inc(&a)
	fmt.Printf("after %v\n", a)
}

func inc(val *int) {
	fmt.Printf("address of val in inc: %v\n", val)
	fmt.Printf("val in inc: %v\n", *val)
	*val++
	fmt.Printf("val in inc: %v\n", *val)
}
before 1
address of val in inc: 0xc0000140e0
val in inc: 1
val in inc: 2
after 2

Array passed by value or by reference

package main

import "fmt"

func main() {
	a := [...]int{4, 7, 12}
	fmt.Printf("before %v\n", a)
	change(a)
	fmt.Printf("after %v\n", a)
	reallyChange(&a)
	fmt.Printf("end %v\n", a)
}

func change(val [3]int) {
	fmt.Printf("val in change: %v\n", val)
	val[1] = 42
	fmt.Printf("val in change: %v\n", val)
}

func reallyChange(val *[3]int) {
	fmt.Printf("val in reallyChange: %v\n", val)
	val[1] = 42
	fmt.Printf("val in reallyChange: %v\n", val)
}
before [4 7 12]
val in change: [4 7 12]
val in change: [4 42 12]
after [4 7 12]
val in reallyChange: &[4 7 12]
val in reallyChange: &[4 42 12]
end [4 42 12]

TODO: pass by value, pass by reference

  • return by value: return variable

  • return by reference: return *variable

  • return named value:

func f() ( result int) { ... return }

does not seem to be very readable

  • Dispatch table

Variable declaration outside of functions

package main

import "fmt"

var g int

// g = 1    // syntax error: non-declaration statement outside function body

var i int = 2

// i := 42   // syntax error: non-declaration statement outside function body

func main() {
	g = 1
	i := 42 // type inferred

	fmt.Println(g)
	fmt.Println(i)
}
// var i int
// i = 42

// var i int = 42
// i := 42   // (is the same but this one cannot be used on the package level

// var (
//    i = 42
//    j = 23
// )

Exercise: Fibonacci series

Implement a function that accepts a positive integer (n) and return the first n numbers of the Fibonacci series.

Exercise: Permutations

Write a program to print all permutations of a given string

For "ABC", it should print the following series: ABC ACB BAC BCA CBA CAB

Exercise: 100 doors

  • There are 100 doors in a row that are all initially closed.
  • You make 100 passes by the doors.
  • The first time through, visit every door and toggle the door (if the door is closed, open it; if it is open, close it).
  • The second time, only visit every 2nd door (door #2, #4, #6, ...), and toggle it.
  • The third time, visit every 3rd door (door #3, #6, #9, ...), etc, until you only visit the 100th door.

Task

  • Answer the question: what state are the doors in after the last pass? Which are open, which are closed?

  • Source

Solution: Fibonacci series

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: fibonacci.go N")
		os.Exit(0)
	}

	n, err := strconv.Atoi(os.Args[1])
	if err != nil {
		fmt.Println("Invalid input")
		os.Exit(0)
	}

	var results = fibonacci(n)
	for _, v := range results {
		fmt.Println(v)
	}
}

func fibonacci(n int) []int {
	var fib = []int{}
	if n < 1 {
		fmt.Println("Not supported")
		return fib
	}
	if n == 1 {
		fib = append(fib, 1)
	}
	if n > 1 {
		fib = append(fib, 1, 1)
	}
	for i := 2; i < n; i++ {
		fib = append(fib, fib[i-2]+fib[i-1])
	}

	return fib
}

Solution: Fibonacci recursive

package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: fibonacci.go N")
		os.Exit(0)
	}

	n, err := strconv.Atoi(os.Args[1])
	if err != nil {
		fmt.Println("Invalid input")
		os.Exit(0)
	}

	var results = fibonacci(n)

	for _, v := range results {
		fmt.Println(v)
	}
}

func fibonacci(n int) []int {
	var fib []int

	for i := 1; i <= n; i++ {
		fib = append(fib, fibo(i))
	}
	return fib
}

func fibo(n int) int {
	if n == 1 || n == 2 {
		return 1
	}
	return fibo(n-1) + fibo(n-2)
}

Solution: single counter

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

func main() {
	var filename = "counter.txt"
	var cnt = 0
	var fhin, err1 = os.Open(filename)
	if err1 == nil {
		reader := bufio.NewReader(fhin)
		var line, _ = reader.ReadString('\n')
		cnt, _ = strconv.Atoi(line)
	}

	cnt++
	fmt.Println(cnt)

	var fhout, err2 = os.Create(filename)
	if err2 == nil {
		fhout.WriteString(fmt.Sprintf("%d", cnt))
		fhout.Close()
	}
}

Solution: Permutations

package main

import (
	"fmt"
)

func main() {
	results := permutations("a")
	fmt.Println(results)
}

// We assume they are unique
func permutations(word string) []string {
	if word == "" {
		return []string{""}
	}
	perms := []string{}
	for i, rn := range word {
		rest := word[:i] + word[i+1:]
		//fmt.Println(rest)
		for _, result := range permutations(rest) {
			perms = append(perms, fmt.Sprintf("%c", rn)+result)
		}
		//perms = append(perms, fmt.Sprintf("%c\n", rn))
	}
	return perms
}
package main

import (
	"fmt"
	"sort"
	"testing"
)

func TestPermutations(t *testing.T) {
	cases := make(map[string][]string)
	cases["a"] = []string{"a"}
	cases["ab"] = []string{"ab", "ba"}
	cases["abc"] = []string{"abc", "acb", "bac", "bca", "cab", "cba"}
	cases["abcd"] = []string{
		"dabc", "dacb", "dbac", "dbca", "dcab", "dcba",
		"adbc", "adcb", "bdac", "bdca", "cdab", "cdba",
		"abdc", "acdb", "badc", "bcda", "cadb", "cbda",
		"abcd", "acbd", "bacd", "bcad", "cabd", "cbad"}

	for inp, expected := range cases {
		actual := permutations(inp)
		if !compare(expected, actual) {
			t.Error(fmt.Sprintf("Expected '%v', Actual '%v'", expected, actual))
		}
	}
}

func compare(a, b []string) bool {
	//fmt.Println(a)
	//fmt.Println(b)
	if len(a) != len(b) {
		return false
	}
	sort.Strings(a)
	sort.Strings(b)

	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

Solution: 100 doors

package main

import "fmt"

func main() {
	for i, open := range doors(10) {
		fmt.Printf("%3v %v\n", i, open)
	}
}

func doors(n int) []bool {
	var doors = make([]bool, n)
	for i := 1; i <= n; i++ {
		d := i
		for d <= n {
			doors[d-1] = !doors[d-1]
			d += i
		}
	}
	return doors
}
package main

import (
	"fmt"
	"testing"
)

func TestDoors(t *testing.T) {
	cases := make(map[int][]bool)
	cases[0] = []bool{}
	cases[1] = []bool{true}
	cases[2] = []bool{true, false}
	cases[3] = []bool{true, false, false}
	cases[4] = []bool{true, false, false, true}
	cases[5] = []bool{true, false, false, true, false}
	cases[6] = []bool{true, false, false, true, false, false}
	cases[7] = []bool{true, false, false, true, false, false, false}
	cases[8] = []bool{true, false, false, true, false, false, false, false}
	cases[9] = []bool{true, false, false, true, false, false, false, false, true}
	cases[10] = []bool{true, false, false, true, false, false, false, false, true, false}
	for i, expected := range cases {
		actual := doors(i)
		if !compare(actual, expected) {
			t.Error(fmt.Sprintf("Expected '%v', Actual '%v'", expected, actual))
		}
	}
}

func compare(a, b []bool) bool {
	//fmt.Println(a)
	//fmt.Println(b)
	if len(a) != len(b) {
		return false
	}
	for i := 0; i < len(a); i++ {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

TODO return array

package main

import (
	"fmt"
	"reflect"
)

func main() {
	a := [3]int{}
	fill(a)
	// fmt.Println(time.Now().UnixNano())
	// rand.Seed(time.Now().UnixNano())
	// n := 1 + rand.Intn(10)
	// fmt.Println(n)
	// slc := make([]int, 0, n)
	// fmt.Println(slc)
	// fmt.Println(len(slc))
	// fmt.Println(cap(slc))
}

func fill(arr interface{}) {
	fmt.Println(arr)
	fmt.Println(reflect.TypeOf(arr))
	x = array(arr)
	//fmt.Println(len(arr))
	// for i, val := range arr {
	// 	fmt.Printf("%v %v", i, val)
	// }
}

Bitwise

bitwise operators

  • <<
  • &
  • |
  • ^
  • &^
&, |, ^, &^

bitshift operators

<<
>>
package main

import "fmt"

func main() {
	a := 0b10101001
	b := 0b10010011
	format := "%-6v %3v %10b\n"
	fmt.Printf(format, "a", a, a)
	fmt.Printf(format, "b", b, b)
	fmt.Println()

	not := ^a
	fmt.Printf(format, "not", not, not)

	and := a & b
	fmt.Printf(format, "and", and, and)

	or := a | b
	fmt.Printf(format, "or", or, or)

	xor := a ^ b
	fmt.Printf(format, "xor", xor, xor)

	andNOT := a &^ b
	fmt.Printf(format, "andNOT", andNOT, andNOT)

	left := a << 1
	fmt.Printf(format, "left", left, left)

	right := a >> 1
	fmt.Printf(format, "right", right, right)
}
a     169   10101001
b     147   10010011

and   129   10000001
or    187   10111011
xor    58     111010
left  338  101010010
right  84    1010100

bitwise left shift

package main

import "fmt"

func main() {
	x := 1
	for i := 0; i <= 8; i++ {
		y := x << i
		fmt.Printf("%3v %9b\n", y, y)
	}
	fmt.Println()

	a := 42
	fmt.Printf("%3v %9b", a, a)

}
  1         1
  2        10
  4       100
  8      1000
 16     10000
 32    100000
 64   1000000
128  10000000
256 100000000

 42    101010

bitwise not

package main

import "fmt"

func main() {
	fmt.Println("uint8")
	numbersUint8 := []uint8{1, 2, 5}
	for _, n := range numbersUint8 {
		fmt.Printf("%3v %9b\n", n, n)
		fmt.Printf("%3v %9b\n", ^n, ^n)

	}

	fmt.Println("\nint8")
	numbersInt8 := []int8{1, 2, 5}
	for _, n := range numbersInt8 {
		fmt.Printf("%3v %9b\n", n, n)
		fmt.Printf("%3v %9b\n", ^n, ^n)
	}

}
uint8
  1         1
254  11111110
  2        10
253  11111101
  5       101
250  11111010

int8
  1         1
 -2       -10
  2        10
 -3       -11
  5       101
 -6      -110

bitwise clear bit - AND NOT

package main

import "fmt"

func main() {
	// bit clear (and not) = set where "the first is set AND the second is NOT set"
	fmt.Printf("1 &^ 1 = %v\n", 1&^1)
	fmt.Printf("1 &^ 0 = %v\n", 1&^0)
	fmt.Printf("0 &^ 1 = %v\n", 0&^1)
	fmt.Printf("0 &^ 0 = %v\n", 0&^0)

}
1 &^ 1 = 0
1 &^ 0 = 1
0 &^ 1 = 0
0 &^ 0 = 0

Formatting

Println

  • println

{% embed include file="src/examples/println/println.go)

Sprintln

  • sprintln

{% embed include file="src/examples/sprintln/sprintln.go)

Sprintf

  • sprintf
  • %v

{% embed include file="src/examples/sprintf/sprintf.go)

Padding and alignment

{% embed include file="src/examples/printf-padding/printf_padding.go)

Filesystem

os.stat information about a file or directory (file exists)

  • os
  • IsNotExist
  • Stat
package main

import (
	"fmt"
	"os"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Printf("Usage %s FILENAME\n", os.Args[0])
		os.Exit(1)
	}
	var filename = os.Args[1]
	var st, err = os.Stat(filename)
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		if os.IsNotExist(err) {
			fmt.Printf("IsNotExist\n")
		}
		os.Exit(1)
	}
	fmt.Println(st)
	fmt.Printf("Name: %s\n", st.Name())
	fmt.Printf("Size: %d\n", st.Size())
}
Error: stat hello/world: no such file or directory

If the directory where the file can be found is not executable by the user who runs this code, we'll get the following error:

Error: stat hello/world: permission denied

List Directory

  • ioutil
  • ReadDir
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Printf("Usage: %s DIRNAME")
		os.Exit(1)
	}

	fmt.Println(os.Args[1])
	files, err := ioutil.ReadDir(".") //os.Args[1])
	if err != nil {
		fmt.Println("Error reading the directory list")
		log.Fatal(err)
	} else {
		for _, f := range files {
			fmt.Println(f.Name())
		}
	}
}

Get Current Working Directory (cwd)

  • cwd
  • pwd
  • Getcwd
package main

import (
	"fmt"
	"log"
	"os"
)

func main() {
	fmt.Println("hello")
	cwd, err := os.Getwd()
	if err != nil {
		log.Panic(err)
	} else {
		fmt.Println(cwd)
	}
}

Create Temporary Directory

  • TempDir
  • RemoveAll
  • rm -rf
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

func main() {
	tempDir, err := ioutil.TempDir("", "demo")
	if err != nil {
		log.Fatal(err)
	}

	defer os.RemoveAll(tempDir)

	fmt.Println(tempDir)
}

{aside} The defer os.RemoveAll(tempDir) will make sure the directory is removed when we exit the program. {/aside}

Traverse directory tree

package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Printf("Usage: %s DIRNAME", os.Args[0])
		os.Exit(1)
	}
	root := os.Args[1]

	// TODO: list of skips or regex for skip or function call to check skip
	subDirToSkip := "skip"

	err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			fmt.Printf("Cannot access dir at path %q: %v", path, err)
			return err
		}
		if info.IsDir() && info.Name() == subDirToSkip {
			fmt.Printf("Skipping directory %+v \n", info.Name())
		}
		fmt.Printf("Visiting %q\n", path)
		return nil
	})
	if err != nil {
		fmt.Printf("error walking the path %q: %v\n", root, err)
	}
}

Join parts of a directory or filepath

  • Join
package main

import (
	"fmt"
	"path/filepath"
)

func main() {
	path := filepath.Join("main", "sub", "other") // main/sub/other   main\sub\other
	fmt.Println(path)
}

Regular Expressions (Regexes, Regexp)

Regexp

package main

import (
	"fmt"
	"regexp"
)

func main() {
	text := "In this text there is a number 123456 and an age: 42 and another number 78"
	var match bool
	var res []byte
	var plex [][]byte
	var str []string

	getNumber := regexp.MustCompile(`\d+`)
	match = getNumber.MatchString(text)
	fmt.Println(match)

	res = getNumber.Find([]byte(text))
	fmt.Printf("%q\n", res)

	str = getNumber.FindStringSubmatch(text)
	fmt.Printf("%q\n", str)

	plex = getNumber.FindAll([]byte(text), -1)
	fmt.Printf("%q\n", plex)
}
true
"123456"
["123456"]
["123456" "42" "78"]

Regexp

package main

import (
	"fmt"
	"regexp"
)

func main() {
	text := "In this text there is a number 123456 and an age: 42 and another number 78"
	//	var match bool
	var res []byte
	//	var plex [][]byte
	var firstSubmtach [][]byte
	var allSubmtaches [][][]byte
	//	var str []string

	getAge := regexp.MustCompile(`age: (\d+)`)
	res = getAge.Find([]byte(text))
	fmt.Printf("%q\n", res)

	fmt.Println()
	firstSubmtach = getAge.FindSubmatch([]byte(text))
	fmt.Printf("%q\n", firstSubmtach)
	fmt.Printf("%q\n", firstSubmtach[1])

	fmt.Println()
	allSubmtaches = getAge.FindAllSubmatch([]byte(text), -1)
	fmt.Printf("%q\n", allSubmtaches)
	fmt.Printf("%q\n", allSubmtaches[0][1])
}
"age: 42"

["age: 42" "42"]
"42"

[["age: 42" "42"]]
"42"

Regex nomatch

package main

import (
	"fmt"
	"regexp"
)

func main() {
	text := "There are no numbers in this text"
	var match bool
	var res []byte
	var plex [][]byte
	var str []string

	getNumber := regexp.MustCompile(`\d+`)
	match = getNumber.MatchString(text)
	fmt.Println(match)

	res = getNumber.Find([]byte(text))
	fmt.Printf("%q\n", res)

	str = getNumber.FindStringSubmatch(text)
	fmt.Printf("%q\n", str)

	plex = getNumber.FindAll([]byte(text), -1)
	fmt.Printf("%q\n", plex)
}
false
""
[]
[]

Regex Markua include

package main

import (
	"fmt"
	"regexp"
)

func main() {
	cases := []string{
		"Some free text",
		"! just text after an excalmation point",
		"![title] title but no url",
		"![Code Maven](https://code-maven.com/)",
	}

	//var includeFile = regexp.MustCompile(`^!`)
	//var includeFile = regexp.MustCompile(`^!\[(.*?)\]`)
	var includeFile = regexp.MustCompile(`^!\[(.*?)\]\((.*)\)$`)

	for _, txt := range cases {
		//fmt.Println(txt)
		// fmt.Println(includeFile.MatchString(txt))
		// res := includeFile.Find([]byte(txt))
		// fmt.Printf("%q\n", res)
		subMatches := includeFile.FindStringSubmatch(txt)
		if len(subMatches) != 0 {
			fmt.Printf("%q\n", subMatches)
			fmt.Printf("%q\n", subMatches[1])
			fmt.Printf("%q\n", subMatches[2])
		}
		//fmt.Println()
	}
}

Exercise: Parse ini file

{% embed include file="src/examples/parse-ini/data.ini)

Exercise: parse hours log file and give report

The log file looks like this

09:20 Introduction
11:00 Exercises
11:15 Break
11:35 Numbers and strings
12:30 Lunch Break
13:30 Exercises
14:10 Solutions
14:30 Break
14:40 Lists
15:40 Exercises
17:00 Solutions
17:30 End

09:30 Lists and Tuples
10:30 Break
10:50 Exercises
12:00 Solutions
12:30 Dictionaries
12:45 Lunch Break
14:15 Exercises
16:00 Solutions
16:15 Break
16:30 Functions
17:00 Exercises
17:30 End

the report should look something like this:

09:20-11:00 Introduction
11:00-11:15 Exercises
11:15-11:35 Break
11:35-12:30 Numbers and strings
12:30-13:30 Lunch Break
13:30-14:10 Exercises
14:10-14:30 Solutions
14:30-14:40 Break
14:40-15:40 Lists
15:40-17:00 Exercises
17:00-17:30 Solutions

09:30-10:30 Lists and Tuples
10:30-10:50 Break
10:50-12:00 Exercises
12:00-12:30 Solutions
12:30-12:45 Dictionaries
12:45-14:15 Lunch Break
14:15-16:00 Exercises
16:00-16:15 Solutions
16:15-16:30 Break
16:30-17:00 Functions
17:00-17:30 Exercises

Break                      65 minutes    6%
Dictionaries               15 minutes    1%
Exercises                 340 minutes   35%
Functions                  30 minutes    3%
Introduction              100 minutes   10%
Lists                      60 minutes    6%
Lists and Tuples           60 minutes    6%
Lunch Break               150 minutes   15%
Numbers and strings        55 minutes    5%
Solutions                  95 minutes    9%

Solution: Parse ini file

package main

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
)

func main() {

}

func parseIni(filename string) (map[string]map[string]string, error) {
	ini := make(map[string]map[string]string)
	var head string

	fh, err := os.Open(filename)
	if err != nil {
		return ini, fmt.Errorf("Could not open file '%v': %v", filename, err)
	}
	sectionHead := regexp.MustCompile(`^\[([^]]*)\]\s*$`)
	keyValue := regexp.MustCompile(`^(\w*)\s*=\s*(.*?)\s*$`)
	reader := bufio.NewReader(fh)
	for {
		line, _ := reader.ReadString('\n')
		//fmt.Print(line)
		result := sectionHead.FindStringSubmatch(line)
		if len(result) > 0 {
			head = result[1]
			//fmt.Printf("found: %s\n", head)
			ini[head] = make(map[string]string)
			continue
		}

		result = keyValue.FindStringSubmatch(line)
		if len(result) > 0 {
			key, value := result[1], result[2]
			//fmt.Printf("kv: '%q'\n", result)
			ini[head][key] = value
			continue
		}

		if line == "" {
			break
		}
	}

	//ini["foo"] = "bar"

	return ini, nil
}
package main

import (
	"fmt"
	"testing"
)

func TestParseIni(t *testing.T) {
	actual, err := parseIni("data.ini")
	if err != nil {
		t.Error(fmt.Sprintf("Error in parsing: '%v'", err))
	}
	expected := make(map[string]map[string]string)
	expected["first section"] = make(map[string]string)
	expected["first section"]["a"] = "23"
	expected["first section"]["b"] = "12"
	expected["second section"] = make(map[string]string)
	expected["second section"]["a"] = "42"
	if !compareMapMap(actual, expected) {
		t.Error(fmt.Sprintf("Expected '%v', Actual '%v'", expected, actual))
	}
}

func compareMapMap(a, b map[string]map[string]string) bool {
	//fmt.Println(a)
	//fmt.Println(b)
	if len(a) != len(b) {
		return false
	}
	for key, value := range a {
		if !compareMap(value, b[key]) {
			return false
		}
	}
	for key, value := range b {
		if !compareMap(value, a[key]) {
			return false
		}
	}
	return true
}

func compareMap(a, b map[string]string) bool {
	//fmt.Println(a)
	//fmt.Println(b)
	if len(a) != len(b) {
		return false
	}
	for key, value := range a {
		if value != b[key] {
			return false
		}
	}
	for key, value := range b {
		if value != a[key] {
			return false
		}
	}
	return true
}

Solution: parse hours log file and give report

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
)

func main() {
	var err error
	if len(os.Args) != 2 {
		log.Fatal("Missing parameter: filename")
	}
	filename := os.Args[1]
	// fmt.Println(filename)

	fh, err := os.Open(filename)
	if err != nil {
		log.Fatalf("Could not open file '%v': %v", filename, err)
		os.Exit(1)
	}
	reader := bufio.NewReader(fh)
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			log.Fatal("Error while reading line")
		}
		fmt.Print(line)
		if line == "" {
			break
		}
	}
}

Flow Control

if-statements

if, else, else if

if cond {
}

if cond {
} else {
}


if cond {
} else if cond {
} else {
}

if - short statement (right before the condition)

if with initializer

package main

import (
	"fmt"
)

func main() {
	counter := map[string]int{
		"foo": 2,
		"bar": 4,
	}

	name := "foo"
	if count, ok := counter[name]; ok {
		fmt.Printf("%v : %v\n", name, count)
	} else {
		fmt.Printf("%v has no value\n", name)
	}

	name = "zorg"
	if count, ok := counter[name]; ok {
		fmt.Printf("%v : %v\n", name, count)
	} else {
		fmt.Printf("%v has no value\n", name)
	}

}

Comparision Operators

<
>
<=
>=
==
!=

Short circuit

switch

  • switch
  • case
package main

import (
	"fmt"
)

func main() {
	a := 1
	switch a {
	case 1:
		fmt.Println("one")
		fmt.Println("still one")
	case 2:
		fmt.Println("two")
		fmt.Println("still two")
	default:
		fmt.Println("Default")
	}
}

type switch

  • interface

  • type

  • A variable defined as an interface can get any type

  • A switch statement can switch on the type of the variable

package main

import (
	"fmt"
)

func main() {
	var x interface{}
	x = 1
	//x = "other"
	//x = 3.14
	switch x.(type) {
	case int:
		fmt.Println("int")
	case float64:
		fmt.Println("float64")
	default:
		fmt.Println("other")
	}

}
  • multiple values in the same case
  • tagless switch statement with real comparisions like x < 23 in the cases, here cases can overlap
  • video
  • fallthrough
  • type switch

JSON

JSON round trip

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	var person = map[string]string{}
	person["name"] = "Foo Bar"
	person["email"] = "foo@bar.com"
	fmt.Println(person)
	myjson, err := json.Marshal(person)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%T %s\n", myjson, myjson)
	reborn := make(map[string]string)
	err = json.Unmarshal(myjson, &reborn)
	fmt.Printf("%v", reborn)
}

Web client

http get request

  • http
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func main() {
	url := "http://httpbin.org/get"
	fmt.Println(url)

	resp, err := http.Get(url)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Println(string(body))
}

URL parsing

  • url
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	myURL := "https://code-maven.com/page/action?name=foo&age=42&name=bar"
	fmt.Println(myURL)
	parsedURL, err := url.Parse(myURL)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(parsedURL.Path)
	fmt.Println(parsedURL.RequestURI())
	fmt.Println(parsedURL.Hostname())
	fmt.Println(parsedURL.Host)

	fmt.Println()
	query := parsedURL.Query()
	fmt.Println(query)

	fmt.Println()
	for k, vals := range query {
		fmt.Printf("%v: ", k)
		for _, v := range vals {
			fmt.Printf("'%v' ", v)
		}
		fmt.Println()
	}
	//fmt.Println(parsedURL.String())
}
https://code-maven.com/page/action?name=foo&age=42&name=bar
/page/action
/page/action?name=foo&age=42&name=bar
code-maven.com
code-maven.com

map[age:[42] name:[foo bar]]

name: 'foo' 'bar' 
age: '42' 

TODO Checking links of a web site

package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/gocolly/colly"
)

func main() {
	var baseURL string
	flag.StringVar(&baseURL, "url", "", "URL where we startr")
	flag.Parse()

	urls := []string{}

	if baseURL == "" {
		fmt.Println("--url reuired")
		os.Exit(1)
	}
	fmt.Println(baseURL)

	c := colly.NewCollector()
	c.OnRequest(func(r *colly.Request) {
		fmt.Println("Visiting", r.URL)
	})
	// c.OnHTML("#content", func(h *colly.HTMLElement) {
	// 	fmt.Println(h)
	// 	//t := h.ChildAttr("a", "href")
	// 	//c.Visit(t)
	// })
	//c.OnHTML(".ytd-playlist-video-renderer", func(h *colly.HTMLElement) {
	c.OnHTML("a", func(h *colly.HTMLElement) {
		//fmt.Println(h)
		fmt.Println(h.Attr("href"))
		//match, _ := regexp.MatchString(`list=`, h.Attr("href"))
		//fmt.Println(match)
		//t := h.ChildAttr("a", "href")
		//c.Visit(t)
	})
	c.Visit(baseURL)
	c.Wait()
	fmt.Prinln(urls)
}

TODO fteching youtube playlist

package main

import (
	"fmt"
	"regexp"

	"github.com/gocolly/colly"
)

func main() {
	listID := "PLQVvvaa0QuDeF3hP0wQoSxpkqgRcgxMqX"
	playlistURL := "https://www.youtube.com/watch?list=" + listID

	// log.Print(playlistURL)
	// resp, err := http.Get(playlistURL)
	// if err != nil {
	// 	log.Fatal(err)
	// }
	// log.Print(resp)

	c := colly.NewCollector()
	c.OnRequest(func(r *colly.Request) {
		fmt.Println("Visiting", r.URL)
	})
	// c.OnHTML("#content", func(h *colly.HTMLElement) {
	// 	fmt.Println(h)
	// 	//t := h.ChildAttr("a", "href")
	// 	//c.Visit(t)
	// })
	//c.OnHTML(".ytd-playlist-video-renderer", func(h *colly.HTMLElement) {
	c.OnHTML("a", func(h *colly.HTMLElement) {
		//fmt.Println(h)
		//fmt.Println(h.Attr("href"))
		match, _ := regexp.MatchString(`list=`, h.Attr("href"))
		fmt.Println(match)
		//t := h.ChildAttr("a", "href")
		//c.Visit(t)
	})
	c.Visit(playlistURL)
}

HTTP GET failure

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func main() {
	url := "http://invalid.code-maven.com"
	fmt.Println(url)

	resp, err := http.Get(url)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	fmt.Println(string(body))
}

HTTP Server

HTTP Hello World

  • http

  • ListenAndServe

  • ResponseWriter

  • Request

  • net/http

package main

import (
	"fmt"
	"log"
	"net/http"
)

func mainPage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World")
}

func main() {
	http.HandleFunc("/", mainPage)
	host := "127.0.0.1"  // "0.0.0.0" to host externally as well "" defaults to it
	port := 5000
	fmt.Printf("Going to listen on http://%v:%v  Ctr-c to stop the server.\n", host, port)
	err := http.ListenAndServe(fmt.Sprintf("%v:%v", host, port), nil)
	if err != nil {
		log.Fatal(err)
	}
}

HTTP Echo GET

  • GET
  • FormValue
package main

import (
	"fmt"
	"log"
	"net/http"
)

func mainPage(w http.ResponseWriter, r *http.Request) {
	text := r.FormValue("text")
	html := `<h1>Echo</h1><form><input type name="text"><input type="submit" value="Echo"></form>`

	if text != "" {
		html += fmt.Sprintf("You said: %v", text)
	}
	fmt.Fprintf(w, html)
}

func main() {
	http.HandleFunc("/", mainPage)
	host := "127.0.0.1"
	port := 5000
	fmt.Printf("Going to listen on http://%v:%v  Ctr-c to stop the server.\n", host, port)
	err := http.ListenAndServe(fmt.Sprintf("%v:%v", host, port), nil)
	if err != nil {
		log.Fatal(err)
	}
}

HTTP Echo POST

  • POST
  • PostFormValue
  • Method
package main

import (
	"fmt"
	"log"
	"net/http"
)

func mainPage(w http.ResponseWriter, r *http.Request) {
	fmt.Println(r.Method)
	text := r.PostFormValue("text")
	html := `<h1>Echo</h1><form method="POST"><input type name="text"><input type="submit" value="Echo"></form>`

	if text != "" {
		html += fmt.Sprintf("You said: %v", text)
	}
	fmt.Fprintf(w, html)
}

func main() {
	http.HandleFunc("/", mainPage)
	host := "127.0.0.1"
	port := 5000
	fmt.Printf("Going to listen on http://%v:%v  Ctr-c to stop the server.\n", host, port)
	err := http.ListenAndServe(fmt.Sprintf("%v:%v", host, port), nil)
	if err != nil {
		log.Fatal(err)
	}
}

text/template

The New call gets a string that becomes the name of this template. I am not sure where is that name used again.

package main

import (
	"os"
	"text/template"
)

func main() {
	tmpl, err := template.New("test").Parse("Hello World!\n")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, nil)
	if err != nil {
		panic(err)
	}
}

text/template with map

package main

import (
	"os"
	"text/template"
)

func main() {
	person := map[string]string{
		"Name": "Joe",
	}
	tmpl, err := template.New("test").Parse("Hello {{.Name}}\n")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, person)
	if err != nil {
		panic(err)
	}
}


text/template with struct

package main

import (
	"os"
	"text/template"
)

func main() {
	tmpl, err := template.New("test").Parse("Hello {{.Name}}\n")
	if err != nil {
		panic(err)
	}

	person := personType{Name: "Jane"}
	err = tmpl.Execute(os.Stdout, person)
	if err != nil {
		panic(err)
	}
}

type personType struct {
	Name string
}

text/template in file

package main

import (
	"os"
	"text/template"
)

func main() {
	tmpl, err := template.ParseFiles("main.txt")
	if err != nil {
		panic(err)
	}

	person := personType{
		Name:  "Jane",
		Email: "jena@code-maven.com",
	}
	err = tmpl.Execute(os.Stdout, person)
	if err != nil {
		panic(err)
	}
}

type personType struct {
	Name  string
	Email string
}
Name: {{.Name}}
Email: {{.Email}}
Name: Jane
Email: jena@code-maven.com

text/template if

package main

import (
	"fmt"
	"os"
	"text/template"
)

func main() {
	tmpl, err := template.ParseFiles("if.txt")
	if err != nil {
		panic(err)
	}

	person := personType{
		Name:  "Jane",
		Email: "jena@code-maven.com",
	}
	err = tmpl.Execute(os.Stdout, person)
	if err != nil {
		panic(err)
	}
	fmt.Println("-----------------------------------------------------------")

	person = personType{
		Name: "Joe",
	}
	err = tmpl.Execute(os.Stdout, person)
	if err != nil {
		panic(err)
	}

}

type personType struct {
	Name  string
	Email string
}
Name: {{.Name}}
{{if .Email}}
Email: {{.Email}}
{{end}}
Name: Jane

Email: jena@code-maven.com
-----------------------------------------------------------
Name: Joe

text/template loop

package main

import (
	"os"
	"text/template"
)

func main() {
	tmpl, err := template.ParseFiles("loop.txt")
	if err != nil {
		panic(err)
	}

	person := personType{
		Name:     "Jane",
		Children: []string{"Alpha", "Beta", "Gamma"},
	}
	err = tmpl.Execute(os.Stdout, person)
	if err != nil {
		panic(err)
	}
}

type personType struct {
	Name     string
	Children []string
}
Name: {{.Name}}
---------------------
{{range .Children}}
  - {{.}}
{{end}}

Name: Jane
---------------------

  - Alpha

  - Beta

  - Gamma


html/template

  • html/template
  • Based on text/template so it has the same API.
  • Automatically escapes dangerous characters.
package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl, err := template.New("test").Parse("Hello World!\n")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, nil)
	if err != nil {
		panic(err)
	}

	person := map[string]string{
		"Name": "Joe",
	}
	tmpl, err = template.New("test").Parse("Hello {{.Name}}\n")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, person)
	if err != nil {
		panic(err)
	}

	other := Person{Name: "Jane"}
	err = tmpl.Execute(os.Stdout, other)
	if err != nil {
		panic(err)
	}
}

type Person struct {
	Name string
}

HTTP Hello World templates

package main

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
)

func mainPage(w http.ResponseWriter, r *http.Request) {
	t, err := template.ParseFiles("main.html")
	if err != nil {
		panic(err)
	}
	p := pageType{Title: "Joe and Jane", Body: "Some long text"}
	t.Execute(w, p)
}

func main() {
	http.HandleFunc("/", mainPage)
	fmt.Println("Going to listen on http://localhost:5000  Ctr-c to stop the server.")
	log.Fatal(http.ListenAndServe("127.0.0.1:5000", nil))
}

type pageType struct {
	Title string
	Body  string
}

<h1>Welcome: {{.Title}}</h1>
From Golang
<div>
    {{.Body}}
</div>

External programs

Find executable (which where)

  • which

  • where

  • exec

  • LookPath

  • Unix: which

  • Windows: where

  • Go:

package main

import (
	"fmt"
	"os"
	"os/exec"
)

func main() {
	path, err := exec.LookPath("python")
	if err != nil {
		fmt.Println("Could not find path")
		os.Exit(1)
	}
	fmt.Printf("%v %T\n", path, path)
}
/home/gabor/venv3/bin/python string

Run external programs

package main

import (
	"fmt"
	"log"
	"os/exec"
	"time"
)

func main() {
	cmd := exec.Command("sleep", "2")
	fmt.Printf("%v - start\n", time.Now().Unix())
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%v - Only after command has finished\n", time.Now().Unix())

	// perl, err := exec.LookPath("perl")
	// if err != nil {
	// 	fmt.Println("Could not find path")
	// 	os.Exit(1)
	// }

	// //fmt.Printf("%v %T", er, path)

	// //perl -E 'say "Hello from Perl"'
	// cmd := exec.Command(perl, "-E", `x'say "Hello from Perl"'`)
	// fmt.Printf("%v\n", cmd)
	// cmd.Stdout = os.Stdout
	// cmd.Stderr = os.Stderr

	// err = cmd.Run()
	// if err != nil {
	// 	fmt.Println(err)
	// 	os.Exit(1)
	// }

	// cmd := exec.Command("ls", "-l")
	// fmt.Printf("%v\n", cmd)
	// cmd.Stdout = os.Stdout
	// cmd.Stderr = os.Stderr

	// err := cmd.Run()
	// if err != nil {
	// 	fmt.Println(err)
	// 	os.Exit(1)
	// }

}

Run external program in the background

package main

import (
	"fmt"
	"log"
	"os/exec"
	"time"
)

func main() {
	cmd := exec.Command("sleep", "2")
	fmt.Printf("%v - Start.\n", time.Now().Unix())
	err := cmd.Start()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%v - We could do here something before we wait for the job to finish.\n", time.Now().Unix())
	err = cmd.Wait()
	fmt.Printf("%v - Command finished\n", time.Now().Unix())
	if err != nil {
		fmt.Printf("There was an error in the process: %v\n", err)
	}
}

Capture the outout of an external program

package main

import (
	"bytes"
	"fmt"
	"log"
	"os/exec"
)

func main() {
	cmd := exec.Command("go", "run", "examples/external/external.go", "23")
	//cmd := exec.Command("ls", "xxx")
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	fmt.Println("-------")
	fmt.Println(stdout.String())
	fmt.Println("-------")
	fmt.Println(stderr.String())
	fmt.Println("-------")
	fmt.Println(cmd.ProcessState.String() == "exit status 2")
	fmt.Println(cmd.ProcessState)
	fmt.Println("-------")
	if err != nil {
		log.Fatal(err)
	}
}
package main

import (
	"fmt"
	"os"
	"strconv"
)

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintln(os.Stderr, "Needs to get the expected exit code as a parameter")
		os.Exit(1)
	}
	exit_code, err := strconv.Atoi(os.Args[1])
	if err != nil {
		fmt.Fprintln(os.Stderr, "Expected integer on the command line")
		os.Exit(-1)
	}

	fmt.Println("To STDOUT")
	fmt.Fprintln(os.Stderr, "To STDERR")
	os.Exit(exit_code)
}

Distribution

Corss Compilation

Create a file called application.go in which you have all of your code.

You can run it as

go run application.go

You can create an executable binary (for the current Operating System and architecture) using:

GOBIN=/tmp go install application.go

This will create a file called application in the /tmp directory.

GOARCH=386 GOBIN=/tmp go install use.go

This will create for the same Opereating system but 32 bit.

GOOS=OS GOARCH=architecture go build PATH_TO
GOOS=OS GOARCH=architecture go install PATH_TO

GOOS:

android
darwin
dragonfly
freebsd
linux
netbsd
openbsd
plan9
solaris
windows

GOARCH

arm
arm64
386
amd64
ppc64
ppc64le
mips
mipsle
mips64
mips64le

Packages

.
├── src
│   └── github.com
│         └── szabgab
│                └── myrepo
│                      └── mymath.go
├── bin
├── pkg
    └── use.go
package main

import (
	"fmt"
	"math"
	"mymath"
)

func main() {
	fmt.Println("Hello World")

	fmt.Println(math.Pi)
	fmt.Println(math.Sin(3))

	fmt.Println(mymath.Add(3, 7))
}
package mymath

func Add(x, y int) int {
    return x + y
}


GOPATH=$(pwd) go run use.go

Cross compile

How to compile a golang application and distribute to multiple platforms. How to cross-compile golang application.

env GOOS=target-OS GOARCH=target-architecture go build package-import-path

How to build executables

Environment variables

  • GOROOT - path tpo the installation of go

  • PATH=$PATH;$GOROOT/bin - so the command line can find the go command

  • GOPATH - where my libraries are going to be located

  • PATH=$PATH;$GOPATH/bin

  • GOPATH - can have multiple directories in in (but then we have to includ the bin directories related to them one-by-one)

  • The first entry in GOPATH will be used by go get to install modules, but all the others will be used to find modules.

Install packages

  • get
go get github.com/nsf/gocode

Installs stuff in ~/go so we might want to add ~/go/bin to out PATH. In ~/.bashrc add

export PATH=$PATH:~/go/bin

then reload it using source ~/.bashrc

Include and distribute external files

How to include external files (e.g. images, html templates) in a golang application.

go workspace layout

src/
bin/
pkg/

Directory of 3rd party packages

  • There is no centralized directory of third party packages.
  • pkg.go.dev is a directory.
  • Just search "golang THING"
  • Awesome go a curated list.

Semantic versioning

go install

go install

Command line arguments (Flag)

Flag

Flags as pointers

  • The resulting variables are pointers
package main

import (
	"flag"
	"fmt"
)

func main() {
	var debug = flag.Bool("debug", false, "Debug or not?")
	var score = flag.Int("score", 0, "The score")
	var name = flag.String("name", "", "The name")
	flag.Parse()
	fmt.Println(*debug)
	fmt.Println(*score)
	fmt.Println(*name)
}

Flags as variables

package main

import (
	"flag"
	"fmt"
)

func main() {
	var debug bool
	var score int
	var name string

	flag.BoolVar(&debug, "debug", false, "Debug or not?")
	flag.IntVar(&score, "score", 0, "The score")
	flag.StringVar(&name, "name", "", "The name")
	flag.Parse()

	fmt.Println(debug)
	fmt.Println(score)
	fmt.Println(name)
}

Appendix

Some advanced topics

  • closures
  • pointers
  • go-routines
  • classes (there are no classes)
  • interfaces
  • Stringers - stringification
  • many standard packages
  • many external packages

Resources

Caller filename

  • caller
  • runtime
package main

import (
	"fmt"
	"runtime"
)

func main() {
	_, file, _, _ := runtime.Caller(0)
	fmt.Println(file)
}

os.Executable

  • Eecutable
package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println(os.Executable())
}

Solution: rectangular (STDIN) Reader

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	fmt.Print("Width: ")
	width, _ := reader.ReadString('\n')
	width = strings.TrimSuffix(width, "\n")

	fmt.Print("Height: ")
	height, _ := reader.ReadString('\n')
	height = strings.TrimSuffix(height, "\n")

	// convert to integer
	w, _ := strconv.Atoi(width) // this will convert a string like "abc" or "2x" to 0 and set err
	h, _ := strconv.Atoi(height)
	fmt.Println(w)
	fmt.Println(h)

	fmt.Println("The size of the rectangular is: ", w*h)
}

Scan int

package main

import "fmt"

func main() {
	var number int
	fmt.Print("Type in a number: ")
	fmt.Scan(&number)
	fmt.Println(number)
}
  • Accepts an integer
  • Accepts a single character (and gives 0) but the second character is left on the SDTIN buffer
  • TODO: try also with int8 type

Function assignment

package main

import (
	"fmt"
)

func main() {
	print := fmt.Println
	print(42)
}

Overwriting built-in functions

package main

import "fmt"

func main() {
	len := len("abc")
	fmt.Println(len)
	// x := len("def")   //  cannot call non-function len (type int)
}
  • TODO: Why does Go allow for this without any complaint?

TODO: stack

queue

  • PushBack

  • Front

  • Remove

  • see lists

package main

import (
	"container/list"
	"fmt"
)

func main() {
	queue := list.New()

	queue.PushBack("Joe")
	queue.PushBack("Jane")
	queue.PushBack("Mary")
	queue.PushBack("Joe")

	for {
		if queue.Len() == 0 {
			break
		}
		item := queue.Front()
		fmt.Println(item.Value)
		queue.Remove(item)
	}
}
Joe
Jane
Mary
Joe

Left over

package main

import "fmt"

func main() {
	var a [5]byte
	a[0] = 'A'
	a[1] = 65
	fmt.Println(a)
}

Open Web browser

  • runtime
package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"runtime"
)

func main() {
	baseURL := "https://code-maven.com/"
	if len(os.Args) == 2 {
		baseURL = os.Args[1]
	}
	openBrowser(baseURL)
}
func openBrowser(targetURL string) {
	var err error

	switch runtime.GOOS {
	case "linux":
		err = exec.Command("xdg-open", targetURL).Start()
		// TODO: "Windows Subsytem for Linux" is also recognized as "linux", but then we need
		// err = exec.Command("rundll32.exe", "url.dll,FileProtocolHandler", targetURL).Start()
	case "windows":
		err = exec.Command("rundll32.exe", "url.dll,FileProtocolHandler", targetURL).Start()
	case "darwin":
		err = exec.Command("open", targetURL).Start()
	default:
		err = fmt.Errorf("unsupported platform %v", runtime.GOOS)
	}
	if err != nil {
		log.Fatal(err)
	}

}

// Based on: https://gist.github.com/hyg/9c4afcd91fe24316cbf0

Unicode

package main

import "fmt"

func main() {
	s := "«ABC»"
	fmt.Println("s = ", s)
	fmt.Println(len(s))
	fmt.Println("byte", 0b11111111)
	π := 3.14
	fmt.Println(π)
	r1 := '™'
	fmt.Printf("%%c %c\n", r1)
	fmt.Println(r1)

	r2 := '\x6A'
	fmt.Printf("%c %v\n", r2, r2)

	r3 := '\u2122'
	fmt.Printf("\\u%04x %c %v\n", r3, r3, r3)
	fmt.Println(r3)

	r4 := '\U00002122'
	fmt.Printf("\\U%08x %c %v\n", r4, r4, r4)
	fmt.Println(r4)

	// https://golang.org/pkg/unicode/
	// release notes: https://golang.org/doc/go1.14 (at the end)

	s = "Peace שלום سلام"

	// `\pL` letter    `\pN`  number (also arabic numbers)
}

golang create io.reader from string

  • NewReader
  • Read
  • EOF

Many tools in Golang expect an io.reader object as an input parameter What happens if you have a string and you'd like to pass that to such a function? You need to create an io.reader object that can read from that string:

package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	someString := "hello world\nand hello go and more"
	myReader := strings.NewReader(someString)

	fmt.Printf("%T", myReader) // *strings.Reader

	buffer := make([]byte, 10)
	for {
		count, err := myReader.Read(buffer)
		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
			}
			break
		}
		fmt.Printf("Count: %v\n", count)
		fmt.Printf("Data: %v\n", string(buffer))
	}

}
*strings.ReaderCount: 10
Data: hello worl
Count: 10
Data: d
and hell
Count: 10
Data: o go and m
Count: 3
Data: oreo and m

Parse HTML Token by token

package main

import (
	"fmt"
	"io"
	"log"
	"strings"

	"golang.org/x/net/html"
)

func main() {
	body := `<html>
	<body>
		<h1>Main title</h1>
		<a href="https://code-maven.com/">Code Maven</a>
	</body>
	</html>`

	reader := strings.NewReader(body)
	tokenizer := html.NewTokenizer(reader)
	for {
		tt := tokenizer.Next()
		if tt == html.ErrorToken {
			if tokenizer.Err() == io.EOF {
				return
			}
			log.Printf("Error: %v", tokenizer.Err())
			return
		}
		fmt.Printf("Token: %v\n", tokenizer.Token())
	}
}
Token: <html>
Token: 
	
Token: <body>
Token: 
		
Token: <h1>
Token: Main title
Token: </h1>
Token: 
		
Token: <a href="https://code-maven.com/">
Token: Code Maven
Token: </a>
Token: 
	
Token: </body>
Token: 
	
Token: </html>

Parse HTML extract tags and attributes

  • TagName
  • TagAttr
package main

import (
	"fmt"
	"io"
	"strings"

	"golang.org/x/net/html"
)

func main() {
	body := `<html>
	<body>
		<h1>Main title</h1>
		<a href="https://code-maven.com/">Code Maven</a>
		<h2 id="subtitle" class="important">Some subtle title</h2>
	</body>
	</html>`

	reader := strings.NewReader(body)
	tokenizer := html.NewTokenizer(reader)
	for {
		tt := tokenizer.Next()
		if tt == html.ErrorToken {
			if tokenizer.Err() == io.EOF {
				return
			}
			fmt.Printf("Error: %v", tokenizer.Err())
			return
		}
		tag, hasAttr := tokenizer.TagName()
		fmt.Printf("Tag: %v\n", string(tag))
		if hasAttr {
			for {
				attrKey, attrValue, moreAttr := tokenizer.TagAttr()
				// if string(attrKey) == "" {
				// 	break
				// }
				fmt.Printf("Attr: %v\n", string(attrKey))
				fmt.Printf("Attr: %v\n", string(attrValue))
				fmt.Printf("Attr: %v\n", moreAttr)
				if !moreAttr {
					break
				}
			}
		}
	}
}
Tag: html
Tag: 
Tag: body
Tag: 
Tag: h1
Tag: 
Tag: h1
Tag: 
Tag: a
Attr: href
Attr: https://code-maven.com/
Attr: false
Tag: 
Tag: a
Tag: 
Tag: h2
Attr: id
Attr: subtitle
Attr: true
Attr: class
Attr: important
Attr: false
Tag: 
Tag: h2
Tag: 
Tag: body
Tag: 
Tag: html

Other

Print to STDERR or STDOUT

  • STDERR
  • STDOUT
  • Stderr
  • WriteString
  • Fprintln
package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println("To STDOUT using fmt.Println")
	fmt.Fprintln(os.Stderr, "To STDERR using fmt.Fprintlt")
	os.Stderr.WriteString("To STDERR using os.Stderr.WriteString\n")
}

Companies using Golang in Israel

Though many of the readers of these slides are from around the world and for them I'd probably need to create a page for companies in their respective countries, but for now I have listed a few companies that have offices in Israel where they use Go along with the titles of job posts I saw on LinkedIn.

  • Alibaba Group - Expert Backend Engineer-Machine Intelligence Israel Lab
  • Apester
  • CATO Networks - Backend Engineer
  • Cisco
  • Check Point - Infrastructure Backend Developer; Cloud Security Full Stack Developer
  • CodeNotary - Senior Golang Developer
  • Crowdstrike - Software Engineer - SecOps
  • Ebay - Technical Lead; Mid Level Developer
  • Elastic - Application Performance Monitoring - Golang Engineer
  • Firedome - Senior Embedded Developer
  • Fiverr - Senior Full Stack Engineer, Algotighm developer
  • Forter - Mobile OS (Cyber) Researcher
  • Gett - in the delivery team, all the backend, like routing optimization, dashboards, etc are written Go.
  • Healthy.io - Backend Developer
  • IntSights - Backend Developer
  • JFrog - Cloud Native DevOps Architect
  • Mobileye - Cloud Software Engineer
  • Odoro - Vice President Research And Development
  • Orca Security - Senior Software Engineer
  • Palo Alto Networks - Fullstack developer; Backend Engineer (Prisma Cloud Compute); Tech Lead - Sr. Software Engineer (Cortex XSOAR)
  • PerimeterX - Software Developer
  • Promo.com - Back End Developer
  • Radware - Cloud Software Engineer
  • Red Hat - Principal Software Engineer - KubeVirt Cloud Native Network Functions; Performance and Scale Engineer; Software Engineer - Foundation CI
  • SAP - Software Developer
  • SolarWinds - Technical Lead Developer
  • Spot.IM - Senior Back End Developer
  • Tonkean - DevOps Engineer
  • Wix - DevOps Engineer; Database Infrastructure Engineer; System Security Engineer; Infrastructure Lead - DevEx Team