Generic Data Structure: Implementing a Type-Agnostic Stack in Go
Go 1.18 introduced generics, allowing you to write code that works with a variety of types without sacrificing type safety. This challenge will test your understanding of Go generics by requiring you to implement a common data structure, a stack, that can hold elements of any comparable type.
Problem Description
Your task is to implement a generic Stack data structure in Go. This Stack should be able to store elements of any type that supports the comparable constraint (meaning elements can be compared using == and !=). The Stack should support the standard stack operations: Push, Pop, Peek, and IsEmpty.
Key Requirements:
- Generic Type Parameter: The
Stacktype must be generic, accepting a type parameterTthat is constrained bycomparable. Push(element T): Adds an element of typeTto the top of the stack.Pop() (T, error): Removes and returns the element from the top of the stack. If the stack is empty, it should return a zero value forTand an error indicating an empty stack.Peek() (T, error): Returns the element from the top of the stack without removing it. If the stack is empty, it should return a zero value forTand an error indicating an empty stack.IsEmpty() bool: Returnstrueif the stack is empty,falseotherwise.- Error Handling: Implement proper error handling for
PopandPeekoperations when the stack is empty.
Expected Behavior:
- The stack should behave according to the Last-In, First-Out (LIFO) principle.
- The generic implementation should allow the creation of stacks for different comparable types (e.g.,
int,string,float64).
Edge Cases:
- Popping or peeking from an empty stack.
- Pushing and popping multiple elements.
- Pushing and popping elements of different comparable types (in separate stack instances).
Examples
Example 1:
Input:
stack := NewStack[int]()
stack.Push(10)
stack.Push(20)
val, err := stack.Pop() // val = 20, err = nil
isEmpty := stack.IsEmpty() // isEmpty = false
Output:
Popped value: 20
Is stack empty? false
Explanation: An integer stack is created. 10 and then 20 are pushed. Popping the stack returns 20, as it was the last element added. The stack is not empty.
Example 2:
Input:
stack := NewStack[string]()
stack.Push("hello")
val, err := stack.Peek() // val = "hello", err = nil
stack.Pop() // Returns "hello" and nil error
val, err = stack.Pop() // val = "", err = "stack is empty"
Output:
Peeked value: hello
Popped value:
Error popping: stack is empty
Explanation: A string stack is created and "hello" is pushed. Peeking returns "hello". Popping then returns "hello". A subsequent pop from the now empty stack returns an error.
Example 3:
Input:
stack := NewStack[bool]()
isEmpty := stack.IsEmpty() // isEmpty = true
val, err := stack.Pop() // val = false, err = "stack is empty"
Output:
Is stack empty? true
Popped value: false
Error popping: stack is empty
Explanation: A boolean stack is created and is initially empty. IsEmpty correctly reports true. Attempting to pop from an empty stack returns the zero value for bool (which is false) and an appropriate error.
Constraints
- The underlying storage for the stack can be a Go slice.
- The stack should handle an arbitrary number of elements, limited only by system memory.
- The solution must use Go generics.
Notes
- Consider what the zero value for a generic type
Tis. This will be important for thePopandPeekmethods when an error occurs. - The
comparableconstraint is crucial here. You are not expected to implement a stack for types that cannot be compared directly (e.g., slices, maps, functions). - Think about how you will signal an error condition for
PopandPeek. Go's standarderrorspackage is your friend. - You will need a constructor function (e.g.,
NewStack[T]()) to create instances of your genericStack.