原文地址:

http://www.limerence2017.com/2019/06/23/cpp01/

做游戏或金融后台开发,经常会遇到设计开发排行榜的需求。比如玩家的充值排行,战力排行等等。而这种排行基本都是即时更新的,快速排序对于单一类型排序可以满足需求,但是对于多种类的排序就很吃力,比如实现一个排行榜,有战力排序,有充值排序,如下图

快速排序的缺陷

如果用快速排序实现,需要定义四种比较规则,而且qsort排序需要一段连续空间,如数组或者vector,为节约内存,每个元素存储玩家基本信息的指针。之后为每种排行类型定义单独的比较规则。代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std; struct PowerCmp;
struct ChargeCmp;
//玩家基本信息
class PlayrInfo{
public:
int getPower()const{
return u_power;
} int getCharge()const{
return u_charge;
}
private:
int u_ind; //玩家唯一id
int u_power; //玩家战力
int u_charge; //玩家充值数量
}; bool chargeCmp(PlayrInfo *a, PlayrInfo *b)
{
return a->getCharge() > b->getCharge();
} bool powerCmp(PlayrInfo *a, PlayrInfo *b)
{
return a->getPower() > b->getPower();
} int main(int argc, char* argv[])
{
//分别实现排序
vector<PlayrInfo*> vecPlayer; sort(vecPlayer.begin(),vecPlayer.end(),powerCmp);
sort(vecPlayer.begin(),vecPlayer.end(),chargeCmp);
getchar();
return 0;
}

这么做有几个坏处
1 每个排行都要开辟一段连续的序列,即使存储指针也会造成空间的浪费。
2 qsort适合中小数量排序,当人数上千或万以上会造成瓶颈。但是可以通过插入排序优化。
下面提出一种新的结构来处理排序,boost::multi_index 实现了多索引结构,支持按照多个键值排序,可以极大减轻压力。

multi_index 多索引处理排行榜

multi_index 是boost库提出的多索引结构表,可以设定多个主键进行排序,如战力,充值等等,但是必须要有一个唯一主键,我们将player的id设为唯一主键。
为了方便输出我们先完善下PlayerInfo类,重载输出运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//玩家基本信息
class PlayrInfo{
public:
PlayrInfo(int id, int power, int charge):u_ind(id),u_charge(charge),u_power(power){}
int getPower()const{
return u_power;
} int getCharge()const{
return u_charge;
}
private:
int u_ind; //玩家唯一id
int u_power; //玩家战力
int u_charge; //玩家充值数量 //重载输出
friend std::ostream& operator<<(std::ostream& os,const boost::shared_ptr<PlayrInfo> & e)
{
//获取引用计数
//cout << e.use_count();
//获取原始指针
//e.get()
os<<e->u_ind<<" "<<e->u_power<<" "<<e->u_charge<<std::endl;
return os;
}
};

由于multi_index 各主键排序需要设置比较规则,默认是从小到大,int类型可以用greater, less等。
greater从大到小, less从小到大,也可以自己实现仿函数。

1
2
3
4
5
6
7
struct powerOperator
{
bool operator()(int a, int b) const
{
return a > b;
}
};

定义operator()一定要加上const,因为没有修改参数数据。

multi_index 表定义

基于playerinfo实现multi_index表如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct by_id;
struct by_power;
struct by_charge;
typedef multi_index_container<
boost::shared_ptr<PlayrInfo>, //插入的主体,当然可以是PlayrInfo本身
indexed_by<
//唯一主键,不可重复, std::greater<int> 为id规则从大到小,默认从小到大
//tag<by_id>为标签名,可写可不写
ordered_unique<tag<by_id> , const_mem_fun<PlayrInfo,int,&PlayrInfo::getId>>, //可重复的主键
ordered_non_unique<tag<by_power>, const_mem_fun<PlayrInfo, int, &PlayrInfo::getPower>, powerOperator >,
ordered_non_unique<tag<by_charge>, const_mem_fun<PlayrInfo, int, &PlayrInfo::getCharge>, greater<int> >
>
> PlayerContainer;

by_id, by_power等都是标签,只需要声明一个结构体,然后tag<标签名>放到索引里。当然可以不带tag,带tag是为了之后获取方便。
powerOperator是定义的战力比较规则,greater定义的充值金额比较规则,是从大到小排序。

multi_index 根据标签获取排序序列

multi_index表在数据插入的时候就根据各个主键排序规则进行排序,所以只需要按照主键取出,获得序列就是排好序的数据。

我们先按照id获取,然后打印输出结果

1
2
auto& ids = con.get<by_id>();
copy(ids.begin(), ids.end(), ostream_iterator<boost::shared_ptr<PlayrInfo> >(cout));

结果如下,可以看出是按照id从小到大排序输出的。

1
2
3
1 1231 10000
2 22222 2000
3 19999 222222

那我们按照战力获取

1
2
auto& powers = con.get<by_power>();
copy(powers.begin(), powers.end(), ostream_iterator<boost::shared_ptr<PlayrInfo> >(cout));

结果能看出是按照战力从大到小输出

1
2
3
2 22222 2000
3 19999 222222
1 1231 10000

multi_index 删除数据

删除元素如果是根据唯一主键删除,可以直接删除,如果是非唯一主键,需要查出所有记录并删除。
删除唯一主键为2的玩家

1
2
3
4
5
6
7
auto &itfind = ids.find(2);

	if (itfind != ids.end()){
ids.erase(itfind);
cout << "after erase: "<<endl;
copy(con.begin(), con.end(), ostream_iterator<boost::shared_ptr<PlayrInfo> >(cout));
}

结果

1
2
3
after erase:
1 1231 10000
3 19999 222222

插入一条战力重复的数据,并且遍历删除所有战力为1231的玩家

1
2
3
4
5
6
7
8
9
10
con.insert(boost::make_shared<PlayrInfo>(4, 1231, 10000));
auto &beginit = powers.lower_bound(1231);
auto & endit = powers.upper_bound(1231);
for( ; beginit != powers.end()&& beginit != endit; ){
cout << "...................." <<endl;
cout << *beginit;
beginit = powers.erase(beginit);
} copy(powers.begin(), powers.end(), ostream_iterator<boost::shared_ptr<PlayrInfo> >(cout));

结果

1
2
3
4
5
....................
1 1231 10000
....................
4 1231 10000
3 19999 222222

lower_bound 获取的是战力为1231的玩家迭代器的起始值,upper_bound获取的是战力为1231的玩家的最大值的下一个元素。

multi_index 修改数据

修改数据可以用replace,也可以用modify
replace 失败不会删除条目,但是二次copy造成效率低下
modify失败会删除对应条目,容易暴力误删除,但是效率高
replace 找到战力为19999的玩家,并且替换为新的数据。

1
2
3
4
5
6
auto piter = powers.find(19999);
if(piter != powers.end() ){
auto newvalue = boost::make_shared<PlayrInfo>(100,200,300);
powers.replace(piter, newvalue);
copy(powers.begin(), powers.end(), ostream_iterator<boost::shared_ptr<PlayrInfo> >(cout));
}

下面用modify修改数据

1
2
3
4
5
6
7
8
piter = powers.find(200);
if(piter != powers.end()){
auto newp = 1024;
powers.modify(piter, [&](boost::shared_ptr<PlayrInfo> & playptr)->void{
playptr->setPower(newp) ;
});
copy(powers.begin(), powers.end(), ostream_iterator<boost::shared_ptr<PlayrInfo> >(cout));
}

modify 第一个参数和replace一样,都是要修改的迭代器,第二个参数是一个函数对象,参数类型为表中元素类型。
到目前为止,multi_index介绍完毕,用该多索引结构可以高效实现多级排序,非常适用于即时排行榜。
源码下载
https://github.com/secondtonone1/boost-multi_index-
我的公众号,谢谢关注

boost::multi_index 提供一种千人在线即时排行榜的设计思路的更多相关文章

  1. 分享一个基于长连接+长轮询+原生的JS及AJAX实现的多人在线即时交流聊天室

    实现网页版的在线聊天室的方法有很多,在没有来到HTML5之前,常见的有:定时轮询.长连接+长轮询.基于第三方插件(如FLASH的Socket),而如果是HTML5,则比较简单,可以直接使用WebSoc ...

  2. 基于JQuery+JSP的无数据库无刷新多人在线聊天室

    JQuery是一款非常强大的javascript插件,本文就针对Ajax前台和JSP后台来实现一个无刷新的多人在线聊天室,该实现的数据全部存储在服务端内存里,没有用到数据库,本文会提供所有源程序,需要 ...

  3. 核心思想:想清楚自己创业的目的(如果你没有自信提供一种更好的产品或服务,那就别做了,比如IM 电商 搜索)

    这个时代对于学 IT 的人来说是幸运的.一个普通的程序员可以相对轻易地找到工作,可以轻易拿到比其他行业高得多的工资,甚至自己创建世界级的企业亦非空想.马云.马化腾等企业家的成功,似乎时刻提醒人们:即便 ...

  4. 一个3D的多人在线游戏, 服务端 + 客户端 【转】

    最近学院组织了一个实训,要求是利用Socket通信和D3D的知识, 写一个多人在线的游戏, 服务端是在linux下, 客户是在Windows下: 写这个的目的是想让大家给我找错, 欢迎大家的意见.我的 ...

  5. 使用boost::multi_index高速构建排行榜

    使用boost::multi_index高速构建排行榜 前几天在boost的maillist上看到boost::multi_index将要支持ranked_index(邮件内容见附件2),这实乃我等苦 ...

  6. 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题

    导语 发布app后,开发者最头疼的问题就是如何解决交付后的用户侧问题的还原和定位,是业界缺乏一整套系统的解决方案的空白领域,闲鱼技术团队结合自己业务痛点在flutter上提出一套全新的技术思路解决这个 ...

  7. 负载均衡--大型在线系统实现的关键(上篇)(再谈QQ游戏百万人在线的技术实现)

    http://blog.csdn.net/sodme/article/details/393165 —————————————————————————————————————————————— 本文作 ...

  8. 关于智普 - 千人免费学|Python培训|国内最权威python培训|html5

    关于智普 - 千人免费学|Python培训|国内最权威python培训|html5 智普教育隶属于北京顶嵌开源科技有限公司,成立于2008年. 智普开源是基于Linux系统的互联网开源学习平台,讲求务 ...

  9. 网页版Rstudio︱RStudio Server多人在线协作开发

    网页版Rstudio︱RStudio Server多人在线协作开发 想了解一下RStudio Server,太给力的应用,可以说成是代码分布式运行,可以节省时间,放大空间. RStudio是一个非常优 ...

随机推荐

  1. ubuntu下python3.6.5import tensorflow显示非法指令(核心已转储)

    1.版本 ubuntu版本为14.04 python为3.6.5 tensorflow为pip3安装的1.8.0版本 2.解决 删除原先的tensorflow:sudo pip3 uninstall ...

  2. 浅入深出Vue:文章列表

    终于到我们小项目的最后一个功能了,那就是列表页展示! 新建组件 先来新建组件 List.vue: <template> <div></div> </templ ...

  3. pgsql 相关函数

    1.COALESCE — 空值替换函数.示例:COALESCE(col, 'replacement') :如果col列的值为null,则col的值将被替换为'replacement' 2.regexp ...

  4. u-boot-2018.09 DTS上 I2C节点的解析 (转)

    这篇理下uboot上I2C总线挂载设备的整个流程. 其他总线(如SPI等)应是类同的思路. uboot 中,以max8997挂载到s3c24xx i2c总线为例, dts里面的写法如下 aliases ...

  5. Python numpy.ZIP 安装问题

    今天在python上安装numpy,按照网上教程,安装pip,然后命令行直接:pip install numpy  .但是一直因为资源问题下载失败. 后来下载了一个numpy-1.11.2.zip 安 ...

  6. printf格式输出

    参考:http://www.cplusplus.com/reference/cstdio/printf/ C string that contains the text to be written t ...

  7. 浮动float和清除clear

    一.浮动(float) float简介 取值:left,right,none,inherit,默认none(不浮动) 可应用与所有元素 没有继承性 不在正常流中,但会影响布局.因为一个元素浮动时,其他 ...

  8. Prism框架中View与Region关联的几种方式

    Prism.Regions命名空间下有2个重要接口:IRegionManager.IRegion IRegionManager接口中的方法与属性:AddToRegion().RegisterViewW ...

  9. CDOJ 1132 酱神赏花 dp+单调栈降低复杂度+滚动数组

    酱神赏花 Time Limit: 3000/1000MS (Java/Others)     Memory Limit: 262143/262143KB (Java/Others) Submit St ...

  10. Harmonic Number (LightOJ 1234)(调和级数 或者 区块储存答案)

    题解:隔一段数字存一个答案,在查询时,只要找到距离n最近而且小于n的存答案值,再把剩余的暴力跑一遍就可以. #include <bits/stdc++.h> using namespace ...