使用一個字段實現第二條件排序

在排序中,經常遇到變量相同情況下的排序問題。在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實現

package main

import (
    "fmt"
    "math/rand"
    "sort"
)

func main() {
    arr := make([]uint64, 10)
    b := rand.Perm(10)
    for i := 0; i < 10; i++ {
        var a uint32
        if i%2 == 0 {
            a = 10
        } else {
            a = 5
        }
        arr[i] = uint64(a)<<32 | uint64(b[i])
        fmt.Println(a, b[i], arr[i])
    }
    sort.Sort(Myuint64(arr))

    fmt.Println("排序後")
    for _, v := range arr {
        fmt.Println(v, v>>32, uint32(v))
    }
}

type Myuint64 []uint64

func (this Myuint64) Len() int {
    return len(this)
}
func (this Myuint64) Less(i, j int) bool {
    return this[i] > this[j]
}

func (this Myuint64) Swap(i, j int) {
    this[i], this[j] = this[j], this[i]
}

簡單說一下代碼,第一排序條件用了間隔使用了5和是這兩個數,第二條件隨機生成了10個不重複的隨機數,不用時間戳的原因是,程序運行的時間很短,取到的時間戳基本是一個數,看不出區別,本質思想都是一樣的。排序使用了go自帶的排序接口,不瞭解的看一下上篇文章,傳送門代碼中fmt.Println(v, v>>32, uint32(v)) 就是解數據, 運算後,前32位是一個數,後32位是另一個數據。取前面的數據只需要把計算後的數據右移32位就行了,取後面的數據,更簡單了,直接強轉成32位的類型就行了。
看一下運行結果:


可以看到,排序前,10和5交替出現,第二條件沒有規律
排序後,先使用第一個數排序,第一個數相同時,使用第二條件排序。而且我們也成功還原了原來的數據。
好了,這樣就實現了用一個數值實現第二條件排序了。在存入redis時只需要把計算後的數據存入redis就行了,顯示數據的時候,只需要解一下數據就行了。
好了,到這就介紹完了,有問題歡迎評論區討論

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章