Golang program to implement a persistent data structure (a stack)


Persistent data structures, such as a stack, are of pivotal significance to programming for organising and chronologically managing data efficiently. This article showcases different examples to implement a persistent data structure in go, focusing on stack. In the first example we use immutable slices to perform the operation, while the second method employs a linked list, here implementation means we are going to demonstrate the operations like insertion, updation and deletion on a persistent data structure.

Explanation

A persistent data structure allows preserving previous versions when modifications are to occur. In this context, a stack as a data structure ensures that every push or pop operation on itself creates a new version, maintaining the old one alongside with the changes.

Let us assume we have a a tree with some values in it and we need to insert more values to it. This is a depiction of a persistent data structure before and after inserting data.

     tree            updated tree (After Insertion)
       10                       10
      /                        /   \
     5          ===>    5     15
      \                      / \      \
      8                    4   8      20

Initially the tree has the node with values 10, 5 and 8 , when new values are inserted to the tree inserting values 15,4 ,20 , the previous version remains intact after new insertion.

The root node remains the same [10], the new value [15] is added to the right forming a right subtree, the value [4] is added to the left of the value [5] preserving it as a binary search tree. The value [20] is added to the right of vale[15] making it a leaf nod.

Syntax

func (s *Stack) Push(val int) *Stack

The syntax defines a function named Push which takes integer value as input, adds it to the top of the stack, and returns new instance of the Stack structure (pointer to stack).

func (s *Stack) Pop() (*Stack, int)

The syntax defines a function named Pop which removes the top element from the stack, and it returns two values including an updated instance of the Stack structure (pointer to the stack) and the integer value that was at the top of the original stack.

Algorithm

  • Start by defining the stack structure using a linked list or array.

  • Implement push operation to create a new version of the stack with added elements.

  • Implement the pop operation to generates a new version of the stack every time the top element is removed.

  • Create a mechanism to navigate between different versions of the stack.

  • Develop functions to access the top element of a specific version of the stack.

Example 1

In this example, we will implement a persistent data structure in go, we build stack data structure using the `Stack` structure which stores integer data as a slice. The push and pop functions are used to handle elements while preserving previous versions. `NewStack` function initializes an empty stack, `Push` method creates a new version of the stack with added elements, while `Pop` method generates a new version every time the top element is removed. In the `main` function, multiple versions of the stack are created and manipulated, demonstrating persistence aspect.

package main
import "fmt"
type Stack struct {
	data []int
}
func NewStack() *Stack {
	return &Stack{}
}
func (s *Stack) Push(val int) *Stack {
	newData := make([]int, len(s.data)+1)
	copy(newData, s.data)
	newData[len(newData)-1] = val
	return &Stack{data: newData}
}
func (s *Stack) Pop() (*Stack, int) {
	if len(s.data) == 0 {
      	return s, -1 
	}
	newData := make([]int, len(s.data)-1)
	copy(newData, s.data)
	return &Stack{data: newData}, s.data[len(s.data)-1]
}
func main() {
    stack1 := NewStack()
	stack2 := stack1.Push(10)
	stack3 := stack2.Push(20)
	fmt.Println("Stack1:", stack1.data) 
	fmt.Println("Stack2:", stack2.data) 
	fmt.Println("Stack3:", stack3.data) 
	stack3, val1 := stack3.Pop()
	stack2, val2 := stack2.Pop()
 	fmt.Println("Popped Value 1:", val1) 
	fmt.Println("Popped Value 2:", val2)
	fmt.Println("Stack1:", stack1.data)
	fmt.Println("Stack2:", stack2.data)
	fmt.Println("Stack3:", stack3.data) 
}

Output

Stack1: []
Stack2: [10]
Stack3: [10 20]
Popped Value 1: 20
Popped Value 2: 10
Stack1: []
Stack2: []
Stack3: [10]

Example 2

In this example, a stack is build using the Node struct which encapsulates an integer value and a reference to the next node. The Stack structure employs a top node pointer. NewStack function initializes an empty stack, Push method creates a new node with the value provided, which then becomes the new top, while Pop method retrieves the top value, advances the top pointer, and handles an empty stack scenario. The main function demonstrates stack manipulation.

package main
import "fmt"
type Node struct {
    value int
	next  *Node
}
type Stack struct {
    top *Node
}
func NewStack() *Stack {
	return &Stack{}
}
func (s *Stack) Push(val int) *Stack {
	newNode := &Node{value: val, next: s.top}
	return &Stack{top: newNode}
}
func (s *Stack) Pop() (*Stack, int) {
    if s.top == nil {
    	return s, -1 
	}
	poppedValue := s.top.value
    return &Stack{top: s.top.next}, poppedValue
}
func main() {
    stack1 := NewStack()
	stack2 := stack1.Push(10)
	stack3 := stack2.Push(20)
	fmt.Println("Stack1:")
	printStack(stack1)
	fmt.Println("Stack2:") 
	printStack(stack2)
	fmt.Println("Stack3:") 
	printStack(stack3)
 	stack3, val1 := stack3.Pop()
	stack2, val2 := stack2.Pop()
	fmt.Println("Popped Value 1:", val1) 
	fmt.Println("Popped Value 2:", val2) 
	fmt.Println("Stack1:")
	printStack(stack1)
	fmt.Println("Stack2:") 
	printStack(stack2)
	fmt.Println("Stack3:") 
	printStack(stack3)
}
func printStack(s *Stack) {
	curr := s.top
    for curr != nil {
    	fmt.Printf("%d -> ", curr.value)
    	curr = curr.next
    }
	fmt.Println()
}

Output

Stack1:
 
Stack2:
10 ->
Stack3:
20 -> 10 ->
Popped Value 1: 20
Popped Value 2: 10
Stack1:
 
Stack2:
 
Stack3:
10 ->

Real Life Implementation

  • Browser History: The browsing history function in web browsers allows users to easily access and navigate a list of previously viewed websites. Implementing a persistent stack-like data structure is one technique to maintaining the history of visited websites. This layout enables visitors to easily return to previously visited websites.

  • Text Editor: Text editors often include undo and redo functions that enable users to rollback or replay changes made to a document. Persistent stacks are a potential method for retaining a chronological record of alterations, allowing reversion to previous document states.

Conclusion

A persistent data structure allows preserving previous versions when modifications are to occur. In this article we explored how to implement a persistent data structure in go, a stack, in Golang using two different methods. The first method uses immutable slices, while the second method employs a linked list. In both approaches, the goal is to maintain the previous versions of the stack efficiently, ensuring historical states are accessible.

Updated on: 18-Oct-2023

83 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements