Golang Basics
This article is aimed at beginners and introduces the basic usage of Golang, including control statements and common data structures from the standard library, to help you quickly get started with solving problems.
Standard Output
Golang's standard output utilizes the fmt
package's Println
and Printf
functions, which are used to print content to the console with a newline or formatted output.
package main
import (
"fmt"
)
func main() {
a := 10
// output: 10
fmt.Println(a)
// can concatenate and output
// output: Hello, World!
fmt.Println("Hello" + ", " + "World!")
s := "abc"
// output: abc 10
fmt.Println(s, a)
// formatted output
// output: abc 10
fmt.Printf("%s %d\n", s, a)
}
Control Statements
Control statements in programming languages are generally straightforward, with the most common being conditional statements and loops. Here is a brief introduction.
Conditional Statements: if else
package main
import (
"fmt"
)
func main() {
a := 10
if a > 5 {
fmt.Println("a > 5")
} else if a == 5 {
fmt.Println("a == 5")
} else {
fmt.Println("a < 5")
}
// Output: a > 5
}
The for
Loop
In many other programming languages, there are two keywords: for
and while
. However, in Golang, there is only the for
keyword, which also provides the functionality of while
loops found in other languages.
package main
import (
"fmt"
)
func main() {
// Output: 0 1 2 3 4
for i := 0; i < 5; i++ {
fmt.Print(i, " ")
}
fmt.Println()
num := 100
// Output: 100 50 25 12 6 3 1
for num > 0 {
fmt.Print(num, " ")
num /= 2
}
fmt.Println()
}
Basic Data Structures
The standard library in Golang offers several commonly used data structures, such as slices, lists, and maps. Below is an introduction to some commonly used data structures and their usage methods.
Dynamic Array Slice
In Golang, a slice is the implementation of a dynamic array. Unlike arrays with a fixed size, slices can dynamically adjust their size as needed.
Initialization method:
package main
import (
"fmt"
)
func main() {
// initialize an empty slice nums
var nums []int
// initialize a slice nums of size 7, with default element values as 0
nums = make([]int, 7)
// initialize a slice nums containing elements 1, 3, 5
nums = []int{1, 3, 5}
// initialize a slice nums of size 7, with all values as 2
nums = make([]int, 7)
for i := range nums {
nums[i] = 2
}
fmt.Println(nums)
// initialize a boolean slice dp of size 3 * 3, with all values initialized to true
var dp [][]bool
dp = make([][]bool, 3)
for i := 0; i < len(dp); i++ {
row := make([]bool, 3)
for j := range row {
row[j] = true
}
dp[i] = row
}
fmt.Println(dp)
}
Other common operations:
package main
import (
"fmt"
)
func main() {
n := 10
// initialize a slice of size 10 with all elements set to 0
nums := make([]int, n)
// output: false
fmt.Println(len(nums) == 0)
// output: 10
fmt.Println(len(nums))
// insert an element 20 at the end of the slice
// the append function returns a new slice, so we need to reassign the return value to nums
nums = append(nums, 20)
// output: 11
fmt.Println(len(nums))
// get the last element of the slice
// output: 20
fmt.Println(nums[len(nums)-1])
// remove the last element of the slice
nums = nums[:len(nums)-1]
// output: 10
fmt.Println(len(nums))
// access or modify directly by index
nums[0] = 11
// output: 11
fmt.Println(nums[0])
// insert an element 99 at index 3
// ... is the spread operator, which means to expand the elements in the slice
nums = append(nums[:3], append([]int{99}, nums[3:]...)...)
// remove the element at index 2
nums = append(nums[:2], nums[3:]...)
// swap nums[0] and nums[1]
nums[0], nums[1] = nums[1], nums[0]
// iterate over the slice
// output: 0 11 99 0 0 0 0 0 0 0
for _, num := range nums {
fmt.Print(num, " ")
}
fmt.Println()
}
Note that when using the append
method to insert elements into a slice, you must reassign the return value to the original slice. This is because adding elements may trigger memory reallocation due to capacity expansion, requiring you to store the result in a variable.
The above are the common methods for Golang slices, mainly including accessing elements by index and methods for adding and deleting elements. These methods are generally sufficient for use in algorithm problems.
Doubly Linked List container/list
The container/list
package in the Golang standard library provides an implementation of a doubly linked list. Compared to slices, linked lists offer better performance for inserting and deleting elements at the head and tail.
Common methods:
package main
import (
"container/list"
"fmt"
)
func main() {
// initialize the list
lst := list.New()
lst.PushBack(1)
lst.PushBack(2)
lst.PushBack(3)
lst.PushBack(4)
lst.PushBack(5)
// check if the list is empty, output: false
fmt.Println(lst.Len() == 0)
// get the size of the list, output: 5
fmt.Println(lst.Len())
// insert element 0 at the front of the list
lst.PushFront(0)
// insert element 6 at the back of the list
lst.PushBack(6)
// get the front and back elements of the list, output: 0 6
front := lst.Front().Value.(int)
back := lst.Back().Value.(int)
fmt.Println(front, back)
// remove the front element of the list
lst.Remove(lst.Front())
// remove the back element of the list
lst.Remove(lst.Back())
// insert an element in the list
// move to the third position
third := lst.Front().Next().Next()
lst.InsertBefore(99, third)
// remove an element from the list
second := lst.Front().Next()
lst.Remove(second)
// traverse the list
// output: 1 99 3 4 5
for e := lst.Front(); e != nil; e = e.Next() {
fmt.Print(e.Value.(int), " ")
}
fmt.Println()
}
Generally, when we want to add or remove elements at the head, we use container/list
because it is more efficient than slices (arrays) for these operations. However, when frequent access to elements by index is needed, we use slices.
Queue
In Golang, there isn't a specific queue type, but a doubly linked list container/list
can be used to simulate queue functionality, as it provides good performance for inserting and deleting elements at both the head and tail.
package main
import (
"container/list"
"fmt"
)
func main() {
// initialize an empty integer queue q
q := list.New()
// add elements to the end of the queue
q.PushBack(10)
q.PushBack(20)
q.PushBack(30)
// check if the queue is empty, output: false
fmt.Println(q.Len() == 0)
// get the size of the queue, output: 3
fmt.Println(q.Len())
// get the front element of the queue
// output: 10
front := q.Front().Value.(int)
fmt.Println(front)
// remove the front element of the queue
q.Remove(q.Front())
// output the new front element: 20
newFront := q.Front().Value.(int)
fmt.Println(newFront)
}
Stack
A stack is a Last In, First Out (LIFO) data structure. The Golang standard library does not provide a specific stack type, but you can simulate stack functionality using slices or doubly linked lists, as both allow efficient addition and removal of elements at the end.
Here, we will use slices to demonstrate the basic operations of a stack:
package main
import (
"fmt"
)
func main() {
// initialize an empty integer stack s
var s []int
// add elements to the top of the stack (end of the slice)
s = append(s, 10)
s = append(s, 20)
s = append(s, 30)
// check if the stack is empty, output: false
fmt.Println(len(s) == 0)
// get the size of the stack, output: 3
fmt.Println(len(s))
// get the top element of the stack, output: 30
fmt.Println(s[len(s)-1])
// remove the top element of the stack
s = s[:len(s)-1]
// output the new top element of the stack: 20
fmt.Println(s[len(s)-1])
}
Hash Table map
Golang's built-in type map
offers hash table functionality, supporting storage based on key-value pairs, and provides constant time complexity for lookup, insertion, and deletion operations.
Initialization method:
package main
import (
"fmt"
)
func main() {
// initialize an empty hashmap
var hashmap map[int]string
hashmap = make(map[int]string)
// initialize a hashmap with some key-value pairs
hashmap = map[int]string{
1: "one",
2: "two",
3: "three",
}
fmt.Println(hashmap)
}
Common Methods:
package main
import (
"fmt"
)
func main() {
// initialize the hashmap
hashmap := make(map[int]string)
hashmap[1] = "one"
hashmap[2] = "two"
hashmap[3] = "three"
// check if the hashmap is empty, output: false
fmt.Println(len(hashmap) == 0)
// get the size of the hashmap, output: 3
fmt.Println(len(hashmap))
// check if a specific key exists
// output: Key 2 -> two
if val, exists := hashmap[2]; exists {
fmt.Println("Key 2 ->", val)
} else {
fmt.Println("Key 2 not found.")
}
// get the value for a specific key, returns empty string if not present
// output:
fmt.Println(hashmap[4])
// insert a new key-value pair
hashmap[4] = "four"
// get the newly inserted value, output: four
fmt.Println(hashmap[4])
// delete a key-value pair
delete(hashmap, 3)
// check if key 3 exists after deletion
// output: Key 3 not found.
if val, exists := hashmap[3]; exists {
fmt.Println("Key 3 ->", val)
} else {
fmt.Println("Key 3 not found.")
}
// iterate over the hashmap
// output (order may vary):
// 1 -> one
// 2 -> two
// 4 -> four
for key, value := range hashmap {
fmt.Printf("%d -> %s\n", key, value)
}
}
Hash Set (a Variant of map
)
Golang does not have a built-in hash set type, but you can simulate a set using a hash table map
, where the keys are the elements and the values are struct{}
or bool
.
package main
import (
"fmt"
)
func main() {
// initialize a hashset containing some elements
hashset := map[int]struct{}{
1: {},
2: {},
3: {},
4: {},
}
// check if the hashset is empty, output: false
fmt.Println(len(hashset) == 0)
// get the size of the hashset, output: 4
fmt.Println(len(hashset))
// check if a specific element exists
// output: Element 3 found.
if _, exists := hashset[3]; exists {
fmt.Println("Element 3 found.")
} else {
fmt.Println("Element 3 not found.")
}
// insert a new element
hashset[5] = struct{}{}
// delete an element
delete(hashset, 2)
// output: Element 2 not found.
if _, exists := hashset[2]; exists {
fmt.Println("Element 2 found.")
} else {
fmt.Println("Element 2 not found.")
}
// iterate over the hashset
// output (order may vary):
// 1
// 3
// 4
// 5
for element := range hashset {
fmt.Println(element)
}
}
It is generally recommended to use map[int]struct{}
to simulate a hash set, as struct{}
does not occupy additional memory space, while the bool
type uses one byte of memory.
Summary
The foundational knowledge above is sufficient for you to start practicing problems.
Of course, some third-party libraries in Golang offer many other data structures and useful features, which are not covered in this article. More advanced data structures will be introduced gradually in later sections on data structures. The API for each structure can be looked up as needed, so there's no need to memorize everything from the start.
I will now guide you through some LeetCode algorithm problems to quickly put these data structures into practice and get familiar with the problem-solving platform.