使用一个字段实现第二条件排序

在排序中,经常遇到变量相同情况下的排序问题。在MySQL中可以使用 ORDER BY 约束多个字段。但是在redis中,使用sorted set排序时,score只能设置一个变量。这样在score相同时,只能使用字典序了(这个是从文档上看到的,具体没验证) 这就会出现一些问题了,最简单,在游戏排行榜中,我原来是排第一的,突然来了个人,分数和我相同,但是排到我前面了,我成第二了,玩家肯定不干了。但是redis只能使用一个值作为排序条件,这就需要我们在一个值里既能记录原有的分数,还要记录另一个值。

比较常见的是用时间作为第二个排序条件,谁先达到这个分数,谁排在前面。 要实现这个功能,要满足 可逆稳定排序 两个要求。

  1. 可逆:把数值处理后还能到的原来的值(分数和时间)
  2. 稳定排序:添加时间后不能影响原来的排序结果,添加的时间只是在分数相同的情况下才起作用

简单的对数值进行加减乘除是没法实现的了,简单的数值加减乘除一是不能恢复原来的数据,二是会影响到原有的排序。

不卖关子了,使用**‘或’运算,可以实现这个需求。把两个数值进行或**运算,生成一个数。简单说一下原理。 运算的规则是在二进制位上,有1 或 运算后还是1,两个都是0 或运算后是 0。 例:1或2 二进制表示 01|10 = 11。但是现在却发现,无法实现数据还原,这就需要移位了。就是两个8位长的数据,或 运算后要成为16位的数据。 还是上个例子:为了简单起见,假设原数据是2位长,先把原来2位长的数据转成4位长,然后左移2位,1就从原来的01变成了0111。2只变成4位,不移位。2从10变成0010。0100|0010 = 0110。这样就能还原数据了。前两位01是原来的1,后两位10就是原来的2。 下面通过代码来实现一下。 代码用golang实现

 1package main
 2
 3import (
 4	"fmt"
 5	"math/rand"
 6	"sort"
 7)
 8
 9func main() {
10	arr := make([]uint64, 10)
11	b := rand.Perm(10)
12	for i := 0; i < 10; i++ {
13		var a uint32
14		if i%2 == 0 {
15			a = 10
16		} else {
17			a = 5
18		}
19		arr[i] = uint64(a)<<32 | uint64(b[i])
20		fmt.Println(a, b[i], arr[i])
21	}
22	sort.Sort(Myuint64(arr))
23
24	fmt.Println("排序后")
25	for _, v := range arr {
26		fmt.Println(v, v>>32, uint32(v))
27	}
28}
29
30type Myuint64 []uint64
31
32func (this Myuint64) Len() int {
33	return len(this)
34}
35func (this Myuint64) Less(i, j int) bool {
36	return this[i] > this[j]
37}
38
39func (this Myuint64) Swap(i, j int) {
40	this[i], this[j] = this[j], this[i]
41}

简单说一下代码,第一排序条件用了间隔使用了5和是这两个数,第二条件随机生成了10个不重复的随机数,不用时间戳的原因是,程序运行的时间很短,取到的时间戳基本是一个数,看不出区别,本质思想都是一样的。 排序使用了go自带的排序接口

代码中fmt.Println(v, v>>32, uint32(v)) 就是解数据, 运算后,前32位是一个数,后32位是另一个数据。 取前面的数据只需要把计算后的数据右移32位就行了,取后面的数据,更简单了,直接强转成32位的类型就行了。 看一下运行结果:

可以看到,排序前,10和5交替出现,第二条件没有规律 排序后,先使用第一个数排序,第一个数相同时,使用第二条件排序。而且我们也成功还原了原来的数据。 好了,这样就实现了用一个数值实现第二条件排序了。在存入redis时只需要把计算后的数据存入redis就行了,显示数据的时候,只需要解一下数据就行了。 好了,到这就介绍完了,有问题欢迎评论区讨论

发表了56篇文章 · 总计128.44k字
本博客已稳定运行
© QX
使用 Hugo 构建
主题 StackJimmy 设计