Skip to main content

Go Advanced Data Structures

Advanced data structures in Go go beyond the built-in types to create sophisticated, efficient, and reusable data models. By combining Go's built-in types with custom logic, you can create powerful data structures that solve complex problems efficiently. Understanding advanced data structures is crucial for building high-performance applications and solving real-world problems. This comprehensive guide will teach you everything you need to know about implementing advanced data structures in Go.

Understanding Advanced Data Structures in Go​

What Are Advanced Data Structures?​

Advanced data structures are custom implementations that extend beyond Go's built-in types to provide:

  • Specialized functionality - Tailored behavior for specific use cases
  • Performance optimization - Optimized for specific operations and data patterns
  • Complex relationships - Handle intricate data relationships and dependencies
  • Reusability - Generic implementations that can be adapted for different scenarios
  • Domain-specific logic - Business logic embedded in data structure operations

Go's Approach to Advanced Data Structures​

Go provides excellent support for creating advanced data structures:

Composition over Inheritance​

Go encourages composition through struct embedding and interface implementation.

Type Safety​

Go's type system ensures compile-time safety for custom data structures.

Performance Focus​

Go's design enables efficient implementations with predictable performance.

Simplicity and Clarity​

Go's syntax makes complex data structures readable and maintainable.

Custom Collection Implementations​

Stack Implementation​

The Stack Data Structure​

A stack is a Last-In-First-Out (LIFO) data structure with push and pop operations.

Generic Stack with Interface​

Using empty interfaces for generic stack implementation.

package main

import "fmt"

func main() {
// Custom collection implementations examples
fmt.Println("Custom collection implementations examples:")

// Stack implementation
type Stack struct {
items []interface{}
}

// NewStack creates a new stack
func NewStack() *Stack {
return &Stack{
items: make([]interface{}, 0),
}
}

// Push adds an item to the top of the stack
func (s *Stack) Push(item interface{}) {
s.items = append(s.items, item)
}

// Pop removes and returns the top item from the stack
func (s *Stack) Pop() interface{} {
if s.IsEmpty() {
return nil
}

index := len(s.items) - 1
item := s.items[index]
s.items = s.items[:index]
return item
}

// Peek returns the top item without removing it
func (s *Stack) Peek() interface{} {
if s.IsEmpty() {
return nil
}

return s.items[len(s.items)-1]
}

// IsEmpty returns true if the stack is empty
func (s *Stack) IsEmpty() bool {
return len(s.items) == 0
}

// Size returns the number of items in the stack
func (s *Stack) Size() int {
return len(s.items)
}

// String returns a string representation of the stack
func (s *Stack) String() string {
return fmt.Sprintf("Stack{%v}", s.items)
}

// Use the stack
stack := NewStack()

// Push items
stack.Push(1)
stack.Push(2)
stack.Push(3)
fmt.Printf("Stack after pushing 1, 2, 3: %s\n", stack)
// Output: Stack after pushing 1, 2, 3: Stack{[1 2 3]}

// Peek at top item
fmt.Printf("Top item: %v\n", stack.Peek())
// Output: Top item: 3

// Pop items
fmt.Printf("Popped: %v\n", stack.Pop())
fmt.Printf("Popped: %v\n", stack.Pop())
fmt.Printf("Stack after popping: %s\n", stack)
// Output:
// Popped: 3
// Popped: 2
// Stack after popping: Stack{[1]}

// Check if empty
fmt.Printf("Is empty: %t\n", stack.IsEmpty())
// Output: Is empty: false

// Pop remaining item
fmt.Printf("Popped: %v\n", stack.Pop())
fmt.Printf("Is empty: %t\n", stack.IsEmpty())
// Output:
// Popped: 1
// Is empty: true
}

Queue Implementation​

The Queue Data Structure​

A queue is a First-In-First-Out (FIFO) data structure with enqueue and dequeue operations.

Efficient Queue with Slice​

Implementing a queue using Go slices with proper memory management.

package main

import "fmt"

func main() {
// Queue implementation examples
fmt.Println("Queue implementation examples:")

// Queue implementation
type Queue struct {
items []interface{}
}

// NewQueue creates a new queue
func NewQueue() *Queue {
return &Queue{
items: make([]interface{}, 0),
}
}

// Enqueue adds an item to the rear of the queue
func (q *Queue) Enqueue(item interface{}) {
q.items = append(q.items, item)
}

// Dequeue removes and returns the front item from the queue
func (q *Queue) Dequeue() interface{} {
if q.IsEmpty() {
return nil
}

item := q.items[0]
q.items = q.items[1:]
return item
}

// Front returns the front item without removing it
func (q *Queue) Front() interface{} {
if q.IsEmpty() {
return nil
}

return q.items[0]
}

// Rear returns the rear item without removing it
func (q *Queue) Rear() interface{} {
if q.IsEmpty() {
return nil
}

return q.items[len(q.items)-1]
}

// IsEmpty returns true if the queue is empty
func (q *Queue) IsEmpty() bool {
return len(q.items) == 0
}

// Size returns the number of items in the queue
func (q *Queue) Size() int {
return len(q.items)
}

// String returns a string representation of the queue
func (q *Queue) String() string {
return fmt.Sprintf("Queue{%v}", q.items)
}

// Use the queue
queue := NewQueue()

// Enqueue items
queue.Enqueue("first")
queue.Enqueue("second")
queue.Enqueue("third")
fmt.Printf("Queue after enqueueing: %s\n", queue)
// Output: Queue after enqueueing: Queue{[first second third]}

// Check front and rear
fmt.Printf("Front: %v\n", queue.Front())
fmt.Printf("Rear: %v\n", queue.Rear())
// Output:
// Front: first
// Rear: third

// Dequeue items
fmt.Printf("Dequeued: %v\n", queue.Dequeue())
fmt.Printf("Dequeued: %v\n", queue.Dequeue())
fmt.Printf("Queue after dequeueing: %s\n", queue)
// Output:
// Dequeued: first
// Dequeued: second
// Queue after dequeueing: Queue{[third]}

// Check size
fmt.Printf("Size: %d\n", queue.Size())
// Output: Size: 1

// Dequeue remaining item
fmt.Printf("Dequeued: %v\n", queue.Dequeue())
fmt.Printf("Is empty: %t\n", queue.IsEmpty())
// Output:
// Dequeued: third
// Is empty: true
}

Linked List Implementation​

The LinkedList Data Structure​

A linked list is a linear data structure where elements are stored in nodes.

Node Structure and Operations​

Implementing a linked list with proper node management.

package main

import "fmt"

func main() {
// Linked list implementation examples
fmt.Println("Linked list implementation examples:")

// Node structure
type Node struct {
data interface{}
next *Node
}

// LinkedList structure
type LinkedList struct {
head *Node
size int
}

// NewLinkedList creates a new linked list
func NewLinkedList() *LinkedList {
return &LinkedList{}
}

// Add adds an item to the end of the list
func (ll *LinkedList) Add(data interface{}) {
newNode := &Node{data: data}

if ll.head == nil {
ll.head = newNode
} else {
current := ll.head
for current.next != nil {
current = current.next
}
current.next = newNode
}
ll.size++
}

// AddAt adds an item at a specific index
func (ll *LinkedList) AddAt(index int, data interface{}) error {
if index < 0 || index > ll.size {
return fmt.Errorf("index out of bounds")
}

newNode := &Node{data: data}

if index == 0 {
newNode.next = ll.head
ll.head = newNode
} else {
current := ll.head
for i := 0; i < index-1; i++ {
current = current.next
}
newNode.next = current.next
current.next = newNode
}
ll.size++
return nil
}

// Remove removes an item at a specific index
func (ll *LinkedList) Remove(index int) (interface{}, error) {
if index < 0 || index >= ll.size {
return nil, fmt.Errorf("index out of bounds")
}

var data interface{}

if index == 0 {
data = ll.head.data
ll.head = ll.head.next
} else {
current := ll.head
for i := 0; i < index-1; i++ {
current = current.next
}
data = current.next.data
current.next = current.next.next
}
ll.size--
return data, nil
}

// Get returns the item at a specific index
func (ll *LinkedList) Get(index int) (interface{}, error) {
if index < 0 || index >= ll.size {
return nil, fmt.Errorf("index out of bounds")
}

current := ll.head
for i := 0; i < index; i++ {
current = current.next
}
return current.data, nil
}

// Size returns the number of items in the list
func (ll *LinkedList) Size() int {
return ll.size
}

// IsEmpty returns true if the list is empty
func (ll *LinkedList) IsEmpty() bool {
return ll.size == 0
}

// String returns a string representation of the list
func (ll *LinkedList) String() string {
if ll.IsEmpty() {
return "LinkedList{}"
}

result := "LinkedList{"
current := ll.head
for current != nil {
result += fmt.Sprintf("%v", current.data)
if current.next != nil {
result += " -> "
}
current = current.next
}
result += "}"
return result
}

// Use the linked list
list := NewLinkedList()

// Add items
list.Add("first")
list.Add("second")
list.Add("third")
fmt.Printf("List after adding: %s\n", list)
// Output: List after adding: LinkedList{first -> second -> third}

// Add at specific index
err := list.AddAt(1, "middle")
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("List after adding at index 1: %s\n", list)
}
// Output: List after adding at index 1: LinkedList{first -> middle -> second -> third}

// Get item at index
item, err := list.Get(2)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Item at index 2: %v\n", item)
}
// Output: Item at index 2: second

// Remove item
removed, err := list.Remove(1)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Removed item: %v\n", removed)
fmt.Printf("List after removal: %s\n", list)
}
// Output:
// Removed item: middle
// List after removal: LinkedList{first -> second -> third}

// Check size
fmt.Printf("Size: %d\n", list.Size())
// Output: Size: 3
}

Generic Data Structure Patterns​

Type-Safe Collections​

Generic Stack with Type Parameters​

Using Go's generics (Go 1.18+) for type-safe collections.

The any Type Alias​

The any type is an alias for interface{} in Go 1.18+.

package main

import "fmt"

func main() {
// Generic data structure patterns examples
fmt.Println("Generic data structure patterns examples:")

// Generic Stack implementation (Go 1.18+)
type GenericStack[T any] struct {
items []T
}

// NewGenericStack creates a new generic stack
func NewGenericStack[T any]() *GenericStack[T] {
return &GenericStack[T]{
items: make([]T, 0),
}
}

// Push adds an item to the top of the stack
func (s *GenericStack[T]) Push(item T) {
s.items = append(s.items, item)
}

// Pop removes and returns the top item from the stack
func (s *GenericStack[T]) Pop() (T, bool) {
if s.IsEmpty() {
var zero T
return zero, false
}

index := len(s.items) - 1
item := s.items[index]
s.items = s.items[:index]
return item, true
}

// Peek returns the top item without removing it
func (s *GenericStack[T]) Peek() (T, bool) {
if s.IsEmpty() {
var zero T
return zero, false
}

return s.items[len(s.items)-1], true
}

// IsEmpty returns true if the stack is empty
func (s *GenericStack[T]) IsEmpty() bool {
return len(s.items) == 0
}

// Size returns the number of items in the stack
func (s *GenericStack[T]) Size() int {
return len(s.items)
}

// String returns a string representation of the stack
func (s *GenericStack[T]) String() string {
return fmt.Sprintf("GenericStack{%v}", s.items)
}

// Use generic stack with integers
intStack := NewGenericStack[int]()
intStack.Push(1)
intStack.Push(2)
intStack.Push(3)
fmt.Printf("Integer stack: %s\n", intStack)
// Output: Integer stack: GenericStack{[1 2 3]}

// Pop from integer stack
if item, ok := intStack.Pop(); ok {
fmt.Printf("Popped integer: %d\n", item)
}
// Output: Popped integer: 3

// Use generic stack with strings
stringStack := NewGenericStack[string]()
stringStack.Push("hello")
stringStack.Push("world")
stringStack.Push("go")
fmt.Printf("String stack: %s\n", stringStack)
// Output: String stack: GenericStack{[hello world go]}

// Pop from string stack
if item, ok := stringStack.Pop(); ok {
fmt.Printf("Popped string: %s\n", item)
}
// Output: Popped string: go

// Use generic stack with custom types
type Person struct {
Name string
Age int
}

personStack := NewGenericStack[Person]()
personStack.Push(Person{Name: "Alice", Age: 30})
personStack.Push(Person{Name: "Bob", Age: 25})
fmt.Printf("Person stack: %s\n", personStack)
// Output: Person stack: GenericStack{[{Alice 30} {Bob 25}]}

// Pop from person stack
if person, ok := personStack.Pop(); ok {
fmt.Printf("Popped person: %+v\n", person)
}
// Output: Popped person: {Name:Bob Age:25}
}

Generic Map with Constraints​

The comparable Constraint​

Using type constraints for generic map implementations.

Type Constraints and Bounds​

Implementing type-safe maps with proper constraints.

package main

import "fmt"

func main() {
// Generic map with constraints examples
fmt.Println("Generic map with constraints examples:")

// Generic Set implementation with comparable constraint
type GenericSet[T comparable] struct {
items map[T]bool
}

// NewGenericSet creates a new generic set
func NewGenericSet[T comparable]() *GenericSet[T] {
return &GenericSet[T]{
items: make(map[T]bool),
}
}

// Add adds an item to the set
func (s *GenericSet[T]) Add(item T) {
s.items[item] = true
}

// Remove removes an item from the set
func (s *GenericSet[T]) Remove(item T) {
delete(s.items, item)
}

// Contains returns true if the item is in the set
func (s *GenericSet[T]) Contains(item T) bool {
return s.items[item]
}

// Size returns the number of items in the set
func (s *GenericSet[T]) Size() int {
return len(s.items)
}

// IsEmpty returns true if the set is empty
func (s *GenericSet[T]) IsEmpty() bool {
return len(s.items) == 0
}

// ToSlice returns a slice of all items in the set
func (s *GenericSet[T]) ToSlice() []T {
result := make([]T, 0, len(s.items))
for item := range s.items {
result = append(result, item)
}
return result
}

// String returns a string representation of the set
func (s *GenericSet[T]) String() string {
return fmt.Sprintf("GenericSet{%v}", s.ToSlice())
}

// Use generic set with integers
intSet := NewGenericSet[int]()
intSet.Add(1)
intSet.Add(2)
intSet.Add(3)
intSet.Add(2) // Duplicate
fmt.Printf("Integer set: %s\n", intSet)
// Output: Integer set: GenericSet{[1 2 3]}

// Check if set contains item
fmt.Printf("Contains 2: %t\n", intSet.Contains(2))
fmt.Printf("Contains 4: %t\n", intSet.Contains(4))
// Output:
// Contains 2: true
// Contains 4: false

// Use generic set with strings
stringSet := NewGenericSet[string]()
stringSet.Add("apple")
stringSet.Add("banana")
stringSet.Add("orange")
stringSet.Add("apple") // Duplicate
fmt.Printf("String set: %s\n", stringSet)
// Output: String set: GenericSet{[apple banana orange]}

// Remove item from set
stringSet.Remove("banana")
fmt.Printf("String set after removal: %s\n", stringSet)
// Output: String set after removal: GenericSet{[apple orange]}

// Use generic set with custom comparable types
type Point struct {
X, Y int
}

pointSet := NewGenericSet[Point]()
pointSet.Add(Point{X: 1, Y: 2})
pointSet.Add(Point{X: 3, Y: 4})
pointSet.Add(Point{X: 1, Y: 2}) // Duplicate
fmt.Printf("Point set: %s\n", pointSet)
// Output: Point set: GenericSet{[{1 2} {3 4}]}
}

Complex Data Relationships​

Tree Data Structures​

Binary Tree Implementation​

Implementing a binary tree with proper node management.

Tree Traversal Algorithms​

Implementing depth-first and breadth-first traversal.

package main

import "fmt"

func main() {
// Complex data relationships examples
fmt.Println("Complex data relationships examples:")

// Binary Tree Node
type TreeNode struct {
data interface{}
left *TreeNode
right *TreeNode
}

// Binary Tree
type BinaryTree struct {
root *TreeNode
}

// NewBinaryTree creates a new binary tree
func NewBinaryTree() *BinaryTree {
return &BinaryTree{}
}

// Insert inserts a value into the tree
func (bt *BinaryTree) Insert(data interface{}) {
if bt.root == nil {
bt.root = &TreeNode{data: data}
} else {
bt.insertNode(bt.root, data)
}
}

// insertNode inserts a value into the tree recursively
func (bt *BinaryTree) insertNode(node *TreeNode, data interface{}) {
// Simple insertion logic (for demonstration)
// In a real implementation, you'd have proper comparison logic
if node.left == nil {
node.left = &TreeNode{data: data}
} else if node.right == nil {
node.right = &TreeNode{data: data}
} else {
// Insert into left subtree
bt.insertNode(node.left, data)
}
}

// InOrderTraversal performs in-order traversal
func (bt *BinaryTree) InOrderTraversal() []interface{} {
var result []interface{}
bt.inOrderTraversal(bt.root, &result)
return result
}

// inOrderTraversal performs in-order traversal recursively
func (bt *BinaryTree) inOrderTraversal(node *TreeNode, result *[]interface{}) {
if node != nil {
bt.inOrderTraversal(node.left, result)
*result = append(*result, node.data)
bt.inOrderTraversal(node.right, result)
}
}

// PreOrderTraversal performs pre-order traversal
func (bt *BinaryTree) PreOrderTraversal() []interface{} {
var result []interface{}
bt.preOrderTraversal(bt.root, &result)
return result
}

// preOrderTraversal performs pre-order traversal recursively
func (bt *BinaryTree) preOrderTraversal(node *TreeNode, result *[]interface{}) {
if node != nil {
*result = append(*result, node.data)
bt.preOrderTraversal(node.left, result)
bt.preOrderTraversal(node.right, result)
}
}

// PostOrderTraversal performs post-order traversal
func (bt *BinaryTree) PostOrderTraversal() []interface{} {
var result []interface{}
bt.postOrderTraversal(bt.root, &result)
return result
}

// postOrderTraversal performs post-order traversal recursively
func (bt *BinaryTree) postOrderTraversal(node *TreeNode, result *[]interface{}) {
if node != nil {
bt.postOrderTraversal(node.left, result)
bt.postOrderTraversal(node.right, result)
*result = append(*result, node.data)
}
}

// LevelOrderTraversal performs level-order (breadth-first) traversal
func (bt *BinaryTree) LevelOrderTraversal() []interface{} {
if bt.root == nil {
return nil
}

var result []interface{}
queue := []*TreeNode{bt.root}

for len(queue) > 0 {
node := queue[0]
queue = queue[1:]

result = append(result, node.data)

if node.left != nil {
queue = append(queue, node.left)
}
if node.right != nil {
queue = append(queue, node.right)
}
}

return result
}

// Use the binary tree
tree := NewBinaryTree()

// Insert values
tree.Insert(1)
tree.Insert(2)
tree.Insert(3)
tree.Insert(4)
tree.Insert(5)

// Perform traversals
fmt.Printf("In-order traversal: %v\n", tree.InOrderTraversal())
fmt.Printf("Pre-order traversal: %v\n", tree.PreOrderTraversal())
fmt.Printf("Post-order traversal: %v\n", tree.PostOrderTraversal())
fmt.Printf("Level-order traversal: %v\n", tree.LevelOrderTraversal())
// Output:
// In-order traversal: [4 2 1 3 5]
// Pre-order traversal: [1 2 4 3 5]
// Post-order traversal: [4 2 5 3 1]
// Level-order traversal: [1 2 3 4 5]
}

Graph Data Structures​

Adjacency List Implementation​

Implementing a graph using adjacency lists.

Graph Traversal Algorithms​

Implementing depth-first and breadth-first search.

package main

import "fmt"

func main() {
// Graph data structures examples
fmt.Println("Graph data structures examples:")

// Graph using adjacency list
type Graph struct {
vertices map[interface{}][]interface{}
}

// NewGraph creates a new graph
func NewGraph() *Graph {
return &Graph{
vertices: make(map[interface{}][]interface{}),
}
}

// AddVertex adds a vertex to the graph
func (g *Graph) AddVertex(vertex interface{}) {
if _, exists := g.vertices[vertex]; !exists {
g.vertices[vertex] = make([]interface{}, 0)
}
}

// AddEdge adds an edge between two vertices
func (g *Graph) AddEdge(from, to interface{}) {
g.AddVertex(from)
g.AddVertex(to)

g.vertices[from] = append(g.vertices[from], to)
// For undirected graph, also add reverse edge
g.vertices[to] = append(g.vertices[to], from)
}

// GetNeighbors returns the neighbors of a vertex
func (g *Graph) GetNeighbors(vertex interface{}) []interface{} {
return g.vertices[vertex]
}

// GetVertices returns all vertices in the graph
func (g *Graph) GetVertices() []interface{} {
vertices := make([]interface{}, 0, len(g.vertices))
for vertex := range g.vertices {
vertices = append(vertices, vertex)
}
return vertices
}

// DFS performs depth-first search starting from a vertex
func (g *Graph) DFS(start interface{}) []interface{} {
visited := make(map[interface{}]bool)
var result []interface{}

g.dfs(start, visited, &result)
return result
}

// dfs performs depth-first search recursively
func (g *Graph) dfs(vertex interface{}, visited map[interface{}]bool, result *[]interface{}) {
if visited[vertex] {
return
}

visited[vertex] = true
*result = append(*result, vertex)

for _, neighbor := range g.vertices[vertex] {
g.dfs(neighbor, visited, result)
}
}

// BFS performs breadth-first search starting from a vertex
func (g *Graph) BFS(start interface{}) []interface{} {
visited := make(map[interface{}]bool)
var result []interface{}
queue := []interface{}{start}

for len(queue) > 0 {
vertex := queue[0]
queue = queue[1:]

if !visited[vertex] {
visited[vertex] = true
result = append(result, vertex)

for _, neighbor := range g.vertices[vertex] {
if !visited[neighbor] {
queue = append(queue, neighbor)
}
}
}
}

return result
}

// String returns a string representation of the graph
func (g *Graph) String() string {
result := "Graph{\n"
for vertex, neighbors := range g.vertices {
result += fmt.Sprintf(" %v: %v\n", vertex, neighbors)
}
result += "}"
return result
}

// Use the graph
graph := NewGraph()

// Add edges
graph.AddEdge("A", "B")
graph.AddEdge("A", "C")
graph.AddEdge("B", "D")
graph.AddEdge("C", "D")
graph.AddEdge("D", "E")

fmt.Printf("Graph: %s\n", graph)
// Output:
// Graph{
// A: [B C]
// B: [A D]
// C: [A D]
// D: [B C E]
// E: [D]
// }

// Perform traversals
fmt.Printf("DFS from A: %v\n", graph.DFS("A"))
fmt.Printf("BFS from A: %v\n", graph.BFS("A"))
// Output:
// DFS from A: [A B D C E]
// BFS from A: [A B C D E]

// Get neighbors
fmt.Printf("Neighbors of D: %v\n", graph.GetNeighbors("D"))
// Output: Neighbors of D: [B C E]
}

Performance Optimization Techniques​

Memory Pool Patterns​

Object Pool Implementation​

Implementing object pools for memory efficiency.

Buffer Pool Management​

Managing buffers efficiently to reduce allocations.

package main

import "fmt"

func main() {
// Performance optimization techniques examples
fmt.Println("Performance optimization techniques examples:")

// Object Pool implementation
type ObjectPool struct {
pool chan interface{}
new func() interface{}
}

// NewObjectPool creates a new object pool
func NewObjectPool(size int, newFunc func() interface{}) *ObjectPool {
return &ObjectPool{
pool: make(chan interface{}, size),
new: newFunc,
}
}

// Get gets an object from the pool
func (op *ObjectPool) Get() interface{} {
select {
case obj := <-op.pool:
return obj
default:
return op.new()
}
}

// Put puts an object back into the pool
func (op *ObjectPool) Put(obj interface{}) {
select {
case op.pool <- obj:
default:
// Pool is full, discard the object
}
}

// Use object pool
pool := NewObjectPool(10, func() interface{} {
return make([]byte, 1024)
})

// Get buffer from pool
buffer1 := pool.Get().([]byte)
buffer2 := pool.Get().([]byte)

fmt.Printf("Got buffer 1: %d bytes\n", len(buffer1))
fmt.Printf("Got buffer 2: %d bytes\n", len(buffer2))
// Output:
// Got buffer 1: 1024 bytes
// Got buffer 2: 1024 bytes

// Put buffers back to pool
pool.Put(buffer1)
pool.Put(buffer2)

// Get buffer from pool again (should reuse)
buffer3 := pool.Get().([]byte)
fmt.Printf("Got buffer 3: %d bytes\n", len(buffer3))
// Output: Got buffer 3: 1024 bytes

// Ring Buffer implementation
type RingBuffer struct {
buffer []interface{}
head int
tail int
size int
}

// NewRingBuffer creates a new ring buffer
func NewRingBuffer(capacity int) *RingBuffer {
return &RingBuffer{
buffer: make([]interface{}, capacity),
head: 0,
tail: 0,
size: 0,
}
}

// Enqueue adds an item to the ring buffer
func (rb *RingBuffer) Enqueue(item interface{}) bool {
if rb.size == len(rb.buffer) {
return false // Buffer is full
}

rb.buffer[rb.tail] = item
rb.tail = (rb.tail + 1) % len(rb.buffer)
rb.size++
return true
}

// Dequeue removes an item from the ring buffer
func (rb *RingBuffer) Dequeue() (interface{}, bool) {
if rb.size == 0 {
return nil, false // Buffer is empty
}

item := rb.buffer[rb.head]
rb.head = (rb.head + 1) % len(rb.buffer)
rb.size--
return item, true
}

// Size returns the number of items in the buffer
func (rb *RingBuffer) Size() int {
return rb.size
}

// IsFull returns true if the buffer is full
func (rb *RingBuffer) IsFull() bool {
return rb.size == len(rb.buffer)
}

// IsEmpty returns true if the buffer is empty
func (rb *RingBuffer) IsEmpty() bool {
return rb.size == 0
}

// Use ring buffer
ringBuffer := NewRingBuffer(3)

// Enqueue items
ringBuffer.Enqueue("first")
ringBuffer.Enqueue("second")
ringBuffer.Enqueue("third")

fmt.Printf("Ring buffer size: %d\n", ringBuffer.Size())
fmt.Printf("Is full: %t\n", ringBuffer.IsFull())
// Output:
// Ring buffer size: 3
// Is full: true

// Try to enqueue when full
success := ringBuffer.Enqueue("fourth")
fmt.Printf("Enqueue when full: %t\n", success)
// Output: Enqueue when full: false

// Dequeue items
if item, ok := ringBuffer.Dequeue(); ok {
fmt.Printf("Dequeued: %v\n", item)
}

fmt.Printf("Ring buffer size after dequeue: %d\n", ringBuffer.Size())
// Output:
// Dequeued: first
// Ring buffer size after dequeue: 2

// Enqueue after dequeue
ringBuffer.Enqueue("fourth")
fmt.Printf("Ring buffer size after enqueue: %d\n", ringBuffer.Size())
// Output: Ring buffer size after enqueue: 3
}

What You've Learned​

Congratulations! You now have a comprehensive understanding of Go's advanced data structures:

Custom Collection Implementations​

  • Understanding and implementing stacks, queues, and linked lists
  • Working with generic data structures using Go's type system
  • Implementing type-safe collections with proper error handling
  • Understanding memory management in custom collections

Generic Data Structure Patterns​

  • Using Go's generics for type-safe implementations
  • Implementing generic stacks, sets, and maps
  • Understanding type constraints and bounds
  • Creating reusable data structure implementations

Complex Data Relationships​

  • Implementing tree data structures with proper traversal
  • Working with graph data structures and algorithms
  • Understanding adjacency lists and traversal algorithms
  • Implementing depth-first and breadth-first search

Performance Optimization Techniques​

  • Implementing object pools for memory efficiency
  • Creating ring buffers for circular data structures
  • Understanding memory management patterns
  • Optimizing data structure performance

Key Concepts​

  • Custom types - Creating specialized data structures
  • Generics - Type-safe generic implementations
  • Memory management - Efficient memory usage patterns
  • Algorithms - Implementing traversal and search algorithms

Next Steps​

You now have a solid foundation in Go's advanced data structures. In the next chapter, we'll explore Go's object-oriented programming features, including methods, interfaces, and composition patterns.

Understanding advanced data structures is crucial for building high-performance applications and solving complex problems. These concepts form the foundation for all the more advanced programming techniques we'll cover in the coming chapters.


Ready to learn about object-oriented programming? Let's explore Go's object-oriented features and learn how to build sophisticated software architectures!