Есть ли способ сделать круговой стек?

Добрый день !

Я пытаюсь сделать какой-то круговой стек. Он должен быть похож на обычный стек LIFO, но без очевидных ограничений. Вместо того, чтобы достигать максимальной емкости, он должен исключить или перепрыгнуть первый элемент, введенный в данный момент времени!

Например:

Допустим, у нас есть стек из 3 элементов: stack[3]

Мы заполняем его, «заталкивая» внутрь 3 элемента: push[a], push[b], push[c].

Но тогда мы захотим добавить 4-й и 5-й элементы: push[d], push[e].

Стандартные стеки сообщат, что стек достиг своего предела и больше не может добавлять элементы.

Но мне нужен круговой стек, который исключит или перепрыгнет через a и b, запомнит c, d и e и выведет e, d и c;

Проект сделан в PlatformIO на ESP32, поэтому у меня нет доступа к C++ STL, а даже если бы и был, я думал, что компилировать такую ​​большую библиотеку всего на 1 стек бессмысленно. Даже если было время, когда я думал, что должен скомпилировать подобную библиотеку, которая должна дать мне доступ к stack или deque, это время давно прошло, потому что сейчас я чувствую себя идиотом, который не может решить математическую задачу. Это беспокоит меня уже больше недели.

Все, что мне удалось найти в Интернете, это следующий циклический буфер FIFO:

class circular_buffer {
public:
    explicit circular_buffer(size_t size) :
        buf_(std::unique_ptr<T[]>(new T[size])),
        max_size_(size)
    {

    }

    void put(T item)
    {
        std::lock_guard<std::mutex> lock(mutex_);

        buf_[head_] = item;

        if(full_) {
            tail_ = (tail_ + 1) % max_size_;
        }

        head_ = (head_ + 1) % max_size_;

        full_ = head_ == tail_;
    }

    T get()
    {
        std::lock_guard<std::mutex> lock(mutex_);

        if(empty())
        {
            return T();
        }

        //Read data and advance the tail (we now have a free space)
        auto val = buf_[tail_];
        full_ = false;      
        tail_ = (tail_ + 1) % max_size_;

        return val;
    }

    void reset()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        head_ = tail_;
        full_ = false;
    }

    bool empty() const
    {
        //if head and tail are equal, we are empty
        return (!full_ && (head_ == tail_));
    }

    bool full() const
    {
        //If tail is ahead the head by 1, we are full
        return full_;
    }

    size_t capacity() const
    {
        return max_size_;
    }

    size_t size() const
    {
        size_t size = max_size_;

        if(!full_)
        {
            if(head_ >= tail_)
            {
                size = head_ - tail_;
            }
            else
            {
                size = max_size_ + head_ - tail_;
            }
        }

        return size;
    }

private:
    std::mutex mutex_;
    std::unique_ptr<T[]> buf_;
    size_t head_ = 0;
    size_t tail_ = 0;
    const size_t max_size_;
    bool full_ = 0;
};

Я возился с ним в течение последних 3 дней, но я просто не могу заставить его работать так, как я хочу. Это, будучи структурой FIFO, будет печатать a, b, c или c, d, e.

Я хочу, чтобы он печатался сверху вниз, в данном случае от головы до хвоста, но я не могу этого понять.


person bleah1    schedule 22.03.2019    source источник
comment
Технически кольцевой стек больше не будет стеком, а будет кольцевым буфером или кольцевым буфером.   -  person Some programmer dude    schedule 22.03.2019


Ответы (1)


Если я правильно понимаю, то все, что вам нужно, это буфер с фиксированным размером, который имеет один указатель на «верхнюю часть» «стека», который увеличивается/уменьшается таким образом, что он обертывает конец буфера . Это автоматически приведет к тому, что самая новая запись всегда перезапишет самую старую, фактически предоставляя вам хранилище LIFO для последних N значений, где N — размер буфера. Например:

#include <cstddef>
#include <memory>
#include <iostream>

template <typename T>
class ForgetfulStack
{
    std::unique_ptr<T[]> buffer;
    std::size_t head = 0;
    std::size_t size = 0;

public:
    ForgetfulStack(std::size_t size)
        : buffer(std::make_unique<T[]>(size)), size(size)
    {
    }

    void push(const T& value)
    {
        buffer[head] = value;
        head = (head + 1) % size;
    }

    T pop()
    {
        head = (head - 1 + size) % size;
        return buffer[head];
    }
};

int main()
{
    ForgetfulStack<int> blub(3);

    blub.push(1);
    blub.push(2);
    blub.push(3);
    blub.push(4);
    blub.push(5);

    std::cout << blub.pop() << ' ' << blub.pop() << ' ' << blub.pop() << std::endl;
}

Обратите внимание, что эта простая реализация не является потокобезопасной…

person Michael Kenzel    schedule 22.03.2019
comment
Я чувствую, что (head - 1 + size) потеряет смысл, если head==0, и его следует переписать как (head + size - 1). - person Useless; 22.03.2019
comment
@ Бесполезно, std::size_t не имеет знака, поэтому это не будет пропущено. По крайней мере, исходя из моего понимания целочисленной арифметики без знака, порядок, в котором здесь выполняются сложение и вычитание, не должен иметь значения!? Я могу ошибаться, конечно… - person Michael Kenzel; 22.03.2019
comment
Он действительно беззнаковый и будет недополняться, но на самом деле это совершенно четко определено для целочисленных типов без знака. Вы все равно получите правильный результат (0u - 1 + size == UINT_MAX + size == size - 1), просто вид потенциального недостаточного потока заставляет меня чувствовать себя немного зудящим. - person Useless; 22.03.2019
comment
@ Бесполезно ах, я думаю, у нас просто было непонимание терминологии. Я предположил, что вы имели в виду недополнение в том смысле, что целое число со знаком выходит за пределы диапазона, что приводит к неопределенному поведению. Если вы просто имели в виду, что он будет оборачиваться на 0, тогда, конечно, он это сделает. Но, как вы сказали, в конце концов результат все равно правильный. Изменение его на (head + size - 1) просто заменит потенциальное зацикливание на 0 на потенциальное зацикливание при максимальном значении… последнее, возможно, будет происходить гораздо реже, конечно… - person Michael Kenzel; 22.03.2019
comment
Ваш пример возвращает случайные значения, ранее сохраненные в стеке, если и когда я продолжаю извлекать значения, когда стек пуст или должен быть пуст. - person bleah1; 25.03.2019
comment
@ bleah1 да, извлечение значений из пустого стека дает вам неопределенное поведение. Контейнеры STL ничем не отличаются. Если по какой-то причине вам нужно поддержать выполнение такой вещи, вы можете легко добавить, если это гарантирует, что есть что-то, что нужно вытолкнуть. Вам также придется изменить интерфейс, например, ввести отдельный способ получения текущей вершины стека, так как pop() тогда не может возвращать значение во всех случаях... - person Michael Kenzel; 25.03.2019