日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

第 11 章

 印度阿三17 2019-07-15

11.1

【出題思路】

理解順序容器和關(guān)聯(lián)容器的不同。

【解答】

學(xué)習(xí)關(guān)聯(lián)容器,理解與順序容器的不同,最關(guān)鍵的是理解其基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),隨后它所表現(xiàn)出來的一些性質(zhì)就很自然能夠理解了。

兩類容器的根本差別在于,順序容器中的元素是 “順序” 存儲的(鏈表容器中的元素雖然不是在內(nèi)存中 “連續(xù)” 存儲的,但仍然是按 “順序” 存儲的)。理解 “順序” 的關(guān)鍵,是理解容器支持的操作形式以及效率。

對于 vector 這樣的順序容器,元素在其中按順序存儲,每個元素有唯一對應(yīng)的位置編號,所有操作都是按編號(位置)進(jìn)行的。例如,獲取元素(頭、尾、用下標(biāo)獲取任意位置)、插入刪除元素(頭、尾、任意位置)、遍歷元素(按元素位置順序逐一訪問)。底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組、鏈表,簡單但已能保證上述操作的高效。而對于依賴值的元素訪問,例如查找(搜索)給定值(find),在這種數(shù)據(jù)結(jié)構(gòu)上的實現(xiàn)是要通過遍歷完成的,效率不佳。

而 map 這種關(guān)聯(lián)容器,就是為了高效實現(xiàn) “按值訪問元素” 這類操作而設(shè)計的。為了達(dá)到這一目的,容器中的元素是按關(guān)鍵字值存儲的,關(guān)鍵字值與元素數(shù)據(jù)建立起對應(yīng)關(guān)系,這就是 “關(guān)聯(lián)” 的含義。底層數(shù)據(jù)結(jié)構(gòu)是紅黑樹、哈希表等,可高效實現(xiàn)按關(guān)鍵字值查找、添加、刪除元素等操作。

11.2

【出題思路】

理解順序容器和關(guān)聯(lián)容器的適用范圍。

【解答】

若元素很?。ɡ?int),大致數(shù)量預(yù)先可知,在程序運(yùn)行過程中不會劇烈變化,大部分情況下只在末尾添加或刪除,需要頻繁訪問任意位置的元素,則 vector 可帶來最高的效率。若需要頻繁在頭部和尾部添加或刪除元素,則 deque 是最后的選擇。

如果元素較大(如大的類對象),數(shù)量預(yù)先不知道,或是程序運(yùn)行過程中頻繁變化,對元素的訪問更多是順序訪問全部或很多元素,則 list 很合適。

map 很適合對一些對象按它們的某個特征進(jìn)行訪問的情形。典型的例如按學(xué)生的姓名來查詢學(xué)生信息,即可將學(xué)生姓名作為關(guān)鍵字,將學(xué)生信息作為元素,保存在 map 中。再比如統(tǒng)計單詞出現(xiàn)的次數(shù)。

set,顧名思義,就是集合類型。當(dāng)需要保存特定的值集合 —— 通常是滿足/不滿足某種要求的值集合,用 set 最為方便。比如黑名單。

11.3

【出題思路】

練習(xí) map 的簡單使用。

【解答】

參照本節(jié)例子完成完整程序即可。

#include <iostream>
#include <map>
#include <string>
#include <algorithm>

using namespace std;

string &trans(string &s) {
    for (int p = 0; p < s.size();   p) {
        if (s[p] >= 'A' && s[p] <= 'Z')
            s[p]  = ('a' - 'A');
        else if (s[p] == ',' || s[p] == '.')
            s.erase(p, 1);
    }
    return s;
}

int main() {
    // 統(tǒng)計每個單詞在輸入中出現(xiàn)的次數(shù)
    map<string, size_t> word_count;     // string 到 size_t 的空 map
    string word;
    while (cin >> word)
          word_count[trans(word)];      // 提取 word 的計數(shù)器并將其加 1
    for (const auto &w : word_count)    // 對 map 中的每個元素
        // 打印結(jié)果
        cout << w.first << " occurs " << w.second
             << ((w.second > 1) ? " times" : " time") << endl;
    return 0;
}
// 運(yùn)行結(jié)果
example. Cplusplus Primer example, Example primer
^D
cplusplus occurs 1 time
example occurs 3 times
primer occurs 2 times

Process finished with exit code 0

11.4

【出題思路】

此題并非練習(xí) set 的使用,而是字符串的處理。

【解答】

編寫函數(shù) trans,將單詞中的標(biāo)點去掉,將大寫都轉(zhuǎn)換為小寫。具體方法是:遍歷字符串,對每個字符首先檢查是否是大寫(ASCII 值在 A 和 Z 之間),若是,將其轉(zhuǎn)換為小寫;否則,檢查它是否帶標(biāo)點,若是,將其刪除。最終,將轉(zhuǎn)換好的字符串返回。

s.erase(pos, len); 刪除從位置 pos 開始的 len 個字符。如果 len 被省略,則刪除從 pos 開始直至 s 末尾的所有字符。返回一個指向 s 的引用。(書 C primer 5th 中文版 \(P_{323}\) 有有關(guān)修改 string 的介紹)

#include <iostream>
#include <map>
#include <string>
#include <algorithm>

using namespace std;

string &trans(string &s) {
    for (int p = 0; p < s.size();   p) {
        if (s[p] >= 'A' && s[p] <= 'Z')
            s[p]  = ('a' - 'A');
        else if (s[p] == ',' || s[p] == '.')
            s.erase(p, 1);
    }
    return s;
}

int main() {
    // 統(tǒng)計每個單詞在輸入中出現(xiàn)的次數(shù)
    map<string, size_t> word_count;     // string 到 size_t 的空 map
    string word;
    while (cin >> word)
          word_count[trans(word)];      // 提取 word 的計數(shù)器并將其加 1
    for (const auto &w : word_count)    // 對 map 中的每個元素
        // 打印結(jié)果
        cout << w.first << " occurs " << w.second
             << ((w.second > 1) ? " times" : " time") << endl;
    return 0;
}
// 運(yùn)行結(jié)果
example. Cplusplus Primer example, Example primer
^D
cplusplus occurs 1 time
example occurs 3 times
primer occurs 2 times

Process finished with exit code 0

11.5

【出題思路】

理解兩種關(guān)聯(lián)容器的差別。

【解答】

當(dāng)需要查找給定值所對應(yīng)的數(shù)據(jù)時,應(yīng)使用 map,其中保存的是 <關(guān)鍵字, 值> 對,按關(guān)鍵字訪問值。

如果只需判定給定值是否存在時,應(yīng)使用 set,它是簡單的值的集合。

11.6

【出題思路】

理解關(guān)聯(lián)容器和順序容器的差別。

【解答】

兩者都可以保存元素集合。

如果只需要順序訪問這些元素,或是按位置訪問元素,那么應(yīng)使用 list。

如果需要快速判定是否有元素等于給定值,則應(yīng)使用 set。

11.7

【出題思路】

理解 map 的稍復(fù)雜的使用。

【解答】

此 map 的關(guān)鍵字類型是 string,值類型是 vector<string>。

我們定義函數(shù) add_family 添加一個家庭,注意,必須先檢查是否已有這個家庭,若不做這個檢查,則可能將已有家庭的孩子名字清空(如 main 函數(shù)中的王姓家庭的添加順序)。若確實還沒有這個家庭,則創(chuàng)建一個空的 vector<string>,表示這個家庭的孩子名字列表。

函數(shù) add_child 向一個已有家庭添加孩子的名字:首先用 [] 運(yùn)算符取出該家庭的 vector,然后調(diào)用 push_back 將名字追加到 vector 末尾。

#include <iostream>
#include <map>
#include <vector>
#include <string>

using namespace std;

void add_family(map<string, vector<string>> &families, const string &family) {
    if (families.find(family) == families.end())
        families[family] = vector<string>();
}

void add_child(map<string, vector<string>> &families, const string &family,
        const string &child) {
    families[family].push_back(child);
}

int main() {
    map<string, vector<string>> families;

    add_family(families, "張");
    add_child(families, "張", "強(qiáng)");
    add_child(families, "張", "三");
    add_child(families, "王", "五");
    add_family(families, "王");

    for (auto f : families) {
        cout << f.first << "家的孩子:";
        for (auto c : f.second)
            cout << c << " ";
        cout << endl;
    }
    return 0;
}
// 運(yùn)行結(jié)果
張家的孩子:強(qiáng) 三 
王家的孩子:五 

Process finished with exit code 0

【其他解題思路】

add_family 的函數(shù)體其實可以有一個非常簡單的實現(xiàn):

families[family];

當(dāng)該家庭已存在時,此語句只是獲取其 vector,不會導(dǎo)致 vector 有任何變化;若該家庭不存在,標(biāo)準(zhǔn)庫 map 的實現(xiàn)機(jī)制是在容器中為該關(guān)鍵字創(chuàng)建一個對象,進(jìn)行默認(rèn)初始化,即構(gòu)造一個空 vector。與 if 版本的效果完全一致。

這也是 add_child 為什么不需要檢查家庭是否存在的原因,當(dāng)家庭存在時,將孩子的名字追加到現(xiàn)有 vector 末尾;若家庭不存在,標(biāo)準(zhǔn)庫會先創(chuàng)建一個新的空 vector,然后我們的程序?qū)⒑⒆用痔砑舆M(jìn)去。

11.8

【出題思路】

通過實際編程理解 set 和 vector 的差別。

【解答】

使用 vector 保存不重復(fù)單詞,需要用 find 查找新讀入的單詞是否在 vector 中,若不在(返回尾后迭代器),才將單詞加入 vector。

而使用 set,檢查是否重復(fù)的工作是由 set 模版負(fù)責(zé)的,程序員無須編寫對應(yīng)代碼,程序簡潔很多。

更深層次的差別,vector 是無序線性表,find 查找指定值只能采用順序查找方式,所花費(fèi)的時間與 vector.size() 呈線性關(guān)系。而 set 是用紅黑樹實現(xiàn)的,花費(fèi)的時間與 vector.size() 呈對數(shù)關(guān)系。當(dāng)單詞數(shù)量已經(jīng)非常多時,set 的性能優(yōu)勢是巨大的。

當(dāng)然,vector 也不是毫無用處。它可以保持單詞的輸入順序,而 set 則不能,遍歷 set,元素是按值的升序被遍歷的。

vector 版本:

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

string &trans(string &s) {
    for (int p = 0; p < s.size();   p) {
        if (s[p] >= 'A' && s[p] <= 'Z')
            s[p]  = ('a' - 'A');
        else if (s[p] == ',' || s[p] == '.')
            s.erase(p, 1);
    }
    return s;
}

int main() {
    vector<string> unique_word;             // 不重復(fù)的單詞
    string word;
    while (cin >> word) {
        trans(word);
        if (find(unique_word.begin(), unique_word.end(), word)
                                        == unique_word.end())
            unique_word.push_back(word);    // 添加不重復(fù)單詞
    }
    for (const auto &w : unique_word)       // 打印不重復(fù)單詞
        // 打印結(jié)果
        cout << w << " ";
    cout << endl;
    return 0;
}
// 運(yùn)行結(jié)果
example. Cplusplus Primer example, Example primer
^D
example cplusplus primer 

Process finished with exit code 0

set 版本:

#include <iostream>
#include <set>
#include <string>
#include <algorithm>

using namespace std;

string &trans(string &s) {
    for (int p = 0; p < s.size();   p) {
        if (s[p] >= 'A' && s[p] <= 'Z')
            s[p]  = ('a' - 'A');
        else if (s[p] == ',' || s[p] == '.')
            s.erase(p, 1);
    }
    return s;
}

int main() {
    set<string> unique_word;            // 不重復(fù)的單詞
    string word;
    while (cin >> word) {
        trans(word);
        unique_word.insert(word);       // 添加不重復(fù)單詞
    }
    for (const auto &w : unique_word)   // 打印不重復(fù)單詞
        // 打印結(jié)果
        cout << w << " ";
    cout << endl;
    return 0;
}
// 運(yùn)行結(jié)果
example. Cplusplus Primer example, Example primer
^D
cplusplus example primer 

Process finished with exit code 0

11.9

【出題思路】

練習(xí) map 的使用。

【解答】

map 的定義為:

map<string, list<int>> word_lineno;

完整程序如下所示。其中用 getline 讀取一行,統(tǒng)計行號。再用字符串流讀取這行中所有單詞,記錄單詞行號。參見第 8 章內(nèi)容。

#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <string>
#include <list>

using namespace std;

string &trans(string &s) {
    for (int p = 0; p < s.size();   p) {
        if (s[p] >= 'A' && s[p] <= 'Z')
            s[p]  = ('a' - 'A');
        else if (s[p] == ',' || s[p] == '.')
            s.erase(p, 1);
    }
    return s;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        cerr << "請給出文件名" << endl;
        return -1;
    }
    ifstream in(argv[1]);
    if (!in) {
        cerr << "無法打開輸入文件" << endl;
        return -1;
    }

    map<string, list<int>> word_lineno;             // 單詞到行號的映射
    string line;
    string word;
    int lineno = 0;
    while (getline(in, line)) {                     // 讀取一行
          lineno;                                   // 行號遞增
        istringstream l_in(line);                   // 構(gòu)造字符串流,讀取單詞
        while (l_in >> word) {
            trans(word);
            word_lineno[word].push_back(lineno);    // 添加行號
        }
    }

    for (const auto &w : word_lineno) {             // 打印單詞行號
        cout << w.first << "所在行:";
        for (const auto &i : w.second)
            cout << i << " ";
        cout << endl;
    }

    return 0;
}

運(yùn)行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 為 ../data 。

注:../data 即為文件 data 的文件名及其相對路徑(是相對于可執(zhí)行程序所在目錄的相對路徑)。

并在文件 data 中寫入如下內(nèi)容:

example. Cplusplus
Primer example, Example
primer
example.
Cplusplus
Primer example, Example primer

運(yùn)行程序,程序執(zhí)行結(jié)果如下所示:

cplusplus所在行:1 5 
example所在行:1 2 2 4 6 6 
primer所在行:2 3 6 6 

Process finished with exit code 0

11.10

【出題思路】

理解關(guān)聯(lián)容器對關(guān)鍵字類型的要求。

【解答】

由于有序容器要求關(guān)鍵字類型必須支持比較操作 <,因此

map<vector<int>::iterator, int> m1;

是可以的,因為 vector 的迭代器支持比較操作。而

map<list<int>::iterator, int> m2;

則不行,因為 list 的元素不是連續(xù)存儲,其迭代器不支持比較操作。

11.11

【出題思路】

本題練習(xí)函數(shù)指針類型的定義。

【解答】

首先用 typedef 定義與 compareIsbn 相容的函數(shù)指針類型,然后用此類型聲明 multiset 即可。

typedef bool (*pf)(const Sales_data &, const Sales_data &);
multiset<Sales_data, pf> bookstore(compareIsbn);

另一個參考的版本 使用關(guān)鍵字 using 定義:

#include "../ch07/ex7_26_sales_data.h"
#include <set>

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

int main() {
    using compareType = bool (*)(const Sales_data &lhs, const Sales_data &rhs);
    // typedef bool(*compareType)(const Sales_data &lhs, const Sales_data &rhs);
    std::multiset<Sales_data, compareType> bookstore(compareIsbn);
}

11.12

【出題思路】

本題練習(xí) pair 的使用。

【解答】

#include <iostream>
#include <vector>
#include <utility>
#include <string>

using namespace std;

int main() {
    vector<pair<string, int>> data;             // pair 的 vector
    string s;
    int v;
    while (cin >> s && cin >> v)                // 讀取一個字符串和一個整數(shù)
        data.push_back(pair<string, int> (s, v));
    for (const auto &d : data)
        // 打印結(jié)果
        cout << d.first << " " << d.second << endl;
    return 0;
}
// 前兩行是測試數(shù)據(jù)
// 運(yùn)行結(jié)果
James 23 Jane 24 Charles 19
James 23 Jane 24 Charles 19
^D
James 23
Jane 24
Charles 19
James 23
Jane 24
Charles 19

Process finished with exit code 0

【其他解題思路】

我們還可以使用更簡潔的列表初始化方式,將 while 的循環(huán)體改為

data.push_back({s, v});

即可。

還可以使用 make_pair:

data.push_back(make_pair(s, v));

11.13

【出題思路】

熟悉 pair 的不同初始化方式。

【解答】

顯然,列表初始化方式最為簡潔易懂。

11.14

【出題思路】

本題練習(xí)稍復(fù)雜的 pair 和關(guān)聯(lián)容器的結(jié)合使用。

【解答】

在本題中,我們將家庭的姓映射到孩子信息的列表,而不是簡單的孩子名的列表。因此,將在 vector 中的元素類型聲明為 pair<string, string>,兩個 string 分別表示孩子的名和生日。在添加孩子信息時,用列表初始化創(chuàng)建名和生日的 pair,添加到 vector 中即可。

#include <iostream>
#include <map>
#include <vector>
#include <string>
#include <utility>

using namespace std;

void add_family(map<string, vector<pair<string, string>>> &families,
        const string &family) {
    families[family];
}

void add_child(map<string, vector<pair<string, string>>> &families,
        const string &family, const string &child, const string &birthday) {
    families[family].push_back({child, birthday});
}

int main() {
    map<string, vector<pair<string, string>>> families;

    add_family(families, "張");
    add_child(families, "張", "強(qiáng)", "1993-1-1");
    add_child(families, "張", "三", "1999-1-2");
    add_child(families, "王", "五", "2000-12-01");
    add_family(families, "王");

    for (auto f : families) {
        cout << f.first << "家的孩子:";
        for (auto c : f.second)
            cout << c.first << "(生日" << c.second << "), ";
        cout << endl;
    }
    return 0;
}
// 運(yùn)行結(jié)果
張家的孩子:強(qiáng)(生日1993-1-1), 三(生日1999-1-2), 
王家的孩子:五(生日2000-12-01), 

Process finished with exit code 0

11.15

【出題思路】

理解關(guān)聯(lián)容器的類型別名。

【解答】

mapped_type 是 vector<int>

key_type 是 int;

value_type 是 pair<const int, vector<int>>

11.16

【出題思路】

理解 map 的迭代器解引用的類型。

【解答】

解引用關(guān)聯(lián)容器的迭代器,得到的是 value_type 的值的引用。因此對 map 而言,得到的是一個 pair 類型的引用,其 first 成員保存 const 的關(guān)鍵字,second 成員保存值。因此,通過迭代器只能修改值,而不能改變關(guān)鍵字。

#include <iostream>
#include <map>
#include <utility>

using namespace std;

int main() {
    map<const int, int> m;
    m[32] = 222;
    map<const int, int>::iterator iter = m.begin();
    iter->second = 0;
    cout << iter->first << endl;
    cout << iter->second;
    return 0;
}
// 運(yùn)行結(jié)果
32
0
Process finished with exit code 0

11.17

【出題思路】

理解 set 的迭代器的特點。

【解答】

set 的迭代器是 const 的,因此只允許訪問 set 中的元素,而不能改變 set。與 map 一樣,set 的關(guān)鍵字也是 const,因此也不能通過迭代器來改變 set 中元素的值。

因此,前兩個調(diào)用試圖將 vector 中的元素復(fù)制到 set 中,是非法的。

而后兩個調(diào)用將 set 中的元素復(fù)制到 vector 中,是合法的。

11.18

【出題思路】

理解 map 的迭代器。

【解答】

map<const string, size_t>::iterator

11.19

【出題思路】

本題繼續(xù)練習(xí)關(guān)聯(lián)容器的迭代器。

本題與練習(xí) 11.11 相關(guān)

【解答】

typedef bool (*pf)(const Sales_data &, const Sales_data &);
multiset<Sales_data, pf> bookstore(compareIsbn);
...
multiset<Sales_data, pf>::iterator iter = bookstore.begin();

與 11.11 中鏈接相對應(yīng)的版本:

using compareType = bool (*)(const Sales_data &lhs, const Sales_data &rhs);
std::multiset<Sales_data, compareType> bookstore(compareIsbn);
std::multiset<Sales_data, compareType>::iterator c_it = bookstore.begin();

11.20

【出題思路】

熟悉關(guān)聯(lián)容器不同的插入方式。

用 insert 改寫練習(xí) 11.3

【解答】

使用 insert 操作的方式是:構(gòu)造一個 pair(單詞, 1),用 insert 將其插入容器,返回一個 pair。若單詞已存在,則返回 pair 的 second 成員為 false,表示插入失敗,程序員還需通過返回 pair 的 first 成員(迭代器)遞增已有單詞的計數(shù)器。判斷單詞是否已存在,并進(jìn)行相應(yīng)操作的工作完全是由程序員負(fù)責(zé)的。

使用下標(biāo)操作的方式是:以單詞作為下標(biāo)獲取元素值,若單詞已存在,則提取出已有元素的值;否則,下標(biāo)操作將 pair(單詞, 1) 插入容器,提取出新元素的值。單詞是否已存在的相關(guān)處理完全是由下標(biāo)操作處理的,程序員不必關(guān)心,直接訪問提取出的值就行了。

顯然,對于單詞計數(shù)問題來說,下標(biāo)操作更簡潔易讀。

#include <iostream>
#include <map>
#include <string>
#include <algorithm>

using namespace std;

string &trans(string &s) {
    for (int p = 0; p < s.size();   p) {
        if (s[p] >= 'A' && s[p] <= 'Z')
            s[p]  = ('a' - 'A');
        else if (s[p] == ',' || s[p] == '.')
            s.erase(p, 1);
    }
    return s;
}

int main() {
    // 統(tǒng)計每個單詞在輸入中出現(xiàn)的次數(shù)
    map<string, size_t> word_count;     // string 到 size_t 的空 map
    string word;
    while (cin >> word) {
        auto ret = word_count.insert({trans(word), 1});
        if (!ret.second)                                // 插入失敗,單詞已存在
              ret.first->second;                // 已有單詞的計數(shù)加 1
    }
    for (const auto &w : word_count)    // 對 map 中的每個元素
        // 打印結(jié)果
        cout << w.first << " occurs " << w.second
             << ((w.second > 1) ? " times" : " time") << endl;
    return 0;
}
// 運(yùn)行結(jié)果
example. Cplusplus Primer example, Example primer
^D
cplusplus occurs 1 time
example occurs 3 times
primer occurs 2 times

Process finished with exit code 0

11.21

【出題思路】

繼續(xù)熟悉關(guān)聯(lián)容器的 insert 操作。

【解答】

循環(huán)不斷從標(biāo)準(zhǔn)輸入讀入單詞(字符串),直至遇到文件結(jié)束或錯誤。

每讀入一個單詞,構(gòu)造 pair {word, 0} ,通過 insert 操作插入到 word_count 中。insert 返回一個 pair,其 first 成員是一個迭代器。若單詞(關(guān)鍵字)已存在于容器中,它(迭代器)指向已有元素;否則,它指向新插入的元素。

因此,.first 會得到這個迭代器,指向 word 對應(yīng)的元素。繼續(xù)使用 ->second ,可獲得元素的值的引用,即單詞的計數(shù)。若單詞是新的,則其值為 0;若已存在,則值為之前出現(xiàn)的次數(shù)。對其(“0” 或 “之前出現(xiàn)的次數(shù)”)進(jìn)行遞增操作,即完成將出現(xiàn)次數(shù)加 1.

用這種方法,上一題可稍微簡單些。

11.22

【出題思路】

繼續(xù)熟悉關(guān)聯(lián)容器的 insert 操作。

【解答】

map<string, vector<int>> m;
返回類型 ret = m.insert(參數(shù)類型);

參數(shù)類型是一個 pair,first 成員的類型是 map 的關(guān)鍵字類型 string ,second 成員的類型是 map 的值類型 vector<int>

pair<string, vector<int>>

返回類型也是一個 pair,first 成員的類型是 map 的迭代器,second 成員的類型是布爾型:

pair<map<string, vector<int>>::iterator, bool>

11.23

【出題思路】

本題練習(xí)允許重復(fù)關(guān)鍵字的關(guān)聯(lián)容器的 insert 操作。

改寫練習(xí) 11.7

【解答】

由于允許重復(fù)關(guān)鍵字,已經(jīng)不需要 vector 保存同一家的孩子的名的列表,直接保存每個孩子的 (姓, 名) pair 即可。容器類型變?yōu)?multimap<string, string> 。

也不再需要 add_family 添加家庭,只保留 add_child 直接用 insert 操作添加孩子即可。

#include <iostream>
#include <map>
#include <vector>
#include <string>

using namespace std;

void add_child(multimap<string, string> &families, const string &family,
               const string &child) {
    families.insert({family, child});
}

int main() {
    multimap<string, string> families;

    add_child(families, "張", "強(qiáng)");
    add_child(families, "張", "三");
    add_child(families, "王", "五");

    for (auto f : families)
        cout << f.first << "家的孩子:" << f.second << endl;

    return 0;
}
// 運(yùn)行結(jié)果
張家的孩子:強(qiáng)
張家的孩子:三
王家的孩子:五

Process finished with exit code 0

11.24

【出題思路】

本題繼續(xù)熟悉 map 的下標(biāo)操作。

【解答】

若 m 中已有關(guān)鍵字 0,下標(biāo)操作提取出其值,賦值語句將值置為 1。否則,下標(biāo)操作會創(chuàng)建一個 pair (0, 0),即關(guān)鍵字為 0, 值為 0(值初始化),將其插入到 m 中,然后提取其值,賦值語句將值置為 1。

11.25

【出題思路】

理解順序容器和關(guān)聯(lián)容器下標(biāo)操作的不同。

【解答】

對于 m,“0” 表示 “關(guān)鍵字 0”。而對于 v,“0” 表示 “位置 0”。

若 v 中已有不少于一個元素,即,存在 “位置 0” 元素,則下標(biāo)操作提取出此位置的元素(左值),賦值操作將其置為 1。而 map 的元素是 pair 類型,下標(biāo)提取的不是元素,而是元素的第二個成員,即元素的值。

如 v 尚為空,則下標(biāo)提取出的是一個非法左值(下標(biāo)操作不做范圍檢查),向其賦值可能導(dǎo)致系統(tǒng)崩潰等嚴(yán)重后果。

11.26

【出題思路】

理解 map 的下標(biāo)操作所涉及的各種類型。

【解答】

對 map 進(jìn)行下標(biāo)操作,應(yīng)使用其 key_type,即關(guān)鍵字的類型。

而下標(biāo)操作返回的類型是 mapped_type,即關(guān)鍵字關(guān)聯(lián)的值的類型。

示例如下:

map 類型:map<string, int>

用來進(jìn)行下標(biāo)操作的類型:string

下標(biāo)操作返回的類型:int

#include <iostream>
#include <string>
#include <map>

using namespace std;

int main() {
    map<string, int> m{{"zhangsan", 22}, {"wangwu", 23}};

    // 下標(biāo)操作的類型 map<string, int>::key_type,即為 string
    map<string, int>::key_type type_of_subscript = "wangwu";
    // 下標(biāo)運(yùn)算符將會返回的類型 map<string, int>::mapped_type,即為 int
    map<string, int>::mapped_type type_of_return = m[type_of_subscript];

    return 0;
}

11.27

【出題思路】

理解關(guān)聯(lián)容器上不同算法的區(qū)別。

【解答】

find 查找關(guān)鍵字在容器中出現(xiàn)的位置,而 count 則還會統(tǒng)計關(guān)鍵字出現(xiàn)的次數(shù)。

因此,當(dāng)我們希望知道(允許重復(fù)關(guān)鍵字的)容器中有多少元素的關(guān)鍵字與給定關(guān)鍵字相同時,使用 count。

當(dāng)我們只關(guān)心關(guān)鍵字是否在容器中時,使用 find 就足夠了。特別是,對于不允許重復(fù)關(guān)鍵字的關(guān)聯(lián)容器,find 和 count 的效果沒有什么區(qū)別,使用 find 就可以了?;蛘撸?dāng)我們需要獲取具有給定關(guān)鍵字的元素(而不只是統(tǒng)計個數(shù))時,也需要使用 find。

find 和下標(biāo)操作有一個重要區(qū)別,當(dāng)給定關(guān)鍵字不在容器中時,下標(biāo)操作會插入一個具有該關(guān)鍵字的元素。因此,當(dāng)我們想檢查給定關(guān)鍵字是否存在時,應(yīng)該用 find 而不是下標(biāo)操作。

11.28

【出題思路】

理解 map 上的 find。

【解答】

find 返回一個迭代器,指向具有給定關(guān)鍵字的元素(若不存在則返回尾后迭代器),因此其返回類型是容器的迭代器。

// map 類型
map<string, vector<int>> m;
// 保存 find 返回結(jié)果的變量
map<string, vector<int>>::iterator iter;

11.29

【出題思路】

熟悉適合 multimap 和 multiset 的基于迭代器的關(guān)鍵字查找方法。

【解答】

lower_bound 和 upper_bound,這兩個操作都接受一個關(guān)鍵字,返回一個迭代器。如果關(guān)鍵字在容器中,lower_bound 返回的迭代器將指向第一個具有給定關(guān)鍵字的元素,而 upper_bound 返回的迭代器則指向最后一個匹配給定關(guān)鍵字的元素之后的位置。若給定關(guān)鍵字不在容器中,則 lower_bound 和 upper_bound 會返回相等的迭代器 —— 都指向給定關(guān)鍵字的插入點,能保持容器中元素順序的插入位置。

equal_range 函數(shù)接受一個關(guān)鍵字,返回一個迭代器 pair。若關(guān)鍵字存在,則第一個迭代器指向第一個與給定關(guān)鍵字匹配的元素,第二個迭代器指向最后一個匹配元素之后的位置。若未找到匹配的元素,則兩個迭代器都指向關(guān)鍵字可以插入的位置。此 pair 的 first 成員保存的迭代器(第一個迭代器)與 lower_bound 返回的迭代器是一樣的,second 成員保存的迭代器(第二個迭代器)與 upper_bound 的返回的迭代器是一樣的。

11.30

【出題思路】

熟悉 equal_range 的使用。

【解答】

equal_range 返回一個迭代器 pair,其 first 成員與 lower_bound 的返回的迭代器相同,即指向容器中第一個具有給定關(guān)鍵字的元素。因此,對其解引用會得到一個 value_type 對象,即一個 pair,其 first 為元素的關(guān)鍵字,即給定關(guān)鍵字,而 second 為關(guān)鍵字關(guān)聯(lián)的值。在本例中,關(guān)鍵字為作者,關(guān)聯(lián)的值為著作的題目。因此 pos.first->second 即獲得給定作者的第一部著作的題目。

11.31

【出題思路】

練習(xí) multimap,需使用 insert 操作。

在 multimap 中查找具有給定關(guān)鍵字的元素,有幾種方法:使用 find 只能查找第一個具有給定關(guān)鍵字的元素,要找到所有具有給定關(guān)鍵字的元素,需編寫循環(huán)(如書 \(P_{389}\) 例子所示);lower_bound 和 upper_bound 配合使用,可找到具有給定關(guān)鍵字的元素的范圍;equal_range 最為簡單,一次即可獲得要查找的元素范圍。

將找到的范圍傳遞給 erase,即可刪除指定作者的所有著作。

為了解決元素不在 multimap 中的情況,首先檢查 equal_range 返回的兩個迭代器,若相等(空范圍),則什么也不做。范圍不為空時,才將迭代器傳遞給 erase,刪除所有元素。

#include <iostream>
#include <string>
#include <map>

using namespace std;

void remove_author(multimap<string, string> &books, const string &author) {
    auto pos = books.equal_range(author);       // 要查找的作者
    if (pos.first == pos.second)
        cout << "沒有" << author << "這個作者" << endl;
    else
        books.erase(pos.first, pos.second);     // 刪除該作者及其所有作品
}

void print_books(multimap<string, string> &books) {
    cout << "當(dāng)前書目包括:" << endl;
    for (auto &book : books)                    // 打印作者及其書目
        cout << book.first << ":《" << book.second << "》" << endl;
}

int main() {
    multimap<string, string> books;
    books.insert({"Barth, John", "Sot-Weed Factor"});
    books.insert({"Barth, John", "Lost in the Funhouse"});
    books.insert({"金庸", "笑傲江湖"});
    books.insert({"金庸", "天龍八部"});
    books.insert({"金庸", "射雕英雄傳"});

    print_books(books);
    remove_author(books, "張三");
    remove_author(books, "Barth, John");
    print_books(books);

    return 0;
}
// 運(yùn)行結(jié)果
當(dāng)前書目包括:
Barth, John:《Sot-Weed Factor》
Barth, John:《Lost in the Funhouse》
金庸:《笑傲江湖》
金庸:《天龍八部》
金庸:《射雕英雄傳》
沒有張三這個作者
當(dāng)前書目包括:
金庸:《笑傲江湖》
金庸:《天龍八部》
金庸:《射雕英雄傳》

Process finished with exit code 0

【其他解題思路】

使用 find 或 lower_bound 和 upper_bound,也可實現(xiàn)本題目標(biāo),但復(fù)雜一些。

11.32

【出題思路】

本題要求理解 multimap 數(shù)據(jù)結(jié)構(gòu)中關(guān)鍵字的順序,以及利用它來實現(xiàn)關(guān)鍵字的有序輸出。

【解答】

multimap 的數(shù)據(jù)結(jié)構(gòu)是紅黑樹,它維護(hù)了元素的關(guān)鍵字的默認(rèn)序。例如,對字符串關(guān)鍵字(作者),紅黑樹會維護(hù)它們的字典序。當(dāng)我們遍歷 multimap(如遍歷 [begin(), end()) ,或更簡單地使用范圍 for 循環(huán))時,就是按關(guān)鍵字的字典序來訪問元素。

因此,上一題的 print_books 實際上已經(jīng)實現(xiàn)了按字典序打印作者(作品沒有實現(xiàn)字典序)。

但是,當(dāng)我們要求的不是關(guān)鍵字的默認(rèn)序(運(yùn)算符 < 定義的順序)時,就要復(fù)雜一些。由于 sort 算法要求給定的兩個迭代器是隨機(jī)訪問迭代器,關(guān)聯(lián)容器的迭代器不符合這一要求,所以不能直接對其使用 sort 算法。其實這不難理解,關(guān)聯(lián)容器的根本特征就是維護(hù)了關(guān)鍵字的默認(rèn)序,從而實現(xiàn)了按關(guān)鍵字的插入、刪除和查找。是不可能通過 sort 使其內(nèi)部元素呈現(xiàn)出另外一種順序的。只有本身不關(guān)心元素的順序容器,才可能隨意安排元素順序(位置)。我們可以在定義 multimap 時使用自己定義的比較操作來定義關(guān)鍵字的序,而不是使用 < 定于的序,但這只是令 multimap 以另外一種序來維護(hù)關(guān)鍵字,仍然不可能在使用 multimap 的過程中來改變關(guān)鍵字的順序。為此,我們只能將 multimap 中的元素拷貝到一個順序容器(如 vector)中,對順序容器執(zhí)行 sort 算法,來獲取關(guān)鍵字的其他序。

下面是我參考網(wǎng)上的代碼(該代碼實現(xiàn)了關(guān)鍵字和關(guān)鍵字關(guān)聯(lián)的值都字典有序輸出。本質(zhì)還是利用了關(guān)聯(lián)容器的關(guān)鍵字的默認(rèn)序 —— 字典序),對代碼做的簡單注釋和運(yùn)行結(jié)果:

#include <map>
#include <set>
#include <string>
#include <iostream>

using std::string;

int main()
{
    std::multimap<string, string> authors{
            {"alan", "DMA"}, {"pezy", "LeetCode"}, {"alan", "CLRS"},
            {"wang", "FTP"}, {"pezy", "CP5"},      {"wang", "CPP-Concurrency"}};

    /*
     * multimap 默認(rèn)是按關(guān)鍵字字典序訪問元素,所以輸出
     * 也是關(guān)鍵字字典序;關(guān)鍵字關(guān)聯(lián)的值無法保證有序
     */
    for (const auto &author : authors)
        std::cout << author.first << ": " << author.second << std::endl;
    std::cout << std::endl;
    /*
     * 創(chuàng)建 map,實現(xiàn)作者和作品的映射
     * key_type 類型為 string
     * mapped_type 類型為 multiset<string>
     * map 和 multiset 的關(guān)鍵字是有序的
     * 這樣就可以實現(xiàn)作者和著作都有序輸出了
     */
    std::map<string, std::multiset<string>> m;
    // 將 authors 中的作者及其著作都插入到 m 中
    for (const auto& author : authors)
        m[author.first].insert(author.second);
    // 遍歷 m,實現(xiàn)作者有序,作者著作也有序
    // 當(dāng)然這里的有序指的是字典序
    for (const auto& s : m) {
        std::cout << s.first << ": ";
        for (const auto& b : s.second)
            std::cout << b << " ";
        std::cout << std::endl;
    }

    return 0;
}
// 運(yùn)行結(jié)果
alan: DMA
alan: CLRS
pezy: LeetCode
pezy: CP5
wang: FTP
wang: CPP-Concurrency

alan: CLRS DMA 
pezy: CP5 LeetCode 
wang: CPP-Concurrency FTP 

Process finished with exit code 0

11.33

【出題思路】

關(guān)聯(lián)容器的綜合練習(xí)。

【解答】

本書配套網(wǎng)站(C Primer, 5th Edition Source Downloads)提供了書中的全部源代碼,其中就有單詞轉(zhuǎn)換程序。嘗試編譯、運(yùn)行它即可。

綜合本小節(jié)內(nèi)容、理解此程序,若仍有不能理解,可利用集成開發(fā)環(huán)境跟蹤功能跟蹤程序運(yùn)行,觀察變量值的變化,來幫助你理解程序。

下面是我運(yùn)行書籍配套代碼的結(jié)果:

/*
 * This file contains code from "C   Primer, Fifth Edition", by Stanley B.
 * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
 * copyright and warranty notices given in that book:
 * 
 * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
 * 
 * 
 * "The authors and publisher have taken care in the preparation of this book,
 * but make no expressed or implied warranty of any kind and assume no
 * responsibility for errors or omissions. No liability is assumed for
 * incidental or consequential damages in connection with or arising out of the
 * use of the information or programs contained herein."
 * 
 * Permission is granted for this code to be used for educational purposes in
 * association with the book, given proper citation if and when posted or
 * reproduced.Any commercial use of this code requires the explicit written
 * permission of the publisher, Addison-Wesley Professional, a division of
 * Pearson Education, Inc. Send your request for permission, stating clearly
 * what code you would like to use, and in what specific way, to the following
 * address: 
 * 
 *  Pearson Education, Inc.
 *  Rights and Permissions Department
 *  One Lake Street
 *  Upper Saddle River, NJ  07458
 *  Fax: (201) 236-3290
*/

#include <map>
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>
#include <sstream>

using std::map; using std::string; using std::vector;
using std::ifstream; using std::cout; using std::endl;
using std::getline;
using std::runtime_error; using std::istringstream;

map<string, string> buildMap(ifstream &map_file)
{
    map<string, string> trans_map;   // holds the transformations
    string key;    // a word to transform
    string value;  // phrase to use instead
    // read the first word into key and the rest of the line into value
    while (map_file >> key && getline(map_file, value))
        if (value.size() > 1) // check that there is a transformation
            trans_map[key] = value.substr(1); // skip leading space
        else
            throw runtime_error("no rule for "   key);
    return trans_map;
}

const string &
transform(const string &s, const map<string, string> &m)
{
    // the actual map work; this part is the heart of the program
    map<string, string>::const_iterator map_it = m.find(s);
    // if this word is in the transformation map
    if (map_it != m.end())
        return map_it->second; // use the replacement word
    else
        return s;              // otherwise return the original unchanged
}

// first argument is the transformations file; 
// second is file to transform
void word_transform(ifstream &map_file, ifstream &input)
{
    map<string, string> trans_map =
            buildMap(map_file); // store the transformations

    // for debugging purposes print the map after its built
    cout << "Here is our transformation map: \n\n";
    for (map<string, string>::const_iterator entry = trans_map.begin();
         entry != trans_map.end();   entry)
        cout << "key: "   << entry->first
             << "\tvalue: " << entry->second << endl;
    cout << "\n\n";

    // do the transformation of the given text
    string text;                    // hold each line from the input
    while (getline(input, text)) {  // read a line of input
        istringstream stream(text); // read each word 
        string word;
        bool firstword = true;      // controls whether a space is printed 
        while (stream >> word) {
            if (firstword)
                firstword = false;
            else
                cout << " ";  // print a space between words
            // transform returns its first argument or its transformation
            cout << transform(word, trans_map); // print the output
        }
        cout << endl;        // done with this line of input
    }
}

int main(int argc, char **argv)
{
    // open and check both files
    if (argc != 3)
        throw runtime_error("wrong number of arguments");

    ifstream map_file(argv[1]); // open transformation file 
    if (!map_file)              // check that open succeeded
        throw runtime_error("no transformation file");

    ifstream input(argv[2]);    // open file of text to transform
    if (!input)                 // check that open succeeded
        throw runtime_error("no input file");

    word_transform(map_file, input);

    return 0;  // exiting main will automatically close the files
}

運(yùn)行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 為 ../map_file ../input 。

注:../map_file ../input 即為 map_file 和 input 文件的文件名及其相對路徑(是相對于可執(zhí)行程序所在目錄的相對路徑)。

并在文件 map_file 中寫入如下內(nèi)容(轉(zhuǎn)換規(guī)則):

brb be right back
k okay?
y why
r are
u you
pic picture
thk thanks!
18r later

input 文件中寫入測試數(shù)據(jù)(待轉(zhuǎn)換的數(shù)據(jù)):

where r u
y dont u send me a pic
k thk 18r

運(yùn)行程序,程序執(zhí)行結(jié)果如下所示:

// 運(yùn)行結(jié)果
Here is our transformation map: 

key: 18r    value: later
key: brb    value: be right back
key: k  value: okay?
key: pic    value: picture
key: r  value: are
key: thk    value: thanks!
key: u  value: you
key: y  value: why


where are you
why dont you send me a picture
okay? thanks! later

Process finished with exit code 0

11.34

【出題思路】

繼續(xù)理解 find 和下標(biāo)操作的區(qū)別。

【解答】

如前所述,find 僅查找給定關(guān)鍵字在容器中是否出現(xiàn),若容器中不存在給定關(guān)鍵字,它返回尾后迭代器。當(dāng)關(guān)鍵字存在時,下標(biāo)運(yùn)算符的行為和 find 類似,但當(dāng)關(guān)鍵字不存在時,下標(biāo)運(yùn)算符會構(gòu)造一個 pair(進(jìn)行值初始化),將其插入到容器中。對于單詞轉(zhuǎn)換程序,若將 find 替換為下標(biāo)運(yùn)算符,若給定關(guān)鍵字 s 不在 m 中,下標(biāo)運(yùn)算符會試圖構(gòu)建新 pair 插入到 m 中,但由于 m 被限定為 const,如無法插入,程序報錯。而且構(gòu)建新的 pair 的到 m 中,也不符合我們的預(yù)期。

transform(const string &s, const map<string, string> &m) {
  ... ...
}

11.35

【出題思路】

繼續(xù)理解 insert 操作和下標(biāo)操作的區(qū)別。

【解答】

當(dāng) map 中沒有給定關(guān)鍵字時,insert 操作與下標(biāo)操作 賦值操作的效果類似,都是將關(guān)鍵字和值的 pair 添加到 map 中。

但當(dāng) map 中已有給定關(guān)鍵字,也就是新的轉(zhuǎn)換規(guī)則與一條已有規(guī)則要轉(zhuǎn)換同一個單詞(關(guān)鍵字)時,兩者的行為是不同的。下標(biāo)操作會獲得具有該關(guān)鍵字的元素(也就是已有規(guī)則)的值,并將新讀入的值賦予它,也就是用新讀入的規(guī)則覆蓋了容器中的已有規(guī)則。但 insert 操作遇到關(guān)鍵字已存在的情況,則不會改變?nèi)萜鲀?nèi)容,而是返回一個值指出插入失敗。因此,當(dāng)規(guī)則文件(map_file)中存在多條規(guī)則轉(zhuǎn)換相同單詞時,下標(biāo) 賦值的版本最終會用最后一條規(guī)則進(jìn)行文本轉(zhuǎn)換,而 insert 版本則會用第一條規(guī)則進(jìn)行文本轉(zhuǎn)換。

11.36

【出題思路】

本題練習(xí)字符串處理的技巧。

【解答】

此題有誤,書中程序已經(jīng)處理了這種情況。

在 buildMap 函數(shù)中,當(dāng)循環(huán)中讀入要轉(zhuǎn)換的單詞和轉(zhuǎn)換的內(nèi)容后,會檢查是否存在轉(zhuǎn)換的內(nèi)容(value.size() > 1),若不存在,則拋出一個異常。

當(dāng)然,程序只處理這一種錯誤。你可以思考還有哪些錯誤,編寫程序完成處理。

11.37

【出題思路】

理解無序關(guān)聯(lián)容器和有序版本的差異。

【解答】

無須版本通常性能更好,使用也更為簡單。有序版本的優(yōu)勢是維護(hù)了關(guān)鍵字的序。

當(dāng)元素的關(guān)鍵字類型沒有明顯的序的關(guān)系,或是維護(hù)元素的代價非常高時,無序容器非常有用。

但當(dāng)應(yīng)用要求必須維護(hù)元素的序時,有序版本就是唯一的選擇。

11.38

【出題思路】

本題練習(xí)使用無序關(guān)聯(lián)容器。

【解答】

對單詞計數(shù)程序(練習(xí) 11.3/11.4)僅有的兩處修改是將包含的頭文件 map 改為 unordered_map,以及將 word_count 類型由 map 改為 unordered_map。

嘗試編譯、運(yùn)行此程序,你會發(fā)現(xiàn),由于無序容器不維護(hù)元素的序,程序的輸出結(jié)果與練習(xí) 11.3/11.4 輸出結(jié)果的順序是不同的。

#include <iostream>
#include <unordered_map>
#include <string>
#include <algorithm>

using namespace std;

string &trans(string &s) {
    for (int p = 0; p < s.size();   p) {
        if (s[p] >= 'A' && s[p] <= 'Z')
            s[p]  = ('a' - 'A');
        else if (s[p] == ',' || s[p] == '.')
            s.erase(p, 1);
    }
    return s;
}

int main() {
    // 統(tǒng)計每個單詞在輸入中出現(xiàn)的次數(shù)
    unordered_map<string, size_t> word_count;   // string 到 size_t 的空 map
    string word;
    while (cin >> word)
          word_count[trans(word)];              // 提取 word 的計數(shù)器并將其加 1
    for (const auto &w : word_count)            // 對 map 中的每個元素
        // 打印結(jié)果
        cout << w.first << " occurs " << w.second
             << ((w.second > 1) ? " times" : " time") << endl;
    return 0;
}
// 運(yùn)行結(jié)果
example. Cplusplus Primer example, Example primer
^D
primer occurs 2 times
cplusplus occurs 1 time
example occurs 3 times

Process finished with exit code 0

單詞轉(zhuǎn)換程序的修改類似。程序如下所示:

/*
 * This file contains code from "C   Primer, Fifth Edition", by Stanley B.
 * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
 * copyright and warranty notices given in that book:
 * 
 * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
 * 
 * 
 * "The authors and publisher have taken care in the preparation of this book,
 * but make no expressed or implied warranty of any kind and assume no
 * responsibility for errors or omissions. No liability is assumed for
 * incidental or consequential damages in connection with or arising out of the
 * use of the information or programs contained herein."
 * 
 * Permission is granted for this code to be used for educational purposes in
 * association with the book, given proper citation if and when posted or
 * reproduced.Any commercial use of this code requires the explicit written
 * permission of the publisher, Addison-Wesley Professional, a division of
 * Pearson Education, Inc. Send your request for permission, stating clearly
 * what code you would like to use, and in what specific way, to the following
 * address: 
 * 
 *  Pearson Education, Inc.
 *  Rights and Permissions Department
 *  One Lake Street
 *  Upper Saddle River, NJ  07458
 *  Fax: (201) 236-3290
*/

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

using std::unordered_map; using std::string; using std::vector;
using std::ifstream; using std::cout; using std::endl;
using std::getline;
using std::runtime_error; using std::istringstream;

unordered_map<string, string> buildMap(ifstream &map_file)
{
    unordered_map<string, string> trans_map;   // holds the transformations
    string key;    // a word to transform
    string value;  // phrase to use instead
    // read the first word into key and the rest of the line into value
    while (map_file >> key && getline(map_file, value))
        if (value.size() > 1) // check that there is a transformation
            trans_map[key] = value.substr(1); // skip leading space
        else
            throw runtime_error("no rule for "   key);
    return trans_map;
}

const string &
transform(const string &s, const unordered_map<string, string> &m)
{
    // the actual unordered_map work; this part is the heart of the program
    // 桶迭代器和容器迭代器是兩個不同的概念,不要混淆
    // const_local_iterator 和 local_iterator 是桶迭代器
    // 而這里的 const_iterator 是無序容器 unordered_map 的迭代器
    unordered_map<string, string>::const_iterator map_it = m.find(s);
    // if this word is in the transformation unordered_map
    if (map_it != m.end())
        return map_it->second; // use the replacement word
    else
        return s;              // otherwise return the original unchanged
}

// first argument is the transformations file; 
// second is file to transform
void word_transform(ifstream &map_file, ifstream &input)
{
    unordered_map<string, string> trans_map =
            buildMap(map_file); // store the transformations

    // for debugging purposes print the unordered_map after its built
    cout << "Here is our transformation unordered_map: \n\n";
    for (unordered_map<string, string>::const_iterator entry = trans_map.begin();
         entry != trans_map.end();   entry)
        cout << "key: "   << entry->first
             << "\tvalue: " << entry->second << endl;
    cout << "\n\n";

    // do the transformation of the given text
    string text;                    // hold each line from the input
    while (getline(input, text)) {  // read a line of input
        istringstream stream(text); // read each word 
        string word;
        bool firstword = true;      // controls whether a space is printed 
        while (stream >> word) {
            if (firstword)
                firstword = false;
            else
                cout << " ";  // print a space between words
            // transform returns its first argument or its transformation
            cout << transform(word, trans_map); // print the output
        }
        cout << endl;        // done with this line of input
    }
}

int main(int argc, char **argv)
{
    // open and check both files
    if (argc != 3)
        throw runtime_error("wrong number of arguments");

    ifstream map_file(argv[1]); // open transformation file 
    if (!map_file)              // check that open succeeded
        throw runtime_error("no transformation file");

    ifstream input(argv[2]);    // open file of text to transform
    if (!input)                 // check that open succeeded
        throw runtime_error("no input file");

    word_transform(map_file, input);

    return 0;  // exiting main will automatically close the files
}

運(yùn)行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 為 ../map_file ../input 。

注:../map_file ../input 即為 map_file 和 input 文件的文件名及其相對路徑(是相對于可執(zhí)行程序所在目錄的相對路徑)。

并在文件 map_file 中寫入如下內(nèi)容(轉(zhuǎn)換規(guī)則):

brb be right back
k okay?
y why
r are
u you
pic picture
thk thanks!
18r later

input 文件中寫入測試數(shù)據(jù)(待轉(zhuǎn)換的數(shù)據(jù)):

where r u
y dont u send me a pic
k thk 18r

注:map_file 和 input 文件內(nèi)容與練習(xí) 11.33 相同。

運(yùn)行程序,程序執(zhí)行結(jié)果如下所示(可與練習(xí) 11.33 比較函數(shù) builtMap 返回的 trans_map 的輸出結(jié)果):

// 運(yùn)行結(jié)果
Here is our transformation unordered_map: 

key: 18r    value: later
key: u  value: you
key: r  value: are
key: brb    value: be right back
key: y  value: why
key: thk    value: thanks!
key: pic    value: picture
key: k  value: okay?


where are you
why dont you send me a picture
okay? thanks! later

Process finished with exit code 0
來源:https://www./content-4-329751.html

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多