cpp

exercises

exercises.cpp⚙️
/**
 * Unit Testing Exercises with Google Test
 * 
 * Practice writing unit tests for various C++ classes and functions.
 * Each exercise provides code to test - write comprehensive test suites.
 * 
 * To compile with Google Test:
 * g++ -std=c++17 exercises.cpp -lgtest -lgtest_main -pthread -o test_exercises
 */

#include <iostream>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <cmath>
#include <stdexcept>
#include <memory>
#include <algorithm>
#include <numeric>
#include <map>

// Uncomment when Google Test is available:
// #include <gtest/gtest.h>

using std::cout;
using std::endl;

// ============================================================================
// EXERCISE 1: Temperature Converter
// Write tests for this temperature conversion class
// ============================================================================

class TemperatureConverter {
public:
    static double celsiusToFahrenheit(double celsius) {
        return celsius * 9.0 / 5.0 + 32.0;
    }
    
    static double fahrenheitToCelsius(double fahrenheit) {
        return (fahrenheit - 32.0) * 5.0 / 9.0;
    }
    
    static double celsiusToKelvin(double celsius) {
        if (celsius < -273.15) {
            throw std::domain_error("Temperature below absolute zero");
        }
        return celsius + 273.15;
    }
    
    static double kelvinToCelsius(double kelvin) {
        if (kelvin < 0) {
            throw std::domain_error("Kelvin cannot be negative");
        }
        return kelvin - 273.15;
    }
};

/*
 * TODO: Write tests for TemperatureConverter
 * 
 * Test cases to consider:
 * 1. Basic conversions (0°C = 32°F, 100°C = 212°F)
 * 2. Negative temperatures
 * 3. Freezing and boiling points
 * 4. Absolute zero handling
 * 5. Round-trip conversions (C -> F -> C should be original)
 * 6. Exception handling for invalid temperatures
 */

/*
TEST(TemperatureConverterTest, CelsiusToFahrenheitFreezing) {
    // TODO: Test that 0°C = 32°F
}

TEST(TemperatureConverterTest, CelsiusToFahrenheitBoiling) {
    // TODO: Test that 100°C = 212°F
}

TEST(TemperatureConverterTest, FahrenheitToCelsius) {
    // TODO: Test reverse conversion
}

TEST(TemperatureConverterTest, RoundTripConversion) {
    // TODO: Verify C -> F -> C returns original value
}

TEST(TemperatureConverterTest, AbsoluteZeroException) {
    // TODO: Test exception for temperatures below -273.15°C
}

TEST(TemperatureConverterTest, KelvinConversions) {
    // TODO: Test Kelvin conversions
}
*/


// ============================================================================
// EXERCISE 2: Stack-based Calculator
// Write tests for this postfix calculator
// ============================================================================

class PostfixCalculator {
public:
    double evaluate(const std::vector<std::string>& tokens) {
        std::stack<double> stack;
        
        for (const auto& token : tokens) {
            if (isOperator(token)) {
                if (stack.size() < 2) {
                    throw std::runtime_error("Invalid expression: insufficient operands");
                }
                double b = stack.top(); stack.pop();
                double a = stack.top(); stack.pop();
                stack.push(applyOperator(token, a, b));
            } else {
                try {
                    stack.push(std::stod(token));
                } catch (const std::exception&) {
                    throw std::runtime_error("Invalid token: " + token);
                }
            }
        }
        
        if (stack.size() != 1) {
            throw std::runtime_error("Invalid expression: too many operands");
        }
        
        return stack.top();
    }
    
private:
    bool isOperator(const std::string& s) const {
        return s == "+" || s == "-" || s == "*" || s == "/" || s == "^";
    }
    
    double applyOperator(const std::string& op, double a, double b) const {
        if (op == "+") return a + b;
        if (op == "-") return a - b;
        if (op == "*") return a * b;
        if (op == "/") {
            if (b == 0) throw std::runtime_error("Division by zero");
            return a / b;
        }
        if (op == "^") return std::pow(a, b);
        throw std::runtime_error("Unknown operator: " + op);
    }
};

/*
 * TODO: Write tests for PostfixCalculator
 * 
 * Test expressions:
 * 1. "3 4 +" = 7
 * 2. "5 2 -" = 3  
 * 3. "3 4 * 5 +" = 17 (3*4+5)
 * 4. "10 2 /" = 5
 * 5. "2 3 ^" = 8
 * 6. Division by zero
 * 7. Invalid expressions
 * 8. Empty expression
 */

/*
class PostfixCalculatorTest : public ::testing::Test {
protected:
    PostfixCalculator calc;
};

TEST_F(PostfixCalculatorTest, SimpleAddition) {
    // TODO: Test "3 4 +" = 7
}

TEST_F(PostfixCalculatorTest, ComplexExpression) {
    // TODO: Test "3 4 * 5 +"
}

TEST_F(PostfixCalculatorTest, DivisionByZero) {
    // TODO: Test exception handling
}

TEST_F(PostfixCalculatorTest, InvalidToken) {
    // TODO: Test with invalid tokens
}
*/


// ============================================================================
// EXERCISE 3: Password Validator
// Write comprehensive tests for password validation
// ============================================================================

class PasswordValidator {
public:
    struct ValidationResult {
        bool isValid;
        std::vector<std::string> errors;
    };
    
    ValidationResult validate(const std::string& password) const {
        ValidationResult result{true, {}};
        
        if (password.length() < minLength_) {
            result.isValid = false;
            result.errors.push_back("Password too short (min " + std::to_string(minLength_) + ")");
        }
        
        if (password.length() > maxLength_) {
            result.isValid = false;
            result.errors.push_back("Password too long (max " + std::to_string(maxLength_) + ")");
        }
        
        if (requireUppercase_ && !hasUppercase(password)) {
            result.isValid = false;
            result.errors.push_back("Password must contain uppercase letter");
        }
        
        if (requireLowercase_ && !hasLowercase(password)) {
            result.isValid = false;
            result.errors.push_back("Password must contain lowercase letter");
        }
        
        if (requireDigit_ && !hasDigit(password)) {
            result.isValid = false;
            result.errors.push_back("Password must contain digit");
        }
        
        if (requireSpecial_ && !hasSpecial(password)) {
            result.isValid = false;
            result.errors.push_back("Password must contain special character");
        }
        
        return result;
    }
    
    // Builder pattern for configuration
    PasswordValidator& setMinLength(size_t len) { minLength_ = len; return *this; }
    PasswordValidator& setMaxLength(size_t len) { maxLength_ = len; return *this; }
    PasswordValidator& requireUppercase(bool req) { requireUppercase_ = req; return *this; }
    PasswordValidator& requireLowercase(bool req) { requireLowercase_ = req; return *this; }
    PasswordValidator& requireDigit(bool req) { requireDigit_ = req; return *this; }
    PasswordValidator& requireSpecial(bool req) { requireSpecial_ = req; return *this; }
    
private:
    size_t minLength_ = 8;
    size_t maxLength_ = 128;
    bool requireUppercase_ = true;
    bool requireLowercase_ = true;
    bool requireDigit_ = true;
    bool requireSpecial_ = false;
    
    bool hasUppercase(const std::string& s) const {
        return std::any_of(s.begin(), s.end(), ::isupper);
    }
    
    bool hasLowercase(const std::string& s) const {
        return std::any_of(s.begin(), s.end(), ::islower);
    }
    
    bool hasDigit(const std::string& s) const {
        return std::any_of(s.begin(), s.end(), ::isdigit);
    }
    
    bool hasSpecial(const std::string& s) const {
        return std::any_of(s.begin(), s.end(), [](char c) {
            return !std::isalnum(c);
        });
    }
};

/*
 * TODO: Write tests for PasswordValidator
 * 
 * Test cases:
 * 1. Valid password with all requirements
 * 2. Too short password
 * 3. Too long password
 * 4. Missing uppercase
 * 5. Missing lowercase
 * 6. Missing digit
 * 7. Multiple errors at once
 * 8. Custom configuration
 * 9. Edge cases (empty, only spaces, etc.)
 */


// ============================================================================
// EXERCISE 4: Shopping Cart
// Write tests for a shopping cart system
// ============================================================================

struct Product {
    std::string id;
    std::string name;
    double price;
    int stock;
};

class ShoppingCart {
public:
    void addItem(const Product& product, int quantity) {
        if (quantity <= 0) {
            throw std::invalid_argument("Quantity must be positive");
        }
        if (quantity > product.stock) {
            throw std::runtime_error("Insufficient stock");
        }
        
        auto it = items_.find(product.id);
        if (it != items_.end()) {
            it->second.quantity += quantity;
        } else {
            items_[product.id] = {product, quantity};
        }
    }
    
    void removeItem(const std::string& productId) {
        auto it = items_.find(productId);
        if (it == items_.end()) {
            throw std::runtime_error("Product not in cart");
        }
        items_.erase(it);
    }
    
    void updateQuantity(const std::string& productId, int newQuantity) {
        if (newQuantity <= 0) {
            removeItem(productId);
            return;
        }
        
        auto it = items_.find(productId);
        if (it == items_.end()) {
            throw std::runtime_error("Product not in cart");
        }
        it->second.quantity = newQuantity;
    }
    
    double getSubtotal() const {
        double total = 0;
        for (const auto& [id, item] : items_) {
            total += item.product.price * item.quantity;
        }
        return total;
    }
    
    double getTotal(double taxRate = 0.0, double discount = 0.0) const {
        double subtotal = getSubtotal();
        double discounted = subtotal * (1.0 - discount);
        return discounted * (1.0 + taxRate);
    }
    
    int getItemCount() const {
        int count = 0;
        for (const auto& [id, item] : items_) {
            count += item.quantity;
        }
        return count;
    }
    
    bool isEmpty() const { return items_.empty(); }
    
    void clear() { items_.clear(); }
    
private:
    struct CartItem {
        Product product;
        int quantity;
    };
    std::map<std::string, CartItem> items_;
};

/*
 * TODO: Write tests for ShoppingCart
 * 
 * Test cases:
 * 1. Empty cart state
 * 2. Add single item
 * 3. Add multiple items
 * 4. Add same item multiple times (quantity accumulation)
 * 5. Remove item
 * 6. Update quantity
 * 7. Subtotal calculation
 * 8. Total with tax
 * 9. Total with discount
 * 10. Total with tax AND discount
 * 11. Exception handling (insufficient stock, negative quantity)
 */

/*
class ShoppingCartTest : public ::testing::Test {
protected:
    ShoppingCart cart;
    Product apple{"P001", "Apple", 1.50, 100};
    Product banana{"P002", "Banana", 0.75, 50};
    Product laptop{"P003", "Laptop", 999.99, 5};
    
    void SetUp() override {
        cart.clear();
    }
};

TEST_F(ShoppingCartTest, StartsEmpty) {
    // TODO: Verify empty cart state
}

TEST_F(ShoppingCartTest, AddItem) {
    // TODO: Test adding items
}

// ... more tests
*/


// ============================================================================
// EXERCISE 5: Linked List
// Write tests for a custom linked list implementation
// ============================================================================

template<typename T>
class LinkedList {
public:
    LinkedList() = default;
    
    ~LinkedList() {
        clear();
    }
    
    // Copy operations
    LinkedList(const LinkedList& other) {
        for (Node* current = other.head_; current; current = current->next) {
            push_back(current->data);
        }
    }
    
    LinkedList& operator=(LinkedList other) {
        swap(*this, other);
        return *this;
    }
    
    // Move operations
    LinkedList(LinkedList&& other) noexcept 
        : head_(other.head_), tail_(other.tail_), size_(other.size_) {
        other.head_ = other.tail_ = nullptr;
        other.size_ = 0;
    }
    
    friend void swap(LinkedList& a, LinkedList& b) noexcept {
        using std::swap;
        swap(a.head_, b.head_);
        swap(a.tail_, b.tail_);
        swap(a.size_, b.size_);
    }
    
    void push_front(const T& value) {
        Node* node = new Node{value, head_};
        head_ = node;
        if (!tail_) tail_ = head_;
        ++size_;
    }
    
    void push_back(const T& value) {
        Node* node = new Node{value, nullptr};
        if (tail_) {
            tail_->next = node;
        } else {
            head_ = node;
        }
        tail_ = node;
        ++size_;
    }
    
    void pop_front() {
        if (!head_) throw std::runtime_error("List is empty");
        Node* temp = head_;
        head_ = head_->next;
        if (!head_) tail_ = nullptr;
        delete temp;
        --size_;
    }
    
    T& front() {
        if (!head_) throw std::runtime_error("List is empty");
        return head_->data;
    }
    
    const T& front() const {
        if (!head_) throw std::runtime_error("List is empty");
        return head_->data;
    }
    
    T& back() {
        if (!tail_) throw std::runtime_error("List is empty");
        return tail_->data;
    }
    
    bool contains(const T& value) const {
        for (Node* current = head_; current; current = current->next) {
            if (current->data == value) return true;
        }
        return false;
    }
    
    size_t size() const { return size_; }
    bool empty() const { return size_ == 0; }
    
    void clear() {
        while (head_) {
            Node* temp = head_;
            head_ = head_->next;
            delete temp;
        }
        tail_ = nullptr;
        size_ = 0;
    }
    
    void reverse() {
        Node* prev = nullptr;
        Node* current = head_;
        tail_ = head_;
        
        while (current) {
            Node* next = current->next;
            current->next = prev;
            prev = current;
            current = next;
        }
        head_ = prev;
    }
    
private:
    struct Node {
        T data;
        Node* next;
    };
    
    Node* head_ = nullptr;
    Node* tail_ = nullptr;
    size_t size_ = 0;
};

/*
 * TODO: Write tests for LinkedList
 * 
 * Test cases:
 * 1. Empty list state
 * 2. push_front and push_back
 * 3. pop_front
 * 4. front() and back() access
 * 5. contains() method
 * 6. size() and empty()
 * 7. clear()
 * 8. reverse()
 * 9. Copy constructor
 * 10. Move constructor
 * 11. Exception handling for empty list operations
 * 12. Memory management (no leaks with valgrind)
 */


// ============================================================================
// EXERCISE 6: Priority Queue
// Write tests for a max-heap based priority queue
// ============================================================================

template<typename T, typename Compare = std::less<T>>
class PriorityQueue {
public:
    void push(const T& value) {
        data_.push_back(value);
        heapifyUp(data_.size() - 1);
    }
    
    void pop() {
        if (empty()) {
            throw std::runtime_error("Queue is empty");
        }
        std::swap(data_.front(), data_.back());
        data_.pop_back();
        if (!empty()) {
            heapifyDown(0);
        }
    }
    
    const T& top() const {
        if (empty()) {
            throw std::runtime_error("Queue is empty");
        }
        return data_.front();
    }
    
    size_t size() const { return data_.size(); }
    bool empty() const { return data_.empty(); }
    
private:
    std::vector<T> data_;
    Compare comp_;
    
    void heapifyUp(size_t index) {
        while (index > 0) {
            size_t parent = (index - 1) / 2;
            if (!comp_(data_[parent], data_[index])) break;
            std::swap(data_[parent], data_[index]);
            index = parent;
        }
    }
    
    void heapifyDown(size_t index) {
        size_t largest = index;
        size_t left = 2 * index + 1;
        size_t right = 2 * index + 2;
        
        if (left < data_.size() && comp_(data_[largest], data_[left])) {
            largest = left;
        }
        if (right < data_.size() && comp_(data_[largest], data_[right])) {
            largest = right;
        }
        
        if (largest != index) {
            std::swap(data_[index], data_[largest]);
            heapifyDown(largest);
        }
    }
};

/*
 * TODO: Write tests for PriorityQueue
 * 
 * Test cases:
 * 1. Empty queue
 * 2. Single element
 * 3. Multiple elements (max-heap property)
 * 4. pop() removes largest
 * 5. Insert in ascending order
 * 6. Insert in descending order
 * 7. Insert duplicates
 * 8. Exception on empty pop/top
 * 9. Custom comparator (min-heap)
 */


// ============================================================================
// EXERCISE 7: Rate Limiter
// Write tests for a token bucket rate limiter
// ============================================================================

#include <chrono>

class RateLimiter {
public:
    RateLimiter(int maxTokens, int refillRate) 
        : maxTokens_(maxTokens), 
          tokens_(maxTokens), 
          refillRate_(refillRate),
          lastRefill_(std::chrono::steady_clock::now()) {}
    
    bool tryAcquire(int tokens = 1) {
        refill();
        if (tokens_ >= tokens) {
            tokens_ -= tokens;
            return true;
        }
        return false;
    }
    
    int availableTokens() const {
        return tokens_;
    }
    
    // For testing: manually set tokens
    void setTokens(int tokens) {
        tokens_ = std::min(tokens, maxTokens_);
    }
    
private:
    void refill() {
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
            now - lastRefill_).count();
        
        if (elapsed > 0) {
            tokens_ = std::min(maxTokens_, tokens_ + static_cast<int>(elapsed * refillRate_));
            lastRefill_ = now;
        }
    }
    
    int maxTokens_;
    int tokens_;
    int refillRate_;  // tokens per second
    std::chrono::steady_clock::time_point lastRefill_;
};

/*
 * TODO: Write tests for RateLimiter
 * 
 * Test cases:
 * 1. Initial state has max tokens
 * 2. Acquire single token
 * 3. Acquire multiple tokens
 * 4. Fail when not enough tokens
 * 5. Tokens don't exceed max
 * 6. Partial token acquisition
 */


// ============================================================================
// EXERCISE 8: Write Parameterized Tests
// Create parameterized tests for mathematical functions
// ============================================================================

namespace MathFunctions {
    int factorial(int n) {
        if (n < 0) throw std::invalid_argument("Negative input");
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }
    
    int fibonacci(int n) {
        if (n < 0) throw std::invalid_argument("Negative input");
        if (n <= 1) return n;
        int a = 0, b = 1;
        for (int i = 2; i <= n; ++i) {
            int temp = a + b;
            a = b;
            b = temp;
        }
        return b;
    }
    
    bool isPrime(int n) {
        if (n < 2) return false;
        if (n == 2) return true;
        if (n % 2 == 0) return false;
        for (int i = 3; i * i <= n; i += 2) {
            if (n % i == 0) return false;
        }
        return true;
    }
    
    int gcd(int a, int b) {
        if (a < 0) a = -a;
        if (b < 0) b = -b;
        while (b) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }
}

/*
 * TODO: Write parameterized tests
 * 
 * Factorial tests:
 * - {0, 1}, {1, 1}, {2, 2}, {3, 6}, {4, 24}, {5, 120}, {10, 3628800}
 * 
 * Fibonacci tests:
 * - {0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}, {10, 55}
 * 
 * Prime tests:
 * - Primes: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29
 * - Non-primes: 0, 1, 4, 6, 8, 9, 10, 15, 21
 * 
 * GCD tests:
 * - {12, 18, 6}, {48, 18, 6}, {17, 5, 1}, {100, 10, 10}
 */

/*
class FactorialTest : public ::testing::TestWithParam<std::pair<int, int>> {};

TEST_P(FactorialTest, ReturnsCorrectResult) {
    auto [input, expected] = GetParam();
    EXPECT_EQ(MathFunctions::factorial(input), expected);
}

INSTANTIATE_TEST_SUITE_P(
    FactorialValues,
    FactorialTest,
    ::testing::Values(
        // TODO: Add test values
    )
);
*/


// ============================================================================
// MAIN - Demo without Google Test
// ============================================================================

int main() {
    cout << "╔══════════════════════════════════════════════════════════════╗" << endl;
    cout << "║              UNIT TESTING EXERCISES                           ║" << endl;
    cout << "╚══════════════════════════════════════════════════════════════╝" << endl;
    
    cout << "\nThis file contains code to be tested with Google Test." << endl;
    cout << "Uncomment the test code and implement the TODO sections." << endl;
    
    cout << "\n=== Demo: TemperatureConverter ===" << endl;
    cout << "  0°C = " << TemperatureConverter::celsiusToFahrenheit(0) << "°F" << endl;
    cout << "  100°C = " << TemperatureConverter::celsiusToFahrenheit(100) << "°F" << endl;
    
    cout << "\n=== Demo: PostfixCalculator ===" << endl;
    PostfixCalculator calc;
    cout << "  '3 4 +' = " << calc.evaluate({"3", "4", "+"}) << endl;
    cout << "  '10 2 /' = " << calc.evaluate({"10", "2", "/"}) << endl;
    
    cout << "\n=== Demo: PasswordValidator ===" << endl;
    PasswordValidator validator;
    auto result = validator.validate("StrongPass123");
    cout << "  'StrongPass123' is " << (result.isValid ? "valid" : "invalid") << endl;
    
    cout << "\n=== Demo: ShoppingCart ===" << endl;
    ShoppingCart cart;
    Product apple{"P001", "Apple", 1.50, 100};
    cart.addItem(apple, 5);
    cout << "  5 apples @ $1.50 = $" << cart.getSubtotal() << endl;
    
    cout << "\n=== Demo: LinkedList ===" << endl;
    LinkedList<int> list;
    list.push_back(1);
    list.push_back(2);
    list.push_back(3);
    cout << "  List size: " << list.size() << endl;
    cout << "  Front: " << list.front() << ", Back: " << list.back() << endl;
    
    cout << "\n=== Demo: MathFunctions ===" << endl;
    cout << "  factorial(5) = " << MathFunctions::factorial(5) << endl;
    cout << "  fibonacci(10) = " << MathFunctions::fibonacci(10) << endl;
    cout << "  isPrime(17) = " << MathFunctions::isPrime(17) << endl;
    cout << "  gcd(48, 18) = " << MathFunctions::gcd(48, 18) << endl;
    
    cout << "\n═══════════════════════════════════════════════════════════════" << endl;
    cout << "Challenge: Write comprehensive tests for each class above!" << endl;
    cout << "Goal: 100% code coverage with meaningful assertions" << endl;
    
    return 0;
}
Exercises - C++ Tutorial | DeepML