Hone logo
Hone
Problems

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:

  1. Generic Type Parameter: The Stack type must be generic, accepting a type parameter T that is constrained by comparable.
  2. Push(element T): Adds an element of type T to the top of the stack.
  3. 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 for T and an error indicating an empty stack.
  4. 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 for T and an error indicating an empty stack.
  5. IsEmpty() bool: Returns true if the stack is empty, false otherwise.
  6. Error Handling: Implement proper error handling for Pop and Peek operations 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 T is. This will be important for the Pop and Peek methods when an error occurs.
  • The comparable constraint 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 Pop and Peek. Go's standard errors package is your friend.
  • You will need a constructor function (e.g., NewStack[T]()) to create instances of your generic Stack.
Loading editor...
go