C++_Primer11.associative_continer

关联容器

associative-container

关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的。而顺序容器是按他们在容器中的位置来顺序保存和访问的。
因此关联容器不支持顺序容器的位置相关操作,比如 push_front, push_back.

两个主要的关联容器:map 和 set。map 是键值对形式;set 每个元素只有一个关键字,支持高效的关键字查询操作(检查一个给定的关键字是否在 set 中)。

标准库提供8个关联容器:

容器 说明
按关键字有序保存元素
map 关联数组,保存键值对
set 关键字即值
multimap 关键字可重复出现的 map
multiset 关键字可重复出现的 set
无序集合
unordered_map 用哈希函数组织的map
unordered_set 用哈希函数组织的set
unordered_multimap 哈希组织的map,关键字可重复出现
unordered_multiset 哈希组织的set,关键字可重复出现

使用关联容器

map 类型通常被称为关联数组(associative array).与正常数组类似,但其下标不必是整数。可以通过一个关键字而不是位置来查找值

根据关键字计数:

map<string, size_t> word_count;
set<string> exclude = {"the", "but", "and", "or", "an", "a"};
string word;
while (cin >> word){
    if (exclude.find(word) == exclude.end()){
        ++word_count[word];
    }
}
for (const auto& w: word_count){
    cout << w.first << "\t:\t" << w.second << endl;
}

概述

关键字类型必须定义元素比较的方法,默认情况下标准库使用关键字类型的 < 运算符比较两个关键字。
(所以,有序关联容器默认按照字典序排列)
对于类类型,比如 Sales_data,没有定义 < 运算符,可以使用自定义比较函数:

bool compareIsbn(const Sales_data& lhs, const Sales_data& rhs){
    return lhs.isbn() < rhs.isbn();
}

// 声明可重复集合,多条记录可以有相同的 isbn
multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);

pair 类型

在头文件 utility 中

保存两个数据成员:

pair<string, string> anon;
pair<string, size_t> word_count;
pair<string, vector<int>> line;

pair<string, string> p1("Tom", "John");
pair<string, string> p2 = {"Tom", "John"};
auto p3 = make_pair("Tom", "John");
cout << "p3: " << p3.first << ", " << p3.second << endl;
auto b = p1 > p2;       // 按字典序比较,依次比较 first 和 second

函数返回 pair 类型:

pare<string, int>
process(vector<string>& v){
    if (!v.empty()){
        return {v.back(), v.back().size()};
        // 或者:
        return make_pair(v.back(), v.back().size());
    } else {
        return pair<string, int>();
    }
}

操作

关联容器定义了以下下类型:

类型 说明
key_type 此容器类型的关键字类型
mapped_type 每个关键字关联的类型,只适用 map
value_type 对于 set,与 key_type 相同
对于 map,为 pair<const key_type, mapped_type>
set<string>::value_type v1;             // string
set<string>::key_type v2;               // string
map<string, int>::value_type v3;        // pair<const string, int>
map<string, int>::key_type v4;          // string
map<string, int>::mapped_type v5;       // int

遍历关联容器

auto map_it = word_count.begin();
while (map_it != word_count.end()){
    cout << map_it->first << ": " << map_it->second << endl;
    // map_it->first 是 const 的,而 second 是可以改变的
    ++map_it->second;
    ++map_it;
}

// set
set<int> iset = {0,1,2,3,4,5};
set<int>::iterator set_it = iset.begin();
if (set_it != iset.end()){
    *set_it = 42;                           // 错误,set 中的关键字是只读的
    cout << *set_it << endl;
}

由于关联容器的关键字是只读的,所以对关联容器只能使用只读的泛型算法。

向关联容器插入元素

// set
vector<int> ivec = {0,1,2,3,4};
set<int> set2;
set2.insert(ivec.cbegin(), ivec.cend());
set2.insert({5,6,7});

// map
string word = "example";
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, int>(word, 1));
word_count.insert(map<string, int>::value_type(word, 1));
插入操作 说明
c.insert(v)
c.emplace(args)
v 是 value_type 类型;args 用来构造一个元素。
对于 map 和 set,只有元素关键字不在 c 中是才插入。
函数返回一个 pair,包含一个迭代器和一个 bool 值。
迭代器指向指定关键字的元素
c.insert(b, e)
c.insert(il)
b 和 e 是迭代器,表示一个 c::value_type 类型的范围。
il 表示花括号列表。函数返回 void
c.insert(p, v)
c.emplace(p, args)
类似 c.insert(v),但插入的位置由迭代器 p 指定

第一种插入方法返回值类型是 pair<map<string, size_t>::iterator, bool> (假设 v 是 map<string, size_t>)
其中的 bool 值代表是否插入新元素,如果已经存在关键字,则不会插入,返回 false,否则为 true.

删除元素

操作 说明
c.erase(k) 删除所有关键字 k 的元素,返回 size_type 类型,表示删除元素的数量
c.erase(p) 删除迭代器 p 指定的元素,p 不能指向 c.end(),返回一个指向 p 之后的元素的迭代器
若 p 指向尾元素,则返回 c.end()
c.erase(b, e) 删除迭代器 b 和 e 表示的范围中的元素,返回 e

map的下标操作

访问 map 的操作:

  • 下标运算符
  • at 函数

当关键词不存在时,下标操作会插入一个新元素。
由于下标运算符可能插入一个新元素,我们只能对非 const 的 map 使用下标操作。

通常情况下,解引用一个迭代器返回的类型和下标运算符返回的类型是一样的,但对 map 则不一样:下标操作返回一个 mapped_type 对象,解引用 map 迭代器返回一个 value_type 对象。
与其他下标运算符相同,map 的下标运算符返回一个左值,可以对其操作:

++word_count["Anna"];

访问元素

set<int> iset = {0,1,2,3,4,5,6};
iset.find(2);           // 返回一个迭代器,指向 key 为2的元素
iset.find(10);          // 返回一个迭代器,等于 iset.end()
iset.count(2);          // 1
iset.count(10);         // 0
查找操作 说明
c.find(k) 查找 key 等于 k 的元素。若 k 不存在,返回尾后迭代器
c.count(k) 计数
c.lower_bound(k) 返回第一个关键字不小于 k 的元素
c.upper_bound(k) 返回第一个大于 k 的元素
c.equal_range(k) 返回一个迭代器 pair,表示关键字等于 k 的元素的范围。若 k 不存在,pair 的两个成员均等于 c.end()
无需容器不支持 c.lower_bound, c.upper_bound

multimap 和 multiset

对于允许重复关键字的容器,相同关键字的元素在容器中会相邻存储。

// 打印同一个作者的所有书
string search_item("侯捷");
auto entries = authors.count(search_item);
auto iter = authors.find(search_item);
while(entries){
    cout << iter->second << endl;
    ++iter;
    --entries;
}
// 使用 lower_bound 和 upper_bound
for (auto beg = authors.lower_bound(search_item),
        end = authors.upper_bound(search_item);
        beg != end; ++beg){
    cout << beg->second << endl;
}
// 使用 equal_range
for (auto range = authors.equal_range(search_item);
        range.first != range.second; ++range.first){
    cout << range.first->second << endl;
}

无序容器

无序容器不是使用比较运算符来组织元素,而是用一个哈希函数和关键字类型的==运算符。

无序容器在存储上组织为一个桶,每个桶保存0个或多个元素,使用一个哈希函数将元素映射到桶。一个桶中的元素都属于同一个关键字。计算一个元素的哈希值和在桶中搜索通常是比较快的操作。但如果一个桶中保存了很多元素,那么查找一个特定元素就需要大量比较操作。

无序容器操作 说明
桶接口
c.bucket_count() 正在使用的桶的数目
c.max_bucket_count() 容器能容纳的做多的桶数量
c.bucket_size(n) 第n个桶中有多少个元素
c.bucket(k) 关键字为 k 的元素在哪个桶中
桶迭代
local_iterator 可以用来访问桶中元素的迭代器类型
const_local_iterator 桶迭代器的 const 版本
c.begin(n), c.end(n) 桶n的首元素迭代器和尾后迭代器
c.cbegin(n), c.cend(n) 与上类似,但返回 const_local_iterator
哈希策略
c.load_factor() 每个桶的平均元素数量,返回 float
c.max_load_factor() c 试图维护的平均桶大小,返回 float。
c 会在需要时添加新的桶,使得 load_factor<=max_load_factor
c.rehash(n) 重组存储,使得 bucket_count>=n 且 bucket_count>size/max_load_factor
c.reserve(n) 重组存储,使得 c 可以保存 n 个元素且不必 rehash

关键字类型

默认情况下,无序容器使用关键字类型的=运算符比较元素。使用一个 hash<key_type> 类型的对象来生成每个元素的哈希值。key_type 可以是内置类型,string 和智能指针类型。

单词转换程序

将一段文字中的缩写替换为全写,缩写和全拼对应关系保存在一个文件中。

TODO:
p396
16.5节(p626)

#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include <sstream>

using namespace std;


unordered_map<string, string> buildMap(string);
const string& transform(const unordered_map<string, string>&, const string&);

int main(int argc, char** argv){
    string map_file("map.ini");
    unordered_map<string, string> umap = buildMap(map_file);

    string line;
    string key;
    while (getline(cin, line)){
        istringstream linestrm(line);
        while (linestrm >> key){
            cout << transform(umap, key) << " ";
        }
        cout << endl;
    }

    return 0;
}

unordered_map<string, string> buildMap(string file_name){
    unordered_map<string, string> umap;
    ifstream strm(file_name);
    string key, value;
    while (strm >> key && getline(strm, value)){
        if (value.size() > 1){
            umap[key] = value.substr(1);
        } else {
            throw runtime_error("no rule for " + key);
        }
    }
    return umap;
}

const string& transform(const unordered_map<string, string>& umap,
                        const string& key){
    auto ele = umap.find(key);
    if (ele != umap.cend()){
        return ele->second;
    }
    return key;
}

小结

关联容器通过关键字高效查找和提取元素,共有8个关联容器,每个容器:

  • 是 map 或 set
    • map 使用键值对,set 只有关键字
  • 关键字唯一或不要求
  • 关键字有序或无序

有序容器默认使用<运算符顺序存储。无序容器使用==和一个 hash<key_type> 类型的对象来组织元素。

允许重复关键字的容器名字都包含 multi;无序容器都以 unordered 开头。

无论有序还是无序容器,具有相同关键字的元素都是相邻存储的。

posted @ 2022-12-11 16:06  keep-minding  阅读(31)  评论(0)    收藏  举报