首页 » 人工智能 » C++ 运用映射的单词频率计数器,我们都拥有海洋简谱。

C++ 运用映射的单词频率计数器,我们都拥有海洋简谱。

福州有家装饰工程通讯 2024-10-03 0

扫一扫用手机浏览

文章目录 [+]

STL 映射容器是一个关联容器。
它由按键值对组织的元素组成。
键用于查找,必须是唯一的。

在这个食谱中,我们将利用 STL 映射容器的唯一键哀求来打算文本文件中每个单词的涌现次数。

C++ 运用映射的单词频率计数器 C++ 运用映射的单词频率计数器 人工智能

如何做到这一点…

C++ 运用映射的单词频率计数器 C++ 运用映射的单词频率计数器 人工智能
(图片来自网络侵删)

这个任务有几个部分我们可以单独办理:

我们须要从文件中获取文本。
我们将为此利用 cin 流。

我们须要从标点符号和其他非单词内容等分离单词。
我们将为此利用 regex(正则表达式)库。

我们须要打算每个单词的频率。
这是食谱的紧张目标。
我们将为此利用 STL 映射容器。

末了,我们须要对结果进行排序,首先按频率排序,然后按单词在频率内的字母顺序排序。
为此我们将利用带有向量容器的 STL 排序算法。

纵然有所有这些任务,天生的代码相对简短,包括头文件在内大约 70 行。
让我们开始:

我们将从一些别名开始,以方便利用:

namespace ranges = std::ranges;namespace regex_constants = std::regex_constants;

对付 std:: 空间内的命名空间,我喜好制作别名,这些别名更短,但仍旧让我知道我正在利用特定命名空间中的令牌。
特殊是 ranges 命名空间,它常常重用现有算法的名称。

我们将正则表达式存储在常量中。
我不喜好弄乱全局命名空间,由于这可能导致冲突。
我方向于为这类事情利用基于我首字母的命名空间:

namespace bw { constexpr const char re{"(\\w+)"};}

往后很随意马虎利用 bw::re 获取它,这确切地见告我它是什么。

在 main() 的顶部,我们定义我们的数据构造:

int main() { map<string, int> wordmap{}; vector<pair<string, int>> wordvec{}; regex word_re(bw::re); size_t total_words{};}

我们的紧张映射称为 wordmap。
我们有一个名为 wordvec 的向量,我们将利用它作为排序容器。
末了,我们的 regex 类,word_re。

for 循环是大部分事情发生的地方。
我们从 cin 流中读取文本,运用正则表达式,并将单词存储在映命中:

for(string s{}; cin >> s; ) { auto words_begin{ sregex_iterator(s.begin(), s.end(), word_re) }; auto words_end{ sregex_iterator() }; for(auto r_it{words_begin}; r_it != words_end; ++r_it) { smatch match{ r_it }; auto word_str{match.str() }; ranges::transform(word_str, word_str.begin(), [](unsigned char c){ return tolower(c); }); auto [map_it, result] = wordmap.try_emplace(word_str, 0); auto & [w, count] = map_it; ++total_words; ++count; }}

我喜好这个 for 循环,由于它许可我包含 s 变量的浸染域。

我们首先为正则表达式结果定义迭代器。
这许可我们纵然只有标点符号包围时也能区分多个单词。
for(r_it...) 循环从 cin 字符串中返回单个单词。

smatch 类是正则表达式字符串匹配类的特化。
它从我们的正则表达式中给我们下一个单词。

然后,我们利用 transform 算法使单词小写——这样我们就可以不分大小写地皮算单词。
(例如,"The" 和 "the" 是同一个单词。

接下来,我们利用 try_emplace() 添加单词到映命中。
如果单词已经在映命中,它将不会被更换。

末了,我们在循环中用 ++count 增加映命中单词的计数。

现在我们的映命中有单词及其频率计数。
但它们是按字母顺序排列的,我们希望它们按频率降序排列。
为此,我们将它们放入向量中并对其进行排序:

auto unique_words = wordmap.size();wordvec.reserve(unique_words);ranges::move(wordmap, back_inserter(wordvec));ranges::sort(wordvec, [](const auto& a, const auto& b) { if(a.second != b.second) return (a.second > b.second); return (a.first < b.first);});cout << format("unique word count: {}\n", total_words);cout << format("unique word count: {}\n", unique_words);

wordvec 是一个包含单词和频率计数的 pair 的向量。
我们利用 ranges::move() 算法添补向量,然后利用 ranges::sort() 算法对其进行排序。
请把稳,排序的谓词 lambda 函数首先按计数(降序)排序,然后按单词(升序)排序。

末了,我们打印结果:

for(int limit{20}; auto& [w, count] : wordvec) { cout << format("{}: {}\n", count, w); if(--limit == 0) break;}

我设置了一个限定,只打印前 20 个条款。
你可以注释掉 if(--limit == 0) break; 行以打印全体列表。

在示例文件中,我包含了一个包含埃德加·艾伦·坡的《乌鸦》副本的文本文件。
这首诗在公共领域。
我们可以利用这个来测试程序:

$ ./word-count < the-raven.txt

输出:

total word count: 1098unique word count: 43956: the38: and32: i24: my21: of17: that17: this15: a14: door11: chamber11: is11: nevermore10: bird10: on10: raven9: me8: at8: from8: in8: lenore

这首诗统共有 1,098 个单词,个中 439 个是唯一的。

它是如何事情的…

食谱的核心是利用 map 工具来打算重复单词。
但还有其他部分值得考虑。

我们利用 cin 流从标准输入读取文本。
默认情形下,cin 在将文本读入字符串工具时会跳过空缺。
通过将字符串工具放在 >> 运算符的右侧(cin >> s),我们可以得到由空格分隔的文本块。
对付许多目的来说,这是一个很好的足够定义的一次一个单词,但我们须要措辞单词。
为此,我们将利用正则表达式。

regex 类供应了正则表达式语法的选择,它默认为 ECMA 语法。
在 ECMA 语法中,正则表达式 "(\w+)" 是 "([A-Za-z0-9_]+)" 的快捷办法。
这将选择包含这些字符的单词。

正则表达式是他们自己的一种措辞。
要理解更多关于正则表达式的信息,我推举阅读 Jeffrey Friedl 的《Mastering Regular Expressions》一书。

当我们从 regex 引擎中获取每个单词时,我们利用 map 工具的 try_emplace() 方法有条件地将单词添加到我们的 wordmap 中。
如果单词不在映命中,我们将单词添加进去,计数为 0。
如果单词已经在映命中,计数不变。
我们在循环中稍后增加计数,以是它总是精确的。

在映命中添补了文件中所有唯一的单词后,我们利用 ranges::move() 算法将其转移到向量中。
move() 算法使这种转移快速高效。
然后我们可以利用 ranges::sort() 在向量中对其进行排序。
排序的谓词 lambda 函数包括对 pair 的两侧的比较,因此我们终极得到一个按单词计数(降序)和单词(升序)排序的结果。

标签:

相关文章

5g网络架构,架构图制作。

5g的网络架构及接口ITU确定了5G的三大类应用场景,我国IMT-2020将其中移动宽带场景做了进一步划分:移动互联网和移动物联网...

人工智能 2024-10-03 阅读1 评论0