用sort實現orderby

工作到了這個年數, 感覺那些基本函數語法已經跟人合一了, 根本不會爲操作一些數據結構而思考半天了. 在做小程序的時候遇到了個orderby的場景, 結果發現沒有以爲的那麼簡單. 也許是之前不求甚解的原因, 那麼現在來解決orderby的問題.

問題的產生與探討方向

在小程序中有個將list的某一條置頂的需求, 在初始化數據到時候可以使用數據庫的orderby, 但在更新數據以後再重新初始化就顯得有些不妥, 所以我嘗試直接使用computed列表來解決這個問題.

所以現在的問題是: 輸入list, 輸出orderby置頂字段.

之前以爲的sort很簡單, 我就嘗試了: arr.sort(i => i.stick). 字面看起來是根據stick字段來排序. 輸出結果一團糟. 仔細思考了下又嘗試了別的方法, 還是失敗, 才決定仔細想一下應該如何處理.

對sort的理解與快速shuffle

先說一下之前對sort的理解.

sort接受的參數返回大於0或者小於0. 根據結果來排序.

所以有一個快速shuffle數組的方法:

arr.sort(() => Math.random() - 0.5)

因爲函數的返回結果一半是大於0一半是小於0的(不嚴格, 但之後也認爲概率是一半一半). 所以任何輸出進行了如此處理, 都會變成一個隨機順序的數組.

另外一個例子, 對一個數組: [1, 2, 3, 4, 5, 10, 11, 12]進行排序, 如果不傳參數排序結果是錯的, 因爲默認是localCompare. 所以要寫成:

arr.sort((a, b) => a - b)

這樣才能得到正確從小到大的排列.

以上就是我多年以來對sort的所有理解. 所以纔會寫出上面的: arr.sort(i => i.stick)這樣搞笑的東西. 因爲理解是有問題的.

sort是如何排序的

因爲不知道sort函數得到了結果後是如何排序的. 所以對sort的理解有問題. 而我們知道reduce就是從頭到尾遍歷並傳遞每次計算的結果. sort卻不知道. 所以打出每次的返回值來看一下每次得到返回值後sort做了些什麼.

我們要對不同數組進行同樣的操作, 排序方法是一樣的, 先寫一下:

const log = (a, b) => {
    console.log(a, b, a - b > 0)
    return a - b
}

開始對不同數組進行排序: 先來1到5

[1, 2, 3, 4, 5].sort(log)

結果: [1, 2, 3, 4, 5]

2 1 true
3 2 true
4 3 true
5 4 true

嘗試: 從5到1

[5, 4, 3, 2, 1].sort(log)

結果: [1, 2, 3, 4, 5]

4 5 false
3 4 false
2 3 false
1 2 false

目前看來, sort應該是插入排序.

[3, 5, 7, 9, 2, 1, 6].sort(log)

看log的時候我把當前排序結果也打一下:

5 3 true [3, 5]
7 5 true [3, 5, 7]
9 7 true [3, 5, 7, 9]
2 9 false // 2還是與當前最大的9比.結果第一次false
2 7 false // 於是一路比下來
2 5 false
2 3 false // 比到最小的, 於是確定了位置 [2, 3, 5, 7, 9]
1 5 false // 1選擇了與5比, 此時5是中間位置的數, 而不是最大的數
1 3 false // 然後一個一個比較下來
1 2 false [1, 2, 3, 5, 7, 9]
6 5 true // 6還是於5比, 此時5也是中間位置的數
6 9 false // 沒有選擇與7, 而是與9比了
6 7 false

從這些log能得出一些粗淺的結論:

  1. sort是插入排序
  2. 每次比較的數字會根據兩個因素來決定: 分別是之前比較的結果和當前排序的位置

如何實現orderby

首先明確思路:

sort認爲每個元素之間的關係是比大小, 所以我們需要做的是寫出任意兩個元素的相對順序的普遍公式.

先構建一組數據:

let gnrt = () => ({ age: Math.round(Math.random() * 50), height: Math.round(Math.random() * 200) })
let arr = Array.from({length: 10}).map(() => gnrt())

我們先建立純數字, 無順序的orderby來理這個思路.

let orderby = function (arr, ...orders) {
    return arr.sort((a, b) => {
        let res = 0
        for (let order of orders) {
            if (a[order] - b[order] !== 0) {
                res = a[order] - b[order]
                break
            } 
        }
        return res
    })
}

調用orderby(arr, 'height', 'age')就得到了理想的orderby結果了: 根據權重排序, 如果都一樣就保持順序.

#後續#

這個思路清晰以後, 做兼容就容易了:

  1. 如果要指定順序, 在排序參數裏帶特徵, 例如'height', '-height', 來決定在執行的時候是a - b 還是b - a.
  2. 如果要指定排序函數(在非數字情況下). 把排序參數改寫成兼容function的, 判斷是string就執行默認, 是function就調用function即可.

當然, 功能越完善的函數就越複雜, 函數本身只是函數複雜度和業務複雜度交換的作用. 具體實現就不寫了.

所以置頂排序如何實現

我們已經想清楚了orderby的實現, 那麼置頂排序是stick這個布爾值字段, 就必須根據我上面說的傳函數進去, 並且改寫orderby函數.

這樣又要多些2個函數, 所以我選擇:

[...arr.filter({stick} => stick), ...arr.filter({stick} => !stick)]

搞定.

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