Go implements data structures such as List, Set, Stack, Deque, etc.
Complete code address (Everyone is welcome ⭐️):
/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-data-structure
If you have been exposed to other languages except Go (such as Java), you may wonder why Go does not have common data containers such as deque, stack, set, and list. Especially for those students who are used to using these containers to solve LeetCode problems, it is even more inconvenient.
1 Why Go native does not provide these containers: for simplicity
Since its birth, Go language has a very clear goal, which is to be concise, efficient and concurrent. To achieve these goals, Go has made many choices in design.
- Simplicity: A core goal of the Go language team is to keep the language simple. They believe that if a feature can be implemented in simple combinations, there is no need to put it into the standard library.
- Although data structures such as deque, stack, set, and list are commonly used, they are not the only way to write efficient and maintainable code. By combining slices and mappings, developers can implement most of the required data structures.
- Encourage best practices: Go language advocates a "minimal surprise" design principle. That is, the code should be as easy to understand and predict as possible. One of the design philosophy of the standard library is to provide minimal but sufficient tools to enable developers to write code in accordance with best practices.
- This philosophy avoids the expansion of the standard library while ensuring code clarity and maintainability.
- The Power of the Community: The Go language ecosystem is very active, with many high-quality third-party libraries that provide the advanced data structures you need. If the standard library contains all the data structures that may be needed, it will become very large and difficult to maintain.
Instead, through community contribution, Go can keep the core simple and flexible.
2 Implementation
Full code address: /ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-data-structure
Although Go does not have these advanced data structures built in, we can still achieve almost all the data structures we need by combining slices and mappings.
- Moreover, this method is more in line with the Go language's concise and efficient design philosophy.
- Most importantly, this also makes us more understanding of the internal implementation of these data structures, rather than simply calling off ready-made APIs.
Therefore, if you encounter similar problems next time, you can try to implement these data structures yourself, which can not only improve coding capabilities, but also have a deeper understanding of the design concept of Go language.
List
Idea: For lists, you usually need to have operations such as Add and Remove. In fact, the native slice of golang is very close to slices, so we can simply encapsulate them.
package main import ( "errors" "fmt" ) /* List: - NewList(): Create a new list - Add(element): Add an element at the end of the list - Remove(index): Remove elements based on index - Size(): Returns the size of the list - Get(index): Get elements based on index - IsEmpty(): Determine whether the list is empty - Clear(): Clear list - GetFirst(): Get the first element - GetLast(): Get the last element - RemoveLast(): Remove the last element - AddFirst(element): Add an element at the beginning of the list - RemoveFirst(): Remove the first element ... */ type List struct { data []interface{} } // NewList creates a new listfunc NewList() *List { return &List{ data: []interface{}{}, } } // Add Add element at the end of the listfunc (l *List) Add(v interface{}) { = append(, v) } // Remove Remove elements based on indexfunc (l *List) Remove(index int) error { if index < 0 || index >= len() { return ("index out of bounds") } = append([:index], [index+1:]...) return nil } // Size Returns the size of the listfunc (l *List) Size() int { return len() } // Get get element based on indexfunc (l *List) Get(index int) (interface{}, error) { if index < 0 || index >= len() { return nil, ("index out of bounds") } return [index], nil } // IsEmpty determines whether the list is emptyfunc (l *List) IsEmpty() bool { return len() == 0 } // Clear Clear listfunc (l *List) Clear() { = []interface{}{} } // GetFirst Get the first elementfunc (l *List) GetFirst() (interface{}, error) { if () { return nil, ("list is empty") } return [0], nil } // GetLast Gets the last elementfunc (l *List) GetLast() (interface{}, error) { if () { return nil, ("list is empty") } return [len()-1], nil } // AddFirst adds elements at the beginning of the listfunc (l *List) AddFirst(v interface{}) { = append([]interface{}{v}, ...) } // RemoveFirst Remove the first elementfunc (l *List) RemoveFirst() error { if () { return ("list is empty") } = [1:] return nil } // RemoveLast Remove the last elementfunc (l *List) RemoveLast() error { if () { return ("list is empty") } = [:len()-1] return nil } func main() { list := NewList() // Test Add and Get (1) (2) (3) value, err := (1) if err != nil { ("Error:", err) } else { ("Value at index 1:", value) // Output: Value at index 1: 2 } // Test Remove err = (1) if err != nil { ("Error:", err) } else { ("Size after remove:", ()) // Output: Size after remove: 2 } // Test GetFirst and GetLast first, err := () if err != nil { ("Error:", err) } else { ("First element:", first) // Output: First element: 1 } last, err := () if err != nil { ("Error:", err) } else { ("Last element:", last) // Output: Last element: 3 } // Test AddFirst and RemoveFirst (0) first, err = () if err != nil { ("Error:", err) } else { ("First element after addFirst:", first) // Output: First element after addFirst: 0 } err = () if err != nil { ("Error:", err) } else { ("Size after removeFirst:", ()) // Output: Size after removeFirst: 2 } // Test RemoveLast err = () if err != nil { ("Error:", err) } else { ("Size after removeLast:", ()) // Output: Size after removeLast: 1 } // Test Clear () ("Is list empty after clear?", ()) // Output: Is list empty after clear? true}
Stack
The biggest feature of Stack is: first in and then out, and we can also use slices to encapsulate the underlying storage.
package main import ( "fmt" ) /* Stack: - Push(item): Stack - Pop(): Output - Peek(): Returns the top element of the stack, but does not delete it - IsEmpty(): Determine whether the stack is empty - Search(item): Search for the position of the item element in the stack. If not found, return -1 - Clear(): Clear stack ... */ type Stack struct { data []interface{} } func NewStack() *Stack { return &Stack{ data: []interface{}{}, } } // Push to the stackfunc (s *Stack) Push(v interface{}) { = append(, v) } // Popfunc (s *Stack) Pop() interface{} { if len() == 0 { return nil } val := [len()-1] = [:len()-1] return val } // Peek returns the top element of the stack, but does not delete itfunc (s *Stack) Peek() interface{} { if len() == 0 { return nil } return [len()-1] } // IsEmpty determines whether the stack is emptyfunc (s *Stack) IsEmpty() bool { return len() == 0 } // Search Search for the position of the item element in the stack. If not found, return -1func (s *Stack) Search(v interface{}) int { for index, value := range { if value == v { return index } } return -1 } // Clear Clear stackfunc (s *Stack) Clear() { = []interface{}{} } func main() { stack := NewStack() // Test Push and Peek (1) (2) (3) ("Top element:", ()) // Output: Top element: 3 // Test Pop ("Popped element:", ()) // Output: Popped element: 3 ("Top element after pop:", ()) // Output: Top element after pop: 2 // Test IsEmpty ("Is stack empty?", ()) // Output: Is stack empty? false // Test Search ("Index of 2:", (2)) // Output: Index of 2: 1 ("Index of 3:", (3)) // Output: Index of 3: -1 // Test Clear () ("Is stack empty after clear?", ()) // Output: Is stack empty after clear? true}
Deque
Deque double-ended queue: can enter and exit both front and back
package main import ( "container/list" "fmt" ) /* Deque: - PushFront: Insert element in front of queue - PushBack: Insert elements in queue backend - PopFront: Remove and return element from queue front end - PopBack: Remove and return elements from queue backend ... */ // Deque double-ended queue structuretype Deque struct { data * } // NewDeque Create a new double-ended queuefunc NewDeque() *Deque { return &Deque{data: ()} } // PushFront inserts elements in the queue front endfunc (d *Deque) PushFront(value interface{}) { (value) } // PushBack inserts elements in queue backendfunc (d *Deque) PushBack(value interface{}) { (value) } // PopFront removes and returns the element in the front end of the queuefunc (d *Deque) PopFront() interface{} { front := () if front != nil { (front) return } return nil } // PopBack removes and returns elements from the queue backendfunc (d *Deque) PopBack() interface{} { back := () if back != nil { (back) return } return nil } func main() { deque := NewDeque() // Test PushFront and PushBack (1) (2) (3) (4) // Test PopFront ("Popped from front:", ()) // Output: Popped from front: 4 ("Popped from front:", ()) // Output: Popped from front: 2 // Test PopBack ("Popped from back:", ()) // Output: Popped from back: 3 ("Popped from back:", ()) // Output: Popped from back: 1 // Test the empty queue ("Popped from front on empty deque:", ()) // Output: Popped from front on empty deque: <nil> ("Popped from back on empty deque:", ()) // Output: Popped from back on empty deque: <nil> // Test PushFront and PushBack again (5) (6) // Test PeekFront and PeekBack ("Front element:", ()) // Output: Front element: 5 ("Back element:", ()) // Output: Back element: 6}
Set
package main import ( "fmt" "sync" ) /* Set: Can remove duplicate elements - Add: Add elements - Remove: Remove elements - Contains: Check if the element exists - IsEmpty: Determine whether the set is empty - Clear: Clear collection - Iterator: Returns an iterator channel ... */ type Set struct { mu data map[interface{}]bool } // NewSet creates a new collectionfunc NewSet() *Set { return &Set{ data: make(map[interface{}]bool), } } // Add Add element to the collectionfunc (s *Set) Add(value interface{}) { () defer () [value] = true } // Remove Remove elements from the collectionfunc (s *Set) Remove(value interface{}) { () defer () delete(, value) } // Contains Check whether the element exists in the collectionfunc (s *Set) Contains(value interface{}) bool { () defer () return [value] } // IsEmpty determines whether the set is emptyfunc (s *Set) IsEmpty() bool { () defer () return len() == 0 } // Clear Clear collectionfunc (s *Set) Clear() { () defer () = make(map[interface{}]bool) } // Iterator returns an iterator channelfunc (s *Set) Iterator() <-chan interface{} { ch := make(chan interface{}) go func() { defer func() { if r := recover(); r != nil { ("Recovered in Iterator:", r) } close(ch) }() () defer () for k := range { ch <- k } }() return ch } func main() { set := NewSet() // Test Add and Contains (1) (2) (3) ("Contains 1:", (1)) // Output: Contains 1: true ("Contains 4:", (4)) // Output: Contains 4: false // Test Remove (2) ("Contains 2 after remove:", (2)) // Output: Contains 2 after remove: false // Test IsEmpty ("Is set empty?", ()) // Output: Is set empty? false // Test Clear () ("Is set empty after clear?", ()) // Output: Is set empty after clear? true // Test Iterator (1) (2) (3) ("Elements in set:") for i := range () { (i) } // Other test codes data := make([]int, 2, 20) data[0] = -1 ("Length of data:", len(data)) // Output: Length of data: 2 ("Capacity of data:", cap(data)) // Output: Capacity of data: 20}
For more data structures, such as LinkedList, you can try it out by yourself.
3 Code Address
Complete code address (Everyone is welcome ⭐️):
/ziyifast/ziyifast-code_instruction/tree/main/go-demo/go-data-structure
This is the article about Go implementing data structures such as List, Set, Stack, Deque, etc. This is all about this. For more information about Go List, Set, Stack, and Deque, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!