Additional delving into Go Generics with a Stack ADT Implementation
Imagine you’re at a family gathering, and after a delightful meal, there’s a stack of plates waiting to be cleaned. This pile of plates is a lot like a Stack Abstract Data Type in programming.
In a stack of dirty plates waiting to be cleaned-up, the plate you can easily access and remove is the one on top. Similarly, in a Stack ADT, the last item you ‘pushed’ onto the stack is the first one you can ‘pop’ off. This is called ‘Last-In-First-Out’ or LIFO, meaning the last plate (or item) added to the stack is the first one to be removed. The most famous tech based on this data type is the Ethereum VM aka a stack VM
Adding and removing plates:
When you add a plate to the stack, you place it on top.
In a Stack ADT, this is akin to the ‘Push’ operation, where you add an item to the top of the stack.
And when you take a plate off to clean it, it’s like the ‘Pop’ operation, where you remove the top item from the stack.
Peeking at the dirty plate:
Sometimes, you might want to see what the top plate looks like without removing it from the stack. In a stack ADT, we have a ‘Peek’ operation for this – it lets you see the last item added without removing it.
Is the sink full of dirty plates empty yet?
Finally, checking if there are any plates left in the stack is like the ‘IsEmpty’ method in Stack ADT, which tells you whether there are any more items .
Key operations of the stack – Push, Pop, Peek, and PrintBottomElement – are implemented, adhering to the Last-In-First-Out (LIFO) principle intrinsic to stack data structures :
-Push method appends an element to the top of the stack, while Pop removes and returns the top element.
-Peek method allows us to look at the top element without removing it,
-PrintBottomElement method (despite its name, it actually returns rather than prints) helps inspect the bottom element of the stack.
The use of generics here is particularly notable.
It not only makes the stack more versatile but also ensures type safety, a significant enhancement over using interface{} types.
The Person struct used in the main function demonstrates the stack’s ability to handle complex data types, further emphasizing the utility of generics in Go.
This implementation is a clear example of how Go’s generics can be used to create robust, reusable data structures that are type-safe and efficient. It’s a step forward in writing more maintainable and clearer Go code, showcasing the evolution of the language.
Let’s call this program ‘stack.go’ :
// We use a fake data generator to populate our struct with dummy struct fields for development purposes only.
package main
import (
"fmt"
"log"
"github.com/go-faker/faker/v4"
// Stack represents a LIFO data structure
type Stack[T any] struct {
items []T
}
// The 'Push' method is defined as a pointer receiver on the 'Stack' struct and takes an integer 'i' as argument.
// The method appends the given integer to the 'items' slice , pushing it on to the top of the stack.
func (stack *Stack[T]) Push (i T) {
stack.items = append(stack.items, i)
}
func (stack *Stack[T]) Pop() (T, error) {
var zero T
if len(stack.items) == 0 {
return zero, fmt.Errorf("the 'Pop' operation failed ! the stack is empty ! ")
}
l := len(stack.items) - 1
toRemove := stack.items[l]
stack.items = stack.items[:l]
return toRemove, nil
}
// The 'Peek' method returns the top value of the stack but does not delete it .
func (stack *Stack[T]) Peek() (T, error) {
if len(stack.items) == 0 {
var zero T
return zero, fmt.Errorf("the 'Peek' operation failed ! the stack is empty ! ")
}
return stack.items[len(stack.items)-1] , nil
}
func (stack *Stack[T]) IsEmpty() (bool) {
return len(stack.items) == 0
}
func (stack *Stack[T]) PrintBottomElement() (T, error) {
var zero T
if len(stack.items) == 0 {
return zero , fmt.Errorf("the 'PrintBottomElement' failed ! the stack is empty ! ")
}
//fmt.Println("The bottom element of the stack consists of value: ", stack.items[0])
return stack.items[0], nil
}
// Person represents information about a person .
type Person struct {
FirstName string
FamilyName string
Country string
Age int
Married bool
}
// String implements the Stringer interface for Person
func (p Person) String() string {
return fmt.Sprintf("Person{FirstName: %s , FamilyName: %s, Country: %s, Age: %d, Married: %t}", p.FirstName, p.FamilyName, p.Country, p.Age, p.Married)
}
func main() {
myPersonStack := Stack[Person]{}
for i :=0; i < 3 ; i++ {
var person Person
// Using faker to populate the struct with dummy data fields.
err := faker.FakeData(&person)
if err != nil {
log.Fatal("Failed to generate dummy data with Faker", err)
}
myPersonStack.Push(person)
fmt.Println("The 'PersonStack' intially contains ->", myPersonStack)
// Popping the top element of the PersonStack i.e removing it .
poppedElement , err := myPersonStack.Pop()
if err != nil {
fmt.Println("Pop error .", err)
} else {
fmt.Println("We pop this element from the stack -> ", poppedElement)
}
// Peeking the top value from the stack without removing it .
topElement, err := myPersonStack.Peek()
if err != nil {
fmt.Println("Peek error .", err)
} else {
fmt.Println("The top element of the stack is now -> ", topElement)
}
// Printing which value stands at the bottom of the stack .
bottomElement, err := myPersonStack.PrintBottomElement()
if err != nil {
fmt.Print("Error", err)
}
fmt.Println("The bottom element of the stack is now ->",bottomElement)
// Checking if the integer stack is empty
fmt.Println("Is the stack empty ?", myPersonStack.IsEmpty())
}
The terminal should print out randomly generated fields as such :
The 'PersonStack' intially contains -> {[{LddwnYZHnJssVowdnAedSWbNj iniXLPojqqPdNxRSSnySTadhR ConcEVaqWLSCOFprqitQmBnYO 83 false} {YMKtVhehgnlTCdwLlHktiRVMT uIdIcEjwJUkMTTyNbqyQqqPkj YDRoqpywMywPBJQlnAMNAdInB 72 true} {AjthOvRrWcRBIeNVhuWWpNOFa RRHfmnbASlENUVxRkoWWJsKoh ZtjirfnCewajSFArjFcvRvCnk 98 true}]}
We pop this element from the stack : Person{FirstName: AjthOvRrWcRBIeNVhuWWpNOFa , FamilyName: RRHfmnbASlENUVxRkoWWJsKoh, Country: ZtjirfnCewajSFArjFcvRvCnk, Age: 98, Married: true}
The top element of the stack is now : Person{FirstName: YMKtVhehgnlTCdwLlHktiRVMT , FamilyName: uIdIcEjwJUkMTTyNbqyQqqPkj, Country: YDRoqpywMywPBJQlnAMNAdInB, Age: 72, Married: true}
The bottom element of the stack is now: Person{FirstName: LddwnYZHnJssVowdnAedSWbNj , FamilyName: iniXLPojqqPdNxRSSnySTadhR, Country: ConcEVaqWLSCOFprqitQmBnYO, Age: 83, Married: false}
Is the stack empty ? false