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.
- Concurrency The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software from 2005
- C10k Problem from 1999
Go Designed by
-
At Google
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.
- Source code
- The Go compiler is written in Go
- How to compile the Go compiler way beyond our needs.
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.
- Linux
- Mac OSX
- MS Windows
- ...
- mobile: Android/iOS
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
- Golang home
- Download
- Install in the standard location offered by the installer!
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.
- Visual Studio Code (It has plugins for Golang)
- Other editors and IDEs
- Any text editor
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
- Slides on Github
- Clone or download as a ZIP file.
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 use
Printf`.
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
-
strconv
-
Itoa
-
nil
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 foruint8
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 fromfmt
.
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
-
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
-
Both the actual length and the capacity grew
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
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
, andFataln
print the message and callos.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.
&
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?
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",
"",
}
//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
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
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
- Golang tour
- 7-hours Video of freeCodeCamp
- Video
- Practical intro to Go
- GoDoc
- Concurrency in Go
- Golang REST API With Mux
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
- see lists
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