C++ 算法:计算单词列表频率的更好方法

C++ 算法:计算单词列表频率的更好方法,c++,performance,algorithm,data-structures,C++,Performance,Algorithm,Data Structures,这个问题其实很简单,但在开始编写代码之前,我想听听一些想法。给定一个文件,每行一个字,计算n个最频繁的数字 不幸的是,我脑海中出现的第一件也是唯一一件事就是使用std::map。我知道C++的同行们会说,无序映射非常合理 我想知道是否有什么可以添加到算法方面,或者这基本上只是“谁选择了最好的数据结构谁就赢”类型的问题。我在互联网上搜索过,读到哈希表和优先级队列可能会提供一个运行时间为O(n)的算法,但我认为实现起来会很复杂 有什么想法吗?这个问题有很多不同的解决方法。它最终取决于场景和其他因素,

这个问题其实很简单,但在开始编写代码之前,我想听听一些想法。给定一个文件,每行一个字,计算n个最频繁的数字

不幸的是,我脑海中出现的第一件也是唯一一件事就是使用
std::map
。我知道C++的同行们会说,
无序映射
非常合理

我想知道是否有什么可以添加到算法方面,或者这基本上只是“谁选择了最好的数据结构谁就赢”类型的问题。我在互联网上搜索过,读到哈希表和优先级队列可能会提供一个运行时间为O(n)的算法,但我认为实现起来会很复杂


有什么想法吗?

这个问题有很多不同的解决方法。它最终取决于场景和其他因素,如文件的大小(如果文件有十亿行),那么
HashMap
将不是一种有效的方法。根据您的问题,您可以做以下几件事:

  • 如果您知道唯一单词的数量非常有限,可以使用
    TreeMap
    ,或者在您的情况下使用
    std::map
  • 如果字数非常大,那么您可以构建一个
    trie
    ,并在另一个数据结构中记录各种字数。这可能是一个大小为
    n
    的堆(最小/最大值取决于您想要做什么)。因此,您不需要存储所有单词,只需存储必要的单词即可
  • 给定一个文件,每行有一个单词,计算最频繁的数字。 ... 我在互联网上搜索过它,读到哈希表和优先级队列可能会提供一个O(n)

    如果你的意思是*n*s相同,那么不,这是不可能的。然而,如果您只是指输入文件大小的时间线性,那么一个带有哈希表的简单实现将满足您的需要

    可能存在具有次线性内存的概率近似算法。

    如果我有很多选择的话,我不会从
    std::map
    (或
    unordered_map
    )开始(尽管我不知道可能应用哪些其他约束)

    这里有两个数据项,一个用作时间的关键部分,另一个用作时间的关键部分。因此,你可能想要一个类似的或可能的东西

    以下是使用Bimap的总体思路:

    #include <boost/bimap.hpp>
    #include <boost/bimap/list_of.hpp>
    #include <iostream>
    
    #define elements(array) ((sizeof(array)/sizeof(array[0])))
    
    class uint_proxy {
        unsigned value;
    public:
        uint_proxy() : value(0) {}
        uint_proxy& operator++() { ++value; return *this; }
        unsigned operator++(int) { return value++; }
        operator unsigned() const { return value; }
    };
    
    int main() {    
        int b[]={2,4,3,5,2,6,6,3,6,4};
    
        boost::bimap<int, boost::bimaps::list_of<uint_proxy> > a;
    
        // walk through array, counting how often each number occurs:
        for (int i=0; i<elements(b); i++) 
            ++a.left[b[i]];
    
        // print out the most frequent:
        std::cout << a.right.rbegin()->second;
    }
    
    #包括
    #包括
    #包括
    #定义元素(数组)((sizeof(数组)/sizeof(数组[0]))
    类uint\u代理{
    无符号值;
    公众:
    uint_proxy():值(0){}
    uint_代理和运算符++(){++value;返回*this;}
    无符号运算符++(int){返回值++;}
    运算符unsigned()常量{返回值;}
    };
    int main(){
    int b[]={2,4,3,5,2,6,6,3,6,4};
    boost::bimap a;
    //遍历数组,计算每个数字出现的频率:
    
    对于(int i=0;i而言,用于此任务的最佳数据结构是Trie:


    在计算字符串时,它的性能优于哈希表。

    如果您只对前N个最常见的单词感兴趣,而不需要精确,那么您可以使用一种非常聪明的结构。我通过Udi Manber听说了这一点,它的工作原理如下:

    您创建了一个由N个元素组成的数组,每个元素跟踪一个值和一个计数,您还保留了一个索引到此数组的计数器。此外,您还有一个从值到索引的映射到该数组。 每次使用值(如文本流中的单词)更新结构时,首先检查映射以查看该值是否已存在于数组中,如果是,则增加该值的计数。如果不是,则减少计数器指向的任何元素的计数,然后增加计数器

    这听起来很简单,而且算法本身并没有让它看起来会产生任何有用的东西,但对于典型的真实数据来说,它往往做得很好。通常,如果你想跟踪前N个东西,你可能希望使这个结构的容量为10*N,因为其中会有很多空值。使用詹姆斯国王的《圣经》输入,以下是此结构列出的最常见单词(无特定顺序):

    以下是十个最常见的单词(按顺序排列):

    您可以看到,它在前10个单词中有9个单词是正确的,并且只使用了50个元素的空间。根据您的使用情况,这里节省的空间可能非常有用。它也非常快

    以下是我使用的topN的实现,用Go编写:

    type Event string
    
    type TopN struct {
      events  []Event
      counts  []int
      current int
      mapped  map[Event]int
    }
    func makeTopN(N int) *TopN {
      return &TopN{
        counts: make([]int, N),
        events: make([]Event, N),
        current: 0,
        mapped: make(map[Event]int, N),
      }
    }
    
    func (t *TopN) RegisterEvent(e Event) {
      if index, ok := t.mapped[e]; ok {
        t.counts[index]++
      } else {
        if t.counts[t.current] == 0 {
          t.counts[t.current] = 1
          t.events[t.current] = e
          t.mapped[e] = t.current
        } else {
          t.counts[t.current]--
          if t.counts[t.current] == 0 {
            delete(t.mapped, t.events[t.current])
          }
        }
      }
      t.current = (t.current + 1) % len(t.counts)
    }
    

    您想知道存储频率或计算频率的最佳方法吗?我很困惑。@Jesse感谢您的评论,并在最短的运行时间内计算频率。
    std::map
    等与计算频率有什么关系?我想我会这样使用它。map[word]++;因此索引将是单词,计数将是其频率。@BenjaminLindley明确表示:感谢替代方法!我将记住这些建议。优雅的解决方案。激励我在未来使用boost双贴图。但是,我认为它缺少
    a.right.sort()
    在最终打印之前。默认情况下,视图列表中的元素将按插入顺序排序(而不是按其值排序),请参阅。对于字典中最长的匹配查找,trie非常有用,即不限于单个标记的匹配,但可以在不确定数量的标记上继续进行。但是对于查找和插入单个标记,一次一个,一个良好实现的哈希(包括
    std::unordered_map
    )根据我的经验,它的速度要快得多。你有数据证实你的说法吗?@jogojapan:在我链接的页面上有带有比较的性能图。符号表存储单个标记,通常存储为try,例如,请参见boost::spirit.Ok,accepted.ned
    0 : the ,  62600
    1 : and ,  37820
    2 : of ,  34513
    3 : to ,  13497
    4 : And ,  12703
    5 : in ,  12216
    6 : that ,  11699
    7 : he ,  9447
    8 : shall ,  9335
    9 : unto ,  8912
    
    type Event string
    
    type TopN struct {
      events  []Event
      counts  []int
      current int
      mapped  map[Event]int
    }
    func makeTopN(N int) *TopN {
      return &TopN{
        counts: make([]int, N),
        events: make([]Event, N),
        current: 0,
        mapped: make(map[Event]int, N),
      }
    }
    
    func (t *TopN) RegisterEvent(e Event) {
      if index, ok := t.mapped[e]; ok {
        t.counts[index]++
      } else {
        if t.counts[t.current] == 0 {
          t.counts[t.current] = 1
          t.events[t.current] = e
          t.mapped[e] = t.current
        } else {
          t.counts[t.current]--
          if t.counts[t.current] == 0 {
            delete(t.mapped, t.events[t.current])
          }
        }
      }
      t.current = (t.current + 1) % len(t.counts)
    }