这是根据实际开发经验总结出来的一个排行榜维护工具。
假设排行榜按积分来排序,核心功能需求:①某一个用户积分改变后,需要立即更新受影响用户的名次;②同分情况下,先来的用户排前面,后来的用户排后面。
实现
用一个支持随机访问的容器(如动态数组)保存用户数据(包括用户ID和用户积分)作为排行榜,下标+1即名次,另外用一个映射容器(如Map、HashMap)存放用户ID到名次的映射,value - 1即数组索引,用于快速获取用户的名次。
情况分三种:
①用户已在排行榜,积分发生了增加或减少;②当新用户进入排行榜。
第一种情况的处理方法:
从映射容器中获取到用户的名次,然后就能获取到前一名次的用户和后一名次的用户数据;如果积分多于前一名次的用户,则与前一名次的用户交换名次,然后继续跟前面的用户比较;如果积分少于后一名次的用户,则与后一名次的用户交换名次,然后继续跟后面的用户比较。
第二种情况的处理方法:
将用户插入排行榜最后面,然后按照第一种情况继续处理。
源码
#ifndef RANKING_LIST_H
#define RANKING_LIST_H
#include <vector>
#include <map>
#include <algorithm>
/*
* 排行榜维护工具,名次从1开始,0为无效名次
* @_Tp 必要的数据(如用户ID)
* @_Score 积分类型
* @_ScoreCompare 积分比较器
* @_UniqueCompare 数据唯一性比较器
* @_MapContainer 映射容器
* @_RandomContainer 随机访问容器
*/
template<typename _Tp,
typename _Score,
typename _ScoreCompare = std::less<_Score>,
typename _UniqueCompare = std::less<_Tp>,
typename _MapContainer = std::map<_Tp, std::size_t, _UniqueCompare>,
typename _RandomContainer = std::vector<std::pair<_Tp, _Score>
>
>
class RankingList
{
public:
typedef _Score score_type;
typedef _Tp value_type;
typedef _Tp& reference;
typedef const _Tp& const_reference;
typedef _Tp* pointer;
typedef const _Tp* const_pointer;
typedef _RandomContainer random_container_type;
typedef _MapContainer map_container_type;
typedef _ScoreCompare score_cmp_type;
public:
RankingList() {}
RankingList(const random_container_type &__r)
: _M_rank_list(__r)
{
std::sort(_M_rank_list.begin(), _M_rank_list.end(), score_cmp_type());
for(std::size_t __i = 0; __i < _M_rank_list.size(); ++__i)
{
_M_rank_mapper[_M_rank_list[__i].first] = __i + 1;
}
}
// 重置数据
void reset(random_container_type &__r)
{
_M_rank_list.swap(__r);
std::sort(_M_rank_list.begin(), _M_rank_list.end(), score_cmp_type());
for(std::size_t __i = 0; __i < _M_rank_list.size(); ++__i)
{
_M_rank_mapper[_M_rank_list[__i].first] = __i + 1;
}
}
void creset(const random_container_type &__r)
{
_M_rank_list = __r;
std::sort(_M_rank_list.begin(), _M_rank_list.end(), score_cmp_type());
for(std::size_t __i = 0; __i < _M_rank_list.size(); ++__i)
{
_M_rank_mapper[_M_rank_list[__i].first] = __i + 1;
}
}
// 数值改变
void update(const_reference __v, const score_type &__s)
{
// 找到对应的名次
typename map_container_type::iterator __f = _M_rank_mapper.find(__v);
if(__f == _M_rank_mapper.end())
{
_M_rank_list.push_back(std::make_pair(__v, __s));
std::pair<typename map_container_type::iterator, bool> __p =
_M_rank_mapper.insert(std::make_pair(__v, _M_rank_list.size()));
__f = __p.first;
}
else
{
_M_rank_list[__f->second - 1].second = __s;
}
score_cmp_type __cmp;
// 向前比较,找到合适的位置
while(__f->second > 1 && _M_rank_list.size() > __f->second)
{
std::size_t __cur_rank = __f->second;
std::size_t __former_rank = __cur_rank - 1;
std::size_t __latter_rank = __cur_rank + 1;
// 前一名小于我,则我与前一名交换名次
if(__cmp(_M_rank_list[__former_rank - 1].second, _M_rank_list[__cur_rank - 1].second))
{
_M_rank_mapper.insert(std::make_pair(_M_rank_list[__former_rank - 1].first, __cur_rank));
std::swap(_M_rank_list[__former_rank - 1], _M_rank_list[__cur_rank - 1]);
__f->second = __former_rank;
}
// 我小于后一名,则我与后一名交换名次
else if(__cmp(_M_rank_list[__cur_rank - 1].second, _M_rank_list[__latter_rank - 1].second))
{
_M_rank_mapper.insert(std::make_pair(_M_rank_list[__latter_rank - 1].first, __cur_rank));
std::swap(_M_rank_list[__cur_rank - 1], _M_rank_list[__latter_rank - 1]);
__f->second = __latter_rank;
}
else break;
}
}
/*
* 获取名次
* 如果未进排行榜,则返回0;否则返回实际名次
*/
std::size_t rank(const_reference __v) const
{
typename map_container_type::const_iterator __f = _M_rank_mapper.find(__v);
return __f == _M_rank_mapper.end() ? 0 : __f->second;
}
/*
* 获取某名次的数据
*/
const_reference data(std::size_t __rank) const
{ return _M_rank_list[__rank - 1].first; }
/*
* 获取某名次的积分
*/
const score_type& score(std::size_t __rank) const
{ return _M_rank_list[__rank - 1].second; }
/*
* 排行榜人数
*/
std::size_t size() const
{ return _M_rank_list.size(); }
/*
* 是否存在名次
*/
bool exists(std::size_t __rank) const
{ return !(__rank == 0 || __rank > _M_rank_list.size()); }
// 获取整个排行榜
const random_container_type & result() const
{ return _M_rank_list; }
private:
random_container_type _M_rank_list; // 排行榜
map_container_type _M_rank_mapper; // 保存每个key对应的名次
};
#endif // RANKING_LIST_H
测试代码:
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include "RankingList.h"
typedef RankingList<unsigned long long, long long> rank_list_type;
static void print_rank(const rank_list_type &rank_list)
{
const rank_list_type::random_container_type &rank = rank_list.result();
rank_list_type::random_container_type::const_iterator it = rank.begin();
for(std::size_t i = 1; it != rank.end(); ++it, ++i)
{
std::cout << "rank " << i << " uid: " << it->first << " score:" << it->second << std::endl;
}
}
static void main_func()
{
rank_list_type rank_list;
srand(time(NULL));
for(int i = 0; i < 10; ++i)
{
rank_list.update(i + 1, rand() % 1000);
}
print_rank(rank_list);
std::size_t order = rank_list.rank(10);
std::cout << "the rank of uid:10 is: " << order << std::endl;
long long score = rank_list.score(order) / 2;
rank_list.update(10, score);
print_rank(rank_list);
}
int main()
{
main_func();
#ifdef __WIN32
system("pause");
#endif
return 0;
}
输出结果: