Stacks & Queues
WARNING: This page may include outdated or invalid syntax. Examples are illustrative and will be updated to current Harneet standards (var/mandatory parameter types/valid function syntax).
Stacks and Queues are fundamental data structures that provide ordered access to elements with different retrieval patterns. Harneet provides enhanced implementations with improved performance characteristics and additional functionality.
Stack (LIFO - Last In, First Out)
A stack follows the Last-In-First-Out principle, where the most recently added element is the first one to be removed. Think of it like a stack of plates - you add and remove plates from the top.
Creating a Stack
| Creating a Stack |
|---|
| import collections
var stack = collections.new_stack()
|
Basic Operations
Push - Adding Elements
| Push Elements |
|---|
| stack.push(42)
stack.push("hello")
stack.push([1, 2, 3])
stack.push({"key": "value"})
fmt.Println("Stack size:", stack.size()) // 4
|
Pop - Removing Elements
| Pop Elements |
|---|
| var item = stack.pop()
fmt.Println("Popped:", item) // {"key": "value"}
// Always check if stack is empty before popping
if !stack.is_empty() {
var next_item = stack.pop()
fmt.Println("Next:", next_item) // [1, 2, 3]
}
|
Peek - Viewing Top Element
| Peek Top Element |
|---|
| if !stack.is_empty() {
var top = stack.peek()
fmt.Println("Top element:", top) // "hello"
// Element remains in stack
fmt.Println("Size still:", stack.size()) // 2
}
|
Enhanced Stack Features
Memory Pre-allocation
| Memory Pre-allocation |
|---|
| // Pre-allocate capacity for better performance
var large_stack = collections.new_stack()
large_stack.reserve(1000) // Pre-allocate space for 1000 elements
|
Batch Operations
| Batch Operations |
|---|
| // Push multiple elements at once
var items = [1, 2, 3, 4, 5]
for item in items {
stack.push(item)
}
// Or use functional approach
items.for_each(((item) =>) stack.push(item))
|
Conversion and Inspection
| Conversion and Inspection |
|---|
| // Convert to array (maintains stack order)
var array = stack.to_array()
fmt.Println("As array:", array)
// Clear all elements
stack.clear()
fmt.Println("After clear:", stack.is_empty()) // true
// String representation
fmt.Println("Stack:", stack.inspect())
|
Stack Use Cases
1. Function Call Management
| Function Call Management |
|---|
| // Simulating function call stack
var call_stack = collections.new_stack()
function enter_function(name) {
call_stack.push(name)
fmt.Println("Entering:", name)
}
function exit_function() {
if !call_stack.is_empty() {
var name = call_stack.pop()
fmt.Println("Exiting:", name)
}
}
enter_function("main")
enter_function("process_data")
enter_function("validate_input")
exit_function() // validate_input
exit_function() // process_data
exit_function() // main
|
2. Expression Evaluation
| Expression Evaluation |
|---|
| // Parentheses matching
function is_balanced(expression) {
var stack = collections.new_stack()
for char in expression {
if char == "(" || char == "[" || char == "{" {
stack.push(char)
} else if char == ")" || char == "]" || char == "}" {
if stack.is_empty() {
return false
}
var open = stack.pop()
if !matches(open, char) {
return false
}
}
}
return stack.is_empty()
}
function matches(open, close) {
return (open == "(" && close == ")") ||
(open == "[" && close == "]") ||
(open == "{" && close == "}")
}
|
3. Undo Operations
| Undo Operations |
|---|
| // Simple undo system
var undo_stack = collections.new_stack()
var current_state = "initial"
function perform_action(action, new_state) {
undo_stack.push(current_state)
current_state = new_state
fmt.Println("Action:", action, "-> State:", current_state)
}
function undo() {
if !undo_stack.is_empty() {
current_state = undo_stack.pop()
fmt.Println("Undone -> State:", current_state)
}
}
perform_action("create_file", "file_created")
perform_action("edit_file", "file_edited")
undo() // Back to file_created
undo() // Back to initial
|
Queue (FIFO - First In, First Out)
A queue follows the First-In-First-Out principle, where the first element added is the first one to be removed. Think of it like a line at a store - first person in line is served first.
Creating a Queue
| Creating a Queue |
|---|
| import collections
var queue = collections.new_queue()
|
Basic Operations
Enqueue - Adding Elements
| Enqueue Elements |
|---|
| queue.enqueue("first")
queue.enqueue("second")
queue.enqueue("third")
fmt.Println("Queue size:", queue.size()) // 3
|
Dequeue - Removing Elements
| Dequeue Elements |
|---|
| var item = queue.dequeue()
fmt.Println("Dequeued:", item) // "first"
// Always check if queue is empty
if !queue.is_empty() {
var next_item = queue.dequeue()
fmt.Println("Next:", next_item) // "second"
}
|
Peek - Viewing Front Element
| Peek Front Element |
|---|
| if !queue.is_empty() {
var front = queue.peek()
fmt.Println("Front element:", front) // "third"
// Element remains in queue
fmt.Println("Size still:", queue.size()) // 1
}
|
Enhanced Queue Features
Circular Buffer Implementation
The enhanced queue uses a circular buffer internally for O(1) enqueue and dequeue operations without element shifting:
| Circular Buffer |
|---|
| // Efficient for high-throughput scenarios
var buffer = collections.new_queue()
buffer.reserve(1000) // Pre-allocate circular buffer
// Fast operations even with many elements
for i in range(10000) {
buffer.enqueue(i)
}
for i in range(5000) {
buffer.dequeue() // Still O(1), no shifting needed
}
|
Batch Processing
| Batch Processing |
|---|
| // Process items in batches
function process_batch(queue, batch_size) {
var batch = []
for i in range(batch_size) {
if queue.is_empty() {
break
}
batch.append(queue.dequeue())
}
if batch.size() > 0 {
fmt.Println("Processing batch:", batch)
// Process batch...
}
}
|
Queue Use Cases
1. Task Scheduling
| Task Scheduling |
|---|
| // Simple task scheduler
var task_queue = collections.new_queue()
struct Task {
name: string
priority: int
action: function()
}
function schedule_task(task) {
task_queue.enqueue(task)
fmt.Println("Scheduled:", task.name)
}
function process_tasks() {
for !task_queue.is_empty() {
var task = task_queue.dequeue()
fmt.Println("Processing:", task.name)
task.action()
}
}
// Usage
schedule_task(Task{name: "backup", priority: 1, action: || fmt.Println("Backing up...")})
schedule_task(Task{name: "cleanup", priority: 2, action: || fmt.Println("Cleaning up...")})
process_tasks()
|
2. Breadth-First Search
| Breadth-First Search |
|---|
| // BFS algorithm using queue
function bfs(graph, start) {
var queue = collections.new_queue()
var visited = collections.new_set()
queue.enqueue(start)
visited.add(start)
for !queue.is_empty() {
var node = queue.dequeue()
fmt.Println("Visiting:", node)
for neighbor in graph.get_neighbors(node) {
if !visited.contains(neighbor) {
visited.add(neighbor)
queue.enqueue(neighbor)
}
}
}
}
|
3. Producer-Consumer Pattern
| Producer-Consumer Pattern |
|---|
| // Simple producer-consumer with queue
var work_queue = collections.new_concurrent_queue() // Thread-safe version
function producer() {
for i in range(10) {
var work_item = "task_" + i.to_string()
work_queue.enqueue(work_item)
fmt.Println("Produced:", work_item)
// Simulate work
sleep(100)
}
}
function consumer() {
for {
if !work_queue.is_empty() {
var item = work_queue.dequeue()
fmt.Println("Consumed:", item)
// Process item...
} else {
sleep(50) // Wait for more work
}
}
}
// Start producer and consumer concurrently
do producer()
do consumer()
|
Time Complexity
| Operation | Stack | Queue | Notes |
| Push/Enqueue | O(1) | O(1) | Amortized for dynamic resizing |
| Pop/Dequeue | O(1) | O(1) | No element shifting in enhanced queue |
| Peek | O(1) | O(1) | View without removal |
| Size | O(1) | O(1) | Cached size counter |
| Is Empty | O(1) | O(1) | Simple size check |
| Clear | O(1) | O(1) | Reset pointers, GC handles cleanup |
Space Complexity
- Stack: O(n) where n is the number of elements
- Queue: O(n) with circular buffer overhead (typically 25-50% extra capacity)
Memory Management
Both structures automatically manage memory:
| Automatic Memory Management |
|---|
| // Automatic resizing
var stack = collections.new_stack()
for i in range(1000000) {
stack.push(i) // Automatically grows as needed
}
// Memory is reclaimed when elements are removed
for i in range(500000) {
stack.pop() // Memory may be reclaimed based on shrink threshold
}
|
Error Handling
Both stacks and queues handle errors consistently:
| Error Handling |
|---|
| // Safe operations
var stack = collections.new_stack()
// Pop from empty stack returns error
var result = stack.pop()
if result.type() == "ERROR" {
fmt.Println("Cannot pop from empty stack")
}
// Better: check before operating
if !stack.is_empty() {
var item = stack.pop() // Safe
}
// Peek is always safe (returns null for empty)
var top = stack.peek()
if top != null {
fmt.Println("Top:", top)
}
|
Best Practices
1. Choose the Right Structure
- Use Stack for: Undo operations, function calls, expression parsing, backtracking algorithms
- Use Queue for: Task scheduling, BFS algorithms, producer-consumer patterns, buffering
2. Pre-allocate When Possible
| Pre-allocate Capacity |
|---|
| // If you know approximate size, pre-allocate
var large_stack = collections.new_stack()
large_stack.reserve(10000) // Avoids multiple reallocations
|
3. Check Emptiness Before Operations
| Check Emptiness |
|---|
| // Always check before pop/dequeue
if !collection.is_empty() {
var item = collection.pop() // or dequeue()
}
|
4. Use Concurrent Versions for Multi-threading
| Concurrent Versions |
|---|
| // For concurrent access
var shared_queue = collections.new_concurrent_queue()
// Thread-safe operations
|
| Batch Operations Performance |
|---|
| // Instead of individual operations in loops
for item in large_array {
queue.enqueue(item) // Many individual calls
}
// Consider batching if available
queue.enqueue_all(large_array) // Single batch operation
|
Advanced Examples
Stack-based Calculator
| Stack-based Calculator |
|---|
| function evaluate_postfix(expression) {
var stack = collections.new_stack()
var tokens = expression.split(" ")
for token in tokens {
if is_number(token) {
stack.push(token.to_int())
} else {
var b = stack.pop()
var a = stack.pop()
var result = apply_operator(a, b, token)
stack.push(result)
}
}
return stack.pop()
}
// Usage: evaluate_postfix("3 4 + 2 *") -> 14
|
Queue-based Rate Limiter
| Queue-based Rate Limiter |
|---|
| struct RateLimiter {
requests: Queue
max_requests: int
time_window: int // milliseconds
}
function new_rate_limiter(max_requests, time_window) {
return RateLimiter{
requests: collections.new_queue(),
max_requests: max_requests,
time_window: time_window
}
}
function (rl *RateLimiter) allow_request() {
var now = current_time_millis()
// Remove old requests outside time window
for !rl.requests.is_empty() {
var oldest = rl.requests.peek()
if now - oldest > rl.time_window {
rl.requests.dequeue()
} else {
break
}
}
// Check if we can allow this request
if rl.requests.size() < rl.max_requests {
rl.requests.enqueue(now)
return true
}
return false
}
|
See Also
This comprehensive guide covers the enhanced stack and queue implementations in Harneet, providing both basic usage and advanced patterns for real-world applications.