C++17 STL Cook Book
  • Introduction
  • 前言
  • 关于本书
  • 各章梗概
  • 第1章 C++17的新特性
    • 使用结构化绑定来解包绑定的返回值
    • 将变量作用域限制在if和switch区域内
    • 新的括号初始化规则
    • 构造函数自动推导模板的类型
    • 使用constexpr-if简化编译
    • 只有头文件的库中启用内联变量
    • 使用折叠表达式实现辅助函数
  • 第2章 STL容器
    • 擦除/移除std::vector元素
    • 以O(1)的时间复杂度删除未排序std::vector中的元素
    • 快速或安全的访问std::vector实例的方法
    • 保持对std::vector实例的排序
    • 向std::map实例中高效并有条件的插入元素
    • 了解std::map::insert新的插入提示语义
    • 高效的修改std::map元素的键值
    • std::unordered_map中使用自定义类型
    • 过滤用户的重复输入,并以字母序将重复信息打印出——std::set
    • 实现简单的逆波兰表示法计算器——std::stack
    • 实现词频计数器——std::map
    • 实现写作风格助手用来查找文本中很长的句子——std::multimap
    • 实现个人待办事项列表——std::priority_queue
  • 第3章 迭代器
    • 建立可迭代区域
    • 让自己的迭代器与STL的迭代器兼容
    • 使用迭代适配器填充通用数据结构
    • 使用迭代器实现算法
    • 使用反向迭代适配器进行迭代
    • 使用哨兵终止迭代
    • 使用检查过的迭代器自动化检查迭代器代码
    • 构建zip迭代适配器
  • 第4章 Lambda表达式
    • 使用Lambda表达式定义函数
    • 使用Lambda为std::function添加多态性
    • 并置函数
    • 通过逻辑连接创建复杂谓词
    • 使用同一输入调用多个函数
    • 使用std::accumulate和Lambda函数实现transform_if
    • 编译时生成笛卡尔乘积
  • 第5章 STL基础算法
    • 容器间相互复制元素
    • 容器元素排序
    • 从容器中删除指定元素
    • 改变容器内容
    • 在有序和无序的vector中查找元素
    • 将vector中的值控制在特定数值范围内——std::clamp
    • 在字符串中定位模式并选择最佳实现——std::search
    • 对大vector进行采样
    • 生成输入序列的序列
    • 实现字典合并工具
  • 第6章 STL算法的高级使用方式
    • 使用STL算法实现单词查找树类
    • 使用树实现搜索输入建议生成器
    • 使用STL数值算法实现傅里叶变换
    • 计算两个vector的误差和
    • 使用ASCII字符曼德尔布罗特集合
    • 实现分割算法
    • 将标准算法进行组合
    • 删除词组间连续的空格
    • 压缩和解压缩字符串
  • 第7章 字符串, 流和正则表达
    • 创建、连接和转换字符串
    • 消除字符串开始和结束处的空格
    • 无需构造获取std::string
    • 从用户的输入读取数值
    • 计算文件中的单词数量
    • 格式化输出
    • 使用输入文件初始化复杂对象
    • 迭代器填充容器——std::istream
    • 迭代器进行打印——std::ostream
    • 使用特定代码段将输出重定向到文件
    • 通过集成std::char_traits创建自定义字符串类
    • 使用正则表达式库标记输入
    • 简单打印不同格式的数字
    • 从std::iostream错误中获取可读异常
  • 第8章 工具类
    • 转换不同的时间单位——std::ratio
    • 转换绝对时间和相对时间——std::chrono
    • 安全的标识失败——std::optional
    • 对元组使用函数
    • 使用元组快速构成数据结构
    • 将void*替换为更为安全的std::any
    • 存储不同的类型——std::variant
    • 自动化管理资源——std::unique_ptr
    • 处理共享堆内存——std::shared_ptr
    • 对共享对象使用弱指针
    • 使用智能指针简化处理遗留API
    • 共享同一对象的不同成员
    • 选择合适的引擎生成随机数
    • 让STL以指定分布方式产生随机数
  • 第9章 并行和并发
    • 标准算法的自动并行
    • 让程序在特定时间休眠
    • 启动和停止线程
    • 打造异常安全的共享锁——std::unique_lock和std::shared_lock
    • 避免死锁——std::scoped_lock
    • 同步并行中使用std::cout
    • 进行延迟初始化——std::call_once
    • 将执行的程序推到后台——std::async
    • 实现生产者/消费者模型——std::condition_variable
    • 实现多生产者/多消费者模型——std::condition_variable
    • 并行ASCII曼德尔布罗特渲染器——std::async
    • 实现一个小型自动化并行库——std::future
  • 第10章 文件系统
    • 实现标准化路径
    • 使用相对路径获取规范的文件路径
    • 列出目录下的所有文件
    • 实现一个类似grep的文本搜索工具
    • 实现一个自动文件重命名器
    • 实现一个磁盘使用统计器
    • 计算文件类型的统计信息
    • 实现一个工具:通过符号链接减少重复文件,从而控制文件夹大小
Powered by GitBook
On this page
  • How to do it...
  • There's more...

Was this helpful?

  1. 第6章 STL算法的高级使用方式

使用树实现搜索输入建议生成器

Previous使用STL算法实现单词查找树类Next使用STL数值算法实现傅里叶变换

Last updated 6 years ago

Was this helpful?

上网时,在搜索引擎中输入要查找的东西时,对应下拉选项中会尝试猜测你想要查找什么。这种猜测是基于之前相关主题被查找的数量。有时搜索引擎十分有趣,其会显示一些奇怪的主题。

本章,我们将使用树类实现一个简单的搜索建议引擎。

How to do it...

本节,我们将实现一个终端应用,其能接受输入,并且能对所要查找的内容进行猜测,当然猜测的依据是我们用文本完成的“数据库”。

  1. 包含必要的头文件和声明所使用的命名空间:

    #include <iostream>
    #include <optional>
    #include <algorithm>
    #include <functional>
    #include <iterator>
    #include <map>
    #include <list>
    #include <string>
    #include <sstream>
    #include <fstream>
    
    using namespace std;
  2. 我们将使用上一节实现的trie类:

    template <typename T>
    class trie
    {
        map<T, trie> tries;
    public:
        template <typename It>
        void insert(It it, It end_it) {
            if (it == end_it) { return; }
            tries[*it].insert(next(it), end_it);
        }
    
        template <typename C>
        void insert(const C &container) {
            insert(begin(container), end(container));
        }
    
        void insert(const initializer_list<T> &il) {
            insert(begin(il), end(il));
        }
    
        void print(list<T> &l) const {
            if (tries.empty()) {
                copy(begin(l), end(l),
                    ostream_iterator<T>{cout, " "});
                cout << '\n';
            }
            for (const auto &p : tries) {
                l.push_back(p.first);
                p.second.print(l);
                l.pop_back();
            }
        }
    
        void print() const {
            list<T> l;
            print(l);
        }
    
        template <typename It>
        optional<reference_wrapper<const trie>>
        subtrie(It it, It end_it) const {
            if (it == end_it) { return ref(*this); }
            auto found (tries.find(*it));
            if (found == end(tries)) { return {}; }
    
            return found->second.subtrie(next(it), end_it);
        }
    
        template <typename C>
        auto subtrie(const C &c) const {
            return subtrie(begin(c), end(c));
        }
    };
  3. 实现一个简单的辅助函数,这个函数将用于提示用户输入他们想要查找的东西:

    static void prompt()
    {
        cout << "Next input please:\n > ";
    }
  4. 主函数中,我们打开一个文本文件,其作为我们的基础数据库。我们逐行读取文本文件的内容,并且将数据放入trie中解析:

    int main()
    {
        trie<string> t;
        fstream infile {"db.txt"};
        for (string line; getline(infile, line);) {
            istringstream iss {line};
            t.insert(istream_iterator<string>{iss}, {});
        }
  5. 现在可以使用构建好的trie类,并且需要实现接收用户查询输入的接口。会提示用户进行输入,并且将用户的输入整行读取:

        prompt();
        for (string line; getline(cin, line);) {
            istringstream iss {line};
  6. 通过文本输入,可以使用trie对其子trie进行查询。如果在数据库中已经有相应的语句,那么会对输入进行建议,否则会告诉用户没有建议给他们:

        if (auto st (t.subtrie(istream_iterator<string>{iss}, {}));
            st) {
            cout << "Suggestions:\n";
            st->get().print();
        } else {
            cout << "No suggestions found.\n";
        }
  7. 之后,将打印一段分割符,并且再次等待用户的输入:

            cout << "----------------\n";
            prompt();
        }
    }
  8. 运行程序之前,我们需要将db.txt文件进行设置。查找的输入可以是任何字符,并且其不确保是已经排过序的。进入trie类的所有语句:

    do ghosts exist
    do goldfish sleep
    do guinea pigs bite
    how wrong can you be
    how could trump become president
    how could this happen to me
    how did bruce lee die
    how did you learn c++
    what would aliens look like
    what would macgiver do
    what would bjarne stroustrup do
    ...
  9. 创建完db.txt之后,我们就可以运行程序了。其内容如下所示:

    hi how are you
    hi i am great thanks
    do ghosts exist
    do goldfish sleep
    do guinea pigs bite
    how wrong can you be
    how could trump become president
    how could this happen to me
    how did bruce lee die
    how did you learn c++
    what would aliens look like
    what would macgiver do
    what would bjarne stroustrup do
    what would chuck norris do
    why do cats like boxes
    why does it rain
    why is the sky blue
    why do cats hate water
    why do cats hate dogs
    why is c++ so hard
  10. 编译并运行程序,然后进行输入查找:

    $ ./word_suggestion
    Next input please:
    > what would
    Suggestions:
    aliens look like
    bjarne stroustrup do
    chuck norris do
    macgiver do
    ----------------
    Next input please:
    > why do
    Suggestions:
    cats hate dogs
    cats hate water
    cats like boxes
    ----------------
    Next input please:
    >

    How it works...

trie是如何工作的,已经在上一节中介绍过了,不过本节我们对其进行填充和查找的过程看起来有些奇怪。让我们来仔细观察一下代码片段,其使用文本数据库文件对空trie类进行填充:

fstream infile {"db.txt"};
for (string line; getline(infile, line);) {
    istringstream iss {line};
    t.insert(istream_iterator<string>{iss}, {});
}

这段代码会逐行的将文本文件中的内容读取出来。然后,我们将字符串拷贝到一个istringstream对象中。我们可以根据输入流对象,创建一个istring_iterator迭代器,其能帮助我们查找子trie。这样,我们就不需要将字符串放入vector或list中了。上述代码中,有一段不必要的内存分配,可以使用移动方式,将line中的内容移动到iss中,避免不必要的内存分配。不过,std::istringstream没有提供构造函数,所以只能将std::string中的内容移动到流中。不过,这里会对输入字符串进行复制。

当在trie中查询用户的输入时,使用了相同的策略,但不使用输入文件流。我们使用std::cin作为替代,因为trie::subtrie对迭代器的操作,和trie::insert如出一辙。

There's more...

这里有必要对每个trie节点添加统计变量,这样我们就能知道各种前缀被查询的频率。因此,我们就可以将程序的建议进行排序,当前的搜索引擎就是这样做的。智能手机触摸屏文本输入的建议,也可以通过这种方式实现。

这个修改就留给读者当作业了。 :)