Program Listing for File CircularBuffer.hpp

Return to documentation for file (doc/include/CircularBuffer.hpp)

#ifndef CIRCULAR_BUFFER_HPP_
#define CIRCULAR_BUFFER_HPP_
#include <optional>
#include <vector>

template <typename T>
class CircularBuffer {
public:
    CircularBuffer(size_t capacity) : buf_(capacity), capacity_(capacity){};
    CircularBuffer() : buf_(20), capacity_(20){};
    inline size_t capacity() const { return capacity_; }
    size_t size() const;

    void push(T item);
    std::optional<T> front() const;
    std::optional<T> back() const;
    void pop();
    std::optional<T> popFront();

    void resize(const size_t capacity);

    void reset(const size_t capacity);

    inline bool empty() const { return is_empty_; }
    void clear();

    void toVector(std::vector<T>& vector) const;

private:
    // std::mutex mutex_;
    // std::unique_ptr<T[capacity_]> buf_;
    std::vector<T> buf_;
    size_t capacity_;
    size_t head_ = 0;
    size_t tail_ = 0;
    bool is_empty_ = true;
};

template <typename T>
void CircularBuffer<T>::push(T item) {
    if (capacity_ == 0) {
        return;
    }

    if (empty()) {
        buf_[0] = item;
        is_empty_ = false;
        tail_ = 0;
        head_ = 0;
        return;
    }

    tail_ = (tail_ + 1) % capacity_;  // increase tail_ pointer

    if (head_ == tail_) {
        head_ = (head_ + 1) % capacity_;  // if tail reached head increase head_ pointer
    }

    buf_[tail_] = item;
    is_empty_ = false;

    return;
}  // TODO:

template <typename T>
std::optional<T> CircularBuffer<T>::front() const {
    if (empty()) {
        return std::nullopt;
    }

    return buf_[head_];
}

template <typename T>
std::optional<T> CircularBuffer<T>::back() const {
    if (empty()) {
        return std::nullopt;
    }

    return buf_[tail_];
}

template <typename T>
void CircularBuffer<T>::pop() {
    if (size() <= 1) {
        clear();
        return;
    }

    head_ = (head_ + 1) % capacity_;
}

template <typename T>
std::optional<T> CircularBuffer<T>::popFront() {
    std::optional<T> f = front();
    pop();
    return f;
}

template <typename T>
size_t CircularBuffer<T>::size() const {
    if (empty() || capacity_ == 0) {
        return 0;
    }

    return (tail_ + capacity_ - head_) % capacity_ + 1;
}

template <typename T>
void CircularBuffer<T>::resize(const size_t capacity) {
    size_t initial_size = size();
    std::vector<T> vec;
    toVector(vec);

    size_t new_size = std::min(capacity, initial_size);

    std::vector<T>(capacity).swap(buf_);  // resize allocated memory to capacity.
    capacity_ = capacity;

    std::copy(vec.end() - new_size, vec.end(), buf_.begin());  // chops buf_ keeping new_len newest elements. buf is ordered, placing head_ at 0;

    head_ = 0;
    if (new_size == 0) {
        tail_ = 0;
        is_empty_ = true;

    } else {
        tail_ = new_size - 1;
    }

    return;
}

template <typename T>
void CircularBuffer<T>::reset(const size_t capacity) {
    clear();

    std::vector<T>(capacity).swap(buf_);
    capacity_ = capacity;
}

template <typename T>
void CircularBuffer<T>::clear() {
    is_empty_ = true;
    head_ = 0;
    tail_ = 0;
}

template <typename T>
void CircularBuffer<T>::toVector(std::vector<T>& vec) const {
    vec.clear();
    if (empty() || capacity_ == 0) {
        return;
    }

    vec.resize(size());

    for (size_t i = 0; i < size(); i++) {
        vec[i] = buf_[(head_ + i) % capacity_];
    }

    return;
}
#endif  // CIRCULAR_BUFFER_HPP_

/*
// This is a main with some unitary tests for the CircularBuffer template.
#include <cstdlib>
#include <iostream>

#include "CircularBuffer.hpp"

long unsigned success_count = 0;
long unsigned fail_count = 0;

#define ASSERT_EQ(a, b)                                                     \
    if (a == b) {                                                           \
        success_count++;                                                    \
    } else {                                                                \
        fail_count++;                                                       \
        printf("\033[1;33mAssertion failed at line %d\033[0m\n", __LINE__); \
    }

int main(void) {
    size_t total_failure_count = 0;
    for (size_t c = 0; c < 10; c++) {
        printf("Testing capacity = \033[1;34m%lu\033[0m\n", c);
        CircularBuffer<int> buf(c);
        success_count = 0;
        fail_count = 0;

        ASSERT_EQ(buf.capacity(), c);
        ASSERT_EQ(buf.size(), 0);
        ASSERT_EQ(buf.empty(), true);

        ASSERT_EQ(buf.front(), std::nullopt);
        ASSERT_EQ(buf.size(), 0);
        ASSERT_EQ(buf.empty(), true);

        ASSERT_EQ(buf.popFront(), std::nullopt);
        ASSERT_EQ(buf.size(), 0);
        ASSERT_EQ(buf.empty(), true);

        buf.pop();
        ASSERT_EQ(buf.size(), 0);
        ASSERT_EQ(buf.empty(), true);

        buf.clear();
        ASSERT_EQ(buf.size(), 0);
        ASSERT_EQ(buf.empty(), true);

        std::vector<int> out;
        std::vector<int> expected_out;
        buf.toVector(out);
        ASSERT_EQ(out, expected_out);

        expected_out.clear();
        out.clear();
        for (size_t i = 0; i < c; i++) {
            expected_out.push_back(i);
            buf.push(i);
        }
        buf.toVector(out);
        ASSERT_EQ(out, expected_out);

        if (c == 0) {
            ASSERT_EQ(buf.popFront(), std::nullopt);
        } else {
            ASSERT_EQ(buf.popFront(), std::make_optional<int>(0));
        }

        if (c <= 1) {
            ASSERT_EQ(buf.empty(), true);
            ASSERT_EQ(buf.size(), 0);
        } else {
            ASSERT_EQ(buf.empty(), false);
            ASSERT_EQ(buf.size(), c - 1);
            ASSERT_EQ(buf.front(), std::make_optional<int>(1));
            buf.pop();
            ASSERT_EQ(buf.size(), c - 2);
        }

        expected_out.clear();
        out.clear();
        buf.clear();

        // Overflowing capacity with capacity + random pushed numbers
        // generating a random number
        srand((unsigned)time(NULL));
        int random = rand() % (5 * (c + 1));

        // pushing
        for (size_t i = 0; i < c + random; i++) {
            buf.push(i);
        }
        buf.toVector(out);

        // building expected output
        for (size_t i = random; i < c + random; i++) {
            expected_out.push_back(i);
        }

        ASSERT_EQ(out, expected_out);

        // Testing resize
        buf.clear();
        buf.resize(c + 1);
        ASSERT_EQ(buf.capacity(), c + 1);
        ASSERT_EQ(buf.size(), 0);

        out.clear();
        expected_out.clear();
        for (int i = 0; i < c; i++) buf.push(i);
        for (int i = c - c / 2; i < c; i++) expected_out.push_back(i);

        buf.resize(c / 2);
        ASSERT_EQ(buf.size(), c / 2);

        buf.toVector(out);
        ASSERT_EQ(out, expected_out);
        if (c / 2 > 0) {
            ASSERT_EQ(buf.front(), std::make_optional(expected_out[0]));
            ASSERT_EQ(buf.back(), std::make_optional(expected_out.back()));
        } else {
            ASSERT_EQ(buf.front(), std::nullopt);
            ASSERT_EQ(expected_out.size(), 0);
            ASSERT_EQ(buf.size(), 0);
        }

        printf("Test results (random number is %d):\n\tSUCCESSES: \033[1;32m%4lu\033[0m\n\tFAILURES:  \033[1;31m%4lu\033[0m\n\n", random, success_count,
               fail_count);
        total_failure_count += fail_count;
    }

    return total_failure_count;
}
*/