在實際項目開發中,無論是後端仍是前端,最基本的操做就是數據的CRUD。換句話說,後端是根據某些條件組裝數據,前端是拿着後端提供的數據,進行數據展現。可是無論在進行數據封裝仍是展現,其中在特定的場景下,須要根據某些條件,對數據進行排序。而在既定的現有框架下,都有現成的方法對數據進行排序處理。javascript
可是,在開發中,有沒有想過,這些排序底層是如何實現的,還有就是針對不一樣的數據,不一樣的排序是否在性能方面有不一樣的體現。前端
後端的數據排序不在本文的討論中,本文只針對前端的排序的一些思路和實踐.(該文只在前端範圍內討論排序的實現)java
如今有一組數據以下:算法
0: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 1}
1: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 1}
2: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 1}
3: {layoutId: 1029, priceCodeId: 1000409, activityId: 1, roomNum: 1}
4: {layoutId: 1029, priceCodeId: 1000409, activityId: 1, roomNum: 1}
5: {layoutId: 1269, priceCodeId: 1000410, activityId: 2, roomNum: 1}
複製代碼
如今有一個需求就是,須要根據layoutId,priceCodeId,activityId
這個三個組合主鍵來對roomNum
進行求和。後端
處理結果以下:數組
0: {layoutId: 1028, priceCodeId: 1000408, activityId: 0, roomNum: 3}
1: {layoutId: 1029, priceCodeId: 1000409, activityId: 1, roomNum: 2}
2: {layoutId: 1069, priceCodeId: 1000410, activityId: 2, roomNum: 1}
複製代碼
不要驚訝,這不是後臺處理邏輯,這是一個真真切切的前端數據處理邏輯。有的前端可能會說,數據處理是後臺的事。這個前端很差處理,我想說,若是是這個思惟方式,感受你被其餘語言開發工程師鄙視是理所固然的。bash
因此,我信奉一個道理:框架
菜並不可怕,不敢正視本身的弱點纔是最可怕的。性能
其實相似上述的問題,可能用JS的一些現有的庫,可能很好實現,可是這個文章沒有選擇這些現有庫,而是以一種最原始的方式來實現。或許還能有更好的方式來實現,歡迎你們批評指導。大數據
須要指出的是,這個案例和本文講的排序沒有很大的關係,可是在文末的實現的時候,用到了一些排序的思路方法和方式。
在開始以前,須要明確幾個概念:原地排序,穩定性。
冒泡排序只會操做相鄰的兩個數據。每次冒泡操做都會對相鄰的兩個元素進行比較,看是否知足大小關係要求。若是不知足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重複 n 次,就完成了 n 個數據的排序工做。
如今咱們假設有29,10,14,37,14
這些數據,須要按升序排序
arr 爲待排序數組,n爲數組個數
function BubbleSort1(arr,n){
if(n<=1) return ;
for(let i=0;i<n;i++){
for(let j = i+1;j<n;j++){
if(arr[i]>arr[j]){
let tempValue = arr[i];
arr[i] = arr[j];
arr[j] = tempValue;
}
}
}
return arr;
}
複製代碼
function bubbleSort2(arr, n) {
if (n <= 1) return;
for (let i = 0; i < n; ++i) {
// 提早退出冒泡循環的標誌位
let flag = false;
for (let j = 0; j < n - i - 1; ++j) {
if (arr[j] > arr[j+1]) { // 交換
let tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag = true; // 表示有數據交換
}
}
if (!flag) break; // 沒有數據交換,提早退出
}
}
複製代碼
插入排序具體的實現思路就是:找到已存數據列表中插入位置,將數據插入對應位置。是一個找茬算法。
將數組中的數據分爲兩個區間,已排序區間和未排序區間。初始已排序區間只有一個元素,就是數組的第一個元素。插入算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間數據一直有序。重複這個過程,直到未排序區間中元素爲空,算法結束。
咱們仍是以29,10,14,37,14
爲例。
arr 爲待排序數組,n爲數組個數
function insertionSort(arr, n) {
if (n <= 1) return;
for (let i = 1; i < n; ++i) {
let value = arr[i];
//肯定已排序區間,這裏的j是一個"哨兵",守衛者"邊界"
let j = i - 1;
// 查找插入的位置(這裏的) --j也就是從已排中找對應合適的位置
for (; j >= 0; --j) {
if (arr[j] > value) {
arr[j+1] = arr[j]; // 數據移動
} else {
break;
}
}
arr[j+1] = value; // 插入數據
}
}
複製代碼
選擇排序算法的實現思路有點相似插入排序,也分已排序區間和未排序區間。可是選擇排序每次會從未排序區間中找到最小的元素,將其放到已排序區間的末尾。
咱們仍是以29,10,14,37,14
爲例。 屏幕錄製 2019-09-27 下午2.30.52_52.gif
function selectionSort(arr) {
let len = arr.length;
let minIndex, temp;
for (let i = 0; i < len - 1; i++) {
minIndex = i;
for (let j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 尋找最小的數
minIndex = j; // 將最小數的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
複製代碼
5,8,5,2,9
這樣一組數據,使用選擇排序算法來排序的話,第一次找到最小元素 2,與第一個 5 交換位置,那第一個 5 和中間的 5 順序就變了,因此就不穩定了。針對這三種時間複雜度都爲O(n²)排序統一作一次對比:
排序方式 | 是否原地排序 | 是否穩定排序 | 最好 最壞 平均 |
---|---|---|---|
冒泡排序 | ✅ | ✅ | O(n) O(n²) O(n²) |
插入排序 | ✅ | ✅ | O(n) O(n²) O(n²) |
選擇排序 | ✅ | × | O(n²) O(n²) O(n²) |
在剛開始的時候,有一個純前端的數據聚合問題。話很少說,來段代碼儘儘興。嗨皮一下
來咯,來咯。
版本1:
function polymerizationData(sourceArr, flagKeyArr, targetKeyArr) {
let tempSourceArr = [...sourceArr];
//肯定已排區間,默認是數組第一個
let targetArr = tempSourceArr.splice(0, 1);
while (tempSourceArr.length) {
for (let i = 0; i < targetArr.length; i++) {
//這裏的i,其實也是一個哨兵,可是他的起始位置是從已排區間的開始位置算
let outerItem = targetArr[i];
let j = 0;
for (; j < tempSourceArr.length; j++) {
//從未排區間,取值,進行數據比對
let innerItem = tempSourceArr[j];
let isAllEqual = true;
for (let flagindex = 0; flagindex < flagKeyArr.length; flagindex++) {
if (innerItem[flagKeyArr[flagindex]] != outerItem[flagKeyArr[flagindex]]) {
isAllEqual = false;
break;
}
}
if (isAllEqual) {
for (let targetKeyIndex = 0; targetKeyIndex < targetKeyArr.length; targetKeyIndex++) {
outerItem[targetKeyArr[targetKeyIndex]] += innerItem[targetKeyArr[targetKeyIndex]]
}
tempSourceArr.splice(j, 1);
j = -1;
} else {
targetArr.push(tempSourceArr.splice(j, 1)[0]);
break;
}
}
}
}
return targetArr;
}
複製代碼
版本2(版本1的升級版)
function AdvancePolymerizationData(sourceArr, flagKeyArr, targetKeyArr) {
let storeDesignationKey = {};
let tempSourceArr = [...sourceArr];
let finalArr = tempSourceArr.splice(0, 1);
flagKeyArr.map(item => {
storeDesignationKey[item] = finalArr[0][item];
})
let i = 0, j = 0;
for (; i < tempSourceArr.length; i++) {
let isEqual = flagKeyArr.every(item => {
return tempSourceArr[i][item] == storeDesignationKey[item]
})
if (isEqual) {
targetKeyArr.map(item => {
finalArr[j][item] += tempSourceArr[i][item]
})
tempSourceArr.splice(i, 1);
i = -1;
} else {
let requirePushItem = tempSourceArr.splice(i, 1)[0];
flagKeyArr.map(item => {
storeDesignationKey[item] = requirePushItem[item];
})
finalArr.push(requirePushItem);
j++;
i = -1;
}
}
return finalArr;
}
複製代碼
上面的代碼調用方式
AdvancePolymerizationData(sourceArray, ["layoutId", "priceCodeId", "activityId"], ["roomNum"]);
複製代碼
其實,相似這種的數據處理,有一個共同的點,就是須要有一個相似插入排序和選擇排序中已排序區,來做爲一個targetArray/finalArr
。在進行isAllEqual
的判斷處理就相似於在排序中的數據判斷。在知足狀況下,進行數據拼接或者數據移動。