C++ 算法:计算单词列表频率的更好方法
这个问题其实很简单,但在开始编写代码之前,我想听听一些想法。给定一个文件,每行一个字,计算n个最频繁的数字 不幸的是,我脑海中出现的第一件也是唯一一件事就是使用C++ 算法:计算单词列表频率的更好方法,c++,performance,algorithm,data-structures,C++,Performance,Algorithm,Data Structures,这个问题其实很简单,但在开始编写代码之前,我想听听一些想法。给定一个文件,每行一个字,计算n个最频繁的数字 不幸的是,我脑海中出现的第一件也是唯一一件事就是使用std::map。我知道C++的同行们会说,无序映射非常合理 我想知道是否有什么可以添加到算法方面,或者这基本上只是“谁选择了最好的数据结构谁就赢”类型的问题。我在互联网上搜索过,读到哈希表和优先级队列可能会提供一个运行时间为O(n)的算法,但我认为实现起来会很复杂 有什么想法吗?这个问题有很多不同的解决方法。它最终取决于场景和其他因素,
std::map
。我知道C++的同行们会说,无序映射
非常合理
我想知道是否有什么可以添加到算法方面,或者这基本上只是“谁选择了最好的数据结构谁就赢”类型的问题。我在互联网上搜索过,读到哈希表和优先级队列可能会提供一个运行时间为O(n)的算法,但我认为实现起来会很复杂
有什么想法吗?这个问题有很多不同的解决方法。它最终取决于场景和其他因素,如文件的大小(如果文件有十亿行),那么
HashMap
将不是一种有效的方法。根据您的问题,您可以做以下几件事:
TreeMap
,或者在您的情况下使用std::map
trie
,并在另一个数据结构中记录各种字数。这可能是一个大小为n
的堆(最小/最大值取决于您想要做什么)。因此,您不需要存储所有单词,只需存储必要的单词即可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)
}