最佳集装箱类型

时间:2017-07-12 03:42:08

标签: c++ containers stdvector deque

我正在使用一个需要'Sliding Window'的应用程序,它只是一个字符串的容器,

Window  = ["the", "dog", "is", "hungry"]

应用程序处理大型文本文件,当满足某些条件时,窗口会在末尾添加一个新字符串并删除第一个元素,

即。说“现在”是下一个要添加的词,然后

Window <- Window.AddToEnd("now") and Window.DeleteFirst()

所以变成了Window = ["dog", "is", "hungry", now"]

每次窗口更改时,都会对顺序很重要的元素(即索引很重要)运行一个过程。

最初,我选择了一个字符串向量并尝试了一个双端队列。 我很好奇人们认为最好的是什么?

总而言之,我需要一个索引字符串的容器,其中VERY会定期添加一个新元素+第一个元素被删除。我还需要遍历容器A LOT。

其他一些信息:

  1. 字符串永远不会在容器中修改一次
  2. 窗口元素永远不会改变(在讨论之外的pop和push之外)
  3. 直到运行时(用户通过的东西)才知道窗口的大小
  4. 窗口永远不会改变大小,一旦在开始时初始化,它就会保持整个应用程序的大小。
  5. 任何建议都会非常感谢,干杯大卫

3 个答案:

答案 0 :(得分:1)

除了v[(beginning_index + i) % v.size()]之外,根据cplusplus.com

  

提供类似于向量的功能,但在序列的开头也有效插入和删除元素,而不仅仅是在其结尾。但是,与矢量不同,deques不能保证将所有元素存储在连续的存储位置

您也可以使用固定大小的矢量,并保存起始索引。

因此它就像一个圆形阵列。 e.g:

最初为[a,b,c,d]的向量v,起始索引= 0;在&#39; e&#39;之后进来,它是[e,b,c,d],但现在开始索引= 1。第i个元素是{{1}}。这个方案很容易由你自己实现。

答案 1 :(得分:1)

以下是循环容器的类模板的示例,该容器的大小在运行时期间是常量,但在类模板的实例化期间设置。要使用它,必须提前知道或预先计算尺寸;如果您不知道大小是多少,那么您可以考虑在没有模板或其他容器的情况下使用类似的算法。如果尺寸不是很大,这确实很有效。正如你在addString()函数中看到的那样,当被添加的元素开始超过所包含数组的大小时,有一个被调用的for循环必须移动该数组中的所有内容。这适用于大小为100或1000的元素的数组,但是当你得到大小为100,000或1,000,000个条目的数组时;这将是瓶颈而且速度很慢,但是这确实提供了将所有内容向左移动一个空格并在数组,列表或容器的末尾添加的机制。

#include <iostream>
#include <memory>
#include <string>


template<unsigned Size>
class CircularContainer {
public:
    const static unsigned SIZE = Size;
private:
    std::string data_[SIZE];
    unsigned counter_;

public:
    CircularContainer() : counter_(0) {}

    void addString( const std::string& str ) {
        // In a real container this would be a member and not static
        // If you have a static here, and you have multiple instances
        // It will still increment across all instances.
        //static unsigned counter = 0;

        if ( counter_ < SIZE ) {
            data_[counter_++ % SIZE] = str;
        } else {
            // This function can be expensive on large data sets
            // due to this for loop but for small structures this
            // is perfectly fine.
            for ( unsigned u = 0; u < SIZE-1; u++ ) {
                data_[u] = data_[u+1];
            }
            data_[SIZE - 1] = str;
        }
    }

    std::string& getString( unsigned idx ) {
        if ( idx < 0 || idx >= SIZE ) {
            return std::string();
        } else {
            return data_[idx];
        }
    }

    unsigned size() const {
        return SIZE;
    }
};

int main() {

    CircularContainer<4> cc;

    cc.addString( "hello" );
    cc.addString( "world" );
    cc.addString( "how" );
    cc.addString( "are" );

    for ( unsigned u = 0; u < cc.size(); u++ ) {
        std::cout << cc.getString( u ) << "\n";
    }
    std::cout << std::endl;

    cc.addString( "you" );
    cc.addString( "today" );

    for ( unsigned u = 0; u < cc.size(); u++ ) {
        std::cout << cc.getString( u ) << "\n";
    }


    std::cout << "\nPress any key and enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}

现在你可以适应这个;并交换此类中的原始字符串数组并使用指针链接堆,然后您只需将begend指针重新分配给相应的数据,因为中间的其他所有内容都是如此将已经像链条一样链接。

修改

我已经扩展了这个类以获取任何类型,而不是使用该数据类型的原始数组,我使用std::shared_ptr替换了数组。这是上面类的重新设计。我还解决了使用static counter并使其成为会员的问题。

#include <iostream>
#include <memory>
#include <string>

template<typename T, unsigned Size>
class CircularBuffer {
public:
    const static unsigned SIZE = Size;
private:
    std::shared_ptr<T> data_[SIZE];
    unsigned counter_;
public:
    CircularBuffer() : counter_(0) {}
    ~CircularBuffer() {}

    void addItem( const T& t ) {

        if ( counter_ < SIZE ) {
            data_[counter_++ % SIZE] = std::make_shared<T>( t );
        } else {
            for ( unsigned u = 0; u < SIZE - 1; u++ ) {
                data_[u] = data_[u + 1];
            }
            data_[SIZE - 1] = std::make_shared<T>( t );
        }
    }

    T getItem( unsigned idx ) {
        if ( idx < 0 || idx >= SIZE ) {
            throw std::exception( "Array Buffer Out of Bounds!" );
        } else {
            return *(data_[idx].get());
        }
    }

    unsigned size() const {
        return SIZE;
    }
};

int main() {
    CircularBuffer<std::string, 5> cb;
    cb.addItem( "hello" );
    cb.addItem( "world" );
    cb.addItem( "how" );
    cb.addItem( "are" );
    cb.addItem( "you" );

    for ( unsigned u = 0; u < cb.size(); u++ ) {
        std::cout << cb.getItem( u ) << "\n";
    }
    std::cout << std::endl;

    cb.addItem( "today" );
    cb.addItem( "my" );
    cb.addItem( "friend" );

    for ( unsigned u = 0; u < cb.size(); u++ ) {
        std::cout << cb.getItem( u ) << "\n";
    }
    std::cout << std::endl;


    std::cout << "\nPress any key and enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}

它仍然使用指定的数组,并使用相同的技术围绕该数组的索引。这里唯一的区别是我使用的是std::shared_ptr<T>数组。这将导致存储的对象位于堆而不是本地堆栈上的类模板范围。清理内存应该自动完成,但并不总能保证。没有必要跟踪head&amp; tail位置,除非您明确需要它们,并且不应该很难添加到此类模板。

答案 2 :(得分:0)

您可以使用std::deque

#include <deque>
#include <string>
#include <vector>

int main() {

    std::deque<std::string> strs;

    // .. initialize;
    std::string tmp;
    while (true) {
        strs.pop_front();
        std::cin >> tmp;
        strs.push_back(tmp);
    }

    return 0;
}

std::list也有相同的功能。

如果您想限制分配/取消分配内存的次数,您也可以使用std::vector,如下所示:

#include <vector>
#include <string>

int main() {

    std::vector<std::string> strs;

    // ...

    std::string tmp;
    while (true) {
        for (size_t i = 1; i < strs.size(); ++i)
            strs[i-1] = std::move(strs[i]);
     // strs.erase(strs.begin()); also works.

        std::cin >> tmp;
        strs.back() = tmp;
    }

    return 0;
}

这使您可以抓住您需要的许多地方,然后只移动值。