在排序中,经常遇到变量相同情况下的排序问题。在MySQL中可以使用 ORDER BY 约束多个字段。但是在redis中,使用sorted set排序时,score只能设置一个变量。这样在score相同时,只能使用字典序了(这个是从文档上看到的,具体没验证) 这就会出现一些问题了,最简单,在游戏排行榜中,我原来是排第一的,突然来了个人,分数和我相同,但是排到我前面了,我成第二了,玩家肯定不干了。但是redis只能使用一个值作为排序条件,这就需要我们在一个值里既能记录原有的分数,还要记录另一个值。
比较常见的是用时间作为第二个排序条件,谁先达到这个分数,谁排在前面。 要实现这个功能,要满足 可逆 ,稳定排序 两个要求。
- 可逆:把数值处理后还能到的原来的值(分数和时间)
- 稳定排序:添加时间后不能影响原来的排序结果,添加的时间只是在分数相同的情况下才起作用
简单的对数值进行加减乘除是没法实现的了,简单的数值加减乘除一是不能恢复原来的数据,二是会影响到原有的排序。
不卖关子了,使用**‘或’运算,可以实现这个需求。把两个数值进行或**运算,生成一个数。简单说一下原理。 或 运算的规则是在二进制位上,有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位的类型就行了。
看一下运行结果: