11.1 泛型算法

所谓的泛型算法,是指对符合特定标准的容器/类,提供某些通用功能的封装,比如排序、查找、取最大最小值。大多数泛型算法都是通过一组迭代器来标记了要处理的元素范围,不但可以使用与vector或list这些标准库类型,还可以用在内置数组类型,甚至自定义的容器类型。

1.入门示例

泛型算法的好处是让算法和容器类型无关,比如我们想用从vector<int>查找给定值,确定它是否存在,就可以使用泛型算法里的find函数。我们用代码来说明一下这个示例。假设我们定义了这样的一个vector

#include <iostream>
#include <ostream>
#include <vector>


#include <algorithm>  // 泛型算法的声明
#include <numeric>    // 泛化的算术算法

using std::cout;
using std::vector;

vector<int> ivec;
ivec.push_back(1);
ivec.push_back(2);
ivec.push_back(3);

我们想要查找vector<int>里是否有给定值,可以通过如下的代码实现来完成:

void test_find(vector<int> &ivec, int val) {

    vector<int>::const_iterator it = std::find(ivec.begin(), ivec.end(), val);
    vector<int>::difference_type diff = it - ivec.begin();

    if( it !=  ivec.end()) {
        cout << "index:" << diff << ", value: " << *it << std::endl;
    }    else {
        cout << "not-found" << std::endl;
    }

}

find函数接受3个形参,前2个是1组迭代器,分别执行第1个元素、最后1个元素的下1个位置,第3个参数给定希望查找的值。find函数拿第3个参数,和迭代器里的每一个元素依次比较,返回第1次匹配的位置,如果没有找到,返回第2个参数的值。如果有一天,我们希望查找list<int>里、int[]里是否有给定值,find函数本身是不需要变的。当然find函数有一个隐性的要求是元素需要支持相等性(==)测试。

如果元素不支持相等性测试,或者你想自定义相等性测试,可以使用find的一个重载版本,提供一个相等性测试的函数。比如我们想找第1个大于等于给定值的元素,我们可以对代码做如下修改。

  1. 提供一个测试函数
#include <functional>

bool gte(int a, int b) {
    return a >= b;
}
  1. 调用find方法
void test_find_if(vector<int> &ivec, int val) {
    using namespace std::placeholders;
    std::function<bool(int)> cmp = std::bind(gte, _1, val);
    vector<int>::const_iterator it = std::find_if(ivec.begin(), ivec.end(), cmp);
    vector<int>::difference_type diff = it - ivec.begin();
    if (it != ivec.end()) {
        cout << "index:" << diff << ", value: " << *it << std::endl;
    } else {
        cout << "not-found" << std::endl;
    }
}

这里的std::function、std::bind已经是函数式编程了,应该说C++对函数式编程的支持,比Java的要强。

2.只读算法

就像前面示例中提到的find函数一样,泛型算法中有很多函数都是只读算法,并不会修改入参给定的数据。比如<numeric>中的accumulate函数、<algorithm>里的find_first_of函数等等。

1. accumulate

<numeric>中提供的泛型算法accumulate用于将给定范围的数据累积,隐性的要求是迭代器指向的元素需要支持+操作。accumulate依赖第3个参数给定累积的类型。比如我们可以对vector<int>的数字累加:

vector<int> ivec;
ivec.push_back(1);
ivec.push_back(2);
ivec.push_back(3);

int sum = std::accumulate(ivec.begin(), ivec.end(), 0);
cout << "sum: " << sum << std::endl;

我们也可以用accumulate对字符串进行拼接

vector<string> ivec;
ivec.push_back("1");
ivec.push_back("2");
ivec.push_back("3");

string sum = std::accumulate(ivec.begin(), ivec.end(), string(""));
cout << "sum: " << sum << std::endl;

这里有一点特别要注意的是accumulate的第3个参数,需要显示的传入string(""),因为字面量""传递的类型是const char*,导致find的返回类型也要求是const char*,但是这个类型并不支持+操作。

2. find_first_of

这个函数接受两组迭代器参数,用于查找第二组迭代器中的元素在第一组迭代器范围内第一次出现的位置。实际的执行过程是依次检查v1里的元素,判断给定元素是否在v2中,如果在的话就返回对于的迭代器位置,如果任意一个元素都不在v2中的话,返回第一组迭代器的第二个参数。

vector<string> v1;
v1.push_back("randy");
v1.push_back("kimi");
v1.push_back("sara");
v1.push_back("lucy");

vector<string> v2;
v2.push_back("tom");
v2.push_back("sara");
v2.push_back("lucy");

vector<string>::const_iterator it = std::find_first_of(v1.begin(), v1.end(), v2.begin(), v2.end());
cout << "find:" << *it << std::endl;
3. count

count函数接受3个参数,分别是1对迭代器,以及1个期望计数的值。比如下面的示例,用来统计vector中出现the的次数

void test_count() {
    vector<string>* svec = to_vector("the quick red fox jumps over the slow red turtle");
    cout << "svec: " << to_str(*svec, ",") << std::endl;
    vector<string>::difference_type cnt = std::count(svec->begin(), svec->end(), string("the"));
    cout << "count:" << cnt << std::endl;
}
4. count_if

count_if允许用户提供一个谓词函数,统计满足谓词函数的元素的个数。接上面的例子,假设我们要统计长度是6以上的字符串个数

bool gt6(const string& s1) {
    return s1.size() >= 6;
}

void test_count_if() {
    vector<string>* svec = to_vector("the quick red fox jumps over the slow red turtle");
    cout << "svec: " << to_str(*svec, ",") << std::endl;
    vector<string>::difference_type cnt = std::count_if(svec->begin(), svec->end(), gt6);
    cout << "count:" << cnt << std::endl;
}

上面的代码确实能完成功能,有一个明显的问题是我们把长度6写死在代码里了,在入门示例里我们使用了标准库<functional>里的std::bind解决了这个问题。C++中还提供了一种机制叫函数对象,也可以解决这类问题

class gtx {
public:
    gtx(size_t val = 0) :bound(val) {}
    bool operator()(const string& s) { return s.size() >= bound; }
private:
    std::string::size_type bound;
};

void test_count_if_op() {
    vector<string>* svec = to_vector("the quick red fox jumps over the slow red turtle");
    cout << "svec: " << to_str(*svec, ",") << std::endl;
    vector<string>::difference_type cnt = std::count_if(svec->begin(), svec->end(), gtx(5));
    cout << "count:" << cnt << std::endl;
}
3. 写入算法

泛型算法里有一些写入算法,根据写入的位置,可以将这些算法分为两类:

  1. 写入到输入序列的函数,如fill、fill_n、sort、stable_sort、unique、replace
  2. 输入到输出序列的函数,如copy、_copy结尾的函数

不管写入到哪里,用户都需要自己保证目标序列有足够的存储空间。

1. fill

fill通过给定一组迭代器限定范围,写入第三个参数给定的值。为了方便输出vector的内容,我们定义了一个to_str函数。

string to_str(const vector<int> &ivec, const char* deli) {
    std::ostringstream os;
    std::copy(ivec.begin(), ivec.end()-1, std::ostream_iterator<int>(os, deli));
    os << ivec.back();
    return os.str();
}

如果我们定义一个有10个元素的vector,用fill方法填充后,输出是10个1

void test_fill() {
    vector<int> ivec(10);
    std::fill(ivec.begin(), ivec.end(), 1);
    cout << "ivec: " << to_str(ivec,",") << std::endl;
}
2. fill_n

fill_n版本把fill版本的第2个参数从迭代器的结尾位置,改为要填充的元素个数

void test_fill() {
    vector<int> ivec(10);
    std::fill_n(ivec.begin(),5, 1);
    cout << "ivec: " << to_str(ivec,",") << std::endl;
}

这个程序的输出是5个1和5个0,如果我们把第2个参数换成15,这时候会得到如下报错,意思是操作迭代器的范围了。

所以使用fill_n的版本要格外注意,避免越界导致异常。

3. sort

我们定义了两个辅助函数,一个用于打印vector<string>,一个用于将字符串转成vector<string>

string to_str(const vector<string> &ivec, const char* deli) {
    std::ostringstream os;
    std::copy(ivec.begin(), ivec.end()-1, std::ostream_iterator<string>(os, deli));
    os << ivec.back();
    return os.str();
}

vector<string>* to_vector(const char* s) {
    std::istringstream is(s);
    vector<string> *svec = new vector<string>;
    string item;
    while (is >> item)
    {
        svec->push_back(item);
    }
    return svec;
}

这里的svec必须是堆上分配的,否则在to_vector外部就会被回收。现在我们可以开始测试sort函数

void test_sort() {
    vector<string> *svec = to_vector("the quick red fox jumps over the slow red turtle");
    cout << "svec: " << to_str(*svec, ",") << std::endl;
    std::sort(svec->begin(), svec->end());
    cout << "svec after sort: " << to_str(*svec, ",") << std::endl;
    delete svec;
}

这一段程序的输出如下

4. stable_sort

stable_sort和sort排序的区别是它采用的是稳定排序的算法,保留元素的原始相对位置。

bool isShorter(const string& s1, const string& s2) {
    return s1.size() < s2.size();
}

void test_sort_predicate() {
    vector<string>* svec = to_vector("the quick red fox jumps over the slow red turtle");
    cout << "svec: " << to_str(*svec, ",") << std::endl;
    std::sort(svec->begin(), svec->end());
    cout << "sort: " << to_str(*svec, ",") << std::endl;
    std::stable_sort(svec->begin(), svec->end(),isShorter);
    cout << "stable_sort: " << to_str(*svec, ",") << std::endl;
    delete svec;
}

sort、stable_sort默认都是按字典排序,都提供了一个重载版本提供一个谓词函数,用于指定排序的方式,比如这里我们提供了isShorter函数,用于按字符串长短排序。

5. unique

unique函数是将以排序的迭代器重复的部分移到最后,返回不重复部分最后一个元素的下一个位置。

void test_unique() {
    vector<string> *svec = to_vector("the quick red fox jumps over the slow red turtle");
    cout << "svec: " << to_str(*svec, ",") << std::endl;
    std::sort(svec->begin(), svec->end());
    cout << "svec after sort: " << to_str(*svec, ",") << std::endl;
    vector<string>::iterator it = std::unique(svec->begin(), svec->end());
    cout << "unique: " << to_str(*svec, ",") << std::endl;
    svec->erase(it, svec->end());
    cout << "erase: " << to_str(*svec, ",") << std::endl;
    delete svec;
}

这里需要特别注意的:

  1. unique只对已经排序的序列生效,两个连续的重复元素才能被识别
  2. unique并不会删除重复元素,只是把重复元素移到最后,unique返回的是不重复的最后一个元素的下一个位置,如果要删除重复元素需要自己调用erase删除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值