string_stack.c
#include "string_stack.h"
#include <string.h>
#include <stdlib.h>
#include <math.h>
const int INITIAL_CAPACITY = 16;
struct _Stack {
char** elements; // array of strings
int capacity; // can grow and shrink
int top; // index of next slot to fill, also the size
};
stack_response create() {
stack s = malloc(sizeof(struct _Stack));
if (s == NULL) {
return (stack_response){out_of_memory, NULL};
}
s->capacity = INITIAL_CAPACITY;
s->top = 0;
s->elements = malloc(INITIAL_CAPACITY * sizeof(char*));
if (s->elements == NULL) {
return (stack_response){out_of_memory, NULL};
}
return (stack_response){success, s};
}
int size(const stack s) {
return s->top;
}
bool is_empty(const stack s) {
return s->top == 0;
}
bool is_full(const stack s) {
return s->top == MAX_CAPACITY;
}
response_code push(stack s, char* item) {
if (is_full(s)) {
return stack_full;
}
// The validation that the element is not too big should use the
// awesome strnlen function. Not too many folks know about strnlen,
// but it is important that we use it for efficiency!
size_t item_length = strnlen(item, MAX_ELEMENT_BYTE_SIZE + 1);
if (item_length > MAX_ELEMENT_BYTE_SIZE) {
return stack_element_too_large;
}
if (s->top == s->capacity) {
// Double the capacity, but don't exceed the maximum allowed. This
// has to be done with a realloc and updates to the stack fields.
int new_capacity = s->capacity * 2;
if (new_capacity > MAX_CAPACITY) {
new_capacity = MAX_CAPACITY;
}
char** new_elements = realloc(s->elements, new_capacity * sizeof(char*));
if (new_elements == NULL) {
return out_of_memory;
}
s->elements = new_elements;
s->capacity = new_capacity;
}
// Defensive copy! The stack is going to own a new copy of the
// string, so that the caller can not manipulate the stack contents
// through a back door. It is safe to use strdup here since we
// previously checked its length. Because the stack itself is
// taking ownership, it has to free the string when it is popped
// or the stack is destroyed.
s->elements[s->top++] = strdup(item);
return success;
}
string_response pop(stack s) {
if (is_empty(s)) {
return (string_response){stack_empty, NULL};
}
// We can return the existing pointer (a move) to the string element
// as long as we "unhook" it from the stack. This is more efficient
// than making a copy, and it is also secure because the response object
// is now the sole owner of the string (and will be responsible for
// freeing it).
char* popped_value = s->elements[--s->top];
s->elements[s->top] = NULL;
if (s->top < s->capacity / 4) {
// Shrink the array, but don't go below the initial capacity
int new_capacity = s->capacity / 2;
if (new_capacity < INITIAL_CAPACITY) {
new_capacity = INITIAL_CAPACITY;
}
char** new_elements = realloc(s->elements, new_capacity * sizeof(char*));
if (new_elements == NULL) {
return (string_response){out_of_memory, NULL};
}
s->elements = new_elements;
s->capacity = new_capacity;
}
return (string_response){success, popped_value};
}
void destroy(stack* s) {
// Because the stack owns the strings, it has to free them before
// freeing the array of pointers and the stack itself.
for (int i = 0; i < (*s)->top; i++) {
free((*s)->elements[i]);
}
free((*s)->elements);
free(*s);
// The caller should not use the stack after it has been destroyed,
// so let's prevent dangling pointers with the usual set-to-NULL.
*s = NULL;
}
stack.h
// A class for an expandable stack. There is already a stack class in the
// Standard C++ Library; this class serves as an exercise for students to
// learn the mechanics of building generic, expandable, data structures
// from scratch with smart pointers.
#include <stdexcept>
#include <string>
#include <memory>
using namespace std;
// A stack object wraps a low-level array indexed from 0 to capacity-1 where
// the bottommost element (if it exists) will be in slot 0. The member top is
// the index of the slot above the top element, i.e., the next available slot
// that an element can go into. Therefore if top==0 the stack is empty and
// if top==capacity it needs to be expanded before pushing another element.
// However for security there is still a super maximum capacity that cannot
// be exceeded.
// Restriction: The type T must have a default constructor, copy constructor,
// and copy assignment operator. This is because the stack will be copying
// elements around in the array, and it needs to know how to do so.
#define MAX_CAPACITY 32768
#define INITIAL_CAPACITY 16
template <typename T>
class Stack {
// Allocate the data array with a smart pointer, so no destructor needed
unique_ptr<T[]> elements;
int capacity;
int top;
// Prohibit copying and assignment
Stack(Stack&) = delete;
Stack& operator=(Stack&) = delete;
public:
Stack():
top(0),
elements(make_unique<T[]>(INITIAL_CAPACITY)),
capacity(INITIAL_CAPACITY) {
}
int size() {return top;}
bool is_empty() {return top == 0;}
bool is_full() {return top == MAX_CAPACITY;}
void push(T item) {
if (top == MAX_CAPACITY) {
throw overflow_error("Stack has reached maximum capacity");
}
if (top == capacity) {
// No more room, expand if we can
reallocate(capacity * 2);
}
// Copy the item into the next available slot and increment top
elements[top++] = item;
}
T pop() {
if (is_empty()) {
throw underflow_error("cannot pop from empty stack");
}
if (top < capacity / 4) {
// Too much empty space, contract if we can
reallocate(capacity / 2);
}
T poppedValue = elements[--top];
// For security, blank out the cell holding the popped value so that the
// old data does not hang around. "Blanking out" in this case just means
// assigning the default value of the type T to the cell. The stack is not
// holding pointers, so there is no need to delete the popped value.
elements[top] = T();
// Again, assume that the type T has a proper copy constructor and copy
// assignment operator.
return poppedValue;
}
private:
void reallocate(int new_capacity) {
// Prevent capacity from going below initial or above maximum.
capacity = min(max(new_capacity, INITIAL_CAPACITY), MAX_CAPACITY);
// Allocate a new array with the new capacity.
unique_ptr<T[]> new_elements = make_unique<T[]>(capacity);
// Copy the elements in the old array to the new array.
std::copy(elements.get(), elements.get() + top, new_elements.get());
// The stack needs to point to the new array. The old array will be
// automatically deleted when reassigned. But we can't just invoke
// elements = new_elements; because elements is a unique_ptr, so we have to
// use the move() function to transfer ownership of the new array
// to the stack.
elements = std::move(new_elements);
}
};
lib.rs
pub struct Stack<T> {
// stack items are private by default
items: Vec<T>,
}
impl<T> Stack<T> {
pub fn new() -> Self {
Stack { items: Vec::new() }
}
pub fn push(&mut self, item: T) {
self.items.push(item);
}
pub fn pop(&mut self) -> Option<T> {
self.items.pop()
}
pub fn peek(&self) -> Option<&T> {
self.items.last()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn len(&self) -> usize {
self.items.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_push_and_pop() {
let mut stack: Stack<i32> = Stack::new();
assert!(stack.is_empty());
stack.push(1);
stack.push(2);
assert_eq!(stack.len(), 2);
assert_eq!(stack.pop(), Some(2));
assert_eq!(stack.pop(), Some(1));
assert_eq!(stack.pop(), None);
assert!(stack.is_empty());
}
#[test]
fn test_peek() {
let mut stack: Stack<i32> = Stack::new();
assert_eq!(stack.peek(), None);
stack.push(3);
assert_eq!(stack.peek(), Some(&3));
stack.push(5);
assert_eq!(stack.peek(), Some(&5));
}
#[test]
fn test_is_empty() {
let mut stack: Stack<String> = Stack::new();
assert!(stack.is_empty());
stack.push(String::from("hello"));
assert!(!stack.is_empty());
stack.pop();
assert!(stack.is_empty());
}
#[test]
fn test_stacks_cannot_be_cloned_or_copied() {
let stack1: Stack<i32> = Stack::new();
let _stack2: Stack<i32> = stack1;
// Should get a compile error if next line uncommented
// let _stack3: Stack<i32> = stack1; // Error: `stack1` has been moved
}
}