需要在字符串和整数之间快速映射

时间:2015-09-01 06:48:44

标签: c++ dictionary

我有一个字符串和无符号的映射,其中我将一个单词存储到以下形式的频率:

map<string,unsigned> mapWordFrequency; //contains 1 billion such mappings

然后我读了一个巨大的文件(100GB),并且只保留文件中频率大于1000的单词。我使用mapWordFrequency [word]&gt; 1000检查文件中单词的频率。然而,结果是我的mapWordFrequency有10亿个映射而且我的文件很大,因此试图检查mapWordFrequency [word]&gt; 1000,文件中的每个单词都非常慢,需要2天以上。有人可以建议我如何提高上述代码的效率。

地图不适合我的内存,交换耗费了大量时间。

将删除具有频率&lt; 1000帮助使用地图的擦除功能?

6 个答案:

答案 0 :(得分:4)

我建议你使用unordered_map而不是map。正如评论中已经讨论的那样,前者将在O(1)中为您提供O(logn)的插入/检索时间,而不是map

正如您已经说过的,内存交换耗费了大量时间。那么如何逐步解决问题呢。将最大数据和unordered_map加载到内存中,哈希并继续。一次通过后,你应该有很多unordered_maps,你可以在后续的传递中开始组合它们。

您可以通过分布式方式提高速度。在不同的计算机上处​​理数据,然后组合数据(这将是无序地图的形式。但是,我没有分布式计算的经验,因此除此之外无法帮助。

另外,如果实现这样的东西太麻烦,我建议你使用外部mergesort。这是一种通过对较小的块进行排序并将它们组合来对文件进行过大排序以适应内存的方法。我建议这样做的原因是外部mergesort是一种非常常见的技术,您可能会发现已经实现的解决方案可满足您的需求。尽管排序的时间复杂度高于使用map的想法,但与地图相比,它将减少交换的开销。正如评论中所指出的,linux中的sort实现了外部mergesort。

答案 1 :(得分:2)

您可以使用散列映射,其中散列字符串将是键,并且出现将是值。它会更快。您可以根据需要选择一个好的字符串哈希。这是一些好的散列函数的链接:

http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx

你也可以使用一些第三方库。

编辑: 伪代码

int mapWordFrequency[MAX_SIZE] = {0} ;// if MAX_SIZE is large go with dynamic memory location
int someHashMethod(string input);

loop: currString in ListOfString
          int key = someHashMethod(currString);
          ++mapWordFrequency[key];
          if(mapWordFrequency[key] > 1000)
              doSomeThing();

更新: 正如@Jens指出的那样,someHashMethod()可能会为两个不同的字符串返回相同的int(hash)。在这种情况下,我们必须解决冲突,然后查找时间将不仅仅是常量。此外,由于输入大小非常大,因此可能无法创建该大小的单个阵列。在这种情况下,我们可能会使用分布式计算概念,但与单机相比,实际查找时间将再次上升。

答案 2 :(得分:1)

根据您的单词的统计分布,在将每个单词添加到地图之前压缩每个单词可能是值得的。只要这是无损压缩,您就可以在过滤后恢复原始单词。您的想法可能是减少平均字大小(从而节省内存和比较密钥的时间)。以下是您可以使用的简单压缩/解压缩过程:

public void addListItems(ArrayList<String> strings) {
    LayoutInflater inflater = LayoutInflater.from(this);
    for(String s : strings) {
        View item = inflater.inflate(R.layout.item_layout, mSimpleList, false);
        TextView text = (TextView) item.findViewById(R.id.item_text);
        text.setText(s);
        mSimpleList.addView(item);//you can add layout params if you want
    }
}

除了使用其他人建议的#include <string> #include <sstream> #include <boost/iostreams/filtering_streambuf.hpp> #include <boost/iostreams/filter/zlib.hpp> #include <boost/iostreams/copy.hpp> inline std::string compress(const std::string& data) { std::stringstream decompressed {data}; boost::iostreams::filtering_streambuf<boost::iostreams::input> stream; stream.push(boost::iostreams::zlib_compressor()); stream.push(decompressed); std::stringstream compressed {}; boost::iostreams::copy(stream, compressed); return compressed.str(); } inline std::string decompress(const std::string& data) { std::stringstream compressed {data}; boost::iostreams::filtering_streambuf<boost::iostreams::input> stream; stream.push(boost::iostreams::zlib_decompressor()); stream.push(compressed); std::stringstream decompressed; boost::iostreams::copy(stream, decompressed); return decompressed.str(); } 之外,您还可以将已经超过1000次的任何字词移出地图,并移至std::unordered_map。这还需要在映射之前检查集合,但是通过执行此操作可能会看到更好的散列性能。如果你采用这种策略,偶尔也可能值得重组。

答案 3 :(得分:1)

您需要另一种解决问题的方法,您的数据太大,无法一次处理。 例如,您可以将文件拆分为多个文件,让我们说最简单的方法就是按字母顺序拆分它们。

100GB/24 letters = 4.17 GB

现在,您每个24都有4.17GB个文件。 您知道任何文件中的单词都不能成为任何其他文件的一部分,这对您有帮助,因为您不必合并结果。 使用4GB文件,现在可以更轻松地在ram中工作。

当你开始使用大量内存时,

std::map会出现问题,因为它会碎片很多。试试std::unordered_map,如果仍然效果不佳,您可以在内存中加载文件并对其进行排序。计算事件将很容易。

假设您有多个重复项,那么mapunordered_map的内存占用量会大大减少。

为每个文件循环运行代码,并将结果附加到另一个文件中。 你应该很快完成。

答案 4 :(得分:1)

主要问题似乎是内存占用,所以我们正在寻找一种耗费很少内存的解决方案。节省内存的一种方法是使用有序vector而不是map。现在,vector具有~log(n)比较的查找时间和n / 2的平均插入时间,这是不好的。好处是你基本上没有内存开销,由于数据分离,要移动的内存很小,你得到顺序内存(缓存友好性),它可以轻松胜过map。所需的内存为每个单词2(wordcount)+ 4(索引)+ 1(\0 - char)+ x(字长)字节。要实现这一点,我们需要摆脱std::string,因为在这种情况下它太大了。

您可以将map拆分为vector<char>一个接一个地保存字符串\0 - 字符,索引vector<unsigned int>和{{1}对于单词计数。代码看起来像这样(经过测试):

vector<short int>

此方法仍会将所有单词保存在内存中。根据{{​​3}},英语中的平均单词长度为5.1个字符。这使您的总内存需求为(5.1 + 7)* 1bn字节= 121亿字节= 12.1GB。假设您有一台具有16 + GB RAM的中途现代计算机,您可以将其全部安装到RAM中。

如果失败(因为你没有英文单词并且它们不适合内存),下一个方法就是内存映射文件。这样,您可以使#include <vector> #include <algorithm> #include <cstring> #include <string> #include <fstream> #include <iostream> std::vector<char> strings; std::vector<unsigned int> indexes; std::vector<short int> wordcount; const int countlimit = 1000; void insertWord(const std::string &str) { //find the word auto stringfinder = [](unsigned int lhs, const std::string &rhs) { return &strings[lhs] < rhs; }; auto index = lower_bound(begin(indexes), end(indexes), str, stringfinder); //increment counter if (index == end(indexes) || strcmp(&strings[*index], str.c_str())) { //unknown word wordcount.insert(begin(wordcount) + (index - begin(indexes)), 1); indexes.insert(index, strings.size()); strings.insert(end(strings), str.c_str(), str.c_str() + str.size() + 1); } else { //known word auto &count = wordcount[index - begin(indexes)]; if (count < countlimit) //prevent overflow count++; } } int main() { std::ifstream f("input.txt"); std::string s; while (f >> s) { //not a good way to read in words insertWord(s); } for (size_t i = 0; i < indexes.size(); ++i) { if (wordcount[i] > countlimit) { std::cout << &strings[indexes[i]] << ": " << wordcount[i] << '\n'; } } } 指向内存映射文件而不是indexes,这样您就可以摆脱strings,但访问时间会受到影响。

如果由于性能低下而失败,您应该查看Wolfram Alpha,这很容易适用于这种情况。它为您提供与计算机一样多的性能。

答案 5 :(得分:1)

  

@TonyD你能用trie举个例子吗? - Rose Sharma

以下是解决此问题的方法示例:

#include <iostream>
#include <string>
#include <limits>
#include <array>

class trie
{
  public:
    void insert(const std::string& s)
    {
        node_.insert(s.c_str());
    }

    friend std::ostream& operator<<(std::ostream& os, const trie& t)
    {
        return os << t.node_;
    }

  private:
    struct Node
    {
        Node() : freq_(0) { }
        uint16_t freq_;
        std::array<Node*, 26> next_letter_{};

        void insert(const char* p)
        {
            if (*p)
            {
                Node*& p_node = next_letter_[*p - 'a'];
                if (!p_node)
                    p_node = new Node;
                p_node->insert(++p);
            }
            else
                if (freq_ < std::numeric_limits<decltype(freq_)>::max()) ++freq_;
        }
    } node_;

    friend std::ostream& operator<<(std::ostream& os, const Node& n)
    {
        os << '(';
        if (n.freq_) os << n.freq_ << ' ';
        for (size_t i = 0; i < 26; ++i)
            if (n.next_letter_[i])
                os << char('a' + i) << *(n.next_letter_[i]);
        return os << ')';
    }
};

int main()
{
    trie my_trie;
    my_trie.insert("abc");
    my_trie.insert("abcd");
    my_trie.insert("abc");
    my_trie.insert("bc");
    std::cout << my_trie << '\n';
}

输出:

(a(b(c(2 d(1 ))))b(c(1 )))

输出是您的词频直方图的压缩/树状表示:abc出现2次,abcd 1bc { {1}}。括号可以被认为是从“堆栈”中推送和弹出字符以形成当前前缀,或者 - 当有数字时 - 。

它是否在地图上有很大改进取决于输入词的变化,但值得一试。如果当前前缀下面的元素很少,那么更高内存效率的实现可能会使用1vector - 甚至是set个空格分隔后缀,然后切换到数组当这可能需要更少的内存时,你可以得到26个指针。