区间range库

《C++11/14高级编程:Boost程序库探秘》笔记

range库在迭代器和容器上抽象出了“区间”的概念,基于迭代器和容器,但要求比容器低很多,不需要容纳元素,只含有区间的两个首末端点位置。vector是区间,string是区间,但stack不是区间,一个pair也是区间,数组也是区间,用元函数has_range_iterator可以判断一个类型是否是区间。
区间都是左闭右开的,可以用成员函数begin()/end()或者自由函数begin()/end()获得其两个端点。

range库的特征元函数
  • range_iterator<R> :返回区间的迭代器类型
  • range_value<R> :返回区间的值类型
  • range_reference<R> :返回区间的引用类型
  • range_pointer<R> :返回区间的指针类型
  • range_category<R> :返回区间的迭代器分类
  • range_size<R> :返回区间的长度类型(无符号整数)
  • range_difference<R> :返回区间的距离类型(有符号整数)
  • range_reverse_iterator<R> :返回区间的逆向迭代器(仅对双向区间)

range库的操作函数
  • begin() :返回区间的起点
  • end() :返回区间的终点
  • rbegin() :返回逆向区间的起点(双向区间)
  • rend() :返回逆向区间的终点(双向区间)
  • empty() :判断区间是否为空
  • distance() :返回区间两端的距离
  • size() :返回区间的大小

begin()和end()对于没有begin()和end()成员函数的符合区间概念的类提供了方便的操作,比如说数组。这两个函数已经被收入了C++11标准。


标准算法

range库在名字空间boost里提供了所有标准算法的区间版本,分成三个头文件:

  • <boost/range/algorithm.hpp> :C++中所有标准算法
  • <boost/range/algorithm_ext.hpp> :一些扩展算法,如erase、itoa
  • <boost/range/numeric.hpp> :数值标准算法

这些区间算法的参数除了把两个迭代器改为一个区间外,与标准算法基本相同,只是减少了写出两个迭代器位置的代码,更加简单:

std::vector<int> v{8,16,1,3,7,3,42}
assert(boost::count(v,3) == 2);     //统计元素数量
//实际调用标准算法std::count(boost::begin(v),boost::end(v),3)
assert(boost::find(v,7) != v.end());    //查找元素
boost::sort(v);

基本上这些区间算法根据返回类型分为两类,第一类算法返回原区间,一般都是变动性算法,处理整个区间,然后再返回原区间,返回值继续能够直接被其他算法使用。

std::vector<int> v(10);
boost::rand48 rnd;
boost::sort(                       //3.排序
    boost::random_shuffle(         //2.随机打乱
        boost::generate(v,rnd)));  //1.用随机数填充区间

(变动性算法包括以下几种:fill/fill_n、generate/generate_n、inplace_merge、random_shuffle、replace/replace_if、reverse/rotate、sort/stable_sort、partial_sort/nth_element、make_heap/sort_heap/push_heap/pop_heap)

第二类算法使用一个模板参数定制返回的区间,一般都与某种形式的查找/分割操作有关。这些算法的普通版本返回一个处理后的迭代器位置(如find算法),我们可以将这个迭代器位置成为found,依据found位置就可以得到两个常用的子区间:[begin(rng),found]和[found,end(rng)]。除了found位置,range库还定义了next(found)和prior(found)位置,和起点终点一起分割成四个部分。
range库在boost名字空间还声明了一个枚举类型range_return_value,作为区间算法的模板参数,为元计算定制返回的区间类型:

enum range_return_value
{
    return_found,
    return_next,
    return_prior,
    return_begin_found,
    return_begin_next,
    return_begin_prior,
    return_found_end,
    return_next_end,
    return_prior_end,
    return_begin_end
};

迭代器区间类

区间的概念本质上就是一对迭代器,可以用std::pair<I,I>表示,但是std::pair<I,I>又过于简单,不能明确地表述出区间的含义,所以range库提供一个专门的区间类:iterator_range,它封装了两个迭代器,接口更标准化,而且轻量级。

std::vector<int> v(10);
typedef iterator_range<vector<int>::iterator> vec_range; //区间类型定义

vec_range r1(v);    //从容器构造一个区间
assert(!r1.empty());
assert(r1.size() == 10);

int a[10];
typedef iterator_range<int*> int_range;
int_range r2(a,a+5);  //从两个迭代器构造区间
assert(r2.size() == 5);

range库提供工厂函数make_iterator_range(),搭配关键字auto使用,可以省去创建iterator_range时需要手动指定迭代器类型的麻烦:

std::vector<int> v(10);
auto r1 = make_iterator_range(v);   //从区间构造
assert(has_range_iterator<decltype(r1)>::value);

int a[10];
auto r2 = make_iterator_range(a,a+5);   //从迭代器构造

auto r3 = make_iterator_range(a,1,-1);  //从指定迭代器的位置
assert(r3.size() == 8)

range库中还有一个模板函数copy_range,可以把区间里的元素拷贝到一个新的容器里:

char a[] = "iterator range";
auto r = boost::find_first(a," ");  //使用find_first字符串算法
assert(r.front() == ' ');           //返回查找到的区间
auto r2 = make_iterator_range(a,r.begin()); //取字符串的前半部分
assert(copy_range<string>(r2) == "iterator");

range库辅助工具

介绍了很多辅助工具(函数),但是get不到用途精髓,而且大部分与迭代器的工具很像。

  • sub_range
    是iterator_range的一个子类,目的在于表示一个子区间,和iterator_range有区别,sub_range的模板参数是一个前向区间类型,iterator_range是一个迭代器类型。sub_range有iterator_range全部功能
  • counting_range
    与counting_iterator相似,返回一个计数迭代器的区间
  • istream_range
    封装了输入流迭代器std::istream_iterator,把输入流的输入过程转化成一个区间
  • irange
    类似counting_range,多了指定步长的功能
  • combined_range
    利用zip_iterator,把多个区间“打包”为一个区间,相当于zip_iterator的区间强化版本。
  • any_range
    使用类型擦除技术,可以应用在符合要求的“任意”容器上。这个有点有趣,就举个例子讲讲。
    它的一个类摘要如下:
template<
    class Vaule,                     //值类型
    class Traversal,                 //迭代器便利类型
    class Reference = Value&,        //引用类型
    class Difference = std::ptrdiff_t,
    class Buffer = use_default
>
class any_range: public iterator_range<...>
{...};

前面两个模板参数分别确定了区间的值类型和遍历类型,不需要关心区间下的容器的具体类型,只要这个容器满足Value和Traversal的要求即可,比如说一个单遍整数的any_range区间,可以任意处理list、vector甚至是set等容器。

typedef any_range<int,          //区间的值类型是int
    boost::single_pass_traversal_tag> range_type;  //区间要求可单向遍历

list<int> l = {1,3,5,7,9};
range_type r(l);
for(const auto& x:r)
{ cout << x << ","; }

vector<int> v = {2,4,6,8,0};
r = v;
for(const auto& x:r)
{ cout << x << ","; }

range库还提供了一个工厂元函数,注意是元函数,它可以由一个区间或容器类型产生合适的any_range:

template<
    class WrappedRange,             //区间要包装的类型
    class Value = use_default,      //值类型
    class Traversal = use_default,  //迭代器遍历类型
    class Reference = use_default,  //引用类型
    class Difference = use_default,
    class Buffer = use_default
>
struct any_range_type_generator
{
    typedef any_range<...> type;   //元计算返回any_range
};

any_range_type_generator只需要提供第一个模板参数区间或容器类型,其余值会自动推导,最后使用::type得到any_range类型,如:
typedef any_range_type_generator<decltype(l)>::type range_type
但any_range_type_generator由于需要提供区间或容器类型,降低了any_range的适配性,导致只能用在特定Traversal属性的容器上,如上面使用list类型产生的any_range,就不能用到vector上了。


区间适配器

把一个区间适配成另一个区间。
同迭代器类似,区间适配器使用boost::copy来驱动数据通过区间和迭代器流动,而由于区间的自表达特性,它还重载了operator| (),支持类似UNIX管道操作符的形式连接多个区间,每个区间执行一定的工作。

range库区间适配器位于名字空间boost::adaptors,需要头文件<boost/range/adaptos.hpp>
range库提供的区间适配器包含以下几种:

  • adjacent_filtered(P):使用谓词P过滤两个邻接的元素
  • copied(n,m) :取子区间[n,m),只能用于随机访问区间
  • filtered(P):使用谓词P过滤元素
  • indexed(i):为迭代器添加一个从i开始的索引号
  • map_keys:取出map容器里的key
  • map_values:取出map容器里的value
  • replaced(x,y):把区间里的x替换为y
  • replaced_if(P,v):把区间里符合谓词P的元素替换为v
  • reversed:逆序区间
  • sliced(n,m):同copied
  • strided(n):在区间上以步长n跳跃前进
  • tokenized():使用boost::regex正则处理
  • transformed(F):类似于std::transform算法,用F处理每一个元素
  • uniqued:过滤掉相邻重复的元素

使用时,把原始区间想象成一个数据源,经过适配器后得到处理的数据,最后由算法来处理这些数据,有点类似流处理:

std::vector<int> v{7,8,4,6,53,2,6};
boost::copy(                //copy算法
    boost::sort(v) |        //先排序,区间改变
        adaptors::uniqued,  //去重,原区间不变
    ostream_iterator<int>(std::cout,","));  //去重后数据输出到标准流
assert(boost::count(v,6) == 2);     //原区间排序但未去重

auto even = [](int x){ return x%2 == 0; };
assert(boost::distance(      //计算区间长度
        v | adaptors::filtered(even)) == 5);  //仅计算偶数

可以灵活组合区间算法和区间适配器,但要注意,有些组合方式不可取,比如v | filtered | copied,i那位filtered返回的是双向区间——即使原始区间是随机访问区间,而copied和sliced都要求是随机访问区间,强行搭配会引发编译错误。


连接区间

range库里还有一个比较常用的函数join(),它可以把两个区间连接为一个区间并不要求两个区间是相邻的。

std::vector<int> v;
boost::copy(boost::irange(0,10),std::back_inserter(v));

auto r1 = make_iterator_range(v.begin(),v.begin() + 3); //子区间0~2
auto r2 = make_iterator_range(v.begin() + 5,v.end()); //子区间5~9

auto r3 = boost::join(r1,r2);
//输出0,1,2,5,6,7,8,9
boost::copy(r3,ostream_iterator<int>(cout,","));
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值